private void testMerge3Way(boolean causeConflict, boolean sharedWrite) throws Exception {
    dropTable(new String[]{"target", "source", "source2"});
    conf.setBoolVar(HiveConf.ConfVars.TXN_WRITE_X_LOCK, !sharedWrite);

    driver.run("create table target (a int, b int) " +
        "partitioned by (p int, q int) clustered by (a) into 2  buckets " +
        "stored as orc TBLPROPERTIES ('transactional'='true')");
    //in practice we don't really care about the data in any of these tables (except as far as
    //it creates partitions, the SQL being test is not actually executed and results of the
    //wrt ACID metadata is supplied manually via addDynamicPartitions().  But having data makes
    //it easier to follow the intent
    driver.run("insert into target partition(p,q) values (1,2,1,2), (3,4,1,2), (5,6,1,3), (7,8,2,2)");
    driver.run("create table source (a int, b int, p int, q int)");
    driver.run("insert into source values " +
        // I-(1/2)            D-(1/2)    I-(1/3)     U-(1/3)     D-(2/2)     I-(1/1) - new part
        "(9,10,1,2),        (3,4,1,2), (11,12,1,3), (5,13,1,3), (7,8,2,2), (14,15,1,1)");
    driver.run("create table source2 (a int, b int, p int, q int)");
    driver.run("insert into source2 values " +
        //cc ? -:U-(1/2)     D-(1/2)         cc ? U-(1/3):-       D-(2/2)       I-(1/1) - new part 2
        "(9,100,1,2),      (3,4,1,2),         (5,13,1,3),       (7,8,2,2), (14,15,2,1)");

    driver.compileAndRespond("merge into target t using source s on t.a=s.b " +
        "when matched and t.a=5 then update set b=s.b " + //updates p=1/q=3
        "when matched and t.a in (3,7) then delete " + //deletes from p=1/q=2, p=2/q=2
        "when not matched and t.a >= 8 then insert values(s.a, s.b, s.p, s.q)", true); //insert p=1/q=2, p=1/q=3 and new part 1/1
    long txnId1 = txnMgr.getCurrentTxnId();
    txnMgr.acquireLocks(driver.getPlan(), ctx, "T1");
    List<ShowLocksResponseElement> locks = getLocks();
    Assert.assertEquals("Unexpected lock count", 5, locks.size());

    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.SHARED_READ),
        LockState.ACQUIRED, "default", "target", null, locks);
    TestTxnDbUtil.checkLock(LockType.SHARED_READ, LockState.ACQUIRED, "default", "source", null, locks);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        LockState.ACQUIRED, "default", "target", "p=1/q=2", locks);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        LockState.ACQUIRED, "default", "target", "p=1/q=3", locks);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        LockState.ACQUIRED, "default", "target", "p=2/q=2", locks);

    //start concurrent txn
    DbTxnManager txnMgr2 = (DbTxnManager) TxnManagerFactory.getTxnManagerFactory().getTxnManager(conf);
    swapTxnManager(txnMgr2);
    driver.compileAndRespond("merge into target t using source2 s on t.a=s.b " +
        //if conflict updates p=1/q=3 else update p=1/q=2
        "when matched and t.a=" + (causeConflict ? 5 : 9) + " then update set b=s.b " +
        //if cc deletes from p=1/q=2, p=2/q=2, else delete nothing
        "when matched and t.a in (" + (causeConflict ? "3,7" : "11, 13")  + ") then delete " +
        //insert p=1/q=2, p=1/q=3 and new part 1/1
        "when not matched and t.a >= 8 then insert values(s.a, s.b, s.p, s.q)", true);
    long txnId2 = txnMgr2.getCurrentTxnId();
    txnMgr2.acquireLocks(driver.getPlan(), ctx, "T1", false);
    locks = getLocks();
    Assert.assertEquals("Unexpected lock count", 10, locks.size());

    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.SHARED_READ),
        LockState.ACQUIRED, "default", "target", null, locks);
    TestTxnDbUtil.checkLock(LockType.SHARED_READ, LockState.ACQUIRED, "default", "source", null, locks);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        LockState.ACQUIRED, "default", "target", "p=1/q=2", locks);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        LockState.ACQUIRED, "default", "target", "p=1/q=3", locks);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        LockState.ACQUIRED, "default", "target", "p=2/q=2", locks);

    long extLockId = TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.SHARED_READ),
        (sharedWrite ? LockState.ACQUIRED : LockState.WAITING),
        "default", "target", null, locks, sharedWrite).getLockid();
    TestTxnDbUtil.checkLock(LockType.SHARED_READ, (sharedWrite ? LockState.ACQUIRED : LockState.WAITING),
        "default", "source2", null, locks);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        (sharedWrite ? LockState.ACQUIRED : LockState.WAITING),
        "default", "target", "p=1/q=2", locks, sharedWrite);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        (sharedWrite ? LockState.ACQUIRED : LockState.WAITING),
        "default", "target", "p=1/q=3", locks, sharedWrite);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        (sharedWrite ? LockState.ACQUIRED : LockState.WAITING),
        "default", "target", "p=2/q=2", locks, sharedWrite);

    Assert.assertEquals(
        "TXN_COMPONENTS mismatch(" + JavaUtils.txnIdToString(txnId1) + "): " +
        TestTxnDbUtil.queryToString(conf, "select * from \"TXN_COMPONENTS\""),
        1,
        TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"TXN_COMPONENTS\" where \"TC_TXNID\"=" + txnId1));

    //complete 1st txn
    long writeId = txnMgr.getTableWriteId("default", "target");
    AddDynamicPartitions adp = new AddDynamicPartitions(txnId1, writeId, "default", "target",
        Collections.singletonList("p=1/q=3")); //update clause
    adp.setOperationType(DataOperationType.UPDATE);
    txnHandler.addDynamicPartitions(adp);
    adp = new AddDynamicPartitions(txnId1, writeId, "default", "target",
        Arrays.asList("p=1/q=2", "p=2/q=2")); //delete clause
    adp.setOperationType(DataOperationType.DELETE);
    txnHandler.addDynamicPartitions(adp);
    adp = new AddDynamicPartitions(txnId1, writeId, "default", "target",
        Arrays.asList("p=1/q=2", "p=1/q=3", "p=1/q=1")); //insert clause
    adp.setOperationType(DataOperationType.INSERT);
    txnHandler.addDynamicPartitions(adp);
    Assert.assertEquals(
        "TXN_COMPONENTS mismatch(" + JavaUtils.txnIdToString(txnId1) + "): " +
        TestTxnDbUtil.queryToString(conf, "select * from \"TXN_COMPONENTS\""),
        1,
        TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"TXN_COMPONENTS\" where \"TC_TXNID\"=" + txnId1 +
            " and \"TC_OPERATION_TYPE\"='u'"));
    Assert.assertEquals(
        "TXN_COMPONENTS mismatch(" + JavaUtils.txnIdToString(txnId1) + "): " +
        TestTxnDbUtil.queryToString(conf, "select * from \"TXN_COMPONENTS\""),
        2,
        TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"TXN_COMPONENTS\" where \"TC_TXNID\"=" + txnId1 +
                " and \"TC_OPERATION_TYPE\"='d'"));
    Assert.assertEquals(
        "TXN_COMPONENTS mismatch(" + JavaUtils.txnIdToString(txnId1) + "): " +
        TestTxnDbUtil.queryToString(conf, "select * from \"TXN_COMPONENTS\""),
        3,
        TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"TXN_COMPONENTS\" where \"TC_TXNID\"=" + txnId1 +
            " and \"TC_OPERATION_TYPE\"='i'"));

    txnMgr.commitTxn(); //commit T1
    Assert.assertEquals(
        "COMPLETED_TXN_COMPONENTS mismatch(" + JavaUtils.txnIdToString(txnId1) + "): " +
        TestTxnDbUtil.queryToString(conf, "select * from \"COMPLETED_TXN_COMPONENTS\""),
        6,
        TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"COMPLETED_TXN_COMPONENTS\" where \"CTC_TXNID\"=" + txnId1));
    Assert.assertEquals(
        "WRITE_SET mismatch(" + JavaUtils.txnIdToString(txnId1) + "): " +
        TestTxnDbUtil.queryToString(conf, "select * from \"WRITE_SET\""),
        1,
        TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"WRITE_SET\" where \"WS_TXNID\"=" + txnId1 +
            " and \"WS_OPERATION_TYPE\"='u'"));
    Assert.assertEquals(
        "WRITE_SET mismatch(" + JavaUtils.txnIdToString(txnId1) + "): " +
        TestTxnDbUtil.queryToString(conf, "select * from \"WRITE_SET\""),
        2,
        TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"WRITE_SET\" where \"WS_TXNID\"=" + txnId1 +
            " and \"WS_OPERATION_TYPE\"='d'"));

    //re-check locks which were in Waiting state - should now be Acquired
    ((DbLockManager)txnMgr2.getLockManager()).checkLock(extLockId);
    locks = getLocks();
    Assert.assertEquals("Unexpected lock count", 5, locks.size());

    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.SHARED_READ),
        LockState.ACQUIRED, "default", "target", null, locks);
    TestTxnDbUtil.checkLock(LockType.SHARED_READ, LockState.ACQUIRED, "default", "source2", null, locks);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        LockState.ACQUIRED, "default", "target", "p=1/q=2", locks);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        LockState.ACQUIRED, "default", "target", "p=1/q=3", locks);
    TestTxnDbUtil.checkLock((sharedWrite ? LockType.SHARED_WRITE : LockType.EXCL_WRITE),
        LockState.ACQUIRED, "default", "target", "p=2/q=2", locks);

    Assert.assertEquals(
        "TXN_COMPONENTS mismatch(" + JavaUtils.txnIdToString(txnId2) + "): " +
        TestTxnDbUtil.queryToString(conf, "select * from \"TXN_COMPONENTS\""),
        1,
        TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"TXN_COMPONENTS\" where \"TC_TXNID\"=" + txnId2));

    //complete 2nd txn
    writeId = txnMgr2.getTableWriteId("default", "target");
    adp = new AddDynamicPartitions(txnId2, writeId, "default", "target",
        Collections.singletonList(causeConflict ? "p=1/q=3" : "p=1/p=2")); //update clause
    adp.setOperationType(DataOperationType.UPDATE);
    txnHandler.addDynamicPartitions(adp);

    if (causeConflict) {
      adp = new AddDynamicPartitions(txnId2, writeId, "default", "target",
          Arrays.asList("p=1/q=2", "p=2/q=2")); //delete clause
      adp.setOperationType(DataOperationType.DELETE);
      txnHandler.addDynamicPartitions(adp);
    }
    adp = new AddDynamicPartitions(txnId2, writeId, "default", "target",
        Arrays.asList("p=1/q=2", "p=1/q=3", "p=1/q=1")); //insert clause
    adp.setOperationType(DataOperationType.INSERT);
    txnHandler.addDynamicPartitions(adp);
    Assert.assertEquals(
        "TXN_COMPONENTS mismatch(" + JavaUtils.txnIdToString(txnId2) + "): " +
        TestTxnDbUtil.queryToString(conf, "select * from \"TXN_COMPONENTS\""),
        1,
        TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"TXN_COMPONENTS\" where \"TC_TXNID\"=" + txnId2 +
            " and \"TC_OPERATION_TYPE\"='u'"));
    Assert.assertEquals(
        "TXN_COMPONENTS mismatch(" + JavaUtils.txnIdToString(txnId2) + "): " +
        TestTxnDbUtil.queryToString(conf, "select * from \"TXN_COMPONENTS\""),
        (causeConflict ? 2 : 0),
        TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"TXN_COMPONENTS\" where \"TC_TXNID\"=" + txnId2 +
            " and \"TC_OPERATION_TYPE\"='d'"));
    Assert.assertEquals(
        "TXN_COMPONENTS mismatch(" + JavaUtils.txnIdToString(txnId2) + "): " +
        TestTxnDbUtil.queryToString(conf, "select * from \"TXN_COMPONENTS\""),
        3,
        TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"TXN_COMPONENTS\" where \"TC_TXNID\"=" + txnId2 +
            " and \"TC_OPERATION_TYPE\"='i'"));

    LockException expectedException = null;
    try {
      txnMgr2.commitTxn(); //commit T2
    }
    catch (LockException e) {
      expectedException = e;
    }
    if (causeConflict && sharedWrite) {
      Assert.assertNotNull("Didn't get exception", expectedException);
      try {
        Assert.assertEquals("Transaction manager has aborted the transaction txnid:4.  Reason: " +
            "Aborting [txnid:4,4] due to a write conflict on default/target/p=1/q=2 " +
            "committed by [txnid:3,4] d/d", expectedException.getMessage());
      } catch (ComparisonFailure ex) {
        //the 2 txns have 3 conflicts between them so check for either failure since which one is
        //reported (among the 3) is not deterministic
        try {
          Assert.assertEquals("Transaction manager has aborted the transaction txnid:4.  Reason: "
              + "Aborting [txnid:4,4] due to a write conflict on default/target/p=2/q=2 "
              + "committed by [txnid:3,4] d/d", expectedException.getMessage());
        } catch (ComparisonFailure ex2) {
          Assert.assertEquals("Transaction manager has aborted the transaction txnid:4.  Reason: " +
              "Aborting [txnid:4,4] due to a write conflict on default/target/p=1/q=3 " +
              "committed by [txnid:3,4] u/u", expectedException.getMessage());
        }
      }
      Assert.assertEquals(
          "COMPLETED_TXN_COMPONENTS mismatch(" + JavaUtils.txnIdToString(txnId2) + "): " +
          TestTxnDbUtil.queryToString(conf, "select * from \"COMPLETED_TXN_COMPONENTS\""),
          0,
          TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"COMPLETED_TXN_COMPONENTS\" where \"CTC_TXNID\"=" + txnId2));
      Assert.assertEquals(
          "WRITE_SET mismatch(" + JavaUtils.txnIdToString(txnId2) + "): " +
          TestTxnDbUtil.queryToString(conf, "select * from \"WRITE_SET\""),
          0,
          TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"WRITE_SET\" where \"WS_TXNID\"=" + txnId2));
    } else {
      Assert.assertNull("Unexpected exception " + expectedException, expectedException);
      Assert.assertEquals(
          "COMPLETED_TXN_COMPONENTS mismatch(" + JavaUtils.txnIdToString(txnId2) + "): " +
          TestTxnDbUtil.queryToString(conf, "select * from \"COMPLETED_TXN_COMPONENTS\""),
        causeConflict ? 6 : 4,
          TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"COMPLETED_TXN_COMPONENTS\" where \"CTC_TXNID\"=" + txnId2));
      Assert.assertEquals(
          "WRITE_SET mismatch(" + JavaUtils.txnIdToString(txnId2) + "): " +
          TestTxnDbUtil.queryToString(conf, "select * from \"WRITE_SET\""),
          1,
          TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"WRITE_SET\" where \"WS_TXNID\"=" + txnId2 +
              " and \"WS_OPERATION_TYPE\"='u'"));
      Assert.assertEquals(
          "WRITE_SET mismatch(" + JavaUtils.txnIdToString(txnId2) + "): " +
          TestTxnDbUtil.queryToString(conf, "select * from \"WRITE_SET\""),
        causeConflict ? 2 : 0,
          TestTxnDbUtil.countQueryAgent(conf, "select count(*) from \"WRITE_SET\" where \"WS_TXNID\"=" + txnId2 +
              " and \"WS_OPERATION_TYPE\"='d'"));
    }
    dropTable(new String[]{"target", "source", "source2"});
  }
@param causeConflict whether to cause a WW conflict or not
ql/src/test/org/apache/hadoop/hive/ql/lockmgr/TestDbTxnManager2.java