/* * Copyright © 2014-2016 Cask Data, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package co.cask.cdap.data2.dataset2.lib.table; import co.cask.cdap.api.common.Bytes; import co.cask.cdap.api.dataset.DatasetAdmin; import co.cask.cdap.api.dataset.DatasetContext; import co.cask.cdap.api.dataset.DatasetProperties; import co.cask.cdap.api.dataset.metrics.MeteredDataset; import co.cask.cdap.api.dataset.table.ConflictDetection; import co.cask.cdap.api.dataset.table.Delete; import co.cask.cdap.api.dataset.table.Get; import co.cask.cdap.api.dataset.table.Increment; import co.cask.cdap.api.dataset.table.Put; import co.cask.cdap.api.dataset.table.Result; import co.cask.cdap.api.dataset.table.Row; import co.cask.cdap.api.dataset.table.Scan; import co.cask.cdap.api.dataset.table.Scanner; import co.cask.cdap.api.dataset.table.Table; import co.cask.cdap.api.metrics.MetricsCollector; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.utils.ImmutablePair; import co.cask.cdap.data2.dataset2.TableAssert; import co.cask.cdap.proto.Id; import co.cask.tephra.Transaction; import co.cask.tephra.TransactionAware; import co.cask.tephra.TransactionManager; import co.cask.tephra.TransactionSystemClient; import co.cask.tephra.inmemory.InMemoryTxSystemClient; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.TreeMap; /** * Base test for Table. * @param <T> table type */ public abstract class TableTest<T extends Table> { static final byte[] R1 = Bytes.toBytes("r1"); static final byte[] R2 = Bytes.toBytes("r2"); static final byte[] R3 = Bytes.toBytes("r3"); static final byte[] R4 = Bytes.toBytes("r4"); static final byte[] R5 = Bytes.toBytes("r5"); static final byte[] C1 = Bytes.toBytes("c1"); static final byte[] C2 = Bytes.toBytes("c2"); static final byte[] C3 = Bytes.toBytes("c3"); static final byte[] C4 = Bytes.toBytes("c4"); static final byte[] C5 = Bytes.toBytes("c5"); static final byte[] V1 = Bytes.toBytes("v1"); static final byte[] V2 = Bytes.toBytes("v2"); static final byte[] V3 = Bytes.toBytes("v3"); static final byte[] V4 = Bytes.toBytes("v4"); static final byte[] V5 = Bytes.toBytes("v5"); static final byte[] L1 = Bytes.toBytes(1L); static final byte[] L2 = Bytes.toBytes(2L); static final byte[] L3 = Bytes.toBytes(3L); static final byte[] L4 = Bytes.toBytes(4L); static final byte[] L5 = Bytes.toBytes(5L); static final byte[] MT = new byte[0]; protected static final Id.Namespace NAMESPACE1 = Id.Namespace.from("ns1"); protected static final Id.Namespace NAMESPACE2 = Id.Namespace.from("ns2"); protected static final String MY_TABLE = "myTable"; protected static final DatasetContext CONTEXT1 = DatasetContext.from(NAMESPACE1.getId()); protected static final DatasetContext CONTEXT2 = DatasetContext.from("ns2"); protected TransactionSystemClient txClient; protected abstract T getTable(DatasetContext datasetContext, String name, ConflictDetection conflictLevel) throws Exception; protected abstract DatasetAdmin getTableAdmin(DatasetContext datasetContext, String name, DatasetProperties props) throws Exception; protected abstract boolean isReadlessIncrementSupported(); protected T getTable(DatasetContext datasetContext, String name) throws Exception { return getTable(datasetContext, name, ConflictDetection.ROW); } protected DatasetAdmin getTableAdmin(DatasetContext datasetContext, String name) throws Exception { return getTableAdmin(datasetContext, name, DatasetProperties.EMPTY); } @Before public void before() { Configuration txConf = HBaseConfiguration.create(); TransactionManager txManager = new TransactionManager(txConf); txManager.startAndWait(); txClient = new InMemoryTxSystemClient(txManager); } @After public void after() { txClient = null; } @Test public void testCreate() throws Exception { DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); Assert.assertFalse(admin.exists()); admin.create(); Assert.assertTrue(admin.exists()); // creation of non-existing table should do nothing admin.create(); admin.drop(); } // TODO figure out what to do with this. As long as ObjectMappedTable writes empty values, we cannot // throw exceptions, and this test is pointless. @Ignore @Test public void testEmptyValuePut() throws Exception { DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); Transaction tx = txClient.startShort(); try { Table myTable = getTable(CONTEXT1, MY_TABLE); try { myTable.put(R1, C1, MT); Assert.fail("Put with empty value should fail."); } catch (IllegalArgumentException e) { // expected } try { myTable.put(R1, a(C1, C2), a(V1, MT)); Assert.fail("Put with empty value should fail."); } catch (IllegalArgumentException e) { // expected } try { myTable.put(new Put(R1).add(C1, V1).add(C2, MT)); Assert.fail("Put with empty value should fail."); } catch (IllegalArgumentException e) { // expected } try { myTable.compareAndSwap(R1, C1, V1, MT); Assert.fail("CompareAndSwap with empty value should fail."); } catch (IllegalArgumentException e) { // expected } } finally { txClient.abort(tx); admin.drop(); } } @Test public void testEmptyGet() throws Exception { DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); try { Transaction tx = txClient.startShort(); Table myTable = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable).startTx(tx); myTable.put(R1, C1, V1); myTable.put(R1, C2, V2); // to be used for validation later TreeMap<byte[], byte[]> expectedColumns = new TreeMap<>(Bytes.BYTES_COMPARATOR); expectedColumns.put(C1, V1); expectedColumns.put(C2, V2); Result expectedResult = new Result(R1, expectedColumns); Result emptyResult = new Result(R1, ImmutableMap.<byte[], byte[]>of()); ((TransactionAware) myTable).commitTx(); txClient.commit(tx); // start another transaction, so that the buffering table doesn't cache the values; the underlying Table // implementations are tested this way. tx = txClient.startShort(); ((TransactionAware) myTable).startTx(tx); Row row = myTable.get(R1, new byte[][]{ C1, C2 }); assertEquals(expectedResult, row); // passing in empty columns returns empty result row = myTable.get(R1, new byte[][]{}); assertEquals(emptyResult, row); // test all the Get constructors and their behavior // constructors specifying only rowkey retrieve all columns Get get = new Get(R1); Assert.assertNull(get.getColumns()); assertEquals(expectedResult, myTable.get(get)); get = new Get(Bytes.toString(R1)); Assert.assertNull(get.getColumns()); assertEquals(expectedResult, myTable.get(get)); get.add(C1); get.add(Bytes.toString(C2)); assertEquals(expectedResult, myTable.get(get)); // constructor specifying columns, but with an empty array/collection retrieve 0 columns get = new Get(R1, new byte[][]{}); Assert.assertNotNull(get.getColumns()); assertEquals(emptyResult, myTable.get(get)); get = new Get(R1, ImmutableList.<byte[]>of()); Assert.assertNotNull(get.getColumns()); assertEquals(emptyResult, myTable.get(get)); get = new Get(Bytes.toString(R1), new String[]{}); Assert.assertNotNull(get.getColumns()); assertEquals(emptyResult, myTable.get(get)); get = new Get(Bytes.toString(R1), ImmutableList.<String>of()); Assert.assertNotNull(get.getColumns()); assertEquals(emptyResult, myTable.get(get)); row = myTable.get(R1, new byte[][]{ }); assertEquals(emptyResult, row); txClient.abort(tx); } finally { admin.drop(); } } private void assertEquals(Row expected, Row actual) { Assert.assertArrayEquals(expected.getRow(), actual.getRow()); Assert.assertTrue(columnsEqual(expected.getColumns(), actual.getColumns())); } // AbstractMap#equals doesn't work, because the entries are byte[] private boolean columnsEqual(Map<byte[], byte[]> expected, Map<byte[], byte[]> actual) { if (expected.size() != actual.size()) { return false; } for (Map.Entry<byte[], byte[]> entry : expected.entrySet()) { byte[] key = entry.getKey(); byte[] value = entry.getValue(); if (value == null) { if (!(actual.get(key) == null && actual.containsKey(key))) { return false; } } else { if (!Arrays.equals(value, actual.get(key))) { return false; } } } return true; } @Test public void testEmptyDelete() throws Exception { DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); try { Transaction tx = txClient.startShort(); Table myTable = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable).startTx(tx); myTable.put(R1, C1, V1); myTable.put(R1, C2, V2); myTable.put(R1, C3, V3); // specifying empty columns means to delete nothing myTable.delete(R1, new byte[][]{}); myTable.delete(new Delete(R1, new byte[][]{})); myTable.delete(new Delete(R1, ImmutableList.<byte[]>of())); myTable.delete(new Delete(Bytes.toString(R1), new String[]{})); myTable.delete(new Delete(Bytes.toString(R1), ImmutableList.<String>of())); // verify the above delete calls deleted none of the rows Row row = myTable.get(R1); Assert.assertEquals(3, row.getColumns().size()); Assert.assertArrayEquals(R1, row.getRow()); Assert.assertArrayEquals(V1, row.get(C1)); Assert.assertArrayEquals(V2, row.get(C2)); Assert.assertArrayEquals(V3, row.get(C3)); // test deletion of only one column Delete delete = new Delete(R1); Assert.assertNull(delete.getColumns()); delete.add(C1); Assert.assertNotNull(delete.getColumns()); myTable.delete(delete); row = myTable.get(R1); Assert.assertEquals(2, row.getColumns().size()); Assert.assertArrayEquals(R1, row.getRow()); Assert.assertArrayEquals(V2, row.get(C2)); Assert.assertArrayEquals(V3, row.get(C3)); // test delete of all columns myTable.delete(new Delete(R1)); Assert.assertEquals(0, myTable.get(R1).getColumns().size()); txClient.abort(tx); } finally { admin.drop(); } } @Test public void testMultiGetWithEmpty() throws Exception { DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); try { Transaction tx = txClient.startShort(); Table myTable = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable).startTx(tx); myTable.put(R1, C1, V1); myTable.put(R1, C2, V2); myTable.put(R1, C3, V3); myTable.put(R1, C4, V4); List<Get> gets = new ArrayList<>(); // the second and fourth Gets are requesting 0 columns. This tests correctness of batch-get logic, when there // is/are empty Gets among them. gets.add(new Get(R1, C1)); gets.add(new Get(R1, ImmutableList.<byte[]>of())); gets.add(new Get(R1, C2, C3)); gets.add(new Get(R1, ImmutableList.<byte[]>of())); gets.add(new Get(R1, C4)); List<Row> rows = myTable.get(gets); // first off, the Gets at index two and four should be empty Assert.assertEquals(0, rows.get(1).getColumns().size()); Assert.assertEquals(0, rows.get(3).getColumns().size()); // verify the results of the other Gets Assert.assertEquals(1, rows.get(0).getColumns().size()); Assert.assertArrayEquals(V1, rows.get(0).get(C1)); Assert.assertEquals(2, rows.get(2).getColumns().size()); Assert.assertArrayEquals(V2, rows.get(2).get(C2)); Assert.assertArrayEquals(V3, rows.get(2).get(C3)); Assert.assertEquals(1, rows.get(4).getColumns().size()); Assert.assertArrayEquals(V4, rows.get(4).get(C4)); txClient.abort(tx); } finally { admin.drop(); } } @Test public void testBasicGetPutWithTx() throws Exception { DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); try { Transaction tx1 = txClient.startShort(); Table myTable1 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable1).startTx(tx1); // write r1->c1,v1 but not commit myTable1.put(R1, a(C1), a(V1)); // TableAssert.verify can see changes inside tx TableAssert.assertRow(a(C1, V1), myTable1.get(R1, a(C1, C2)).getColumns()); Assert.assertArrayEquals(V1, myTable1.get(R1, C1)); Assert.assertArrayEquals(null, myTable1.get(R1, C2)); Assert.assertArrayEquals(null, myTable1.get(R2, C1)); TableAssert.assertRow(a(C1, V1), myTable1.get(R1).getColumns()); // start new tx (doesn't see changes of the tx1) Transaction tx2 = txClient.startShort(); Table myTable2 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable2).startTx(tx2); // TableAssert.verify doesn't see changes of tx1 TableAssert.assertRow(a(), myTable2.get(R1, a(C1, C2))); Assert.assertArrayEquals(null, myTable2.get(R1, C1)); Assert.assertArrayEquals(null, myTable2.get(R1, C2)); TableAssert.assertRow(a(), myTable2.get(R1)); // write r2->c2,v2 in tx2 myTable2.put(R2, a(C2), a(V2)); // TableAssert.verify can see own changes TableAssert.assertRow(a(C2, V2), myTable2.get(R2, a(C1, C2))); Assert.assertArrayEquals(null, myTable2.get(R2, C1)); Assert.assertArrayEquals(V2, myTable2.get(R2, C2)); TableAssert.assertRow(a(C2, V2), myTable2.get(R2)); // TableAssert.verify tx1 cannot see changes of tx2 TableAssert.assertRow(a(), myTable1.get(R2, a(C1, C2))); Assert.assertArrayEquals(null, myTable1.get(R2, C1)); Assert.assertArrayEquals(null, myTable1.get(R2, C2)); TableAssert.assertRow(a(), myTable1.get(R2)); // committing tx1 in stages to check races are handled well // * first, flush operations of table Assert.assertTrue(txClient.canCommit(tx1, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); // start tx3 and TableAssert.verify that changes of tx1 are not visible yet (even though they are flushed) Transaction tx3 = txClient.startShort(); Table myTable3 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable3).startTx(tx3); TableAssert.assertRow(a(), myTable3.get(R1, a(C1, C2))); Assert.assertArrayEquals(null, myTable3.get(R1, C1)); Assert.assertArrayEquals(null, myTable3.get(R1, C2)); TableAssert.assertRow(a(), myTable3.get(R1)); Assert.assertTrue(txClient.canCommit(tx3, ((TransactionAware) myTable3).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable3).commitTx()); Assert.assertTrue(txClient.commit(tx3)); // * second, make tx visible Assert.assertTrue(txClient.commit(tx1)); // start tx4 and TableAssert.verify that changes of tx1 are now visible // NOTE: table instance can be re-used in series of transactions Transaction tx4 = txClient.startShort(); ((TransactionAware) myTable3).startTx(tx4); TableAssert.assertRow(a(C1, V1), myTable3.get(R1, a(C1, C2))); Assert.assertArrayEquals(V1, myTable3.get(R1, C1)); Assert.assertArrayEquals(null, myTable3.get(R1, C2)); TableAssert.assertRow(a(C1, V1), myTable3.get(R1)); // but tx2 still doesn't see committed changes of tx2 TableAssert.assertRow(a(), myTable2.get(R1, a(C1, C2))); Assert.assertArrayEquals(null, myTable2.get(R1, C1)); Assert.assertArrayEquals(null, myTable2.get(R1, C2)); TableAssert.assertRow(a(), myTable2.get(R1)); // and tx4 doesn't see changes of tx2 TableAssert.assertRow(a(), myTable3.get(R2, a(C1, C2))); Assert.assertArrayEquals(null, myTable3.get(R2, C1)); Assert.assertArrayEquals(null, myTable3.get(R2, C2)); TableAssert.assertRow(a(), myTable3.get(R2)); // committing tx4 Assert.assertTrue(txClient.canCommit(tx4, ((TransactionAware) myTable3).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable3).commitTx()); Assert.assertTrue(txClient.commit(tx4)); // do change in tx2 that is conflicting with tx1 myTable2.put(R1, a(C1), a(V2)); // change is OK and visible inside tx2 TableAssert.assertRow(a(C1, V2), myTable2.get(R1, a(C1, C2))); Assert.assertArrayEquals(V2, myTable2.get(R1, C1)); Assert.assertArrayEquals(null, myTable2.get(R1, C2)); TableAssert.assertRow(a(C1, V2), myTable2.get(R1)); // cannot commit: conflict should be detected Assert.assertFalse(txClient.canCommit(tx2, ((TransactionAware) myTable2).getTxChanges())); // rolling back tx2 changes and aborting tx ((TransactionAware) myTable2).rollbackTx(); txClient.abort(tx2); // TableAssert.verifying that none of the changes of tx2 made it to be visible to other txs // NOTE: table instance can be re-used in series of transactions Transaction tx5 = txClient.startShort(); ((TransactionAware) myTable3).startTx(tx5); TableAssert.assertRow(a(C1, V1), myTable3.get(R1, a(C1, C2))); Assert.assertArrayEquals(V1, myTable3.get(R1, C1)); Assert.assertArrayEquals(null, myTable3.get(R1, C2)); TableAssert.assertRow(a(C1, V1), myTable3.get(R1)); TableAssert.assertRow(a(), myTable3.get(R2, a(C1, C2))); Assert.assertArrayEquals(null, myTable3.get(R2, C1)); Assert.assertArrayEquals(null, myTable3.get(R2, C2)); TableAssert.assertRow(a(), myTable3.get(R2)); Assert.assertTrue(txClient.canCommit(tx5, ((TransactionAware) myTable3).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable3).commitTx()); Assert.assertTrue(txClient.commit(tx5)); } finally { admin.drop(); } } @Test public void testBasicCompareAndSwapWithTx() throws Exception { DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); try { Transaction tx1 = txClient.startShort(); Table myTable1 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable1).startTx(tx1); // write r1->c1,v1 but not commit myTable1.put(R1, a(C1), a(V1)); // write r1->c2,v2 but not commit Assert.assertTrue(myTable1.compareAndSwap(R1, C2, null, V5)); // TableAssert.verify compare and swap result visible inside tx before commit TableAssert.assertRow(a(C1, V1, C2, V5), myTable1.get(R1, a(C1, C2))); Assert.assertArrayEquals(V1, myTable1.get(R1, C1)); Assert.assertArrayEquals(V5, myTable1.get(R1, C2)); Assert.assertArrayEquals(null, myTable1.get(R1, C3)); TableAssert.assertRow(a(C1, V1, C2, V5), myTable1.get(R1)); // these should fail Assert.assertFalse(myTable1.compareAndSwap(R1, C1, null, V1)); Assert.assertFalse(myTable1.compareAndSwap(R1, C1, V2, V1)); Assert.assertFalse(myTable1.compareAndSwap(R1, C2, null, V2)); Assert.assertFalse(myTable1.compareAndSwap(R1, C2, V2, V1)); // but this should succeed Assert.assertTrue(myTable1.compareAndSwap(R1, C2, V5, V2)); // start new tx (doesn't see changes of the tx1) Transaction tx2 = txClient.startShort(); // committing tx1 in stages to check races are handled well // * first, flush operations of table Assert.assertTrue(txClient.canCommit(tx1, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); // check that tx2 doesn't see changes (even though they were flushed) of tx1 by trying to compareAndSwap // assuming current value is null Table myTable2 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable2).startTx(tx2); Assert.assertTrue(myTable2.compareAndSwap(R1, C1, null, V3)); // start tx3 and TableAssert.verify same thing again Transaction tx3 = txClient.startShort(); Table myTable3 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable3).startTx(tx3); Assert.assertTrue(myTable3.compareAndSwap(R1, C1, null, V2)); // * second, make tx visible Assert.assertTrue(txClient.commit(tx1)); // TableAssert.verify that tx2 cannot commit because of the conflicts... Assert.assertFalse(txClient.canCommit(tx2, ((TransactionAware) myTable2).getTxChanges())); ((TransactionAware) myTable2).rollbackTx(); txClient.abort(tx2); // start tx4 and TableAssert.verify that changes of tx1 are now visible Transaction tx4 = txClient.startShort(); Table myTable4 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable4).startTx(tx4); TableAssert.assertRow(a(C1, V1, C2, V2), myTable4.get(R1, a(C1, C2))); Assert.assertArrayEquals(V1, myTable4.get(R1, C1)); Assert.assertArrayEquals(V2, myTable4.get(R1, C2)); Assert.assertArrayEquals(null, myTable4.get(R1, C3)); TableAssert.assertRow(a(C2, V2), myTable4.get(R1, a(C2))); TableAssert.assertRow(a(C1, V1, C2, V2), myTable4.get(R1)); // tx3 still cannot see tx1 changes Assert.assertTrue(myTable3.compareAndSwap(R1, C2, null, V5)); // and it cannot commit because its changes cause conflicts Assert.assertFalse(txClient.canCommit(tx3, ((TransactionAware) myTable3).getTxChanges())); ((TransactionAware) myTable3).rollbackTx(); txClient.abort(tx3); // TableAssert.verify we can do some ops with tx4 based on data written with tx1 Assert.assertFalse(myTable4.compareAndSwap(R1, C1, null, V4)); Assert.assertFalse(myTable4.compareAndSwap(R1, C2, null, V5)); Assert.assertTrue(myTable4.compareAndSwap(R1, C1, V1, V3)); Assert.assertTrue(myTable4.compareAndSwap(R1, C2, V2, V4)); myTable4.delete(R1, a(C1)); // committing tx4 Assert.assertTrue(txClient.canCommit(tx4, ((TransactionAware) myTable3).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable4).commitTx()); Assert.assertTrue(txClient.commit(tx4)); // TableAssert.verifying the result contents in next transaction Transaction tx5 = txClient.startShort(); // NOTE: table instance can be re-used in series of transactions ((TransactionAware) myTable4).startTx(tx5); TableAssert.assertRow(a(C2, V4), myTable4.get(R1, a(C1, C2))); Assert.assertArrayEquals(null, myTable4.get(R1, C1)); Assert.assertArrayEquals(V4, myTable4.get(R1, C2)); TableAssert.assertRow(a(C2, V4), myTable4.get(R1)); Assert.assertTrue(txClient.canCommit(tx5, ((TransactionAware) myTable3).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable3).commitTx()); Assert.assertTrue(txClient.commit(tx5)); } finally { admin.drop(); } } @Test public void testBasicIncrementWithTx() throws Exception { DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); Table myTable1, myTable2, myTable3, myTable4; try { Transaction tx1 = txClient.startShort(); myTable1 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable1).startTx(tx1); myTable1.put(R1, a(C1), a(L4)); TableAssert.assertColumns(a(C1), lb(1L), myTable1.incrementAndGet(R1, a(C1), la(-3L))); TableAssert.assertColumns(a(C2), lb(2L), myTable1.incrementAndGet(R1, a(C2), la(2L))); // TableAssert.verify increment result visible inside tx before commit TableAssert.assertRow(a(C1, L1, C2, L2), myTable1.get(R1, a(C1, C2))); Assert.assertArrayEquals(L1, myTable1.get(R1, C1)); Assert.assertArrayEquals(L2, myTable1.get(R1, C2)); Assert.assertArrayEquals(null, myTable1.get(R1, C3)); TableAssert.assertRow(a(C1, L1), myTable1.get(R1, a(C1))); TableAssert.assertRow(a(C1, L1, C2, L2), myTable1.get(R1)); // incrementing non-long value should fail myTable1.put(R1, a(C5), a(V5)); try { myTable1.incrementAndGet(R1, a(C5), la(5L)); Assert.assertTrue(false); } catch (NumberFormatException e) { // Expected } // previous increment should not do any change TableAssert.assertRow(a(C5, V5), myTable1.get(R1, a(C5))); Assert.assertArrayEquals(V5, myTable1.get(R1, C5)); // start new tx (doesn't see changes of the tx1) Transaction tx2 = txClient.startShort(); // committing tx1 in stages to check races are handled well // * first, flush operations of table Assert.assertTrue(txClient.canCommit(tx1, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); // check that tx2 doesn't see changes (even though they were flushed) of tx1 // assuming current value is null myTable2 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable2).startTx(tx2); TableAssert.assertRow(a(), myTable2.get(R1, a(C1, C2, C5))); Assert.assertArrayEquals(null, myTable2.get(R1, C1)); Assert.assertArrayEquals(null, myTable2.get(R1, C2)); Assert.assertArrayEquals(null, myTable2.get(R1, C5)); TableAssert.assertRow(a(), myTable2.get(R1)); TableAssert.assertColumns(a(C1), lb(55L), myTable2.incrementAndGet(R1, a(C1), la(55L))); // start tx3 and TableAssert.verify same thing again Transaction tx3 = txClient.startShort(); myTable3 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable3).startTx(tx3); TableAssert.assertRow(a(), myTable3.get(R1, a(C1, C2, C5))); Assert.assertArrayEquals(null, myTable3.get(R1, C1)); Assert.assertArrayEquals(null, myTable3.get(R1, C2)); Assert.assertArrayEquals(null, myTable3.get(R1, C5)); TableAssert.assertRow(a(), myTable3.get(R1)); TableAssert.assertColumns(a(C1), lb(4L), myTable3.incrementAndGet(R1, a(C1), la(4L))); // * second, make tx visible Assert.assertTrue(txClient.commit(tx1)); // TableAssert.verify that tx2 cannot commit because of the conflicts... Assert.assertFalse(txClient.canCommit(tx2, ((TransactionAware) myTable2).getTxChanges())); ((TransactionAware) myTable2).rollbackTx(); txClient.abort(tx2); // start tx4 and TableAssert.verify that changes of tx1 are now visible Transaction tx4 = txClient.startShort(); myTable4 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable4).startTx(tx4); TableAssert.assertRow(a(C1, L1, C2, L2, C5, V5), myTable4.get(R1, a(C1, C2, C3, C4, C5))); TableAssert.assertRow(a(C2, L2), myTable4.get(R1, a(C2))); Assert.assertArrayEquals(L1, myTable4.get(R1, C1)); Assert.assertArrayEquals(L2, myTable4.get(R1, C2)); Assert.assertArrayEquals(null, myTable4.get(R1, C3)); Assert.assertArrayEquals(V5, myTable4.get(R1, C5)); TableAssert.assertRow(a(C1, L1, C5, V5), myTable4.get(R1, a(C1, C5))); TableAssert.assertRow(a(C1, L1, C2, L2, C5, V5), myTable4.get(R1)); // tx3 still cannot see tx1 changes, only its own TableAssert.assertRow(a(C1, L4), myTable3.get(R1, a(C1, C2, C5))); Assert.assertArrayEquals(L4, myTable3.get(R1, C1)); Assert.assertArrayEquals(null, myTable3.get(R1, C2)); Assert.assertArrayEquals(null, myTable3.get(R1, C5)); TableAssert.assertRow(a(C1, L4), myTable3.get(R1)); // and it cannot commit because its changes cause conflicts Assert.assertFalse(txClient.canCommit(tx3, ((TransactionAware) myTable3).getTxChanges())); ((TransactionAware) myTable3).rollbackTx(); txClient.abort(tx3); // TableAssert.verify we can do some ops with tx4 based on data written with tx1 TableAssert.assertColumns(a(C1, C2, C3), lb(3L, 3L, 5L), myTable4.incrementAndGet(R1, a(C1, C2, C3), la(2L, 1L, 5L))); myTable4.delete(R1, a(C2)); TableAssert.assertColumns(a(C4), lb(3L), myTable4.incrementAndGet(R1, a(C4), la(3L))); myTable4.delete(R1, a(C1)); // committing tx4 Assert.assertTrue(txClient.canCommit(tx4, ((TransactionAware) myTable3).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable4).commitTx()); Assert.assertTrue(txClient.commit(tx4)); // TableAssert.verifying the result contents in next transaction Transaction tx5 = txClient.startShort(); // NOTE: table instance can be re-used in series of transactions ((TransactionAware) myTable4).startTx(tx5); TableAssert.assertRow(a(C3, L5, C4, L3, C5, V5), myTable4.get(R1, a(C1, C2, C3, C4, C5))); Assert.assertArrayEquals(null, myTable4.get(R1, C1)); Assert.assertArrayEquals(null, myTable4.get(R1, C2)); Assert.assertArrayEquals(L5, myTable4.get(R1, C3)); Assert.assertArrayEquals(L3, myTable4.get(R1, C4)); Assert.assertArrayEquals(V5, myTable4.get(R1, C5)); TableAssert.assertRow(a(C3, L5, C4, L3, C5, V5), myTable4.get(R1)); Assert.assertTrue(txClient.canCommit(tx5, ((TransactionAware) myTable3).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable3).commitTx()); Assert.assertTrue(txClient.commit(tx5)); } finally { admin.drop(); } } // todo: unify with testBasicIncrementWithTx - it is exactly same @Test public void testBasicIncrementWriteWithTxSmall() throws Exception { DatasetProperties props = DatasetProperties.builder().add( Table.PROPERTY_READLESS_INCREMENT, Boolean.TRUE.toString()).build(); DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE, props); admin.create(); Table myTable = getTable(CONTEXT1, MY_TABLE); // start 1st tx Transaction tx = txClient.startShort(); ((TransactionAware) myTable).startTx(tx); myTable.increment(R1, a(C1), la(-3L)); // we'll use this one to test that we can delete increment and increment again myTable.increment(R2, a(C2), la(5L)); commitAndAssertSuccess(tx, (TransactionAware) myTable); // start 2nd tx tx = txClient.startShort(); ((TransactionAware) myTable).startTx(tx); Assert.assertArrayEquals(Bytes.toBytes(-3L), myTable.get(R1, C1)); myTable.increment(R1, a(C1), la(-3L)); Assert.assertArrayEquals(Bytes.toBytes(-6L), myTable.get(R1, C1)); Assert.assertArrayEquals(Bytes.toBytes(5L), myTable.get(R2, C2)); myTable.delete(R2, C2); Assert.assertArrayEquals(null, myTable.get(R2, C2)); commitAndAssertSuccess(tx, (TransactionAware) myTable); // start 3rd tx tx = txClient.startShort(); ((TransactionAware) myTable).startTx(tx); Assert.assertArrayEquals(Bytes.toBytes(-6L), myTable.get(R1, C1)); Assert.assertArrayEquals(null, myTable.get(R2, C2)); myTable.increment(R2, a(C2), la(7L)); Assert.assertArrayEquals(Bytes.toBytes(7L), myTable.get(R2, C2)); commitAndAssertSuccess(tx, (TransactionAware) myTable); // start 4rd tx tx = txClient.startShort(); ((TransactionAware) myTable).startTx(tx); Assert.assertArrayEquals(Bytes.toBytes(7L), myTable.get(R2, C2)); commitAndAssertSuccess(tx, (TransactionAware) myTable); admin.drop(); } private void commitAndAssertSuccess(Transaction tx, TransactionAware txAware) throws Exception { Assert.assertTrue(txClient.canCommit(tx, txAware.getTxChanges())); Assert.assertTrue(txAware.commitTx()); Assert.assertTrue(txClient.commit(tx)); } @Test public void testBasicIncrementWriteWithTx() throws Exception { DatasetProperties props = DatasetProperties.builder().add( Table.PROPERTY_READLESS_INCREMENT, Boolean.TRUE.toString()).build(); DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE, props); admin.create(); Table myTable1, myTable2, myTable3, myTable4; try { Transaction tx1 = txClient.startShort(); myTable1 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable1).startTx(tx1); myTable1.put(R1, a(C1), a(L4)); myTable1.increment(R1, a(C1), la(-3L)); myTable1.increment(R1, a(C2), la(2L)); // TableAssert.verify increment result visible inside tx before commit TableAssert.assertRow(a(C1, L1, C2, L2), myTable1.get(R1, a(C1, C2))); Assert.assertArrayEquals(L1, myTable1.get(R1, C1)); Assert.assertArrayEquals(L2, myTable1.get(R1, C2)); Assert.assertArrayEquals(null, myTable1.get(R1, C3)); TableAssert.assertRow(a(C1, L1), myTable1.get(R1, a(C1))); TableAssert.assertRow(a(C1, L1, C2, L2), myTable1.get(R1)); // incrementing non-long value should fail myTable1.put(R1, a(C5), a(V5)); try { myTable1.increment(R1, a(C5), la(5L)); Assert.assertTrue(false); } catch (NumberFormatException e) { // Expected } // previous increment should not do any change TableAssert.assertRow(a(C5, V5), myTable1.get(R1, a(C5))); Assert.assertArrayEquals(V5, myTable1.get(R1, C5)); // start new tx (doesn't see changes of the tx1) Transaction tx2 = txClient.startShort(); // committing tx1 in stages to check races are handled well // * first, flush operations of table Assert.assertTrue(txClient.canCommit(tx1, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); // check that tx2 doesn't see changes (even though they were flushed) of tx1 // assuming current value is null myTable2 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable2).startTx(tx2); TableAssert.assertRow(a(), myTable2.get(R1, a(C1, C2, C5))); Assert.assertArrayEquals(null, myTable2.get(R1, C1)); Assert.assertArrayEquals(null, myTable2.get(R1, C2)); Assert.assertArrayEquals(null, myTable2.get(R1, C5)); TableAssert.assertRow(a(), myTable2.get(R1)); myTable2.increment(R1, a(C1), la(55L)); // start tx3 and TableAssert.verify same thing again Transaction tx3 = txClient.startShort(); myTable3 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable3).startTx(tx3); TableAssert.assertRow(a(), myTable3.get(R1, a(C1, C2, C5))); Assert.assertArrayEquals(null, myTable3.get(R1, C1)); Assert.assertArrayEquals(null, myTable3.get(R1, C2)); Assert.assertArrayEquals(null, myTable3.get(R1, C5)); TableAssert.assertRow(a(), myTable3.get(R1)); myTable3.increment(R1, a(C1), la(4L)); // * second, make tx visible Assert.assertTrue(txClient.commit(tx1)); // TableAssert.verify that tx2 cannot commit because of the conflicts... Assert.assertFalse(txClient.canCommit(tx2, ((TransactionAware) myTable2).getTxChanges())); ((TransactionAware) myTable2).rollbackTx(); txClient.abort(tx2); // start tx4 and TableAssert.verify that changes of tx1 are now visible Transaction tx4 = txClient.startShort(); myTable4 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable4).startTx(tx4); TableAssert.assertRow(a(C1, L1, C2, L2, C5, V5), myTable4.get(R1, a(C1, C2, C3, C4, C5))); TableAssert.assertRow(a(C2, L2), myTable4.get(R1, a(C2))); Assert.assertArrayEquals(L1, myTable4.get(R1, C1)); Assert.assertArrayEquals(L2, myTable4.get(R1, C2)); Assert.assertArrayEquals(null, myTable4.get(R1, C3)); Assert.assertArrayEquals(V5, myTable4.get(R1, C5)); TableAssert.assertRow(a(C1, L1, C5, V5), myTable4.get(R1, a(C1, C5))); TableAssert.assertRow(a(C1, L1, C2, L2, C5, V5), myTable4.get(R1)); // tx3 still cannot see tx1 changes, only its own TableAssert.assertRow(a(C1, L4), myTable3.get(R1, a(C1, C2, C5))); Assert.assertArrayEquals(L4, myTable3.get(R1, C1)); Assert.assertArrayEquals(null, myTable3.get(R1, C2)); Assert.assertArrayEquals(null, myTable3.get(R1, C5)); TableAssert.assertRow(a(C1, L4), myTable3.get(R1)); // and it cannot commit because its changes cause conflicts Assert.assertFalse(txClient.canCommit(tx3, ((TransactionAware) myTable3).getTxChanges())); ((TransactionAware) myTable3).rollbackTx(); txClient.abort(tx3); // TableAssert.verify we can do some ops with tx4 based on data written with tx1 myTable4.increment(R1, a(C1, C2, C3), la(2L, 1L, 5L)); myTable4.delete(R1, a(C2)); myTable4.increment(R1, a(C4), la(3L)); myTable4.delete(R1, a(C1)); // committing tx4 Assert.assertTrue(txClient.canCommit(tx4, ((TransactionAware) myTable3).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable4).commitTx()); Assert.assertTrue(txClient.commit(tx4)); // TableAssert.verifying the result contents in next transaction Transaction tx5 = txClient.startShort(); // NOTE: table instance can be re-used in series of transactions ((TransactionAware) myTable4).startTx(tx5); TableAssert.assertRow(a(C3, L5, C4, L3, C5, V5), myTable4.get(R1, a(C1, C2, C3, C4, C5))); Assert.assertArrayEquals(null, myTable4.get(R1, C1)); Assert.assertArrayEquals(null, myTable4.get(R1, C2)); Assert.assertArrayEquals(L5, myTable4.get(R1, C3)); Assert.assertArrayEquals(L3, myTable4.get(R1, C4)); Assert.assertArrayEquals(V5, myTable4.get(R1, C5)); TableAssert.assertRow(a(C3, L5, C4, L3, C5, V5), myTable4.get(R1)); Assert.assertTrue(txClient.canCommit(tx5, ((TransactionAware) myTable3).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable3).commitTx()); Assert.assertTrue(txClient.commit(tx5)); } finally { admin.drop(); } } @Test public void testBasicDeleteWithTx() throws Exception { // we will test 3 different delete column ops and one delete row op // * delete column with delete // * delete column with put null value // * delete column with put byte[0] value // * delete row with delete DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); try { // write smth and commit Transaction tx1 = txClient.startShort(); Table myTable1 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable1).startTx(tx1); myTable1.put(R1, a(C1, C2), a(V1, V2)); myTable1.put(R2, a(C1, C2), a(V2, V3)); myTable1.put(R3, a(C1, C2), a(V3, V4)); myTable1.put(R4, a(C1, C2), a(V4, V5)); Assert.assertTrue(txClient.canCommit(tx1, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); Assert.assertTrue(txClient.commit(tx1)); // Now, we will test delete ops // start new tx2 Transaction tx2 = txClient.startShort(); Table myTable2 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable2).startTx(tx2); // TableAssert.verify tx2 sees changes of tx1 TableAssert.assertRow(a(C1, V1, C2, V2), myTable2.get(R1, a(C1, C2))); // TableAssert.verify tx2 sees changes of tx1 TableAssert.assertRow(a(C1, V2, C2, V3), myTable2.get(R2)); // delete c1, r2 myTable2.delete(R1, a(C1)); myTable2.delete(R2); // same as delete a column myTable2.put(R3, C1, null); // TableAssert.verify can see deletes in own changes before commit TableAssert.assertRow(a(C2, V2), myTable2.get(R1, a(C1, C2))); Assert.assertArrayEquals(null, myTable2.get(R1, C1)); Assert.assertArrayEquals(V2, myTable2.get(R1, C2)); TableAssert.assertRow(a(), myTable2.get(R2)); TableAssert.assertRow(a(C2, V4), myTable2.get(R3)); // overwrite c2 and write new value to c1 myTable2.put(R1, a(C1, C2), a(V3, V4)); myTable2.put(R2, a(C1, C2), a(V4, V5)); myTable2.put(R3, a(C1, C2), a(V1, V2)); myTable2.put(R4, a(C1, C2), a(V2, V3)); // TableAssert.verify can see changes in own changes before commit TableAssert.assertRow(a(C1, V3, C2, V4), myTable2.get(R1, a(C1, C2, C3))); Assert.assertArrayEquals(V3, myTable2.get(R1, C1)); Assert.assertArrayEquals(V4, myTable2.get(R1, C2)); Assert.assertArrayEquals(null, myTable2.get(R1, C3)); TableAssert.assertRow(a(C1, V4, C2, V5), myTable2.get(R2)); TableAssert.assertRow(a(C1, V1, C2, V2), myTable2.get(R3)); TableAssert.assertRow(a(C1, V2, C2, V3), myTable2.get(R4)); // delete c2 and r2 myTable2.delete(R1, a(C2)); myTable2.delete(R2); myTable2.put(R1, C2, null); // TableAssert.verify that delete is there (i.e. not reverted to whatever was persisted before) TableAssert.assertRow(a(C1, V3), myTable2.get(R1, a(C1, C2))); Assert.assertArrayEquals(V3, myTable2.get(R1, C1)); Assert.assertArrayEquals(null, myTable2.get(R1, C2)); TableAssert.assertRow(a(), myTable2.get(R2)); Assert.assertArrayEquals(V1, myTable2.get(R3, C1)); Assert.assertArrayEquals(V2, myTable2.get(R4, C1)); // start tx3 and TableAssert.verify that changes of tx2 are not visible yet Transaction tx3 = txClient.startShort(); // NOTE: table instance can be re-used between tx ((TransactionAware) myTable1).startTx(tx3); TableAssert.assertRow(a(C1, V1, C2, V2), myTable1.get(R1, a(C1, C2))); Assert.assertArrayEquals(V1, myTable1.get(R1, C1)); Assert.assertArrayEquals(V2, myTable1.get(R1, C2)); Assert.assertArrayEquals(null, myTable1.get(R1, C3)); TableAssert.assertRow(a(C1, V1, C2, V2), myTable1.get(R1)); TableAssert.assertRow(a(C1, V2, C2, V3), myTable1.get(R2)); TableAssert.assertRow(a(C1, V3, C2, V4), myTable1.get(R3)); TableAssert.assertRow(a(C1, V4, C2, V5), myTable1.get(R4)); Assert.assertTrue(txClient.canCommit(tx3, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); Assert.assertTrue(txClient.commit(tx3)); // starting tx4 before committing tx2 so that we can check conflicts are detected wrt deletes Transaction tx4 = txClient.startShort(); // starting tx5 before committing tx2 so that we can check conflicts are detected wrt deletes Transaction tx5 = txClient.startShort(); // commit tx2 in stages to see how races are handled wrt delete ops Assert.assertTrue(txClient.canCommit(tx2, ((TransactionAware) myTable2).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable2).commitTx()); // start tx6 and TableAssert.verify that changes of tx2 are not visible yet (even though they are flushed) Transaction tx6 = txClient.startShort(); // NOTE: table instance can be re-used between tx ((TransactionAware) myTable1).startTx(tx6); TableAssert.assertRow(a(C1, V1, C2, V2), myTable1.get(R1, a(C1, C2))); Assert.assertArrayEquals(V1, myTable1.get(R1, C1)); Assert.assertArrayEquals(V2, myTable1.get(R1, C2)); Assert.assertArrayEquals(null, myTable1.get(R1, C3)); TableAssert.assertRow(a(C1, V1, C2, V2), myTable1.get(R1)); TableAssert.assertRow(a(C1, V2, C2, V3), myTable1.get(R2)); TableAssert.assertRow(a(C1, V3, C2, V4), myTable1.get(R3)); TableAssert.assertRow(a(C1, V4, C2, V5), myTable1.get(R4)); Assert.assertTrue(txClient.canCommit(tx6, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); Assert.assertTrue(txClient.commit(tx6)); // make tx2 visible Assert.assertTrue(txClient.commit(tx2)); // start tx7 and TableAssert.verify that changes of tx2 are now visible Transaction tx7 = txClient.startShort(); // NOTE: table instance can be re-used between tx ((TransactionAware) myTable1).startTx(tx7); TableAssert.assertRow(a(C1, V3), myTable1.get(R1, a(C1, C2))); TableAssert.assertRow(a(C1, V3), myTable1.get(R1)); Assert.assertArrayEquals(V3, myTable1.get(R1, C1)); Assert.assertArrayEquals(null, myTable1.get(R1, C2)); TableAssert.assertRow(a(C1, V3), myTable1.get(R1, a(C1, C2))); TableAssert.assertRow(a(), myTable1.get(R2)); Assert.assertArrayEquals(V1, myTable1.get(R3, C1)); Assert.assertArrayEquals(V2, myTable1.get(R4, C1)); Assert.assertTrue(txClient.canCommit(tx6, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); Assert.assertTrue(txClient.commit(tx7)); // but not visible to tx4 that we started earlier than tx2 became visible // NOTE: table instance can be re-used between tx ((TransactionAware) myTable1).startTx(tx4); TableAssert.assertRow(a(C1, V1, C2, V2), myTable1.get(R1, a(C1, C2))); Assert.assertArrayEquals(V1, myTable1.get(R1, C1)); Assert.assertArrayEquals(V2, myTable1.get(R1, C2)); Assert.assertArrayEquals(null, myTable1.get(R1, C3)); TableAssert.assertRow(a(C1, V1, C2, V2), myTable1.get(R1)); TableAssert.assertRow(a(C1, V2, C2, V3), myTable1.get(R2)); TableAssert.assertRow(a(C1, V3, C2, V4), myTable1.get(R3)); TableAssert.assertRow(a(C1, V4, C2, V5), myTable1.get(R4)); // writing to deleted column, to check conflicts are detected (delete-write conflict) myTable1.put(R1, a(C2), a(V5)); Assert.assertFalse(txClient.canCommit(tx4, ((TransactionAware) myTable1).getTxChanges())); ((TransactionAware) myTable1).rollbackTx(); txClient.abort(tx4); // deleting changed column, to check conflicts are detected (write-delete conflict) // NOTE: table instance can be re-used between tx ((TransactionAware) myTable1).startTx(tx5); TableAssert.assertRow(a(C1, V1, C2, V2), myTable1.get(R1, a(C1, C2))); Assert.assertArrayEquals(V1, myTable1.get(R1, C1)); Assert.assertArrayEquals(V2, myTable1.get(R1, C2)); Assert.assertArrayEquals(null, myTable1.get(R1, C3)); TableAssert.assertRow(a(C1, V1, C2, V2), myTable1.get(R1)); TableAssert.assertRow(a(C1, V2, C2, V3), myTable1.get(R2)); TableAssert.assertRow(a(C1, V3, C2, V4), myTable1.get(R3)); TableAssert.assertRow(a(C1, V4, C2, V5), myTable1.get(R4)); // NOTE: we are TableAssert.verifying conflict in one operation only. We may want to test each... myTable1.delete(R1, a(C1)); Assert.assertFalse(txClient.canCommit(tx5, ((TransactionAware) myTable1).getTxChanges())); ((TransactionAware) myTable1).rollbackTx(); txClient.abort(tx5); } finally { admin.drop(); } } @Test public void testBasicScanWithTx() throws Exception { // todo: make work with tx well (merge with buffer, conflicts) and add tests for that DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); try { // write r1...r5 and commit Transaction tx1 = txClient.startShort(); Table myTable1 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable1).startTx(tx1); myTable1.put(R1, a(C1), a(V1)); myTable1.put(R2, a(C2), a(V2)); myTable1.put(R3, a(C3, C4), a(V3, V4)); myTable1.put(R4, a(C4), a(V4)); myTable1.put(R5, a(C5), a(V5)); Assert.assertTrue(txClient.canCommit(tx1, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); Assert.assertTrue(txClient.commit(tx1)); // Now, we will test scans // currently not testing races/conflicts/etc as this logic is not there for scans yet; so using one same tx Transaction tx2 = txClient.startShort(); Table myTable2 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable2).startTx(tx2); // bounded scan TableAssert.assertScan(a(R2, R3, R4), aa(a(C2, V2), a(C3, V3, C4, V4), a(C4, V4)), myTable2, new Scan(R2, R5)); // open start scan TableAssert.assertScan(a(R1, R2, R3), aa(a(C1, V1), a(C2, V2), a(C3, V3, C4, V4)), myTable2, new Scan(null, R4)); // open end scan TableAssert.assertScan(a(R3, R4, R5), aa(a(C3, V3, C4, V4), a(C4, V4), a(C5, V5)), myTable2, new Scan(R3, null)); // open ends scan TableAssert.assertScan(a(R1, R2, R3, R4, R5), aa(a(C1, V1), a(C2, V2), a(C3, V3, C4, V4), a(C4, V4), a(C5, V5)), myTable2, new Scan(null, null)); // adding/changing/removing some columns myTable2.put(R2, a(C1, C2, C3), a(V4, V3, V2)); myTable2.delete(R3, a(C4)); Assert.assertTrue(txClient.canCommit(tx2, ((TransactionAware) myTable2).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable2).commitTx()); Assert.assertTrue(txClient.commit(tx2)); // Checking that changes are reflected in new scans in new tx Transaction tx3 = txClient.startShort(); // NOTE: table can be re-used betweet tx ((TransactionAware) myTable1).startTx(tx3); TableAssert.assertScan(a(R2, R3, R4), aa(a(C1, V4, C2, V3, C3, V2), a(C3, V3), a(C4, V4)), myTable1, new Scan(R2, R5)); Assert.assertTrue(txClient.canCommit(tx3, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); Assert.assertTrue(txClient.commit(tx3)); } finally { admin.drop(); } } @Test public void testMultiGetWithTx() throws Exception { String testMultiGet = "testMultiGet"; DatasetAdmin admin = getTableAdmin(CONTEXT1, testMultiGet); admin.create(); try { Transaction tx = txClient.startShort(); Table table = getTable(CONTEXT1, testMultiGet); ((TransactionAware) table).startTx(tx); for (int i = 0; i < 100; i++) { table.put(new Put(Bytes.toBytes("r" + i)).add(C1, V1).add(C2, V2)); } Assert.assertTrue(txClient.canCommit(tx, ((TransactionAware) table).getTxChanges())); Assert.assertTrue(((TransactionAware) table).commitTx()); Assert.assertTrue(txClient.commit(tx)); Transaction tx2 = txClient.startShort(); ((TransactionAware) table).startTx(tx2); List<Get> gets = Lists.newArrayListWithCapacity(100); for (int i = 0; i < 100; i++) { gets.add(new Get(Bytes.toBytes("r" + i))); } List<Row> results = table.get(gets); Assert.assertTrue(txClient.commit(tx2)); for (int i = 0; i < 100; i++) { Row row = results.get(i); Assert.assertArrayEquals(Bytes.toBytes("r" + i), row.getRow()); byte[] val = row.get(C1); Assert.assertNotNull(val); Assert.assertArrayEquals(V1, val); byte[] val2 = row.get(C2); Assert.assertNotNull(val2); Assert.assertArrayEquals(V2, val2); } Transaction tx3 = txClient.startShort(); ((TransactionAware) table).startTx(tx3); gets = Lists.newArrayListWithCapacity(100); for (int i = 0; i < 100; i++) { gets.add(new Get("r" + i).add(C1)); } results = table.get(gets); Assert.assertTrue(txClient.commit(tx3)); for (int i = 0; i < 100; i++) { Row row = results.get(i); Assert.assertArrayEquals(Bytes.toBytes("r" + i), row.getRow()); byte[] val = row.get(C1); Assert.assertNotNull(val); Assert.assertArrayEquals(V1, val); // should have only returned column 1 byte[] val2 = row.get(C2); Assert.assertNull(val2); } // retrieve different columns per row Transaction tx4 = txClient.startShort(); ((TransactionAware) table).startTx(tx4); gets = Lists.newArrayListWithCapacity(100); for (int i = 0; i < 100; i++) { Get get = new Get("r" + i); // evens get C1, odds get C2 get.add(i % 2 == 0 ? C1 : C2); gets.add(get); } results = table.get(gets); Assert.assertTrue(txClient.commit(tx4)); for (int i = 0; i < 100; i++) { Row row = results.get(i); Assert.assertArrayEquals(Bytes.toBytes("r" + i), row.getRow()); byte[] val1 = row.get(C1); byte[] val2 = row.get(C2); if (i % 2 == 0) { Assert.assertNotNull(val1); Assert.assertArrayEquals(V1, val1); Assert.assertNull(val2); } else { Assert.assertNull(val1); Assert.assertNotNull(val2); Assert.assertArrayEquals(V2, val2); } } } finally { admin.drop(); } } @Test public void testScanAndDelete() throws Exception { DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); try { // Transaction tx1 = txClient.startShort(); Table myTable1 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable1).startTx(tx1); myTable1.put(Bytes.toBytes("1_09a"), a(C1), a(V1)); Assert.assertTrue(txClient.canCommit(tx1, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); Assert.assertTrue(txClient.commit(tx1)); // Transaction tx2 = txClient.startShort(); ((TransactionAware) myTable1).startTx(tx2); myTable1.delete(Bytes.toBytes("1_09a")); myTable1.put(Bytes.toBytes("1_08a"), a(C1), a(V1)); myTable1.put(Bytes.toBytes("1_09b"), a(C1), a(V1)); Assert.assertTrue(txClient.canCommit(tx2, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); Assert.assertTrue(txClient.commit(tx2)); // Transaction tx3 = txClient.startShort(); ((TransactionAware) myTable1).startTx(tx3); TableAssert.assertScan(a(Bytes.toBytes("1_08a"), Bytes.toBytes("1_09b")), aa(a(C1, V1), a(C1, V1)), myTable1, new Scan(Bytes.toBytes("1_"), Bytes.toBytes("2_"))); myTable1.delete(Bytes.toBytes("1_08a")); myTable1.put(Bytes.toBytes("1_07a"), a(C1), a(V1)); myTable1.delete(Bytes.toBytes("1_09b")); myTable1.put(Bytes.toBytes("1_08b"), a(C1), a(V1)); myTable1.put(Bytes.toBytes("1_09c"), a(C1), a(V1)); Assert.assertTrue(txClient.canCommit(tx3, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); Assert.assertTrue(txClient.commit(tx3)); // Now, we will test scans Transaction tx4 = txClient.startShort(); ((TransactionAware) myTable1).startTx(tx4); TableAssert.assertScan(a(Bytes.toBytes("1_07a"), Bytes.toBytes("1_08b"), Bytes.toBytes("1_09c")), aa(a(C1, V1), a(C1, V1), a(C1, V1)), myTable1, new Scan(Bytes.toBytes("1_"), Bytes.toBytes("2_"))); } finally { admin.drop(); } } @Test public void testScanWithFuzzyRowFilter() throws Exception { DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); try { Transaction tx1 = txClient.startShort(); Table table = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) table).startTx(tx1); // write data byte[] abc = { 'a', 'b', 'c' }; for (byte b1 : abc) { for (byte b2 : abc) { for (byte b3 : abc) { for (byte b4 : abc) { table.put(new Put(new byte[] { b1, b2, b3, b4 }).add(C1, V1)); } } } } // we should have 81 (3^4) rows now Assert.assertEquals(81, countRows(table)); // check that filter works against data written in same tx verifyScanWithFuzzyRowFilter(table); // commit tx, start new and TableAssert.verify scan again against "persisted" data Assert.assertTrue(txClient.canCommit(tx1, ((TransactionAware) table).getTxChanges())); Assert.assertTrue(((TransactionAware) table).commitTx()); Assert.assertTrue(txClient.commit(tx1)); ((TransactionAware) table).postTxCommit(); Transaction tx2 = txClient.startShort(); ((TransactionAware) table).startTx(tx2); verifyScanWithFuzzyRowFilter(table); } finally { admin.drop(); } } private static void verifyScanWithFuzzyRowFilter(Table table) { FuzzyRowFilter filter = new FuzzyRowFilter( ImmutableList.of(ImmutablePair.of(new byte[]{'*', 'b', '*', 'b'}, new byte[]{0x01, 0x00, 0x01, 0x00}))); Scanner scanner = table.scan(new Scan(null, null, filter)); int count = 0; while (true) { Row entry = scanner.next(); if (entry == null) { break; } Assert.assertTrue(entry.getRow()[1] == 'b' && entry.getRow()[3] == 'b'); Assert.assertEquals(1, entry.getColumns().size()); Assert.assertTrue(entry.getColumns().containsKey(C1)); Assert.assertArrayEquals(V1, entry.get(C1)); count++; } Assert.assertEquals(9, count); } private static int countRows(Table table) throws Exception { Scanner scanner = table.scan(null, null); int count = 0; while (scanner.next() != null) { count++; } return count; } @Test public void testBasicColumnRangeWithTx() throws Exception { // todo: test more tx logic (or add to get/put unit-test) DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); try { // write test data and commit Transaction tx1 = txClient.startShort(); Table myTable1 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable1).startTx(tx1); myTable1.put(R1, a(C1, C2, C3, C4, C5), a(V1, V2, V3, V4, V5)); myTable1.put(R2, a(C1), a(V2)); Assert.assertTrue(txClient.canCommit(tx1, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); Assert.assertTrue(txClient.commit(tx1)); // Now, we will test column range get Transaction tx2 = txClient.startShort(); Table myTable2 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable2).startTx(tx2); // bounded range TableAssert.assertRow(a(C2, V2, C3, V3, C4, V4), myTable2.get(R1, C2, C5, Integer.MAX_VALUE)); // open start range TableAssert.assertRow(a(C1, V1, C2, V2, C3, V3), myTable2.get(R1, null, C4, Integer.MAX_VALUE)); // open end range TableAssert.assertRow(a(C3, V3, C4, V4, C5, V5), myTable2.get(R1, C3, null, Integer.MAX_VALUE)); // open ends range TableAssert.assertRow(a(C1, V1, C2, V2, C3, V3, C4, V4, C5, V5), myTable2.get(R1, null, null, Integer.MAX_VALUE)); // same with limit // bounded range with limit TableAssert.assertRow(a(C2, V2), myTable2.get(R1, C2, C5, 1)); // open start range with limit TableAssert.assertRow(a(C1, V1, C2, V2), myTable2.get(R1, null, C4, 2)); // open end range with limit TableAssert.assertRow(a(C3, V3, C4, V4), myTable2.get(R1, C3, null, 2)); // open ends range with limit TableAssert.assertRow(a(C1, V1, C2, V2, C3, V3, C4, V4), myTable2.get(R1, null, null, 4)); // adding/changing/removing some columns myTable2.put(R1, a(C1, C2, C3), a(V4, V3, V2)); myTable2.delete(R1, a(C4)); Assert.assertTrue(txClient.canCommit(tx2, ((TransactionAware) myTable2).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable2).commitTx()); Assert.assertTrue(txClient.commit(tx2)); // Checking that changes are reflected in new scans in new tx Transaction tx3 = txClient.startShort(); // NOTE: table can be re-used betweet tx ((TransactionAware) myTable1).startTx(tx3); TableAssert.assertRow(a(C2, V3, C3, V2), myTable1.get(R1, C2, C5, Integer.MAX_VALUE)); Assert.assertTrue(txClient.canCommit(tx3, ((TransactionAware) myTable1).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable1).commitTx()); Assert.assertTrue(txClient.commit(tx3)); } finally { admin.drop(); } } @Test public void testBatchWritableKeyIsIgnored() throws Exception { String tableName = "batchWritableTable"; getTableAdmin(CONTEXT1, tableName).create(); try { // write in a transaction, three times, with key = null, a, q, always Put with row = a Transaction tx = txClient.startShort(); Table table = getTable(CONTEXT1, tableName); ((TransactionAware) table).startTx(tx); table.write(null, new Put("a").add("x", "x")); table.write(new byte[]{'q'}, new Put("a").add("y", "y")); table.write(new byte[]{'a'}, new Put("a").add("z", "z")); Assert.assertTrue(txClient.canCommit(tx, ((TransactionAware) table).getTxChanges())); ((TransactionAware) table).commitTx(); Assert.assertTrue(txClient.commit(tx)); // validate that all writes went to row a, and row q was not written tx = txClient.startShort(); ((TransactionAware) table).startTx(tx); Assert.assertTrue(table.get(new Get("q")).isEmpty()); Row row = table.get(new Get("a")); Assert.assertEquals(3, row.getColumns().size()); Assert.assertEquals("x", row.getString("x")); Assert.assertEquals("y", row.getString("y")); Assert.assertEquals("z", row.getString("z")); ((TransactionAware) table).commitTx(); txClient.abort(tx); } finally { getTableAdmin(CONTEXT1, tableName).drop(); } } @Test public void testTxUsingMultipleTables() throws Exception { String table1 = "table1"; String table2 = "table2"; String table3 = "table3"; String table4 = "table4"; getTableAdmin(CONTEXT1, table1).create(); getTableAdmin(CONTEXT1, table2).create(); getTableAdmin(CONTEXT1, table3).create(); getTableAdmin(CONTEXT1, table4).create(); try { // We will be changing: // * table1 and table2 in tx1 // * table2 and table3 in tx2 << will conflict with first one // * table3 and table4 in tx3 Transaction tx1 = txClient.startShort(); Transaction tx2 = txClient.startShort(); Transaction tx3 = txClient.startShort(); // Write data in tx1 and commit Table table11 = getTable(CONTEXT1, table1); ((TransactionAware) table11).startTx(tx1); // write r1->c1,v1 but not commit table11.put(R1, a(C1), a(V1)); Table table21 = getTable(CONTEXT1, table2); ((TransactionAware) table21).startTx(tx1); // write r1->c1,v2 but not commit table21.put(R1, a(C1), a(V2)); // TableAssert.verify writes inside same tx TableAssert.assertRow(a(C1, V1), table11.get(R1, a(C1))); TableAssert.assertRow(a(C1, V2), table21.get(R1, a(C1))); // commit tx1 Assert.assertTrue(txClient.canCommit(tx1, ImmutableList.copyOf( Iterables.concat(((TransactionAware) table11).getTxChanges(), ((TransactionAware) table21).getTxChanges())))); Assert.assertTrue(((TransactionAware) table11).commitTx()); Assert.assertTrue(((TransactionAware) table21).commitTx()); Assert.assertTrue(txClient.commit(tx1)); // Write data in tx2 and check that cannot commit because of conflicts Table table22 = getTable(CONTEXT1, table2); ((TransactionAware) table22).startTx(tx2); // write r1->c1,v1 but not commit table22.put(R1, a(C1), a(V2)); Table table32 = getTable(CONTEXT1, table3); ((TransactionAware) table32).startTx(tx2); // write r1->c1,v2 but not commit table32.put(R1, a(C1), a(V3)); // TableAssert.verify writes inside same tx TableAssert.assertRow(a(C1, V2), table22.get(R1, a(C1))); TableAssert.assertRow(a(C1, V3), table32.get(R1, a(C1))); // try commit tx2 Assert.assertFalse(txClient.canCommit(tx2, ImmutableList.copyOf( Iterables.concat(((TransactionAware) table22).getTxChanges(), ((TransactionAware) table32).getTxChanges())))); Assert.assertTrue(((TransactionAware) table22).rollbackTx()); Assert.assertTrue(((TransactionAware) table32).rollbackTx()); txClient.abort(tx2); // Write data in tx3 and check that can commit (no conflicts) Table table33 = getTable(CONTEXT1, table3); ((TransactionAware) table33).startTx(tx3); // write r1->c1,v1 but not commit table33.put(R1, a(C1), a(V3)); Table table43 = getTable(CONTEXT1, table4); ((TransactionAware) table43).startTx(tx3); // write r1->c1,v2 but not commit table43.put(R1, a(C1), a(V4)); // TableAssert.verify writes inside same tx TableAssert.assertRow(a(C1, V3), table33.get(R1, a(C1))); TableAssert.assertRow(a(C1, V4), table43.get(R1, a(C1))); // commit tx3 Assert.assertTrue(txClient.canCommit(tx3, ImmutableList.copyOf( Iterables.concat(((TransactionAware) table33).getTxChanges(), ((TransactionAware) table43).getTxChanges())))); Assert.assertTrue(((TransactionAware) table33).commitTx()); Assert.assertTrue(((TransactionAware) table43).commitTx()); Assert.assertTrue(txClient.commit(tx3)); } finally { getTableAdmin(CONTEXT1, table1).drop(); getTableAdmin(CONTEXT1, table2).drop(); getTableAdmin(CONTEXT1, table3).drop(); getTableAdmin(CONTEXT1, table4).drop(); } } @Test public void testConflictsNoneLevel() throws Exception { testConflictDetection(ConflictDetection.NONE); } @Test public void testConflictsOnRowLevel() throws Exception { testConflictDetection(ConflictDetection.ROW); } @Test public void testConflictsOnColumnLevel() throws Exception { testConflictDetection(ConflictDetection.COLUMN); } private void testConflictDetection(ConflictDetection level) throws Exception { // we use tableX_Y format for variable names which means "tableX that is used in tx Y" String table1 = "table1"; String table2 = "table2"; getTableAdmin(CONTEXT1, table1).create(); getTableAdmin(CONTEXT1, table2).create(); try { // 1) Test conflicts when using different tables Transaction tx1 = txClient.startShort(); Table table11 = getTable(CONTEXT1, table1, level); ((TransactionAware) table11).startTx(tx1); // write r1->c1,v1 but not commit table11.put(R1, a(C1), a(V1)); // start new tx Transaction tx2 = txClient.startShort(); Table table22 = getTable(CONTEXT1, table2, level); ((TransactionAware) table22).startTx(tx2); // change in tx2 same data but in different table table22.put(R1, a(C1), a(V2)); // start new tx Transaction tx3 = txClient.startShort(); Table table13 = getTable(CONTEXT1, table1, level); ((TransactionAware) table13).startTx(tx3); // change in tx3 same data in same table as tx1 table13.put(R1, a(C1), a(V2)); // committing tx1 Assert.assertTrue(txClient.canCommit(tx1, ((TransactionAware) table11).getTxChanges())); Assert.assertTrue(((TransactionAware) table11).commitTx()); Assert.assertTrue(txClient.commit(tx1)); // no conflict should be when committing tx2 Assert.assertTrue(txClient.canCommit(tx2, ((TransactionAware) table22).getTxChanges())); // but conflict should be when committing tx3 if (level != ConflictDetection.NONE) { Assert.assertFalse(txClient.canCommit(tx3, ((TransactionAware) table13).getTxChanges())); ((TransactionAware) table13).rollbackTx(); txClient.abort(tx3); } else { Assert.assertTrue(txClient.canCommit(tx3, ((TransactionAware) table13).getTxChanges())); } // 2) Test conflicts when using different rows Transaction tx4 = txClient.startShort(); Table table14 = getTable(CONTEXT1, table1, level); ((TransactionAware) table14).startTx(tx4); // write r1->c1,v1 but not commit table14.put(R1, a(C1), a(V1)); // start new tx Transaction tx5 = txClient.startShort(); Table table15 = getTable(CONTEXT1, table1, level); ((TransactionAware) table15).startTx(tx5); // change in tx5 same data but in different row table15.put(R2, a(C1), a(V2)); // start new tx Transaction tx6 = txClient.startShort(); Table table16 = getTable(CONTEXT1, table1, level); ((TransactionAware) table16).startTx(tx6); // change in tx6 in same row as tx1 table16.put(R1, a(C2), a(V2)); // committing tx4 Assert.assertTrue(txClient.canCommit(tx4, ((TransactionAware) table14).getTxChanges())); Assert.assertTrue(((TransactionAware) table14).commitTx()); Assert.assertTrue(txClient.commit(tx4)); // no conflict should be when committing tx5 Assert.assertTrue(txClient.canCommit(tx5, ((TransactionAware) table15).getTxChanges())); // but conflict should be when committing tx6 iff we resolve on row level if (level == ConflictDetection.ROW) { Assert.assertFalse(txClient.canCommit(tx6, ((TransactionAware) table16).getTxChanges())); ((TransactionAware) table16).rollbackTx(); txClient.abort(tx6); } else { Assert.assertTrue(txClient.canCommit(tx6, ((TransactionAware) table16).getTxChanges())); } // 3) Test conflicts when using different columns Transaction tx7 = txClient.startShort(); Table table17 = getTable(CONTEXT1, table1, level); ((TransactionAware) table17).startTx(tx7); // write r1->c1,v1 but not commit table17.put(R1, a(C1), a(V1)); // start new tx Transaction tx8 = txClient.startShort(); Table table18 = getTable(CONTEXT1, table1, level); ((TransactionAware) table18).startTx(tx8); // change in tx8 same data but in different column table18.put(R1, a(C2), a(V2)); // start new tx Transaction tx9 = txClient.startShort(); Table table19 = getTable(CONTEXT1, table1, level); ((TransactionAware) table19).startTx(tx9); // change in tx9 same column in same column as tx1 table19.put(R1, a(C1), a(V2)); // committing tx7 Assert.assertTrue(txClient.canCommit(tx7, ((TransactionAware) table17).getTxChanges())); Assert.assertTrue(((TransactionAware) table17).commitTx()); Assert.assertTrue(txClient.commit(tx7)); // no conflict should be when committing tx8 iff we resolve on column level if (level == ConflictDetection.COLUMN || level == ConflictDetection.NONE) { Assert.assertTrue(txClient.canCommit(tx8, ((TransactionAware) table18).getTxChanges())); } else { Assert.assertFalse(txClient.canCommit(tx8, ((TransactionAware) table18).getTxChanges())); ((TransactionAware) table18).rollbackTx(); txClient.abort(tx8); } // but conflict should be when committing tx9 if (level != ConflictDetection.NONE) { Assert.assertFalse(txClient.canCommit(tx9, ((TransactionAware) table19).getTxChanges())); ((TransactionAware) table19).rollbackTx(); txClient.abort(tx9); } else { Assert.assertTrue(txClient.canCommit(tx9, ((TransactionAware) table19).getTxChanges())); } } finally { // NOTE: we are doing our best to cleanup junk between tests to isolate errors, but we are not going to be // crazy about it getTableAdmin(CONTEXT1, table1).drop(); getTableAdmin(CONTEXT1, table2).drop(); } } @Test public void testRollingBackPersistedChanges() throws Exception { DatasetAdmin admin = getTableAdmin(CONTEXT1, MY_TABLE); admin.create(); try { // write and commit one row/column Transaction tx0 = txClient.startShort(); Table myTable0 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable0).startTx(tx0); myTable0.put(R2, a(C2), a(V2)); Assert.assertTrue(txClient.canCommit(tx0, ((TransactionAware) myTable0).getTxChanges())); Assert.assertTrue(((TransactionAware) myTable0).commitTx()); Assert.assertTrue(txClient.commit(tx0)); ((TransactionAware) myTable0).postTxCommit(); Transaction tx1 = txClient.startShort(); Table myTable1 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable1).startTx(tx1); // write r1->c1,v1 but not commit myTable1.put(R1, a(C1), a(V1)); // also overwrite the value from tx0 myTable1.put(R2, a(C2), a(V3)); // TableAssert.verify can see changes inside tx TableAssert.assertRow(a(C1, V1), myTable1.get(R1, a(C1))); // persisting changes Assert.assertTrue(((TransactionAware) myTable1).commitTx()); // let's pretend that after persisting changes we still got conflict when finalizing tx, so // rolling back changes Assert.assertTrue(((TransactionAware) myTable1).rollbackTx()); // making tx visible txClient.abort(tx1); // start new tx Transaction tx2 = txClient.startShort(); Table myTable2 = getTable(CONTEXT1, MY_TABLE); ((TransactionAware) myTable2).startTx(tx2); // TableAssert.verify don't see rolled back changes TableAssert.assertRow(a(), myTable2.get(R1, a(C1))); // TableAssert.verify we still see the previous value TableAssert.assertRow(a(C2, V2), myTable2.get(R2, a(C2))); } finally { admin.drop(); } } // this test ensures that an existing client survives the truncating or dropping and recreating of a table @Test public void testClientSurvivesTableReset() throws Exception { final String tableName = "survive"; DatasetAdmin admin = getTableAdmin(CONTEXT1, tableName); admin.create(); Table table = getTable(CONTEXT1, tableName); // write some values Transaction tx0 = txClient.startShort(); ((TransactionAware) table).startTx(tx0); table.put(R1, a(C1), a(V1)); Assert.assertTrue(txClient.canCommit(tx0, ((TransactionAware) table).getTxChanges())); Assert.assertTrue(((TransactionAware) table).commitTx()); Assert.assertTrue(txClient.commit(tx0)); ((TransactionAware) table).postTxCommit(); // TableAssert.verify Transaction tx1 = txClient.startShort(); ((TransactionAware) table).startTx(tx1); TableAssert.assertRow(a(C1, V1), table.get(R1)); // drop table and recreate admin.drop(); admin.create(); // TableAssert.verify can read but nothing there TableAssert.assertRow(a(), table.get(R1)); txClient.abort(tx1); // only did read, safe to abort // create a new client and write another value Table table2 = getTable(CONTEXT1, tableName); Transaction tx2 = txClient.startShort(); ((TransactionAware) table2).startTx(tx2); table2.put(R1, a(C2), a(V2)); Assert.assertTrue(txClient.canCommit(tx2, ((TransactionAware) table2).getTxChanges())); Assert.assertTrue(((TransactionAware) table2).commitTx()); Assert.assertTrue(txClient.commit(tx2)); ((TransactionAware) table2).postTxCommit(); // TableAssert.verify it is visible Transaction tx3 = txClient.startShort(); ((TransactionAware) table).startTx(tx3); TableAssert.assertRow(a(C2, V2), table.get(R1)); // truncate table admin.truncate(); // TableAssert.verify can read but nothing there TableAssert.assertRow(a(), table.get(R1)); txClient.abort(tx3); // only did read, safe to abort // write again with other client Transaction tx4 = txClient.startShort(); ((TransactionAware) table2).startTx(tx4); table2.put(R1, a(C3), a(V3)); Assert.assertTrue(txClient.canCommit(tx4, ((TransactionAware) table2).getTxChanges())); Assert.assertTrue(((TransactionAware) table2).commitTx()); Assert.assertTrue(txClient.commit(tx4)); ((TransactionAware) table2).postTxCommit(); // TableAssert.verify it is visible Transaction tx5 = txClient.startShort(); ((TransactionAware) table).startTx(tx5); TableAssert.assertRow(a(C3, V3), table.get(R1)); txClient.abort(tx5); // only did read, safe to abort // drop table admin.drop(); } @Test public void testMetrics() throws Exception { final String tableName = "survive"; DatasetAdmin admin = getTableAdmin(CONTEXT1, tableName); admin.create(); Table table = getTable(CONTEXT1, tableName); final Map<String, Long> metrics = Maps.newHashMap(); ((MeteredDataset) table).setMetricsCollector(new MetricsCollector() { @Override public void increment(String metricName, long value) { Long old = metrics.get(metricName); metrics.put(metricName, old == null ? value : old + value); } @Override public void gauge(String metricName, long value) { metrics.put(metricName, value); } }); // Note that we don't need to finish tx for metrics to be reported Transaction tx0 = txClient.startShort(); ((TransactionAware) table).startTx(tx0); int writes = 0; int reads = 0; table.put(new Put(R1, C1, V1)); verifyDatasetMetrics(metrics, ++writes, reads); table.compareAndSwap(R1, C1, V1, V2); verifyDatasetMetrics(metrics, ++writes, ++reads); // note: will not write anything as expected value will not match table.compareAndSwap(R1, C1, V1, V2); verifyDatasetMetrics(metrics, writes, ++reads); table.increment(new Increment(R2, C2, 1L)); if (isReadlessIncrementSupported()) { verifyDatasetMetrics(metrics, ++writes, reads); } else { verifyDatasetMetrics(metrics, ++writes, ++reads); } table.incrementAndGet(new Increment(R2, C2, 1L)); verifyDatasetMetrics(metrics, ++writes, ++reads); table.get(new Get(R1, C1, V1)); verifyDatasetMetrics(metrics, writes, ++reads); Scanner scanner = table.scan(new Scan(null, null)); while (scanner.next() != null) { verifyDatasetMetrics(metrics, writes, ++reads); } table.delete(new Delete(R1, C1, V1)); verifyDatasetMetrics(metrics, ++writes, reads); // drop table admin.drop(); } @Test public void testReadOwnWrite() throws Exception { final String tableName = "readOwnWrite"; DatasetAdmin admin = getTableAdmin(CONTEXT1, tableName); admin.create(); Table table = getTable(CONTEXT1, tableName); Transaction tx = txClient.startShort(); try { ((TransactionAware) table).startTx(tx); // Write some data, then flush it by calling commitTx. table.put(new Put(R1, C1, V1)); ((TransactionAware) table).commitTx(); // Try to read the previous write. Assert.assertArrayEquals(V1, table.get(new Get(R1, C1)).get(C1)); } finally { txClient.commit(tx); } // drop table admin.drop(); } private void verifyDatasetMetrics(Map<String, Long> metrics, long writes, long reads) { Assert.assertEquals(writes, getLong(metrics, Constants.Metrics.Name.Dataset.WRITE_COUNT)); Assert.assertEquals(reads, getLong(metrics, Constants.Metrics.Name.Dataset.READ_COUNT)); Assert.assertEquals(writes + reads, getLong(metrics, Constants.Metrics.Name.Dataset.OP_COUNT)); } private long getLong(Map<String, Long> map, String key) { Long value = map.get(key); return value == null ? 0 : value; } static long[] la(long... elems) { return elems; } static byte[][] lb(long... elems) { byte[][] elemBytes = new byte[elems.length][]; for (int i = 0; i < elems.length; i++) { elemBytes[i] = Bytes.toBytes(elems[i]); } return elemBytes; } // to array static byte[][] a(byte[]... elems) { return elems; } // to array of array static byte[][][] aa(byte[][]... elems) { return elems; } }