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 testCompareWithPostgreSQL() throws Exception {
    ArrayList<Statement> statements = New.arrayList();
    ArrayList<Transaction> transactions = New.arrayList();
    ArrayList<TransactionMap<Integer, String>> maps = New.arrayList();
    int connectionCount = 3, opCount = 1000, rowCount = 10;
    try {
      Class.forName("org.postgresql.Driver");
      for (int i = 0; i < connectionCount; i++) {
        Connection conn = DriverManager.getConnection("jdbc:postgresql:test", "sa", "sa");
        statements.add(conn.createStatement());
      }
    } catch (Exception e) {
      // database not installed - ok
      return;
    }
    statements.get(0).execute("drop table if exists test cascade");
    statements.get(0).execute("create table test(id int primary key, name varchar(255))");

    MVStore s = MVStore.open(null);
    TransactionStore ts = new TransactionStore(s);
    for (int i = 0; i < connectionCount; i++) {
      Statement stat = statements.get(i);
      // 100 ms to avoid blocking (the test is single threaded)
      stat.execute("set statement_timeout to 100");
      Connection c = stat.getConnection();
      c.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
      c.setAutoCommit(false);
      Transaction transaction = ts.begin();
      transactions.add(transaction);
      TransactionMap<Integer, String> map;
      map = transaction.openMap("test");
      maps.add(map);
    }
    StringBuilder buff = new StringBuilder();

    Random r = new Random(1);
    try {
      for (int i = 0; i < opCount; i++) {
        int connIndex = r.nextInt(connectionCount);
        Statement stat = statements.get(connIndex);
        Transaction transaction = transactions.get(connIndex);
        TransactionMap<Integer, String> map = maps.get(connIndex);
        if (transaction == null) {
          transaction = ts.begin();
          map = transaction.openMap("test");
          transactions.set(connIndex, transaction);
          maps.set(connIndex, map);

          // read all data, to get a snapshot
          ResultSet rs = stat.executeQuery("select * from test order by id");
          buff.append(i).append(": [" + connIndex + "]=");
          int size = 0;
          while (rs.next()) {
            buff.append(' ');
            int k = rs.getInt(1);
            String v = rs.getString(2);
            buff.append(k).append(':').append(v);
            assertEquals(v, map.get(k));
            size++;
          }
          buff.append('\n');
          if (size != map.sizeAsLong()) {
            assertEquals(size, map.sizeAsLong());
          }
        }
        int x = r.nextInt(rowCount);
        int y = r.nextInt(rowCount);
        buff.append(i).append(": [" + connIndex + "]: ");
        ResultSet rs = null;
        switch (r.nextInt(7)) {
          case 0:
            buff.append("commit");
            stat.getConnection().commit();
            transaction.commit();
            transactions.set(connIndex, null);
            break;
          case 1:
            buff.append("rollback");
            stat.getConnection().rollback();
            transaction.rollback();
            transactions.set(connIndex, null);
            break;
          case 2:
            // insert or update
            String old = map.get(x);
            if (old == null) {
              buff.append("insert " + x + "=" + y);
              if (map.tryPut(x, "" + y)) {
                stat.execute("insert into test values(" + x + ", '" + y + "')");
              } else {
                buff.append(" -> row was locked");
                // the statement would time out in PostgreSQL
                // TODO test sometimes if timeout occurs
              }
            } else {
              buff.append("update " + x + "=" + y + " (old:" + old + ")");
              if (map.tryPut(x, "" + y)) {
                int c = stat.executeUpdate("update test set name = '" + y + "' where id = " + x);
                assertEquals(1, c);
              } else {
                buff.append(" -> row was locked");
                // the statement would time out in PostgreSQL
                // TODO test sometimes if timeout occurs
              }
            }
            break;
          case 3:
            buff.append("delete " + x);
            try {
              int c = stat.executeUpdate("delete from test where id = " + x);
              if (c == 1) {
                map.remove(x);
              } else {
                assertNull(map.get(x));
              }
            } catch (SQLException e) {
              assertTrue(map.get(x) != null);
              assertFalse(map.tryRemove(x));
              // PostgreSQL needs to rollback
              buff.append(" -> rollback");
              stat.getConnection().rollback();
              transaction.rollback();
              transactions.set(connIndex, null);
            }
            break;
          case 4:
          case 5:
          case 6:
            rs = stat.executeQuery("select * from test where id = " + x);
            String expected = rs.next() ? rs.getString(2) : null;
            buff.append("select " + x + "=" + expected);
            assertEquals("i:" + i, expected, map.get(x));
            break;
        }
        buff.append('\n');
      }
    } catch (Exception e) {
      e.printStackTrace();
      fail(buff.toString());
    }
    for (Statement stat : statements) {
      stat.getConnection().close();
    }
    ts.close();
    s.close();
  }