Thread

  1. ask for review of MERGE

    Boxuan Zhai <bxzhai2010@gmail.com> — 2010-09-23T10:31:22Z

    Dear All,
    
    I have just generate a new patch of MERGE command.
    
    One main change in this edition is the removal of RASIE ERROR action from
    MEREG, because its semantics is not well defined yet.
    
    I also rewrote the regress test file merge.sql, to make it consistent with
    the examples I used in my wiki page.
    
    Some little (error and warning) bugs are fixed.
    
    In this patch, all the main features of MERGE (sublinks, explain, rule and
    trigger, inheritance) are not changed. And so is the DO NOTHING action.
    
    I do wish the MERGE command can be added into psql 9.1. And I wonder what
    improvement should be made on current edition.
    
    Could you please have a review on this patch, if you have time and interest?
    
    Your feedback will be highly appreciated.
    
    Thanks
    
    Yours Boxuan
    
  2. Re: ask for review of MERGE

    Thom Brown <thom@linux.com> — 2010-09-23T10:59:19Z

    On 23 September 2010 11:31, Boxuan Zhai <bxzhai2010@gmail.com> wrote:
    > Dear All,
    > I have just generate a new patch of MERGE command.
    > One main change in this edition is the removal of RASIE ERROR action from
    > MEREG, because its semantics is not well defined yet.
    > I also rewrote the regress test file merge.sql, to make it consistent with
    > the examples I used in my wiki page.
    > Some little (error and warning) bugs are fixed.
    > In this patch, all the main features of MERGE (sublinks, explain, rule and
    > trigger, inheritance) are not changed. And so is the DO NOTHING action.
    > I do wish the MERGE command can be added into psql 9.1. And I wonder what
    > improvement should be made on current edition.
    > Could you please have a review on this patch, if you have time and interest?
    > Your feedback will be highly appreciated.
    > Thanks
    >
    > Yours Boxuan
    
    A few corrections:
    
    in src/backend/executor/nodeModifyTable.c:
        s/excute/execute/
        s/orignial/original/
    
    in src/backend/optimizer/plan/planner.c
        s/expreesions/expressions/
        s/So,we/So, we/
        s/comand/command/
        s/fileds/fields/
    
    in src/backend/optimizer/prep/preptlist.c:
        s/aggresive/aggressive/
    
    in src/backend/optimizer/util/var.c
        s/targe/target/ -- this appears twice
        s/sourse/source/
    
    in src/backend/parser/analyze.c
        s/takend/taken/
        s/relaion/relation/
        s/targe/target/ -- this appears twice
        s/consitency/consistency/
        s/commond/command/
        s/seperate/separate/
    
    in src/backend/rewrite/rewriteHandler.c
        s/acton/action/
    
    in src/include/nodes/execnodes.h
        s/meger/merge/
    
    in src/include/nodes/parsenodes.h
        s/proecess/process/
        s/allwo/allow/
        s/elments/elements/
    
    in src/test/regress/expected/merge.out
        s/qulifications/qualifications/ -- this appears twice
        s/suceeds/succeeds/ -- this appears twice
    
    -- 
    Thom Brown
    Twitter: @darkixion
    IRC (freenode): dark_ixion
    Registered Linux user: #516935
    
    
  3. Re: ask for review of MERGE

    Marko Tiikkaja <marko.tiikkaja@cs.helsinki.fi> — 2010-09-23T11:55:29Z

    On 2010-09-23 1:31 PM +0300, Boxuan Zhai wrote:
    > I have just generate a new patch of MERGE command.
    
    I haven't followed the discussion very closely, but this part in the 
    regression tests caught my attention:
    
    +-- we now have a duplicate key in Buy, so when we join to
    +-- Stock we will generate 2 matching rows, not one.
    +-- According to standard this command should fail.
    +-- But it suceeds in PostgreSQL implementation by simply ignoring the 
    second
    
    It doesn't seem like a very good idea to go against the standard here. 
    The "second" row is not well defined in this case so the results are 
    unpredictable.
    
    
    The patch is also missing a (trivial) change to explain.c.
    
    
    Regards,
    Marko Tiikkaja
    
    
  4. Re: ask for review of MERGE

    Boxuan Zhai <bxzhai2010@gmail.com> — 2010-09-23T13:48:57Z

    On Thu, Sep 23, 2010 at 7:55 PM, Marko Tiikkaja <
    marko.tiikkaja@cs.helsinki.fi> wrote:
    
    > On 2010-09-23 1:31 PM +0300, Boxuan Zhai wrote:
    >
    >> I have just generate a new patch of MERGE command.
    >>
    >
    > I haven't followed the discussion very closely, but this part in the
    > regression tests caught my attention:
    >
    > +-- we now have a duplicate key in Buy, so when we join to
    > +-- Stock we will generate 2 matching rows, not one.
    > +-- According to standard this command should fail.
    > +-- But it suceeds in PostgreSQL implementation by simply ignoring the
    > second
    >
    > It doesn't seem like a very good idea to go against the standard here. The
    > "second" row is not well defined in this case so the results are
    > unpredictable.
    >
    >
    Yes, the result is uncertain. It depends on which row is scanned first,
    which is almost out of the control of users.
    
    But, in postgres, this is what the system do for UPDATE.
    
    For example, consider a simple update query like the following:
    
    CREATE TABLE target (id int, val int);
    INSERT INTO target VALUES (1, 10);
    
    CREATE TABLE source (id int, add int);
    INSERT INTO source VALUES (1, 100);
    INSERT INTO source VALUES (1, 100000);
    
    -- DO the update query with source table, which has multiple matched rows
    
    UPDATE target SET val = val + add FROM source
    WHERE source.id = target.id;
    
    t=# SELECT * FROM target;
     id | val
    ----+-----
      1 | 110
    (1 row)
    
    The target tuple has two matched source tuples, but it is only updated once.
    And, yet, this query is not forbidden by postgres. The result is also
    uncertain.
    
    
    > The patch is also missing a (trivial) change to explain.c.
    >
    >
    Sorry, I massed up the files. Here comes the new patch file, with EXPLAIN in
    it.
    
    
    >
    > Regards,
    > Marko Tiikkaja
    >
    
  5. Re: ask for review of MERGE

    Greg Smith <greg@2ndquadrant.com> — 2010-09-24T08:13:19Z

    Finding time for a review as large as this one is a bit tough, but I've 
    managed to set aside a couple of days for it over the next week.  I'm 
    delivering a large project tonight and intend to start in on the review 
    work tomorrow onced that's cleared up.  If you're ever not sure who is 
    working on your patch and what state they feel it's in, check 
    https://commitfest.postgresql.org/action/commitfest_view?id=7 for an 
    update; that's where we keep track of all that information.
    
    Did you ever end up keeping a current version of this patch in an 
    alternate repository location, such as github?  I thought I saw a 
    suggestion from you about that, but after looking through the history 
    here all I see are the diff patches you've been sending to the list.  
    That's fine, just trying to confirm where everything is at.
    
    -- 
    Greg Smith, 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
    PostgreSQL Training, Services and Support  www.2ndQuadrant.us
    Author, "PostgreSQL 9.0 High Performance"    Pre-ordering at:
    https://www.packtpub.com/postgresql-9-0-high-performance/book
    
    
    
  6. Re: ask for review of MERGE

    Greg Smith <greg@2ndquadrant.com> — 2010-09-29T06:44:35Z

    Starting looking at the latest MERGE patch from Boxuan here tonight. The 
    regression tests pass for me here, good starting sign. I expect to move 
    onto trying to break it more creatively next, then onto performance 
    tests. Nothing much more exciting than that to report so far.
    
    It had suffered some bit rot, I think because of the security label 
    changes. Attached is a rebased version against the new git HEAD so 
    nobody else has to duplicate that to apply the patch. Also, to provide 
    an alternate interface for anyone who wants to do testing/browsing of 
    this patch, I've made a Github fork with a merge branch in it. I plan to 
    commit intermediate stuff to there that keeps up to date with review 
    changes: http://github.com/greg2ndQuadrant/postgres/tree/merge
    
    Probably easier to read 
    http://github.com/greg2ndQuadrant/postgres/compare/merge than most local 
    patch viewers, so I gzip'ed the attached updated patch to save some bytes.
    
    One compiler warning I noticed that needs to get resolved:
    
    src/backend/commands/explain.c:
    
    explain.c: In function ‘ExplainMergeActions’:
    explain.c:1528: warning: comparison of distinct pointer types lacks a cast
    
    That is complaining about this section:
    
    if (mt_planstate->operation != CMD_MERGE ||
    mt_planstate->mt_mergeActPstates == NIL)
    return;
    
    So presumably that comparison with NIL needs a cast. Once I get more 
    familiar with the code I'll fix that myself if Boxuan doesn't offer a 
    suggestion first.
    
    The rest of the compiler warnings I saw didn't look related to his code, 
    maybe stuff my picky Ubuntu compiler is noticing that was done recently 
    to HEAD. I haven't checked HEAD without this patch yet to confirm, and 
    am done for the night now. Here's the list if anyone is interested:
    
    Warning in src/backend/parser/scan.c:
    
    gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith 
    -Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing 
    -fwrapv -g -I../../../src/include -D_GNU_SOURCE -c -o index.o index.c 
    -MMD -MP -MF .deps/index.Po
    In file included from gram.y:12172:
    scan.c: In function ‘yy_try_NUL_trans’:
    scan.c:16256: warning: unused variable ‘yyg’
    
    Warning in src/backend/utils/error/elog.c:
    
    gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith 
    -Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing 
    -fwrapv -g -I../../../../src/include -D_GNU_SOURCE -c -o ts_cache.o 
    ts_cache.c -MMD -MP -MF .deps/ts_cache.Po
    elog.c: In function ‘write_console’:
    elog.c:1698: warning: ignoring return value of ‘write’, declared with 
    attribute warn_unused_result
    elog.c: In function ‘write_pipe_chunks’:
    elog.c:2388: warning: ignoring return value of ‘write’, declared with 
    attribute warn_unused_result
    elog.c:2397: warning: ignoring return value of ‘write’, declared with 
    attribute warn_unused_result
    
    Warning in src/bin/scripts/common.c:
    
    gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith 
    -Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing 
    -fwrapv -g -I. -I. -I../../../src/interfaces/libpq 
    -I../../../src/bin/pg_dump -I../../../src/include -D_GNU_SOURCE -c -o 
    input.o input.c -MMD -MP -MF .deps/input.Po
    common.c: In function ‘handle_sigint’:
    common.c:247: warning: ignoring return value of ‘write’, declared with 
    attribute warn_unused_result
    common.c:250: warning: ignoring return value of ‘write’, declared with 
    attribute warn_unused_result
    common.c:251: warning: ignoring return value of ‘write’, declared with 
    attribute warn_unused_result
    
    -- 
    Greg Smith, 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
    PostgreSQL Training, Services and Support  www.2ndQuadrant.us
    Author, "PostgreSQL 9.0 High Performance"    Pre-ordering at:
    https://www.packtpub.com/postgresql-9-0-high-performance/book
    
    
  7. Re: ask for review of MERGE

    Josh Kupershmidt <schmiddy@gmail.com> — 2010-09-29T13:13:31Z

    On Wed, Sep 29, 2010 at 2:44 AM, Greg Smith <greg@2ndquadrant.com> wrote:
    
    > The rest of the compiler warnings I saw didn't look related to his code,
    > maybe stuff my picky Ubuntu compiler is noticing that was done recently to
    > HEAD. I haven't checked HEAD without this patch yet to confirm, and am done
    > for the night now. Here's the list if anyone is interested:
    >
    > Warning in src/backend/parser/scan.c:
    >
    > gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith
    > -Wdeclaration-after-statement -Wendif-labels -fno-strict-aliasing -fwrapv -g
    > -I../../../src/include -D_GNU_SOURCE -c -o index.o index.c -MMD -MP -MF
    > .deps/index.Po
    > In file included from gram.y:12172:
    > scan.c: In function ‘yy_try_NUL_trans’:
    > scan.c:16256: warning: unused variable ‘yyg’
    
    Known problem: http://archives.postgresql.org/pgsql-hackers/2009-07/msg00657.php
    
    I'm pretty sure I've seen the warn_unused_result warnings on HEAD as well.
    
    Josh
    
    
  8. Re: ask for review of MERGE

    Robert Haas <robertmhaas@gmail.com> — 2010-09-29T13:16:38Z

    On Wed, Sep 29, 2010 at 2:44 AM, Greg Smith <greg@2ndquadrant.com> wrote:
    > One compiler warning I noticed that needs to get resolved:
    >
    > src/backend/commands/explain.c:
    >
    > explain.c: In function ‘ExplainMergeActions’:
    > explain.c:1528: warning: comparison of distinct pointer types lacks a cast
    >
    > That is complaining about this section:
    >
    > if (mt_planstate->operation != CMD_MERGE ||
    > mt_planstate->mt_mergeActPstates == NIL)
    > return;
    >
    > So presumably that comparison with NIL needs a cast. Once I get more
    > familiar with the code I'll fix that myself if Boxuan doesn't offer a
    > suggestion first.
    
    Possibly NULL was meant instead of NIL.  NIL is specifically for a List.
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise Postgres Company
    
    
  9. Re: ask for review of MERGE

    Boxuan Zhai <bxzhai2010@gmail.com> — 2010-09-29T13:29:09Z

    On Wed, Sep 29, 2010 at 9:16 PM, Robert Haas <robertmhaas@gmail.com> wrote:
    
    > On Wed, Sep 29, 2010 at 2:44 AM, Greg Smith <greg@2ndquadrant.com> wrote:
    > > One compiler warning I noticed that needs to get resolved:
    > >
    > > src/backend/commands/explain.c:
    > >
    > > explain.c: In function ‘ExplainMergeActions’:
    > > explain.c:1528: warning: comparison of distinct pointer types lacks a
    > cast
    > >
    > > That is complaining about this section:
    > >
    > > if (mt_planstate->operation != CMD_MERGE ||
    > > mt_planstate->mt_mergeActPstates == NIL)
    > > return;
    > >
    > > So presumably that comparison with NIL needs a cast. Once I get more
    > > familiar with the code I'll fix that myself if Boxuan doesn't offer a
    > > suggestion first.
    >
    > Possibly NULL was meant instead of NIL.  NIL is specifically for a List.
    >
    >
    Yes, it should be NULL instead of NIL.
    
    At first, I designed this filed as a List. But I changed it into an array in
    the last editions. This is why I have an unmatched assignment here. Sorry
    for that.
    
    
    > --
    > Robert Haas
    > EnterpriseDB: http://www.enterprisedb.com
    > The Enterprise Postgres Company
    >
    
  10. Re: ask for review of MERGE

    Robert Haas <robertmhaas@gmail.com> — 2010-10-14T01:27:28Z

    On Wed, Sep 29, 2010 at 2:44 AM, Greg Smith <greg@2ndquadrant.com> wrote:
    > Starting looking at the latest MERGE patch from Boxuan here tonight. The
    > regression tests pass for me here, good starting sign. I expect to move onto
    > trying to break it more creatively next, then onto performance tests.
    > Nothing much more exciting than that to report so far.
    
    Greg, are you still working on a review of this patch?
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
  11. Re: ask for review of MERGE

    Greg Smith <greg@2ndquadrant.com> — 2010-10-14T14:23:24Z

    Robert Haas wrote:
    > Greg, are you still working on a review of this patch?
    >   
    
    Yes, just had more distractions while coming to speed up on this area 
    than I'd hoped.  I'll get a second round of looking at this done by the 
    weekend.
    
    -- 
    Greg Smith, 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
    PostgreSQL Training, Services and Support  www.2ndQuadrant.us
    
    
    
    
  12. Re: ask for review of MERGE

    Greg Smith <greg@2ndquadrant.com> — 2010-10-18T00:20:14Z

    Boxuan Zhai wrote:
    > Yes, it should be NULL instead of NIL. 
    
    OK, I applied that patch to my local copy and pushed it out to github:
    
    http://github.com/greg2ndQuadrant/postgres/commit/9013ba9e81490e3623add1b029760817021297c0
    
    That represents what I tested against.  However, when I tried to merge 
    against HEAD once I was finished, I discovered this patch has bit-rotted 
    significantly.  If you have a local copy that works for you, I would not 
    recommend pulling in the PostgreSQL repo updates done in the last couple 
    of weeks yet.  Friday's "Allow WITH clauses to be attached to INSERT, 
    UPDATE, DELETE statements" commit in particular conflicts quite a bit 
    with your changes.  Attached is a rebased patch that applies to HEAD now 
    after a round of fixes to resolve those.  But it doesn't work yet, 
    because of recent change to the ExecUpdate and ExecDelete functions 
    you're calling from src/backend/executor/nodeModifyTable.c inside 
    ExecMerge.  If you can continue working on the patch without merging 
    recent repo work, I can hack away at fixing that once I figure out what 
    got changed there recently.  It's taken some painful git work to sort 
    out what I've done so far, there's more left to do, and I know that's 
    not an area you specialize in.
    
    Back to the feature review...I dove into how I expected this to work, 
    relative to what it actually does at the moment.  That didn't really go 
    too well so far, but I don't know that this represents any fundamental 
    issue with the patch.  Just situations the existing code didn't really 
    anticipate we have to flush out.  As a general community FYI here, while 
    it's taken me a while to get up to speed on this whole feature, I expect 
    to keep chugging away on this regardless of the CommitFest boundaries.  
    This feature is both too big and too important to just stop working on 
    it because a date has passed.
    
    Onto the test cases.  The examples that Boxuan has been working with, 
    and that the regression tests included with the patch exercise, all 
    involve two tables being joined together using MERGE.  The use case I 
    decided to test instead was when you're trying to simulate an UPSERT 
    where only a single table is involved.  I couldn't get to this to work 
    correctly.  Maybe I'm just using MERGE wrong here, but I tried so many 
    different variations without success (including one that's basically 
    copied from Simon's original regression test set suggestions) that I 
    suspect there may be a subtle problem with the implementation instead.
    
    To replicate the most straightforward variations of what I ran into, you 
    can start with the same data that's populated by the regression test set:
    
    CREATE TABLE Stock(item_id int UNIQUE, balance int);
    INSERT INTO Stock VALUES (10, 2200);
    INSERT INTO Stock VALUES (20, 1900);
    SELECT * FROM Stock;
    
     item_id | balance
    ---------+---------
          10 |    2200
          20 |    1900
    
    If you now execute the following:
    
    MERGE INTO Stock t
      USING (SELECT * FROM Stock WHERE item_id=10) AS s
      ON s.item_id=t.item_id
      WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
      WHEN NOT MATCHED THEN INSERT VALUES (10,1)
      ;
    
    This works fine, and updates the matching row:
    
     item_id | balance
    ---------+---------
          20 |    1900
          10 |    2201
    
    
    But if I give it a key that doesn't exist instead:
    
    MERGE INTO Stock t
      USING (SELECT * FROM Stock WHERE item_id=30) AS s
      ON s.item_id=t.item_id
      WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
      WHEN NOT MATCHED THEN INSERT VALUES (30,1)
      ;
    
    This doesn't execute the NOT MATCHED case and INSERT the way I expected 
    it to.  It just gives back "MERGE 0".
    
    Since I wasn't sure if the whole "subquery in the USING clause" case was 
    really implemented fully, I then tried to do this with something more 
    like the working regression test examples.  I expected this to do the 
    same thing as the first example:
    
    MERGE INTO Stock t
      USING Stock s
      ON s.item_id=10 AND s.item_id=t.item_id
      WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
      WHEN NOT MATCHED THEN INSERT VALUES (10,1)
      ;
    
    But it gives back this:
    
    ERROR:  duplicate key value violates unique constraint "stock_item_id_key"
    DETAIL:  Key (item_id)=(10) already exists.
    
    Can't tell from that whether it's hitting the MATCHED or NOT MATCHED 
    side of things to generate that.  But it doesn't work any better if you 
    give it an example that doesn't exist:
    
    MERGE INTO Stock t
      USING Stock s
      ON s.item_id=30 AND s.item_id=t.item_id
      WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
      WHEN NOT MATCHED THEN INSERT VALUES (30,1)
      ;
     
    ERROR:  duplicate key value violates unique constraint "stock_item_id_key"
    DETAIL:  Key (item_id)=(30) already exists.
    
    Now that one is really weird, because that key value certainly doesn't 
    exist yet in the table.  There seem to be a couple of issues in the area 
    of joining a table with itself here.  Feel free to tell me I just don't 
    know how to use MERGE if that's the case in any of these.
    
    The other thing I noticed that may take some work to sort out is that I 
    haven't had any luck getting MERGE to execute from within a plpgsql 
    function.  I was hoping I could use this to update the pgbench tables:
    
    CREATE OR REPLACE FUNCTION merge_account_balance(key INT, delta NUMERIC) 
    RETURNS VOID AS
    $$
    BEGIN
    MERGE INTO pgbench_accounts t USING (SELECT * FROM pgbench_accounts 
    WHERE aid = key) AS s ON t.aid=s.aid WHEN MATCHED THEN UPDATE SET 
    abalance = s.abalance + delta WHEN NOT MATCHED THEN INSERT VALUES 
    (key,1+(key / 100000)::integer,delta,'');
    END;
    $$
    LANGUAGE plpgsql;
    
    But I just get this instead:
    
    ERROR:  "pgbench_accounts" is not a known variable
    LINE 4: MERGE INTO pgbench_accounts t USING (SELECT * FROM p...
    
    The other way I wrote the MERGE statement above (not using a subquery) 
    does the same thing.  I know that error messages is coming from the 
    changes introduced in 
    http://archives.postgresql.org/pgsql-committers/2010-01/msg00161.php but 
    I'm not familiar enough with the whole grammar implementation to know 
    what that means yet.
    
    That's what I found so far in my second pass over this.  Once the first 
    problem here is sorted out, I've already worked out how to test the 
    performance of the code using pgbench.  Have all the scripts ready to go 
    once the correct MERGE statement is plugged into them, just ran into 
    this same class of problems when I tried them.  So far I was only able 
    to see how fast the UPDATE path worked though, which isn't very helpful 
    yet.  My hope here is to test the MERGE implementation vs. the classic 
    pl/pgsql implementation of UPSERT, calling both within a function so 
    it's a fair comparison, and see how that goes.  This may flush out 
    concurrency bugs that are in the MERGE code as well.
    
    -- 
    Greg Smith, 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
    PostgreSQL Training, Services and Support  www.2ndQuadrant.us
    
    
    
  13. Re: ask for review of MERGE

    Robert Haas <robertmhaas@gmail.com> — 2010-10-18T13:54:41Z

    I think that MERGE is supposed to trigger one rule for each row in the
    source data.  So:
    
    On Sun, Oct 17, 2010 at 8:20 PM, Greg Smith <greg@2ndquadrant.com> wrote:
    > MERGE INTO Stock t
    >  USING (SELECT * FROM Stock WHERE item_id=10) AS s
    >  ON s.item_id=t.item_id
    >  WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
    >  WHEN NOT MATCHED THEN INSERT VALUES (10,1)
    >  ;
    >
    > This works fine, and updates the matching row:
    >
    > item_id | balance
    > ---------+---------
    >     20 |    1900
    >     10 |    2201
    
    Here you have one row of source data, and you got one action (the WHEN
    MATCHED case).
    
    > But if I give it a key that doesn't exist instead:
    >
    > MERGE INTO Stock t
    >  USING (SELECT * FROM Stock WHERE item_id=30) AS s
    >  ON s.item_id=t.item_id
    >  WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
    >  WHEN NOT MATCHED THEN INSERT VALUES (30,1)
    >  ;
    >
    > This doesn't execute the NOT MATCHED case and INSERT the way I expected it
    > to.  It just gives back "MERGE 0".
    
    Here you have no rows of source data (the USING (SELECT ...) doesn't
    return anything, since no rows exist) so nothing happens.
    
    > Since I wasn't sure if the whole "subquery in the USING clause" case was
    > really implemented fully, I then tried to do this with something more like
    > the working regression test examples.  I expected this to do the same thing
    > as the first example:
    >
    > MERGE INTO Stock t
    >  USING Stock s
    >  ON s.item_id=10 AND s.item_id=t.item_id
    >  WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
    >  WHEN NOT MATCHED THEN INSERT VALUES (10,1)
    >  ;
    >
    > But it gives back this:
    >
    > ERROR:  duplicate key value violates unique constraint "stock_item_id_key"
    > DETAIL:  Key (item_id)=(10) already exists.
    
    Here you have two rows of source data.  The ON clause represents the
    join condition.  The item_id=10 row matches - so you get an update,
    presumably, though we can't see that as things turn out - and the
    item_id=20 row doesn't match - so you try to insert (10, 1), which is
    a duplicate key, thus the error.
    
    > Can't tell from that whether it's hitting the MATCHED or NOT MATCHED side of
    > things to generate that.  But it doesn't work any better if you give it an
    > example that doesn't exist:
    >
    > MERGE INTO Stock t
    >  USING Stock s
    >  ON s.item_id=30 AND s.item_id=t.item_id
    >  WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
    >  WHEN NOT MATCHED THEN INSERT VALUES (30,1)
    >  ;
    >
    > ERROR:  duplicate key value violates unique constraint "stock_item_id_key"
    > DETAIL:  Key (item_id)=(30) already exists.
    
    In this case neither row of the source data matches the join condition
    (s.item_id=30 might as well be constant FALSE as far as the test data
    is concerned) so you attempt to execute the NOT MATCHED side twice.
    So this one also looks correct to me.
    
    > The other thing I noticed that may take some work to sort out is that I
    > haven't had any luck getting MERGE to execute from within a plpgsql
    > function.  I was hoping I could use this to update the pgbench tables:
    
    Good catch.  Considering the size of this patch, I have no problem
    leaving this to the eventual committer to fix, or to a subsequent
    commit.
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
  14. Re: ask for review of MERGE

    Boxuan Zhai <bxzhai2010@gmail.com> — 2010-10-18T14:09:30Z

    On Mon, Oct 18, 2010 at 9:54 PM, Robert Haas <robertmhaas@gmail.com> wrote:
    
    > I think that MERGE is supposed to trigger one rule for each row in the
    > source data.  So:
    >
    > On Sun, Oct 17, 2010 at 8:20 PM, Greg Smith <greg@2ndquadrant.com> wrote:
    > > MERGE INTO Stock t
    > >  USING (SELECT * FROM Stock WHERE item_id=10) AS s
    > >  ON s.item_id=t.item_id
    > >  WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
    > >  WHEN NOT MATCHED THEN INSERT VALUES (10,1)
    > >  ;
    > >
    > > This works fine, and updates the matching row:
    > >
    > > item_id | balance
    > > ---------+---------
    > >     20 |    1900
    > >     10 |    2201
    >
    > Here you have one row of source data, and you got one action (the WHEN
    > MATCHED case).
    >
    > > But if I give it a key that doesn't exist instead:
    > >
    > > MERGE INTO Stock t
    > >  USING (SELECT * FROM Stock WHERE item_id=30) AS s
    > >  ON s.item_id=t.item_id
    > >  WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
    > >  WHEN NOT MATCHED THEN INSERT VALUES (30,1)
    > >  ;
    > >
    > > This doesn't execute the NOT MATCHED case and INSERT the way I expected
    > it
    > > to.  It just gives back "MERGE 0".
    >
    > Here you have no rows of source data (the USING (SELECT ...) doesn't
    > return anything, since no rows exist) so nothing happens.
    >
    >
    Yes.
    The MERGE process is based on a left join between the source table and
    target table.
    Since here the source table is empty, no join is carried, and thus no MERGE
    action is taken.
    
    But, is it correct logically? I mean, should we insert some rows in the
    above example rather  than do nothing?
    
    
    > > Since I wasn't sure if the whole "subquery in the USING clause" case was
    > > really implemented fully, I then tried to do this with something more
    > like
    > > the working regression test examples.  I expected this to do the same
    > thing
    > > as the first example:
    > >
    > > MERGE INTO Stock t
    > >  USING Stock s
    > >  ON s.item_id=10 AND s.item_id=t.item_id
    > >  WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
    > >  WHEN NOT MATCHED THEN INSERT VALUES (10,1)
    > >  ;
    > >
    > > But it gives back this:
    > >
    > > ERROR:  duplicate key value violates unique constraint
    > "stock_item_id_key"
    > > DETAIL:  Key (item_id)=(10) already exists.
    >
    > Here you have two rows of source data.  The ON clause represents the
    > join condition.  The item_id=10 row matches - so you get an update,
    > presumably, though we can't see that as things turn out - and the
    > item_id=20 row doesn't match - so you try to insert (10, 1), which is
    > a duplicate key, thus the error.
    >
    > > Can't tell from that whether it's hitting the MATCHED or NOT MATCHED side
    > of
    > > things to generate that.  But it doesn't work any better if you give it
    > an
    > > example that doesn't exist:
    > >
    > > MERGE INTO Stock t
    > >  USING Stock s
    > >  ON s.item_id=30 AND s.item_id=t.item_id
    > >  WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
    > >  WHEN NOT MATCHED THEN INSERT VALUES (30,1)
    > >  ;
    > >
    > > ERROR:  duplicate key value violates unique constraint
    > "stock_item_id_key"
    > > DETAIL:  Key (item_id)=(30) already exists.
    >
    > In this case neither row of the source data matches the join condition
    > (s.item_id=30 might as well be constant FALSE as far as the test data
    > is concerned) so you attempt to execute the NOT MATCHED side twice.
    > So this one also looks correct to me.
    >
    >
    Yes, that is what happened in the above two examples.
    
    > The other thing I noticed that may take some work to sort out is that I
    > haven't had any luck getting MERGE to execute from within a plpgsql
    > function.  I was hoping I could use this to update the pgbench tables:
    
    Good catch.  Considering the size of this patch, I have no problem
    leaving this to the eventual committer to fix, or to a subsequent
    commit.
    
    --
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
  15. Re: ask for review of MERGE

    Robert Haas <robertmhaas@gmail.com> — 2010-10-18T14:17:37Z

    On Mon, Oct 18, 2010 at 10:09 AM, Boxuan Zhai <bxzhai2010@gmail.com> wrote:
    >
    >
    > On Mon, Oct 18, 2010 at 9:54 PM, Robert Haas <robertmhaas@gmail.com> wrote:
    >>
    >> I think that MERGE is supposed to trigger one rule for each row in the
    >> source data.  So:
    >>
    >> On Sun, Oct 17, 2010 at 8:20 PM, Greg Smith <greg@2ndquadrant.com> wrote:
    >> > MERGE INTO Stock t
    >> >  USING (SELECT * FROM Stock WHERE item_id=10) AS s
    >> >  ON s.item_id=t.item_id
    >> >  WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
    >> >  WHEN NOT MATCHED THEN INSERT VALUES (10,1)
    >> >  ;
    >> >
    >> > This works fine, and updates the matching row:
    >> >
    >> > item_id | balance
    >> > ---------+---------
    >> >     20 |    1900
    >> >     10 |    2201
    >>
    >> Here you have one row of source data, and you got one action (the WHEN
    >> MATCHED case).
    >>
    >> > But if I give it a key that doesn't exist instead:
    >> >
    >> > MERGE INTO Stock t
    >> >  USING (SELECT * FROM Stock WHERE item_id=30) AS s
    >> >  ON s.item_id=t.item_id
    >> >  WHEN MATCHED THEN UPDATE SET balance=s.balance + 1
    >> >  WHEN NOT MATCHED THEN INSERT VALUES (30,1)
    >> >  ;
    >> >
    >> > This doesn't execute the NOT MATCHED case and INSERT the way I expected
    >> > it
    >> > to.  It just gives back "MERGE 0".
    >>
    >> Here you have no rows of source data (the USING (SELECT ...) doesn't
    >> return anything, since no rows exist) so nothing happens.
    >>
    >
    > Yes.
    > The MERGE process is based on a left join between the source table and
    > target table.
    > Since here the source table is empty, no join is carried, and thus no MERGE
    > action is taken.
    > But, is it correct logically? I mean, should we insert some rows in the
    > above example rather  than do nothing?
    
    I don't think so.  I think the right way to write UPSERT is something
    along the lines of:
    
    MERGE INTO Stock t USING (VALUES (10, 1)) s(item_id, balance) ON
    s.item_id = t.item_id ...
    
    (untested)
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
  16. Re: ask for review of MERGE

    Boxuan Zhai <bxzhai2010@gmail.com> — 2010-10-20T01:20:13Z

    Hi,
    
    I considered the empty source table situation again. I think it is correct
    to upsert nothing in this case.
    
    Back to the original logic of MERGE command, it is main purpose is to add
    the supplementary data from the source table into the target table. Thus, an
    empty source table means no input data is available, so no upsert is needed
    in target table.
    
    
    Regards,
    Boxuan
    
  17. Re: ask for review of MERGE

    Greg Smith <greg@2ndquadrant.com> — 2010-10-21T18:36:17Z

    Robert Haas wrote:
    > I think the right way to write UPSERT is something
    > along the lines of:
    >
    > MERGE INTO Stock t USING (VALUES (10, 1)) s(item_id, balance) ON
    > s.item_id = t.item_id ...
    >   
    
    That led in the right direction, after a bit more fiddling I was finally 
    able to get something that does what I wanted:  a single table UPSERT 
    implemented with this MERGE implementation.  Here's a log of a test 
    session, suitable for eventual inclusion in the regression tests:
    
    CREATE TABLE Stock(item_id int UNIQUE, balance int);
    INSERT INTO Stock VALUES (10, 2200);
    INSERT INTO Stock VALUES (20, 1900);
    SELECT * FROM Stock ORDER BY item_id;
    
     item_id | balance
    ---------+---------
          10 |    2200
          20 |    1900
    
    MERGE INTO Stock t
     USING (VALUES(10,100)) AS s(item_id,balance)
     ON s.item_id=t.item_id
     WHEN MATCHED THEN UPDATE SET balance=t.balance + s.balance
     WHEN NOT MATCHED THEN INSERT VALUES(s.item_id,s.balance)
     ;
    
    MERGE 1
    
    SELECT * FROM Stock ORDER BY item_id;
     item_id | balance
    ---------+---------
          10 |    2300
          20 |    1900
    
    MERGE INTO Stock t
     USING (VALUES(30,2000)) AS s(item_id,balance)
     ON s.item_id=t.item_id
     WHEN MATCHED THEN UPDATE SET balance=t.balance + s.balance
     WHEN NOT MATCHED THEN INSERT VALUES(s.item_id,s.balance)
     ;
    
    MERGE 1
    SELECT * FROM Stock ORDER BY item_id;
     item_id | balance
    ---------+---------
          10 |    2300
          20 |    1900
          30 |    2000
    
    I'm still a little uncertain as to whether any of my other examples 
    should have worked under the spec but just didn't work here, but I'll 
    worry about that later.
    
    Here's what the query plan looks like on a MATCH:
    
     Merge  (cost=0.00..8.29 rows=1 width=22) (actual time=0.166..0.166 
    rows=0 loops=1)
       Action 1: Update When Matched
       Action 2: Insert When Not Mactched
       MainPlan:
       ->  Nested Loop Left Join  (cost=0.00..8.29 rows=1 width=22) (actual 
    time=0.050..0.061 rows=1 loops=1)
             ->  Values Scan on "*VALUES*"  (cost=0.00..0.01 rows=1 width=8) 
    (actual time=0.009..0.010 rows=1 loops=1)
             ->  Index Scan using stock_item_id_key on stock t  
    (cost=0.00..8.27 rows=1 width=14) (actual time=0.026..0.030 rows=1 loops=1)
                   Index Cond: ("*VALUES*".column1 = item_id)
     Total runtime: 0.370 ms
    
    
    And here's a miss:
    
     Merge  (cost=0.00..8.29 rows=1 width=22) (actual time=0.145..0.145 
    rows=0 loops=1)
       Action 1: Update When Matched
       Action 2: Insert When Not Mactched
       MainPlan:
       ->  Nested Loop Left Join  (cost=0.00..8.29 rows=1 width=22) (actual 
    time=0.028..0.033 rows=1 loops=1)
             ->  Values Scan on "*VALUES*"  (cost=0.00..0.01 rows=1 width=8) 
    (actual time=0.004..0.005 rows=1 loops=1)
             ->  Index Scan using stock_item_id_key on stock t  
    (cost=0.00..8.27 rows=1 width=14) (actual time=0.015..0.015 rows=0 loops=1)
                   Index Cond: ("*VALUES*".column1 = item_id)
     Total runtime: 0.255 ms
    
    Next steps here:
    1) Performance/concurrency tests against trigger-based UPSERT approach.
    2) Finish bit rot cleanup against HEAD.
    3) Work out more complicated test cases to try and fine more unexpected 
    behavior edge cases and general bugs.
    
    -- 
    Greg Smith, 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
    PostgreSQL Training, Services and Support  www.2ndQuadrant.us
    
    
    
    
  18. Re: ask for review of MERGE

    Stefan Kaltenbrunner <stefan@kaltenbrunner.cc> — 2010-10-22T15:35:19Z

    On 10/21/2010 08:36 PM, Greg Smith wrote:
    > Robert Haas wrote:
    >> I think the right way to write UPSERT is something
    >> along the lines of:
    >>
    >> MERGE INTO Stock t USING (VALUES (10, 1)) s(item_id, balance) ON
    >> s.item_id = t.item_id ...
    
    [...]
    
    > Here's what the query plan looks like on a MATCH:
    >
    > Merge (cost=0.00..8.29 rows=1 width=22) (actual time=0.166..0.166 rows=0
    > loops=1)
    > Action 1: Update When Matched
    > Action 2: Insert When Not Mactched
    
    "Mactched"? - is this a c&p error or the actual output of EXPLAIN? :)
    
    
    lg
    
    
    Stefan
    
    
  19. Re: ask for review of MERGE

    Greg Smith <greg@2ndquadrant.com> — 2010-10-23T05:34:58Z

    There are now two branches of MERGE code in review progress available.  
    http://github.com/greg2ndQuadrant/postgres/tree/merge-unstable has the 
    bit-rotted version that doesn't quite work against HEAD yet, while 
    http://github.com/greg2ndQuadrant/postgres/tree/merge is what I'm still 
    testing against until I get that sorted out.
    
    Attached is a tar file containing an initial concurrent MERGE test 
    case.  What it does is create is a pgbench database with a scale of 1.  
    Then it runs a custom test that does an UPSERT using MERGE against 
    pgbench_accounts, telling pgbench the database scale is actually 2.  
    This means that approximately half of the statements executed will hit 
    the MATCHED side of the merge and update existing rows, while half will 
    hit the NOT MATCHED one and create new account records.  Did some small 
    tests using pgbench's debug mode where you see all the statements it 
    executes, and all the output looked correct.  Successive tests runs are 
    not deterministically perfect performance comparisons, but with enough 
    transactions I hope that averages out.
    
    For comparison sake, there's an almost identical test case that does the 
    same thing using the pl/pgsql example function from the PostgreSQL 
    manual for the UPSERT instead also in there.  You just run test-merge.sh 
    or test-function.sh and it runs the whole test, presuming you built and 
    installed pgbench and don't mind that it will blow away any database 
    named pgbench you already have.
    
    Since the current MERGE implementation is known not to be optimized for 
    concurrency, my main goal here wasn't to see how fast it was.  That 
    number is interesting though.  With the sole postgresql.conf change of:
    
    checkpoint_settings=32
    
    And a debug/assertion build using 8 concurrent clients, I got 1607 TPS 
    of UPSERT out of the trigger approach @ 6MB/s of writes to disk, while 
    the MERGE one delivered 988 TPS @ 4.5MB/s of writes.  Will explore this 
    more later.
    
    This did seem to find a bug in the implementation after running for a while:
    
    TRAP: FailedAssertion("!(epqstate->origslot != ((void *)0))", File: 
    "execMain.c", Line: 1732)
    
    Line number there is relative to what you can see at 
    http://github.com/greg2ndQuadrant/postgres/blob/merge/src/backend/executor/execMain.c
    and that's a null pointer check at the beginning of 
    EvalPlanQualFetchRowMarks.  Haven't explored why this happened or how 
    repeatable this is, but since Boxuan was looking for some bugs to chase 
    I wanted to deliver him one to chew on.
    
    While the performance doesn't need to be great in V1, there needs to be 
    at least some minimal protection against concurrency issues before this 
    is commit quality.  Will continue to shake this code out looking for 
    them now that I have some basic testing that works for doing so.
    
    -- 
    Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
    PostgreSQL Training, Services and Support        www.2ndQuadrant.us
    
    
    
  20. Re: ask for review of MERGE

    Marko Tiikkaja <marko.tiikkaja@cs.helsinki.fi> — 2010-10-23T12:50:01Z

    On 2010-10-23 8:34 AM +0300, Greg Smith wrote:
    > While the performance doesn't need to be great in V1, there needs to be
    > at least some minimal protection against concurrency issues before this
    > is commit quality.
    
    What's been bothering me is that so far there has not been an agreement 
    on whether we need to protect against concurrency issues or not.  In 
    fact, there has been almost no discussion about the concurrency issues 
    which AIUI have been the biggest single reason we don't have MERGE 
    already.  Right now, this patch fails in even the simplest scenario:
    
    =# create table foo(a int unique);
    NOTICE:  CREATE TABLE / UNIQUE will create implicit index "foo_a_key" 
    for table "foo"
    CREATE TABLE
    
    T1=# begin;
    BEGIN
    T1=# merge into foo using (values (1)) s(i) on s.i = foo.a when matched 
    then update set a=a+1 when not matched then insert values (s.i);
    MERGE 1
    
    T2=# merge into foo using (values (1)) s(i) on s.i = foo.a when matched 
    then update set a=a+1 when not matched then insert values (s.i);
    -- blocks
    
    T1=# commit;
    COMMIT
    
    .. and T2 gets a unique constraint violation.
    
    
    As far as I'm concerned, the reason people want MERGE is to avoid these 
    problems; the nicer syntax is just a bonus.  Having to LOCK the target 
    table makes this feature a lot less useful, even though there are a few 
    use cases where locking the table would be OK.
    
    
    Regards,
    Marko Tiikkaja
    
    
  21. Re: ask for review of MERGE

    Greg Smith <greg@2ndquadrant.com> — 2010-10-23T14:31:05Z

    Marko Tiikkaja wrote:
    > What's been bothering me is that so far there has not been an 
    > agreement on whether we need to protect against concurrency issues or 
    > not.  In fact, there has been almost no discussion about the 
    > concurrency issues which AIUI have been the biggest single reason we 
    > don't have MERGE already.
    
    Sure there were, they just happened a long time ago.  I tried to 
    summarize the previous discussion at 
    http://wiki.postgresql.org/wiki/SQL_MERGE with the following:
    
    "PostgreSQL doesn't have a good way to lock access to a key value that 
    doesn't exist yet--what other databases call key range 
    locking...Improvements to the index implementation are needed to allow 
    this feature."
    
    You can sift through the other archive links in there, the discussion 
    leading up to that conclusion is in there somewhere.  And we had a 
    discussion of this at the last developer's meeting where this was 
    identified as a key issue to sort out before this was done; see the 
    table at the end of 
    http://wiki.postgresql.org/wiki/PgCon_2010_Developer_Meeting and note 
    the warning associated with this feature.
    
    The deadlock on developing this feature has been that there's little 
    benefit to building key range locking on its own, or even a good way to 
    prove that it works, without a visible feature that uses it.  It's much 
    easier to justify development time on if the rest of MERGE is known to 
    work, and just needs that plugged in to improve concurrency.  And since 
    Boxuan appeared at the right time to do the syntax and implementation 
    part first as well, that's the order it's ended up happening in.  We're 
    only making the concurrency part a second priority right now in order to 
    break the development into managable chunks.
    
    -- 
    Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
    PostgreSQL Training, Services and Support        www.2ndQuadrant.us
    
    
    
    
  22. Re: ask for review of MERGE

    Tom Lane <tgl@sss.pgh.pa.us> — 2010-10-23T16:12:44Z

    Greg Smith <greg@2ndquadrant.com> writes:
    > Marko Tiikkaja wrote:
    >> What's been bothering me is that so far there has not been an 
    >> agreement on whether we need to protect against concurrency issues or 
    >> not.  In fact, there has been almost no discussion about the 
    >> concurrency issues which AIUI have been the biggest single reason we 
    >> don't have MERGE already.
    
    > Sure there were, they just happened a long time ago.  I tried to 
    > summarize the previous discussion at 
    > http://wiki.postgresql.org/wiki/SQL_MERGE with the following:
    
    > "PostgreSQL doesn't have a good way to lock access to a key value that 
    > doesn't exist yet--what other databases call key range 
    > locking...Improvements to the index implementation are needed to allow 
    > this feature."
    
    This seems to be presuming there's a consensus that that's the way to
    implement it, which is news to me.  Why wouldn't we try the INSERT
    first, and if we get a unique-key failure, do UPDATE instead?  I am not
    at all in agreement that we ought to build key range locking, nor that
    it'd be particularly helpful for this problem even if we had it.
    
    			regards, tom lane
    
    
  23. Re: ask for review of MERGE

    Boxuan Zhai <bxzhai2010@gmail.com> — 2010-10-23T20:30:10Z

    >
    >
    > This did seem to find a bug in the implementation after running for a
    > while:
    >
    > TRAP: FailedAssertion("!(epqstate->origslot != ((void *)0))", File:
    > "execMain.c", Line: 1732)
    >
    > Line number there is relative to what you can see at
    > http://github.com/greg2ndQuadrant/postgres/blob/merge/src/backend/executor/execMain.c
    > and that's a null pointer check at the beginning of
    > EvalPlanQualFetchRowMarks.  Haven't explored why this happened or how
    > repeatable this is, but since Boxuan was looking for some bugs to chase I
    > wanted to deliver him one to chew on.
    >
    >
    I am not sure how this bug comes. I will try to find out the reason for it.
    
  24. Re: ask for review of MERGE

    Greg Stark <gsstark@mit.edu> — 2010-10-23T22:59:13Z

    On Sat, Oct 23, 2010 at 9:12 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
    >> "PostgreSQL doesn't have a good way to lock access to a key value that
    >> doesn't exist yet--what other databases call key range
    >> locking...Improvements to the index implementation are needed to allow
    >> this feature."
    >
    > This seems to be presuming there's a consensus that that's the way to
    > implement it, which is news to me.  Why wouldn't we try the INSERT
    > first, and if we get a unique-key failure, do UPDATE instead?  I am not
    > at all in agreement that we ought to build key range locking, nor that
    > it'd be particularly helpful for this problem even if we had it.
    
    Except we *do* have range locking for the special case of inserting
    equal values into a unique btree index. In that case the btree insert
    locks the leaf page containing the conflicting key value --
    effectively locking out anyone else from inserting the same key value.
     That lock is held until the index entry pointing to the newly
    inserted value is finished. That's what prevents two inserters from
    inserting the same key value.
    
    So the trick for MERGE on equality would be to refactor the btree api
    so that you could find the matching leaf page and *keep* that page
    locked. Then do an update against the conflicting row found there if
    any without ever releasing that page lock. Someone else can come along
    and delete the row (or have already deleted it) before the update
    locks it but if they do you can insert your row without worrying about
    anyone else inserting a conflicting entry since you're still holding a
    lock on the page they'll need to insert the btree entry on and have
    been holding it continuously for the whole operation.
    
    I'm not sure how the new exclusion constraints stuff affects this. I
    think they would work the same way except instead of holding a page
    lock on the leaf page they would store the value being edited in the
    in-memory array -- effectively another form of key range locking.
    
    I think the blocker with MERGE has always been that the standard is
    *so* general that it could apply to conditions that *aren't* covered
    by a unique constraint or exclusion constriant. Personally, I'm fine
    saying that those cases will perform poorly, locking the table, or
    aren't allowed and print a warning explaining exactly what exclusion
    constraint or btree unique index would be necessary to allow it. I
    think virtually every case where people will want to use it will be on
    a primary key anyways.
    
    -- 
    greg
    
    
  25. Re: ask for review of MERGE

    Josh Berkus <josh@agliodbs.com> — 2010-10-23T23:03:36Z

    > So the trick for MERGE on equality would be to refactor the btree api
    > so that you could find the matching leaf page and *keep* that page
    > locked. Then do an update against the conflicting row found there if
    > any without ever releasing that page lock. Someone else can come along
    > and delete the row (or have already deleted it) before the update
    > locks it but if they do you can insert your row without worrying about
    > anyone else inserting a conflicting entry since you're still holding a
    > lock on the page they'll need to insert the btree entry on and have
    > been holding it continuously for the whole operation.
    
    I think that such a lock would also be useful for improving the FK 
    deadlock issues we have.
    
    -- 
                                       -- Josh Berkus
                                          PostgreSQL Experts Inc.
                                          http://www.pgexperts.com
    
    
  26. Re: ask for review of MERGE

    Greg Stark <gsstark@mit.edu> — 2010-10-24T07:32:13Z

    On Sat, Oct 23, 2010 at 4:03 PM, Josh Berkus <josh@agliodbs.com> wrote:
    > I think that such a lock would also be useful for improving the FK deadlock
    > issues we have.
    
    I don't see how. I think the problem you're referring to occurs when
    different plans update rows in different orders and the resulting
    locks on foreign key targets are taken in different orders. In which
    case the problem isn't that we're unable to lock the resources --
    they're locked using regular row locks -- but rather that there's
    nothing controlling the ordering of the locks.
    
    I don't think it would be acceptable to hold low level btree page
    locks across multiple independent row operations on different rows and
    I don't see how they would be any better than the row locks we have
    now. Worse, the resulting deadlock would no longer be detectable (one
    of the reasons it wouldn't be acceptable to hold the lock for so
    long).
    
    That does point out a problem with the logic I sketched. If you go to
    do an update and find there's an uncommitted update pending you have
    to wait on it. You can't do that while holding the index page lock. I
    assume then you release it and wait on the uncommitted transaction and
    when it's finished you start over doing the btree lookup and
    reacquiring the lock. I haven't thought it through in detail though.
    
    -- 
    greg
    
    
  27. Re: ask for review of MERGE

    Martijn van Oosterhout <kleptog@svana.org> — 2010-10-24T09:50:09Z

    On Sat, Oct 23, 2010 at 03:59:13PM -0700, Greg Stark wrote:
    > I think the blocker with MERGE has always been that the standard is
    > *so* general that it could apply to conditions that *aren't* covered
    > by a unique constraint or exclusion constriant. Personally, I'm fine
    > saying that those cases will perform poorly, locking the table, or
    > aren't allowed and print a warning explaining exactly what exclusion
    > constraint or btree unique index would be necessary to allow it. I
    > think virtually every case where people will want to use it will be on
    > a primary key anyways.
    
    Can we please not get MERGE hung up on trying to make it atomic. The
    standard requires no such thing and there are plenty of uses of MERGE
    that don't involve high concurrency updates of the same row by
    different processes. If we want an atomic UPSERT, make that a seperate
    command, MERGE without atomicity is useful enough already.
    
    Simply, if the row is visible in the current snapshot you do an update
    otherwise an insert. If you get an error, raise it. Icing can be added
    later.
    
    Have a nice day,
    -- 
    Martijn van Oosterhout   <kleptog@svana.org>   http://svana.org/kleptog/
    > Patriotism is when love of your own people comes first; nationalism,
    > when hate for people other than your own comes first. 
    >                                       - Charles de Gaulle
    
  28. Re: ask for review of MERGE

    Robert Haas <robertmhaas@gmail.com> — 2010-10-24T12:05:29Z

    On Sun, Oct 24, 2010 at 5:50 AM, Martijn van Oosterhout
    <kleptog@svana.org> wrote:
    > On Sat, Oct 23, 2010 at 03:59:13PM -0700, Greg Stark wrote:
    >> I think the blocker with MERGE has always been that the standard is
    >> *so* general that it could apply to conditions that *aren't* covered
    >> by a unique constraint or exclusion constriant. Personally, I'm fine
    >> saying that those cases will perform poorly, locking the table, or
    >> aren't allowed and print a warning explaining exactly what exclusion
    >> constraint or btree unique index would be necessary to allow it. I
    >> think virtually every case where people will want to use it will be on
    >> a primary key anyways.
    >
    > Can we please not get MERGE hung up on trying to make it atomic. The
    > standard requires no such thing and there are plenty of uses of MERGE
    > that don't involve high concurrency updates of the same row by
    > different processes. If we want an atomic UPSERT, make that a seperate
    > command, MERGE without atomicity is useful enough already.
    >
    > Simply, if the row is visible in the current snapshot you do an update
    > otherwise an insert. If you get an error, raise it. Icing can be added
    > later.
    
    Long term, I'm wondering if the permanent fix for this problem might
    involve something similar to what EXCLUDE does - use a dirty snapshot
    for the scan of the target table, and block on in=d
    
    Yeah, I couldn't have said it better.  It's an annoying implementation
    restriction but only that.  I think it's a perfectly acceptable
    restriction for an initial commit.  It's no secret that our process
    has trouble absorbing large patches.
    
    I am also wondering exactly what the semantics are supposed to be
    under concurrency.  We can't assume that it's OK to treat WHEN NOT
    MATCHED as WHEN MATCHED if a unique-key violation is encountered.  The
    join condition could be something else entirely.  I guess we could
    scan the target table with a dirty snapshot and block on any in-doubt
    tuples, similar to what EXCLUDE constraints do internally, but
    throwing MVCC out the window doesn't seem right either.
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
  29. Re: ask for review of MERGE

    Greg Smith <greg@2ndquadrant.com> — 2010-10-24T16:15:54Z

    Robert Haas wrote:
    > I am also wondering exactly what the semantics are supposed to be
    > under concurrency.  We can't assume that it's OK to treat WHEN NOT
    > MATCHED as WHEN MATCHED if a unique-key violation is encountered.  The
    > join condition could be something else entirely.  I guess we could
    > scan the target table with a dirty snapshot and block on any in-doubt
    > tuples, similar to what EXCLUDE constraints do internally, but
    > throwing MVCC out the window doesn't seem right either.
    >   
    
    You've answered Tom's question of why not to just INSERT and trap the 
    error here.  Presuming a unique key violation is what will kick back in 
    this situation and to just follow the other side doesn't cover all the 
    ways this can get used.
    
    I'm thinking of this problem now as being like the one that happens when 
    a concurrent UPDATE happens.  To quote from the docs here:
    
    "a target row might have already been updated (or deleted or locked) by 
    another concurrent transaction by the time it is found. In this case, 
    the would-be updater will wait for the first updating transaction to 
    commit or roll back (if it is still in progress). If the first updater 
    rolls back, then its effects are negated and the second updater can 
    proceed with updating the originally found row. If the first updater 
    commits, the second updater will ignore the row if the first updater 
    deleted it, otherwise it will attempt to apply its operation to the 
    updated version of the row. The search condition of the command (the 
    WHERE clause) is re-evaluated to see if the updated version of the row 
    still matches the search condition. If so, the second updater proceeds 
    with its operation using the updated version of the row."
    
    What I think we can do with adding a key reservation is apply the same 
    sort of logic to INSERTs too, given a way to lock an index entry before 
    the row itself is complete.  If MERGE hits a WHERE condition that finds 
    a key lock entry in the index, it has to sit and wait for that other 
    command to finish.  There isn't any other sensible behavior I can see in 
    that situation, just like there isn't one for UPDATE right now.
    
    If the transaction that was locking the index entry commits, it has to 
    start over with re-evaluating the WHEN MATCHED part (the equivilent of 
    the WHERE in the UPDATE case).  But it shouldn't need to do a new JOIN 
    again, because that condition was proven to be met already by the lock 
    squatting on that index key, without even having the rest of the row 
    there yet to check its data.  If the other entry commits, it would then 
    follow the WHEN MATCHED path and in the UPSERT case do an UPDATE.  If 
    the transaction who had the key reservervation rolls back, the 
    re-evaluation of the MERGE WHEN MATCHED will fail, at which point it 
    follows the WHERE FOUND INSERT path.  As it will now be the locker of 
    the key reservation it had been waiting on earlier at that point, it 
    will be free to do the INSERT with no concerns about race conditions or 
    retries.  In the SERIALIZABLE case, again just try to follow the same 
    slightly different rules that exist for concurrent UPDATE commands.
    
    There may be a tricky point here that we will need to warn people that 
    UPSERT implementations need to make sure they only reference the columns 
    of the primary key in the MERGE conditions for this to work as expected, 
    because otherwise they might get back a unique key violation error 
    anyway.  I don't know how other databases deal with that particular 
    problem.  I have considered following the somewhat distasteful path of 
    installing SQL Server or Oracle here just to see how they handle some of 
    the pathological test cases I'm now thinking about for this command.
    
    This particular weak spot in MVCC is already there for UPDATE, and this 
    approach seems to me to be the most logical way to extend the same basic 
    implementation to also eliminate some concurrent MERGE race conditions, 
    including the most common one the UPSERT case is stuck with.  But I have 
    no intention of halting work on the basic MERGE to wait for this problem 
    to be solved even if I'm completely wrong about what I've outlined 
    above.  That style of thinking--don't even start until every a perfect 
    solution for every possible problem is known--is something I consider a 
    problem in this community's development model, one that has contributed 
    to why no work has been done on this feature until now.  This one splits 
    nicely into "get the implemenation working" and "improve the 
    concurrency" parts, and I haven't heard a good reason yet why those need 
    to coupled together.
    
    -- 
    Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
    PostgreSQL Training, Services and Support        www.2ndQuadrant.us
    
    
    
    
  30. Re: ask for review of MERGE

    Robert Haas <robertmhaas@gmail.com> — 2010-10-24T17:43:35Z

    On Sun, Oct 24, 2010 at 12:15 PM, Greg Smith <greg@2ndquadrant.com> wrote:
    > Robert Haas wrote:
    >> I am also wondering exactly what the semantics are supposed to be
    >> under concurrency.  We can't assume that it's OK to treat WHEN NOT
    >> MATCHED as WHEN MATCHED if a unique-key violation is encountered.  The
    >> join condition could be something else entirely.  I guess we could
    >> scan the target table with a dirty snapshot and block on any in-doubt
    >> tuples, similar to what EXCLUDE constraints do internally, but
    >> throwing MVCC out the window doesn't seem right either.
    >
    > [discussion of EPQ behavior for UPDATE statements]
    >
    > What I think we can do with adding a key reservation is apply the same sort
    > of logic to INSERTs too, given a way to lock an index entry before the row
    > itself is complete.  If MERGE hits a WHERE condition that finds a key lock
    > entry in the index, it has to sit and wait for that other command to finish.
    >  There isn't any other sensible behavior I can see in that situation, just
    > like there isn't one for UPDATE right now.
    
    Well, there's no guarantee that any index at all exists on the target
    table, so any solution based on index locks can't be fully general.
    
    But let's back up and talk about MVCC for a minute.  Suppose we have
    three source tuples, (1), (2), and (3); and the target table contains
    tuples (1) and (2), of which only (1) is visible to our MVCC snapshot;
    suppose also an equijoin.  Clearly, source tuple (1) should fire the
    MATCHED rule and source tuple (3) should fire the NOT MATCHED rule,
    but what in the world should source tuple (2) do?  AFAICS, the only
    sensible behavior is to throw a serialization error, because no matter
    what you do the results won't be equivalent to a serial execution of
    the transaction that committed target tuple (2) and the transaction
    that contains the MERGE.
    
    Even with predicate locks, it's not obvious how to me how solve this
    problem.  Target tuple (2) may already be there, and its transaction
    already committed, by the time the MERGE statement gets around to
    looking at the source data.  In fact, even taking an
    AccessExclusiveLock on the target table doesn't fix this, because the
    snapshot would be taken before the lock.
    
    > been done on this feature until now.  This one splits nicely into "get the
    > implemenation working" and "improve the concurrency" parts, and I haven't
    > heard a good reason yet why those need to coupled together.
    
    Sounds like we're all on the same page.
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
  31. Re: ask for review of MERGE

    Greg Smith <greg@2ndquadrant.com> — 2010-10-24T21:39:12Z

    Robert Haas wrote:
    > Well, there's no guarantee that any index at all exists on the target
    > table, so any solution based on index locks can't be fully general.
    >   
    
    Sure, but in the most common use case I think we're targeting at first, 
    no indexes means there's also no unique constraints either.  I don't 
    think people can reasonable expect to MERGE or anything else to 
    guarantee they won't accidentally create two rows that conflict via the 
    terms of some non-database enforced key.
    
    I am too brain-dead this particular Sunday to think of exactly how to 
    deal with the 3-tuple case you described, except to say that I think 
    it's OK for complicated situations to give up and throw a serialization 
    error.  I'm already collecting a list of pathological tests and will try 
    to add something based on your problem case, then see what we can do 
    with it later.
    
    -- 
    Greg Smith, 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
    PostgreSQL Training, Services and Support  www.2ndQuadrant.us
    
    
    
    
  32. Re: ask for review of MERGE

    Greg Stark <gsstark@mit.edu> — 2010-10-24T23:11:51Z

    On Sun, Oct 24, 2010 at 2:50 AM, Martijn van Oosterhout
    <kleptog@svana.org> wrote:
    > Can we please not get MERGE hung up on trying to make it atomic. The
    > standard requires no such thing and there are plenty of uses of MERGE
    > that don't involve high concurrency updates of the same row by
    > different processes. If we want an atomic UPSERT, make that a seperate
    > command, MERGE without atomicity is useful enough already.
    
    Really? I don't really see any point in a non-atomic MERGE. Nor in a
    non-atomic UPDATE or INSERT or any other operation. The A in ACID is
    as important as any other letter.
    
    For that matter if you don't care about atomicity then this is a
    problem already solvable easily solvable in the application. Why
    bother providing a special command for it. The whole reason to want a
    special command is precisely because the database can implement it
    atomically more easily and more efficiently than any application
    implementation.
    
    Moreover having a case which is non-atomic and allows inconsistent
    results or errors in the face of concurrent updates is a foot-gun.
    Someone will come along and try to use it and it will appear to work
    in their application but introduce nasty hidden race conditions.
    
    -- 
    greg
    
    
  33. Re: ask for review of MERGE

    Greg Stark <gsstark@mit.edu> — 2010-10-24T23:16:56Z

    On Sun, Oct 24, 2010 at 2:39 PM, Greg Smith <greg@2ndquadrant.com> wrote:
    > Sure, but in the most common use case I think we're targeting at first, no
    > indexes means there's also no unique constraints either.  I don't think
    > people can reasonable expect to MERGE or anything else to guarantee they
    > won't accidentally create two rows that conflict via the terms of some
    > non-database enforced key.
    
    I'm fine with either a) having the merge constraint be required to
    match exactly either a exclusion constraint or unique index or throw
    an error or b) lock the table if you perform a merge against a table
    on a non-indexed condition like foreign keys do if you're missing the
    relevant index.
    
    Either way I expect this case to be a rare use case where users are
    either doing data loads and locking the table against concurrent
    updates is fine or they will immediately realize the error of their
    ways and create the corresponding unique or exclusion constraint
    index.
    
    Other than bulk data loads I don't see any useful use case that
    doesn't have the corresponding exclusion constraint or unique index as
    a hard requirement anyways. It'll be necessary for both correctness
    and performance.
    
    -- 
    greg
    
    
  34. Re: ask for review of MERGE

    Robert Haas <robertmhaas@gmail.com> — 2010-10-25T03:19:26Z

    On Sun, Oct 24, 2010 at 7:11 PM, Greg Stark <gsstark@mit.edu> wrote:
    > On Sun, Oct 24, 2010 at 2:50 AM, Martijn van Oosterhout
    > <kleptog@svana.org> wrote:
    >> Can we please not get MERGE hung up on trying to make it atomic. The
    >> standard requires no such thing and there are plenty of uses of MERGE
    >> that don't involve high concurrency updates of the same row by
    >> different processes. If we want an atomic UPSERT, make that a seperate
    >> command, MERGE without atomicity is useful enough already.
    >
    > Really? I don't really see any point in a non-atomic MERGE. Nor in a
    > non-atomic UPDATE or INSERT or any other operation. The A in ACID is
    > as important as any other letter.
    
    I think we're really discussing the "I" in ACID, not the "A".  There's
    no question that the MERGE transaction will either commit or fail.
    What we're talking about is what happens when there are concurrent
    table modifications in progress; and the answer is that you might get
    serialization anomalies.  But we have serialization anomalies without
    MERGE, too - see the discussions around Kevin Grittner's SSI patch
    which, come to think of it, might be useful for this case, too.
    
    I posted an example upthread which I think demonstrates very clearly
    that MERGE will result in unavoidable serialization failures - so  if
    the standard is that we mustn't have any serialization failures then
    the standard can never be met.  The best we can hope for is that we'll
    detect them and roll back if they occur, rather than going on to
    commit but perhaps with some anomaly.  And I'm pretty sure that's what
    KG's SSI patch is going to give us.  So I'm not sure there's really
    anything to get worked up about here in terms of concurrency issues.
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
  35. Re: ask for review of MERGE

    Greg Stark <gsstark@mit.edu> — 2010-10-25T17:42:35Z

    On Sun, Oct 24, 2010 at 10:43 AM, Robert Haas <robertmhaas@gmail.com> wrote:
    > But let's back up and talk about MVCC for a minute.  Suppose we have
    > three source tuples, (1), (2), and (3); and the target table contains
    > tuples (1) and (2), of which only (1) is visible to our MVCC snapshot;
    > suppose also an equijoin.  Clearly, source tuple (1) should fire the
    > MATCHED rule and source tuple (3) should fire the NOT MATCHED rule,
    > but what in the world should source tuple (2) do?  AFAICS, the only
    > sensible behavior is to throw a serialization error, because no matter
    > what you do the results won't be equivalent to a serial execution of
    > the transaction that committed target tuple (2) and the transaction
    > that contains the MERGE.
    
    So the behaviour we get with UPDATE in this situation is that we
    update (2) so I would expect this to execute the MATCHED rule. The key
    distinction is that since we're not returning the data to the user the
    user sees we want to update the most recent version and it's "almost"
    as if we ran "after" all the other transactions. It's not really
    serializable and I think in serializable mode we throw a serialization
    failure instead but in most usage patterns it's precisely what the
    user wants.
    
    Here "bbb" contained two records when we began with values "1" and "2"
    but the "2" was inserted in a transaction which hadn't committed yet.
    It commited after the update.
    
    postgres=> begin;
    BEGIN
    postgres=> select * from bbb;
     i
    ---
     1
    (1 row)
    
    postgres=> update bbb set i = i+1;
    UPDATE 2
    postgres=> commit;
    COMMIT
    postgres=> select * from bbb;
     i
    ---
     2
     3
    (2 rows)
    
    
    
    -- 
    greg
    
    
  36. Re: ask for review of MERGE

    Kevin Grittner <kevin.grittner@wicourts.gov> — 2010-10-25T18:07:52Z

    Greg Stark <gsstark@mit.edu> wrote:
    > Robert Haas <robertmhaas@gmail.com> wrote:
    >> But let's back up and talk about MVCC for a minute.  Suppose we
    >> have three source tuples, (1), (2), and (3); and the target table
    >> contains tuples (1) and (2), of which only (1) is visible to our
    >> MVCC snapshot; suppose also an equijoin.  Clearly, source tuple
    >> (1) should fire the MATCHED rule and source tuple (3) should fire
    >> the NOT MATCHED rule, but what in the world should source tuple
    >> (2) do?  AFAICS, the only sensible behavior is to throw a
    >> serialization error, because no matter what you do the results
    >> won't be equivalent to a serial execution of the transaction that
    >> committed target tuple (2) and the transaction that contains the
    >> MERGE.
    > 
    > So the behaviour we get with UPDATE in this situation is that we
    > update (2) so I would expect this to execute the MATCHED rule.
     
    Certainly that would be consistent with the behavior of READ
    COMMITTED -- wait for commit or rollback of the concurrent
    transaction, and then proceed with whatever data is there after
    completion of the other transaction.  With REPEATABLE READ or
    SERIALIZABLE you would block until commit of the other transaction
    and terminate with a write conflict -- a form of serialization
    failure.  If the other transaction rolls back you INSERT.
     
    At least, that would be the least surprising behavior to me.
     
    -Kevin
    
    
  37. Re: ask for review of MERGE

    Robert Haas <robertmhaas@gmail.com> — 2010-10-25T18:09:54Z

    On Mon, Oct 25, 2010 at 1:42 PM, Greg Stark <gsstark@mit.edu> wrote:
    > On Sun, Oct 24, 2010 at 10:43 AM, Robert Haas <robertmhaas@gmail.com> wrote:
    >> But let's back up and talk about MVCC for a minute.  Suppose we have
    >> three source tuples, (1), (2), and (3); and the target table contains
    >> tuples (1) and (2), of which only (1) is visible to our MVCC snapshot;
    >> suppose also an equijoin.  Clearly, source tuple (1) should fire the
    >> MATCHED rule and source tuple (3) should fire the NOT MATCHED rule,
    >> but what in the world should source tuple (2) do?  AFAICS, the only
    >> sensible behavior is to throw a serialization error, because no matter
    >> what you do the results won't be equivalent to a serial execution of
    >> the transaction that committed target tuple (2) and the transaction
    >> that contains the MERGE.
    >
    > So the behaviour we get with UPDATE in this situation is that we
    > update (2) so I would expect this to execute the MATCHED rule.
    
    Not exactly.  Consider this example:
    
    rhaas=# create table concurrent (x integer primary key);
    NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index
    "concurrent_pkey" for table "concurrent"
    CREATE TABLE
    rhaas=# insert into x values (1);
    rhaas=# begin;
    BEGIN
    rhaas=# insert into concurrent values (2);
    INSERT 0 1
    
    <switch to a different window>
    
    rhaas=# update concurrent set x=x where x=2;
    UPDATE 0
    
    > The key
    > distinction is that since we're not returning the data to the user the
    > user sees we want to update the most recent version and it's "almost"
    > as if we ran "after" all the other transactions. It's not really
    > serializable and I think in serializable mode we throw a serialization
    > failure instead but in most usage patterns it's precisely what the
    > user wants.
    
    I think it would be perfectly reasonable to have a transaction
    isolation level that does not use a snapshot at all and instead runs
    everything relative to SnapshotNow, and people could use it with MERGE
    if they were so inclined.  I think this would correspond more or less
    to the READ COMMITTED isolation level specified in the standard; what
    we now call READ COMMITTED is actually better than READ COMMITTED but
    not quite as good as REPEATABLE READ.  That, combined with an
    exclusive lock on the table (or, perhaps, some kind of predicate
    locking mechanism) would be sufficient to prevent serialization
    anomalies.
    
    However, I don't think that implementing those semantics for just this
    one command (or part of it) makes a whole lot of sense.  The EPQ
    behavior of our current default isolation level is really pretty
    strange, and adding a random wart that the target table (but not the
    source table) in a MERGE query gets scanned using SnapshotNow would be
    one more piece of strangeness atop the strangeness we already have.
    And, as we just saw with the enum stuff, SnapshotNow can lead to some
    awfully strange behavior - you could end up processing half of the
    data from a concurrent transaction and missing the other half.
    
    > Here "bbb" contained two records when we began with values "1" and "2"
    > but the "2" was inserted in a transaction which hadn't committed yet.
    > It commited after the update.
    >
    > postgres=> begin;
    > BEGIN
    > postgres=> select * from bbb;
    >  i
    > ---
    >  1
    > (1 row)
    >
    > postgres=> update bbb set i = i+1;
    > UPDATE 2
    > postgres=> commit;
    > COMMIT
    > postgres=> select * from bbb;
    >  i
    > ---
    >  2
    >  3
    > (2 rows)
    
    Well, at least on my system, if the transaction inserting (2) hasn't
    committed yet, that UPDATE statement will block until it does, because
    trying to change i from 1 to 2 causes the update of the unique index
    to block, since there's an in-doubt tuple with (2) already.  Then it
    will continue on as you've shown here, due to EPQ.  But if you do the
    same statement with i = i + 10 instead of + 1, then it doesn't block,
    and only updates the one row that's visible.
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
  38. Re: ask for review of MERGE

    Kevin Grittner <kevin.grittner@wicourts.gov> — 2010-10-25T19:15:14Z

    Robert Haas <robertmhaas@gmail.com> wrote:
     
    > rhaas=# create table concurrent (x integer primary key);
    > NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index
    > "concurrent_pkey" for table "concurrent"
    > CREATE TABLE
    > rhaas=# insert into x values (1);
    > rhaas=# begin;
    > BEGIN
    > rhaas=# insert into concurrent values (2);
    > INSERT 0 1
    > 
    > <switch to a different window>
    > 
    > rhaas=# update concurrent set x=x where x=2;
    > UPDATE 0
     
    That surprised me.  I would have thought that the INSERT would have
    created an "in doubt" tuple which would block the UPDATE.  What is
    the reason for not doing so?
     
    FWIW I did a quick test and REPEATABLE READ also lets this pass but
    with the SSI patch SERIALIZABLE seems to cover this correctly,
    generating a serialization failure where such access is involved in
    write skew:
     
    test=# create table concurrent (x integer primary key);
    NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index
    "concurrent_pkey" for table "concurrent"
    CREATE TABLE
    test=# insert into concurrent select generate_series(1, 20000);
    INSERT 0 20000
    test=# begin isolation level serializable;
    BEGIN
    test=# insert into concurrent values (0);
    INSERT 0 1
    test=# update concurrent set x = 30001 where x = 30000;
    UPDATE 0
     
    <different session>
     
    test=# begin isolation level serializable;
    BEGIN
    test=# insert into concurrent values (30000);
    INSERT 0 1
    test=# update concurrent set x = -1 where x = 0;
    UPDATE 0
    test=# commit;
    ERROR:  could not serialize access due to read/write dependencies
    among transactions
    HINT:  The transaction might succeed if retried.
     
    I'll need to add a test to cover this, because it might have broken
    with one of the optimizations on my list, had you not point out this
    behavior.
     
    On the other hand:
     
    <session 1>
     
    test=# drop table concurrent ;
    DROP TABLE
    test=# create table concurrent (x integer primary key);
    NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index
    "concurrent_pkey" for table "concurrent"
    CREATE TABLE
    test=# insert into concurrent select generate_series(1, 20000);
    INSERT 0 20000
    test=# begin isolation level serializable;
    BEGIN
    test=# insert into concurrent values (0);
    INSERT 0 1
     
    <session 2>
     
    test=# begin isolation level serializable;
    BEGIN
    test=# select * from concurrent where x = 0;
     x
    ---
    (0 rows)
    
    test=# insert into concurrent values (0);
    <blocks>
     
    <session 1>
     
    test=# commit;
    COMMIT
     
    <session 2>
     
    ERROR:  duplicate key value violates unique constraint
    "concurrent_pkey"
    DETAIL:  Key (x)=(0) already exists.
     
    Anyway, I thought this might be of interest in terms of the MERGE
    patch concurrency issues, since the SSI patch has been mentioned.
     
    -Kevin
    
    
  39. Re: ask for review of MERGE

    Robert Haas <robertmhaas@gmail.com> — 2010-10-25T19:40:07Z

    On Mon, Oct 25, 2010 at 3:15 PM, Kevin Grittner
    <Kevin.Grittner@wicourts.gov> wrote:
    > Robert Haas <robertmhaas@gmail.com> wrote:
    >
    >> rhaas=# create table concurrent (x integer primary key);
    >> NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index
    >> "concurrent_pkey" for table "concurrent"
    >> CREATE TABLE
    >> rhaas=# insert into x values (1);
    >> rhaas=# begin;
    >> BEGIN
    >> rhaas=# insert into concurrent values (2);
    >> INSERT 0 1
    >>
    >> <switch to a different window>
    >>
    >> rhaas=# update concurrent set x=x where x=2;
    >> UPDATE 0
    >
    > That surprised me.  I would have thought that the INSERT would have
    > created an "in doubt" tuple which would block the UPDATE.  What is
    > the reason for not doing so?
    
    This is just standard MVCC - readers don't block writers, nor writers
    readers.  You might also think about what would happen if the UPDATE
    were run before the INSERT of (2).  There's no serialization anomaly
    here, because either concurrent case is equivalent to the serial
    schedule where the update precedes the insert.
    
    In the case of a MERGE that matches a just-inserted invisible tuple
    but no visible tuple, things are a bit stickier.  Let's suppose we're
    trying to use MERGE to get UPSERT semantics.  If the MERGE command has
    the obvious behavior of ignoring the invisible tuple just as UPDATE or
    DELETE would do, then clearly any equivalent serial schedule must run
    the MERGE before the INSERT (because if it were run after the INSERT,
    it would fire the MATCHED action rather than the NOT MATCHED action).
    But if the merge were run before the INSERT, then the INSERT would
    have failed with a unique key violation; instead, the merge fails with
    a unique key violation.  On the other hand, if the MERGE sees the
    invisible tuple, essentially using SnapshotNow semantics, as Greg
    Stark proposed, you get a different (and probably worse) class of
    serialization anomalies.  For example, suppose the table has rows
    1-100 and you do an update adding 1000 to each value concurrently with
    merging in the values 51-100.  You might get something like this:
    
    - MERGE scans rows 1-75, firing MATCHED for rows 51-75.
    - UPDATE commits.
    - MERGE scans rows 76-100, firing NOT MATCHED for each.
    
    Now, as Greg says, that might be what some people want, but it's
    certainly monumentally unserializable.  In a serial execution
    schedule, the MERGE will either run before the UPDATE, in which case
    MATCHED will fire for rows 51-100, or else the UPDATE will run before
    the MERGE, in which case NOT MATCHED will fire for rows 51-100.  No
    serial schedule is going to fire MATCHED for some rows and NOT MATCHED
    for others.
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
  40. Re: ask for review of MERGE

    Greg Stark <gsstark@mit.edu> — 2010-10-25T20:10:48Z

    On Mon, Oct 25, 2010 at 12:40 PM, Robert Haas <robertmhaas@gmail.com> wrote:
    > Now, as Greg says, that might be what some people want, but it's
    > certainly monumentally unserializable.
    
    To be clear when I said it's what people want what I meant was that in
    the common cases it's doing exactly what people want. As opposed to
    getting closer to what people want in general but not quite hitting
    the mark in the common cases.
    
    Just as an example I think it's important that in the simplest case,
    upsert of a single record, it be 100% guaranteed to do the naive
    upsert. If two users are doing the merge of a single key at the same
    time one of them had better insert and one of them had better update
    or else users are going to be monumentally surprised.
    
    I guess I hadn't considered all the cases and I agree it's important
    that our behaviour make some kind of sense and be consistent with how
    we handle updates and of existing in-doubt tuples. I wasn't trying to
    introduce a whole new mode of operation, just work from analogy from
    the way update works. It's clear that even with our existing semantics
    there are strange corner cases once you get to multiple updates
    happening in a single transaction. But we get the simple cases right
    and even in the more complex cases, while it's not truly serializable
    we should be able to come up with some basic smell tests that we pass.
    
    My understanding is that currently we generally treat DML in one of
    two ways depending on whether it's returning data to the user or
    updating data in the table (include select for share). If it's
    returning data to the user we use a snapshot to give the user a
    consistent view of the database. If it's altering data in the database
    we use the snapshot to get a consistent set of records and then apply
    the updates to the most recent version.
    
    The anomaly you showed with update and the problem with MERGE are both
    because the operation was simultaneously doing a "read" -- the WHERE
    clause and the uniqueness check in the MERGE -- and a write. This is
    already the kind of case where we do weird things -- what kind of
    behaviour would be consistent with our existing, somewhat weird,
    behaviour?
    
    
    -- 
    greg
    
    
  41. Re: ask for review of MERGE

    Kevin Grittner <kevin.grittner@wicourts.gov> — 2010-10-25T20:17:16Z

    Robert Haas <robertmhaas@gmail.com> wrote:
    > Kevin Grittner <Kevin.Grittner@wicourts.gov> wrote:
     
    >> I would have thought that the INSERT would have
    >> created an "in doubt" tuple which would block the UPDATE.
     
    > This is just standard MVCC - readers don't block writers, nor
    > writers readers.
     
    Sure, but I tend to think of both INSERT and UPDATE as writes.  ;-)
     
    > You might also think about what would happen if the UPDATE
    > were run before the INSERT
     
    > either concurrent case is equivalent to the serial
    > schedule where the update precedes the insert.
     
    I guess that's persuasive enough.  It feels funny, but the argument
    looks sound, so I guess it's just a case of my intuition being
    faulty.
     
    > In the case of a MERGE that matches a just-inserted invisible
    > tuple but no visible tuple, things are a bit stickier.
     
    Well, more generally it can lead to anomalies in a more complex
    combination of actions, since it creates, as you imply above, a
    rw-dependency from the transaction doing the UPDATE to the
    transaction doing the INSERT, so the combination can form part of a
    cycle in apparent order of execution which can produce an anomaly. 
    The more I look at it, the more clear it is that current behavior is
    correct and what the implications are.  I've just missed that detail
    until now, wrongly assuming that it would be a write conflict.
     
    > [example of MERGE which can not serialize with a concurrent
    > transaction, and possible outcomes if there is no serialization
    > failure]
     
    > Now, as Greg says, that might be what some people want, but it's
    > certainly monumentally unserializable.
     
    Yeah.  MERGE should probably be sensitive to the transaction
    isolation level, at least to the extent that MERGE in a SERIALIZABLE
    transaction plays nice with other SERIALIZABLE transactions.  That
    would be necessary to allow business rules enforced through triggers
    to be able to guarantee data integrity.  It would mean that a MERGE
    involving tables which were the target of modifications from
    concurrent SERIALIZABLE transactions would be likely to fail and/or
    to cause other transactions to fail.
     
    -Kevin
    
    
  42. Re: ask for review of MERGE

    Robert Haas <robertmhaas@gmail.com> — 2010-10-25T20:58:15Z

    On Mon, Oct 25, 2010 at 4:10 PM, Greg Stark <gsstark@mit.edu> wrote:
    > On Mon, Oct 25, 2010 at 12:40 PM, Robert Haas <robertmhaas@gmail.com> wrote:
    >> Now, as Greg says, that might be what some people want, but it's
    >> certainly monumentally unserializable.
    >
    > To be clear when I said it's what people want what I meant was that in
    > the common cases it's doing exactly what people want. As opposed to
    > getting closer to what people want in general but not quite hitting
    > the mark in the common cases.
    >
    > Just as an example I think it's important that in the simplest case,
    > upsert of a single record, it be 100% guaranteed to do the naive
    > upsert. If two users are doing the merge of a single key at the same
    > time one of them had better insert and one of them had better update
    > or else users are going to be monumentally surprised.
    
    Hmm, so let's think about that case.
    
    The first merge comes along and finds no match so it fires the NOT
    MATCHED rule, which inserts a tuple.  The second merge comes along and
    finds no match, so it also fires the NOT MATCHED rule and tries to
    insert a tuple.  But upon consulting the PRIMARY KEY index it finds
    that an in-doubt tuple exists so it goes to sleep waiting for the
    first transaction to commit or abort.  If the first transaction
    commits it then decides that the jig is up and fails.  We could
    (maybe) fix this by doing something similar to what EPQ does for
    updates: when the first transaction commits, instead of redoing the
    insert, we back up and recheck whether the new tuple would have
    matched the join clause and, if so, we instead fire the MATCHED action
    on the updated tuple.  If not, we fire NOT MATCHED anyway.  I'm not
    sure how hard that would be, or whether it would introduce any other
    nasty anomalies in more complex cases.
    
    Alternatively, we could introduce an UPSERT or REPLACE statement
    intended to handle exactly this case and leave MERGE for more complex
    situations.  It's pretty easy to imagine what the coding of that
    should look like: if we encounter an in-doubt tuple in we wait on its
    xmin.  If the transaction aborts, we insert.  If it commits, and we're
    in READ COMMITTED mode, we update it; but if we're in REPEATABLE READ
    or SERIALIZABLE mode, we abort with a serialization error.  That's a
    lot simpler to understand and reason about than MERGE in its full
    generality.
    
    I think it's pretty much hopeless to think that MERGE is going to work
    in complex concurrent scenarios without creating serialization
    anomalies, or at least rollbacks.  I think that's baked into the
    nature of what the statement does.  To simulate MERGE, you need to
    read from the target table and then do writes that depend on what you
    read.  If you do that with the commands that are available today,
    you're going to get serialization anomalies and/or rollbacks under
    concurrency.  The mere fact of that logic being inside the database
    rather than outside isn't going to make that go away.  Now sometimes,
    as with exclusion constraints, you can play games with dirty snapshots
    to get the semantics you want, but whether that's possible in a
    particular case depends on the details of the operation being
    performed, and here I think it can't be done.  Some operations are
    *fundamentally* unserializable.
    
    A very simple example of this is a sequence that is guaranteed not to
    have gaps (a feature we've occasionally been requested to provide).
    If N processes request a sequence number simultaneously, you have to
    hand out a value to the first guy and wait and see whether he commits
    or aborts before deciding what number to give the second guy.  That
    sucks, so usually we just design our applications not to require that
    sequences be gap-free.  Similarly here.
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
  43. Re: ask for review of MERGE

    Simon Riggs <simon@2ndquadrant.com> — 2010-10-26T12:10:38Z

    On Mon, 2010-10-25 at 16:58 -0400, Robert Haas wrote:
    > On Mon, Oct 25, 2010 at 4:10 PM, Greg Stark <gsstark@mit.edu> wrote:
    > > On Mon, Oct 25, 2010 at 12:40 PM, Robert Haas <robertmhaas@gmail.com> wrote:
    > >> Now, as Greg says, that might be what some people want, but it's
    > >> certainly monumentally unserializable.
    > >
    > > To be clear when I said it's what people want what I meant was that in
    > > the common cases it's doing exactly what people want. As opposed to
    > > getting closer to what people want in general but not quite hitting
    > > the mark in the common cases.
    > >
    > > Just as an example I think it's important that in the simplest case,
    > > upsert of a single record, it be 100% guaranteed to do the naive
    > > upsert. If two users are doing the merge of a single key at the same
    > > time one of them had better insert and one of them had better update
    > > or else users are going to be monumentally surprised.
    > 
    > Hmm, so let's think about that case.
    > 
    > The first merge comes along and finds no match so it fires the NOT
    > MATCHED rule, which inserts a tuple.  The second merge comes along and
    > finds no match, so it also fires the NOT MATCHED rule and tries to
    > insert a tuple.  But upon consulting the PRIMARY KEY index it finds
    > that an in-doubt tuple exists so it goes to sleep waiting for the
    > first transaction to commit or abort.  If the first transaction
    > commits it then decides that the jig is up and fails.  We could
    > (maybe) fix this by doing something similar to what EPQ does for
    > updates: when the first transaction commits, instead of redoing the
    > insert, we back up and recheck whether the new tuple would have
    > matched the join clause and, if so, we instead fire the MATCHED action
    > on the updated tuple.  If not, we fire NOT MATCHED anyway.  I'm not
    > sure how hard that would be, or whether it would introduce any other
    > nasty anomalies in more complex cases.
    > 
    > Alternatively, we could introduce an UPSERT or REPLACE statement
    > intended to handle exactly this case and leave MERGE for more complex
    > situations.  It's pretty easy to imagine what the coding of that
    > should look like: if we encounter an in-doubt tuple in we wait on its
    > xmin.  If the transaction aborts, we insert.  If it commits, and we're
    > in READ COMMITTED mode, we update it; but if we're in REPEATABLE READ
    > or SERIALIZABLE mode, we abort with a serialization error.  That's a
    > lot simpler to understand and reason about than MERGE in its full
    > generality.
    > 
    > I think it's pretty much hopeless to think that MERGE is going to work
    > in complex concurrent scenarios without creating serialization
    > anomalies, or at least rollbacks.  I think that's baked into the
    > nature of what the statement does.  To simulate MERGE, you need to
    > read from the target table and then do writes that depend on what you
    > read.  If you do that with the commands that are available today,
    > you're going to get serialization anomalies and/or rollbacks under
    > concurrency.  The mere fact of that logic being inside the database
    > rather than outside isn't going to make that go away.  Now sometimes,
    > as with exclusion constraints, you can play games with dirty snapshots
    > to get the semantics you want, but whether that's possible in a
    > particular case depends on the details of the operation being
    > performed, and here I think it can't be done.  Some operations are
    > *fundamentally* unserializable.
    
    I agree with your analysis. Let me review...
    
    There is a case that locking alone won't resolve, however that locking
    works. The transaction history for that is:
    
    T1: takes snapshot
    T2: INSERT row1
    T2: COMMIT;
    T1: attempts to determine if MATCHED or NOT MATCHED. 
    
    The answer is neither of those two answers. If we say it is NOT MATCHED
    then we will just fail on any INSERT, or if we say it is MATCHED then
    technically we can't see the row so the UPDATE should fail. The COMMIT
    of T2 releases any locks that would have helped resolve this, and note
    that even if T1 attempts to lock that row as early as possible, only a
    table level lock would prevent T2 from doing that action.
    
    Two options for resolution are
    
    1) Throw SERIALIZABLE error
    
    2) Implement something similar to EvalPlanQual
    As you say, we already resolve this situation for concurrent updates by
    following the update chain from a row that is visible to the latest row.
    For MERGE the semantics need to be subtely different here: we need to
    follow the update chain to the latest row, but from a row that we
    *can't* see.
    
    MERGE is still very useful without the need for 2), though fails in some
    cases for concurrent use. The failure rate would increase as the number
    of concurrent MERGErs and/or number of rows in source table. Those
    errors are no more serious than are possible now.
    
    So IMHO we should implement 1) now and come back later to implement 2).
    Given right now there may be other issues with 2) it seems unsafe to
    rely on the assumption that we'll fix them by end of release.
    
    -- 
     Simon Riggs           http://www.2ndQuadrant.com/books/
     PostgreSQL Development, 24x7 Support, Training and Services
     
    
    
    
  44. Re: ask for review of MERGE

    Robert Haas <robertmhaas@gmail.com> — 2010-10-26T20:08:32Z

    On Tue, Oct 26, 2010 at 8:10 AM, Simon Riggs <simon@2ndquadrant.com> wrote:
    > I agree with your analysis. Let me review...
    > [review]
    
    Sounds like we're on the same page.
    
    > Two options for resolution are
    >
    > 1) Throw SERIALIZABLE error
    >
    > 2) Implement something similar to EvalPlanQual
    > As you say, we already resolve this situation for concurrent updates by
    > following the update chain from a row that is visible to the latest row.
    > For MERGE the semantics need to be subtely different here: we need to
    > follow the update chain to the latest row, but from a row that we
    > *can't* see.
    >
    > MERGE is still very useful without the need for 2), though fails in some
    > cases for concurrent use. The failure rate would increase as the number
    > of concurrent MERGErs and/or number of rows in source table. Those
    > errors are no more serious than are possible now.
    >
    > So IMHO we should implement 1) now and come back later to implement 2).
    > Given right now there may be other issues with 2) it seems unsafe to
    > rely on the assumption that we'll fix them by end of release.
    
    Yeah.  In fact, I'm not sure we're ever going to want to implement #2
    - I think that needs more study to determine whether there's even
    something there that makes sense to implement at all.  But certainly I
    wouldn't count on it happening for 9.1.
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
  45. Re: ask for review of MERGE

    Simon Riggs <simon@2ndquadrant.com> — 2010-10-26T21:00:16Z

    On Tue, 2010-10-26 at 16:08 -0400, Robert Haas wrote:
    > On Tue, Oct 26, 2010 at 8:10 AM, Simon Riggs <simon@2ndquadrant.com> wrote:
    > > I agree with your analysis. Let me review...
    > > [review]
    > 
    > Sounds like we're on the same page.
    > 
    > > Two options for resolution are
    > >
    > > 1) Throw SERIALIZABLE error
    > >
    > > 2) Implement something similar to EvalPlanQual
    > > As you say, we already resolve this situation for concurrent updates by
    > > following the update chain from a row that is visible to the latest row.
    > > For MERGE the semantics need to be subtely different here: we need to
    > > follow the update chain to the latest row, but from a row that we
    > > *can't* see.
    > >
    > > MERGE is still very useful without the need for 2), though fails in some
    > > cases for concurrent use. The failure rate would increase as the number
    > > of concurrent MERGErs and/or number of rows in source table. Those
    > > errors are no more serious than are possible now.
    > >
    > > So IMHO we should implement 1) now and come back later to implement 2).
    > > Given right now there may be other issues with 2) it seems unsafe to
    > > rely on the assumption that we'll fix them by end of release.
    > 
    > Yeah.  In fact, I'm not sure we're ever going to want to implement #2
    > - I think that needs more study to determine whether there's even
    > something there that makes sense to implement at all.  But certainly I
    > wouldn't count on it happening for 9.1.
    
    2) sounds weird, until you realise it is exactly how you would need to
    code a PL/pgSQL procedure to do the equivalent of MERGE. Not doing it
    just makes no sense in the longer term. I agree it will take a while to
    think it through in sufficient detail.
    
    In the meantime it's a good argument for the ELSE construct at the end
    of the WHEN clauses, so we can do something other than skip a row or
    throw an ERROR.
    
    -- 
     Simon Riggs           http://www.2ndQuadrant.com/books/
     PostgreSQL Development, 24x7 Support, Training and Services
     
    
    
    
  46. Re: ask for review of MERGE

    Bruce Momjian <bruce@momjian.us> — 2010-11-12T18:04:03Z

    Kevin Grittner wrote:
    > Robert Haas <robertmhaas@gmail.com> wrote:
    >  
    > > rhaas=# create table concurrent (x integer primary key);
    > > NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index
    > > "concurrent_pkey" for table "concurrent"
    > > CREATE TABLE
    > > rhaas=# insert into x values (1);
    > > rhaas=# begin;
    > > BEGIN
    > > rhaas=# insert into concurrent values (2);
    > > INSERT 0 1
    > > 
    > > <switch to a different window>
    > > 
    > > rhaas=# update concurrent set x=x where x=2;
    > > UPDATE 0
    >  
    > That surprised me.  I would have thought that the INSERT would have
    > created an "in doubt" tuple which would block the UPDATE.  What is
    > the reason for not doing so?
    
    When Kevin gets surprised, I get worried.  LOL
    
    -- 
      Bruce Momjian  <bruce@momjian.us>        http://momjian.us
      EnterpriseDB                             http://enterprisedb.com
    
      + It's impossible for everything to be true. +