private void testSavepoint() {
    MVStore s = MVStore.open(null);
    TransactionStore ts = new TransactionStore(s);
    Transaction tx;
    TransactionMap<String, String> m;

    tx = ts.begin();
    m = tx.openMap("test");
    m.put("1", "Hello");
    m.put("2", "World");
    m.put("1", "Hallo");
    m.remove("2");
    m.put("3", "!");
    long logId = tx.setSavepoint();
    m.put("1", "Hi");
    m.put("2", ".");
    m.remove("3");
    tx.rollbackToSavepoint(logId);
    assertEquals("Hallo", m.get("1"));
    assertNull(m.get("2"));
    assertEquals("!", m.get("3"));
    tx.rollback();

    tx = ts.begin();
    m = tx.openMap("test");
    assertNull(m.get("1"));
    assertNull(m.get("2"));
    assertNull(m.get("3"));

    ts.close();
    s.close();
  }
  private void testKeyIterator() {
    MVStore s = MVStore.open(null);
    TransactionStore ts = new TransactionStore(s);
    Transaction tx, tx2;
    TransactionMap<String, String> m, m2;
    Iterator<String> it, it2;

    tx = ts.begin();
    m = tx.openMap("test");
    m.put("1", "Hello");
    m.put("2", "World");
    m.put("3", ".");
    tx.commit();

    tx2 = ts.begin();
    m2 = tx2.openMap("test");
    m2.remove("2");
    m2.put("3", "!");
    m2.put("4", "?");

    tx = ts.begin();
    m = tx.openMap("test");
    it = m.keyIterator(null);
    assertTrue(it.hasNext());
    assertEquals("1", it.next());
    assertTrue(it.hasNext());
    assertEquals("2", it.next());
    assertTrue(it.hasNext());
    assertEquals("3", it.next());
    assertFalse(it.hasNext());

    it2 = m2.keyIterator(null);
    assertTrue(it2.hasNext());
    assertEquals("1", it2.next());
    assertTrue(it2.hasNext());
    assertEquals("3", it2.next());
    assertTrue(it2.hasNext());
    assertEquals("4", it2.next());
    assertFalse(it2.hasNext());

    s.close();
  }
  @Override
  public void add(Session session, Row row) {
    if (mainIndexColumn == -1) {
      if (row.getKey() == 0) {
        row.setKey(++lastKey);
      }
    } else {
      long c = row.getValue(mainIndexColumn).getLong();
      row.setKey(c);
    }

    if (mvTable.getContainsLargeObject()) {
      for (int i = 0, len = row.getColumnCount(); i < len; i++) {
        Value v = row.getValue(i);
        Value v2 = v.link(database, getId());
        if (v2.isLinked()) {
          session.unlinkAtCommitStop(v2);
        }
        if (v != v2) {
          row.setValue(i, v2);
        }
      }
    }

    TransactionMap<Value, Value> map = getMap(session);
    Value key = ValueLong.get(row.getKey());
    Value old = map.getLatest(key);
    if (old != null) {
      String sql = "PRIMARY KEY ON " + table.getSQL();
      if (mainIndexColumn >= 0 && mainIndexColumn < indexColumns.length) {
        sql += "(" + indexColumns[mainIndexColumn].getSQL() + ")";
      }
      DbException e = DbException.get(ErrorCode.DUPLICATE_KEY_1, sql);
      e.setSource(this);
      throw e;
    }
    try {
      map.put(key, ValueArray.get(row.getValueList()));
    } catch (IllegalStateException e) {
      throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, table.getName());
    }
    lastKey = Math.max(lastKey, row.getKey());
  }
  private void testSingleConnection() {
    MVStore s = MVStore.open(null);

    TransactionStore ts = new TransactionStore(s);

    Transaction tx;
    TransactionMap<String, String> m;

    // add, rollback
    tx = ts.begin();
    m = tx.openMap("test");
    m.put("1", "Hello");
    assertEquals("Hello", m.get("1"));
    m.put("2", "World");
    assertEquals("World", m.get("2"));
    tx.rollback();
    tx = ts.begin();
    m = tx.openMap("test");
    assertNull(m.get("1"));
    assertNull(m.get("2"));

    // add, commit
    tx = ts.begin();
    m = tx.openMap("test");
    m.put("1", "Hello");
    m.put("2", "World");
    assertEquals("Hello", m.get("1"));
    assertEquals("World", m.get("2"));
    tx.commit();
    tx = ts.begin();
    m = tx.openMap("test");
    assertEquals("Hello", m.get("1"));
    assertEquals("World", m.get("2"));

    // update+delete+insert, rollback
    tx = ts.begin();
    m = tx.openMap("test");
    m.put("1", "Hallo");
    m.remove("2");
    m.put("3", "!");
    assertEquals("Hallo", m.get("1"));
    assertNull(m.get("2"));
    assertEquals("!", m.get("3"));
    tx.rollback();
    tx = ts.begin();
    m = tx.openMap("test");
    assertEquals("Hello", m.get("1"));
    assertEquals("World", m.get("2"));
    assertNull(m.get("3"));

    // update+delete+insert, commit
    tx = ts.begin();
    m = tx.openMap("test");
    m.put("1", "Hallo");
    m.remove("2");
    m.put("3", "!");
    assertEquals("Hallo", m.get("1"));
    assertNull(m.get("2"));
    assertEquals("!", m.get("3"));
    tx.commit();
    tx = ts.begin();
    m = tx.openMap("test");
    assertEquals("Hallo", m.get("1"));
    assertNull(m.get("2"));
    assertEquals("!", m.get("3"));

    ts.close();
    s.close();
  }
  private void testConcurrentTransactionsReadCommitted() {
    MVStore s = MVStore.open(null);

    TransactionStore ts = new TransactionStore(s);

    Transaction tx1, tx2;
    TransactionMap<String, String> m1, m2;

    tx1 = ts.begin();
    m1 = tx1.openMap("test");
    m1.put("1", "Hi");
    m1.put("3", ".");
    tx1.commit();

    tx1 = ts.begin();
    m1 = tx1.openMap("test");
    m1.put("1", "Hello");
    m1.put("2", "World");
    m1.remove("3");
    tx1.commit();

    // start new transaction to read old data
    tx2 = ts.begin();
    m2 = tx2.openMap("test");

    // start transaction tx1, update/delete/add
    tx1 = ts.begin();
    m1 = tx1.openMap("test");
    m1.put("1", "Hallo");
    m1.remove("2");
    m1.put("3", "!");

    assertEquals("Hello", m2.get("1"));
    assertEquals("World", m2.get("2"));
    assertNull(m2.get("3"));

    tx1.commit();

    assertEquals("Hallo", m2.get("1"));
    assertNull(m2.get("2"));
    assertEquals("!", m2.get("3"));

    tx1 = ts.begin();
    m1 = tx1.openMap("test");
    m1.put("2", "World");

    assertNull(m2.get("2"));
    assertFalse(m2.tryRemove("2"));
    assertFalse(m2.tryPut("2", "Welt"));

    tx2 = ts.begin();
    m2 = tx2.openMap("test");
    assertNull(m2.get("2"));
    m1.remove("2");
    assertNull(m2.get("2"));
    tx1.commit();

    tx1 = ts.begin();
    m1 = tx1.openMap("test");
    assertNull(m1.get("2"));
    m1.put("2", "World");
    m1.put("2", "Welt");
    tx1.rollback();

    tx1 = ts.begin();
    m1 = tx1.openMap("test");
    assertNull(m1.get("2"));

    ts.close();
    s.close();
  }
  private void testStopWhileCommitting() throws Exception {
    String fileName = getBaseDir() + "/testStopWhileCommitting.h3";
    FileUtils.delete(fileName);
    Random r = new Random(0);

    for (int i = 0; i < 10; ) {
      MVStore s;
      TransactionStore ts;
      Transaction tx;
      TransactionMap<Integer, String> m;

      s = MVStore.open(fileName);
      ts = new TransactionStore(s);
      tx = ts.begin();
      s.setReuseSpace(false);
      m = tx.openMap("test");
      final String value = "x" + i;
      for (int j = 0; j < 1000; j++) {
        m.put(j, value);
      }
      final AtomicInteger state = new AtomicInteger();
      final MVStore store = s;
      final MVMap<Integer, String> other = s.openMap("other");
      Task task =
          new Task() {

            @Override
            public void call() throws Exception {
              for (int i = 0; !stop; i++) {
                state.set(i);
                other.put(i, value);
                store.commit();
              }
            }
          };
      task.execute();
      // wait for the task to start
      while (state.get() < 1) {
        Thread.yield();
      }
      // commit while writing in the task
      tx.commit();
      // wait for the task to stop
      task.get();
      store.close();
      s = MVStore.open(fileName);
      // roll back a bit, until we have some undo log entries
      assertTrue(s.hasMap("undoLog"));
      for (int back = 0; back < 100; back++) {
        int minus = r.nextInt(10);
        s.rollbackTo(Math.max(0, s.getCurrentVersion() - minus));
        MVMap<?, ?> undo = s.openMap("undoLog");
        if (undo.size() > 0) {
          break;
        }
      }
      ts = new TransactionStore(s);
      List<Transaction> list = ts.getOpenTransactions();
      if (list.size() != 0) {
        tx = list.get(0);
        if (tx.getStatus() == Transaction.STATUS_COMMITTING) {
          i++;
        }
      }
      s.close();
      FileUtils.delete(fileName);
      assertFalse(FileUtils.exists(fileName));
    }
  }
  private void testTwoPhaseCommit() {
    String fileName = getBaseDir() + "/testTwoPhaseCommit.h3";
    FileUtils.delete(fileName);

    MVStore s;
    TransactionStore ts;
    Transaction tx;
    Transaction txOld;
    TransactionMap<String, String> m;
    List<Transaction> list;

    s = MVStore.open(fileName);
    ts = new TransactionStore(s);
    tx = ts.begin();
    assertEquals(null, tx.getName());
    tx.setName("first transaction");
    assertEquals("first transaction", tx.getName());
    assertEquals(0, tx.getId());
    assertEquals(Transaction.STATUS_OPEN, tx.getStatus());
    m = tx.openMap("test");
    m.put("1", "Hello");
    list = ts.getOpenTransactions();
    assertEquals(1, list.size());
    txOld = list.get(0);
    assertTrue(tx.getId() == txOld.getId());
    s.commit();
    ts.close();
    s.close();

    s = MVStore.open(fileName);
    ts = new TransactionStore(s);
    tx = ts.begin();
    assertEquals(1, tx.getId());
    m = tx.openMap("test");
    assertEquals(null, m.get("1"));
    m.put("2", "Hello");
    list = ts.getOpenTransactions();
    assertEquals(2, list.size());
    txOld = list.get(0);
    assertEquals(0, txOld.getId());
    assertEquals(Transaction.STATUS_OPEN, txOld.getStatus());
    assertEquals("first transaction", txOld.getName());
    txOld.prepare();
    assertEquals(Transaction.STATUS_PREPARED, txOld.getStatus());
    s.commit();
    s.close();

    s = MVStore.open(fileName);
    ts = new TransactionStore(s);
    tx = ts.begin();
    m = tx.openMap("test");
    // TransactionStore was not closed, so we lost some ids
    assertEquals(65, tx.getId());
    list = ts.getOpenTransactions();
    assertEquals(2, list.size());
    txOld = list.get(1);
    assertEquals(1, txOld.getId());
    assertEquals(Transaction.STATUS_OPEN, txOld.getStatus());
    assertEquals(null, txOld.getName());
    txOld.rollback();
    txOld = list.get(0);
    assertEquals(0, txOld.getId());
    assertEquals(Transaction.STATUS_PREPARED, txOld.getStatus());
    assertEquals("first transaction", txOld.getName());
    txOld.commit();
    assertEquals("Hello", m.get("1"));
    s.close();

    FileUtils.delete(fileName);
  }
  private void testGetModifiedMaps() {
    MVStore s = MVStore.open(null);
    TransactionStore ts = new TransactionStore(s);
    Transaction tx;
    TransactionMap<String, String> m1, m2, m3;
    long sp;

    tx = ts.begin();
    m1 = tx.openMap("m1");
    m2 = tx.openMap("m2");
    m3 = tx.openMap("m3");
    assertFalse(tx.getChanges(0).hasNext());
    tx.commit();

    tx = ts.begin();
    m1 = tx.openMap("m1");
    m2 = tx.openMap("m2");
    m3 = tx.openMap("m3");
    m1.put("1", "100");
    sp = tx.setSavepoint();
    m2.put("1", "100");
    m3.put("1", "100");
    Iterator<Change> it = tx.getChanges(sp);
    assertTrue(it.hasNext());
    Change c;
    c = it.next();
    assertEquals("m3", c.mapName);
    assertEquals("1", c.key.toString());
    assertNull(c.value);
    assertTrue(it.hasNext());
    c = it.next();
    assertEquals("m2", c.mapName);
    assertEquals("1", c.key.toString());
    assertNull(c.value);
    assertFalse(it.hasNext());

    it = tx.getChanges(0);
    assertTrue(it.hasNext());
    c = it.next();
    assertEquals("m3", c.mapName);
    assertEquals("1", c.key.toString());
    assertNull(c.value);
    assertTrue(it.hasNext());
    c = it.next();
    assertEquals("m2", c.mapName);
    assertEquals("1", c.key.toString());
    assertNull(c.value);
    assertTrue(it.hasNext());
    c = it.next();
    assertEquals("m1", c.mapName);
    assertEquals("1", c.key.toString());
    assertNull(c.value);
    assertFalse(it.hasNext());

    tx.rollbackToSavepoint(sp);

    it = tx.getChanges(0);
    assertTrue(it.hasNext());
    c = it.next();
    assertEquals("m1", c.mapName);
    assertEquals("1", c.key.toString());
    assertNull(c.value);
    assertFalse(it.hasNext());

    tx.commit();

    s.close();
  }