/*
* Copyright © 2014 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.api.dataset.lib;
import co.cask.cdap.api.common.Bytes;
import co.cask.cdap.api.dataset.DatasetProperties;
import co.cask.cdap.api.dataset.table.Delete;
import co.cask.cdap.api.dataset.table.Get;
import co.cask.cdap.api.dataset.table.Put;
import co.cask.cdap.api.dataset.table.Row;
import co.cask.cdap.api.dataset.table.Scanner;
import co.cask.cdap.data2.dataset2.DatasetFrameworkTestUtil;
import co.cask.cdap.data2.dataset2.TableAssert;
import co.cask.cdap.proto.Id;
import co.cask.tephra.TransactionExecutor;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
/**
* Tests for Index table.
*/
public class IndexedTableTest {
@ClassRule
public static DatasetFrameworkTestUtil dsFrameworkUtil = new DatasetFrameworkTestUtil();
private static final Id.DatasetInstance tabInstance =
Id.DatasetInstance.from(DatasetFrameworkTestUtil.NAMESPACE_ID, "tab");
private static IndexedTable table;
static byte[] idxCol = { 'i', 'd', 'x' };
static byte[] valCol = { 'v', 'a', 'l' };
static byte[] keyA = { 'a' };
static byte[] keyAA = { 'a', 'a' };
static byte[] keyB = { 'b' };
static byte[] keyC = { 'c' };
static byte[] keyD = { 'd' };
static byte[] keyE = { 'e' };
static byte[] valA = { 'a' };
static byte[] valAA = { 'a', 'a' };
static byte[] valAB = { 'a', 'b' };
static byte[] valB = { 'b' };
static byte[] valBB = { 'b', 'b' };
static byte[] valC = { 'c' };
static byte[] valD = { 'd' };
static byte[] valE = { 'e' };
static byte[] idx1 = { '1' };
static byte[] idx2 = { '2' };
static byte[] idx3 = { '3' };
static byte[] idx4 = { '4' };
static byte[] idx5 = { '5' };
static String idxColString = Bytes.toString(idxCol);
static byte[][] colIdxVal = { idxCol, valCol };
@BeforeClass
public static void beforeClass() throws Exception {
dsFrameworkUtil.createInstance("indexedTable", tabInstance, DatasetProperties.builder()
.add(IndexedTableDefinition.INDEX_COLUMNS_CONF_KEY, idxColString)
.build());
table = dsFrameworkUtil.getInstance(tabInstance);
}
@AfterClass
public static void afterClass() throws Exception {
dsFrameworkUtil.deleteInstance(tabInstance);
}
@Test
public void testKeyPrefix() {
// tests generation of byte key prefixes for index scanning
// placed here since used by IndexedTable and we lack of a better home for Bytes testing
byte[] start = { 0x00 };
byte[] stop = Bytes.stopKeyForPrefix(start);
assertArrayEquals(new byte[]{0x01}, stop);
stop = Bytes.stopKeyForPrefix(stop);
assertArrayEquals(new byte[]{0x02}, stop);
start = new byte[]{ 0x01, (byte) 0xff };
stop = Bytes.stopKeyForPrefix(start);
assertArrayEquals(new byte[]{0x02}, stop);
start = new byte[]{ (byte) 0xff, (byte) 0xff };
stop = Bytes.stopKeyForPrefix(start);
Assert.assertNull(stop);
}
@Test
public void testIndexedOperations() throws Exception {
TransactionExecutor txnl = dsFrameworkUtil.newTransactionExecutor(table);
// start a new transaction
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// add a value c with idx = 1, and b with idx = 2
table.put(new Put(keyC).add(idxCol, idx1).add(valCol, valC));
table.put(new Put(keyB).add(idxCol, idx2).add(valCol, valB));
}
});
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// read by key c
Row row = table.get(new Get(keyC, colIdxVal));
TableAssert.assertColumns(row, colIdxVal, new byte[][]{idx1, valC});
// read by key b
row = table.get(new Get(keyB, colIdxVal));
TableAssert.assertColumns(row, colIdxVal, new byte[][]{idx2, valB});
// read by idx 1 -> c
row = readFirst(table.readByIndex(idxCol, idx1));
TableAssert.assertColumns(row, colIdxVal, new byte[][]{idx1, valC});
// read by idx 2 -> b
row = readFirst(table.readByIndex(idxCol, idx2));
TableAssert.assertColumns(row, colIdxVal, new byte[][]{idx2, valB});
// test read over empty index (idx 3)
assertEmpty(table.readByIndex(idxCol, idx3));
}
});
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// add a value a with idx = 1
table.put(new Put(keyA).add(idxCol, idx1).add(valCol, valA));
}
});
// read by idx 1 -> a
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Row row = readFirst(table.readByIndex(idxCol, idx1));
TableAssert.assertColumns(row, colIdxVal, new byte[][]{idx1, valA});
}
});
// start a new transaction
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// delete value a
table.delete(new Delete(keyA, colIdxVal));
}
});
// read by idx 1 -> c
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Row row = readFirst(table.readByIndex(idxCol, idx1));
TableAssert.assertColumns(row, colIdxVal, new byte[][]{idx1, valC});
}
});
// start a new transaction
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// add a value aa with idx 2
table.put(new Put(keyAA).add(idxCol, idx2).add(valCol, valAA));
}
});
// read by idx 2 -> aa
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Row row = readFirst(table.readByIndex(idxCol, idx2));
TableAssert.assertColumns(row, colIdxVal, new byte[][]{idx2, valAA});
}
});
// start a new transaction
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// swap value for aa to ab
Assert.assertTrue(table.compareAndSwap(keyAA, valCol, valAA, valAB));
}
});
// read by idx 2 -> ab
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Row row = readFirst(table.readByIndex(idxCol, idx2));
TableAssert.assertColumns(row, colIdxVal, new byte[][]{idx2, valAB});
}
});
// start a new transaction
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// swap value for aa to bb
Assert.assertTrue(table.compareAndSwap(keyAA, valCol, valAB, valBB));
}
});
// read by idx 2 -> bb (value of key aa)
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Row row = readFirst(table.readByIndex(idxCol, idx2));
TableAssert.assertColumns(row, colIdxVal, new byte[][]{idx2, valBB});
}
});
// start a new transaction
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// swap value for aa to null
Assert.assertTrue(table.compareAndSwap(keyAA, valCol, valBB, null));
}
});
// read by idx 2 -> null (value of b)
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Row row = readFirst(table.readByIndex(idxCol, idx2));
TableAssert.assertColumn(row, idxCol, idx2);
}
});
// start a new transaction
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// swap idx for c to 3
Assert.assertTrue(table.compareAndSwap(keyC, idxCol, idx1, idx3));
}
});
// read by idx 1 -> null (no row has that any more)
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
assertEmpty(table.readByIndex(idxCol, idx1));
// read by idx 3 > c
Row row = readFirst(table.readByIndex(idxCol, idx3));
TableAssert.assertColumns(row, new byte[][]{idxCol, valCol}, new byte[][]{idx3, valC});
}
});
}
@Test
public void testIndexedRangeLookups() throws Exception {
Id.DatasetInstance indexRangedLookupDs =
Id.DatasetInstance.from(DatasetFrameworkTestUtil.NAMESPACE_ID, "rangeLookup");
dsFrameworkUtil.createInstance("indexedTable", indexRangedLookupDs, DatasetProperties.builder()
.add(IndexedTableDefinition.INDEX_COLUMNS_CONF_KEY, idxColString)
.build());
final IndexedTable iTable = dsFrameworkUtil.getInstance(indexRangedLookupDs);
TransactionExecutor txnl = dsFrameworkUtil.newTransactionExecutor(iTable);
try {
// start a new transaction
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// perform 5 puts, using idx values 1,2,3,4,5
iTable.put(new Put(keyE).add(idxCol, idx4).add(valCol, valE));
iTable.put(new Put(keyC).add(idxCol, idx1).add(valCol, valC));
iTable.put(new Put(keyD).add(idxCol, idx5).add(valCol, valA));
iTable.put(new Put(keyB).add(idxCol, idx2).add(valCol, valB));
iTable.put(new Put(keyA).add(idxCol, idx3).add(valCol, valD));
}
});
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// do a scan using idx value range [idx2, idx5). Assert that we retrieve idx2, idx3, idx4.
Scanner scanner = iTable.scanByIndex(idxCol, idx2, idx5);
Row next = scanner.next();
Assert.assertNotNull(next);
Assert.assertTrue(Bytes.equals(keyB, next.getRow()));
Assert.assertTrue(Bytes.equals(valB, next.get(valCol)));
next = scanner.next();
Assert.assertNotNull(next);
Assert.assertTrue(Bytes.equals(keyA, next.getRow()));
Assert.assertTrue(Bytes.equals(valD, next.get(valCol)));
next = scanner.next();
Assert.assertNotNull(next);
Assert.assertTrue(Bytes.equals(keyE, next.getRow()));
Assert.assertTrue(Bytes.equals(valE, next.get(valCol)));
assertEmpty(scanner);
}
});
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// do a scan using idx value range [null (first row), idx3). Assert that we retrieve the values corresponding
// to idx1, idx2.
Scanner scanner = iTable.scanByIndex(idxCol, null, idx3);
Row next = scanner.next();
Assert.assertNotNull(next);
Assert.assertTrue(Bytes.equals(keyC, next.getRow()));
Assert.assertTrue(Bytes.equals(valC, next.get(valCol)));
next = scanner.next();
Assert.assertNotNull(next);
Assert.assertTrue(Bytes.equals(keyB, next.getRow()));
Assert.assertTrue(Bytes.equals(valB, next.get(valCol)));
assertEmpty(scanner);
}
});
} finally {
dsFrameworkUtil.deleteInstance(indexRangedLookupDs);
}
}
@Test
public void testIndexKeyDelimiterAmbiguity() throws Exception {
final byte[] a = { 'a' };
final byte[] ab = { 'a', 0, 'b' };
final byte[] abc = { 'a', 0, 'b', 0, 'c' };
final byte[] bc = { 'b', 0, 'c' };
final byte[] bcd = { 'b', 0, 'c', 'd' };
final byte[] c = { 'c' };
final byte[] d = { 'd' };
final byte[] w = { 'w' };
final byte[] x = { 'x' };
final byte[] y = { 'y' };
final byte[] z = { 'z' };
Id.DatasetInstance delimTabInstance = Id.DatasetInstance.from(DatasetFrameworkTestUtil.NAMESPACE_ID, "delimtab");
dsFrameworkUtil.createInstance("indexedTable", delimTabInstance, DatasetProperties.builder()
.add(IndexedTableDefinition.INDEX_COLUMNS_CONF_KEY, Bytes.toString(a) + "," + Bytes.toString(ab))
.build());
final IndexedTable iTable = dsFrameworkUtil.getInstance(delimTabInstance);
try {
TransactionExecutor tx = dsFrameworkUtil.newTransactionExecutor(iTable);
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
iTable.put(x, a, bc);
iTable.put(y, ab, c);
iTable.put(w, a, bcd);
iTable.put(z, abc, d);
}
});
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// ensure that readByIndex filters teh false positive rows in index
Scanner scanner = iTable.readByIndex(a, bc);
try {
Row row = scanner.next();
Assert.assertNotNull(row);
Assert.assertArrayEquals(x, row.getRow());
Assert.assertArrayEquals(bc, row.get(a));
assertEmpty(scanner);
} finally {
scanner.close();
}
scanner = iTable.readByIndex(ab, c);
try {
Row row = scanner.next();
Assert.assertNotNull(row);
Assert.assertArrayEquals(y, row.getRow());
Assert.assertArrayEquals(c, row.get(ab));
assertEmpty(scanner);
} finally {
scanner.close();
}
// ensure that scanByIndex filters the false positive rows in index
scanner = iTable.scanByIndex(a, bcd, null);
try {
Row row = scanner.next();
Assert.assertNotNull(row);
Assert.assertArrayEquals(w, row.getRow());
Assert.assertArrayEquals(bcd, row.get(a));
assertEmpty(scanner);
} finally {
scanner.close();
}
scanner = iTable.scanByIndex(a, null, bcd);
try {
Row row = scanner.next();
Assert.assertNotNull(row);
Assert.assertArrayEquals(x, row.getRow());
Assert.assertArrayEquals(bc, row.get(a));
assertEmpty(scanner);
} finally {
scanner.close();
}
}
});
} finally {
dsFrameworkUtil.deleteInstance(delimTabInstance);
}
}
@Test
public void testMultipleIndexedColumns() throws Exception {
Id.DatasetInstance multiColumnTabInstance =
Id.DatasetInstance.from(DatasetFrameworkTestUtil.NAMESPACE_ID, "multicolumntab");
dsFrameworkUtil.createInstance("indexedTable", multiColumnTabInstance, DatasetProperties.builder()
.add(IndexedTableDefinition.INDEX_COLUMNS_CONF_KEY, "idx1,idx2,idx3")
.build());
final byte[] idxCol1 = Bytes.toBytes("idx1");
final byte[] idxCol2 = Bytes.toBytes("idx2");
final byte[] idxCol3 = Bytes.toBytes("idx3");
final IndexedTable mcTable = dsFrameworkUtil.getInstance(multiColumnTabInstance);
try {
TransactionExecutor tx = dsFrameworkUtil.newTransactionExecutor(mcTable);
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// write multiple rows with two indexed columns
// every row has idx1 = 1
// even rows have idx2 = 2
// every row has idx3 = index mod 3
for (int i = 1; i < 10; i++) {
Put put = new Put(Bytes.toBytes("row" + i));
put.add(idxCol1, idx1);
if (i % 2 == 0) {
put.add(idxCol2, idx2);
}
put.add(idxCol3, Bytes.toBytes(i % 3));
put.add(valCol, valA);
mcTable.put(put);
}
}
});
final byte[][] allColumns = new byte[][]{ idxCol1, idxCol2, idxCol3, valCol };
final byte[][] oddColumns = new byte[][]{ idxCol1, idxCol3, valCol };
final byte[] zero = Bytes.toBytes(0);
final byte[] one = Bytes.toBytes(1);
final byte[] two = Bytes.toBytes(2);
// read by index 1
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Scanner scanner = mcTable.readByIndex(idxCol1, idx1);
try {
// should have all rows, all data
Row row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row1"), oddColumns, new byte[][]{idx1, one, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row2"), allColumns, new byte[][]{idx1, idx2, two, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row3"), oddColumns, new byte[][]{idx1, zero, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row4"), allColumns, new byte[][]{idx1, idx2, one, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row5"), oddColumns, new byte[][]{idx1, two, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row6"), allColumns, new byte[][]{idx1, idx2, zero, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row7"), oddColumns, new byte[][]{idx1, one, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row8"), allColumns, new byte[][]{idx1, idx2, two, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row9"), oddColumns, new byte[][]{idx1, zero, valA});
// should be end of rows
assertEmpty(scanner);
} finally {
scanner.close();
}
}
});
// read by index 2
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Scanner scanner = mcTable.readByIndex(idxCol2, idx2);
try {
// Should have only even rows
Row row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row2"), allColumns, new byte[][]{idx1, idx2, two, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row4"), allColumns, new byte[][]{idx1, idx2, one, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row6"), allColumns, new byte[][]{idx1, idx2, zero, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row8"), allColumns, new byte[][]{idx1, idx2, two, valA});
// should be at the end
assertEmpty(scanner);
} finally {
scanner.close();
}
}
});
// read by index 3
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// 0 should have rows 3, 6, 9
Scanner scanner = mcTable.readByIndex(idxCol3, zero);
try {
Row row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row3"), oddColumns, new byte[][]{idx1, zero, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row6"), allColumns, new byte[][]{idx1, idx2, zero, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row9"), oddColumns, new byte[][]{idx1, zero, valA});
// should be end of rows
assertEmpty(scanner);
} finally {
scanner.close();
}
// 1 should have rows 1, 4, 7
scanner = mcTable.readByIndex(idxCol3, one);
try {
Row row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row1"), oddColumns, new byte[][]{idx1, one, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row4"), allColumns, new byte[][]{idx1, idx2, one, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row7"), oddColumns, new byte[][]{idx1, one, valA});
// should be end of rows
assertEmpty(scanner);
} finally {
scanner.close();
}
// 2 should have rows 2, 5, 8
scanner = mcTable.readByIndex(idxCol3, two);
try {
Row row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row2"), allColumns, new byte[][]{idx1, idx2, two, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row5"), oddColumns, new byte[][]{idx1, two, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row8"), allColumns, new byte[][]{idx1, idx2, two, valA});
// should be end of rows
assertEmpty(scanner);
} finally {
scanner.close();
}
}
});
// update idx2 value for rows 2 & 4
final byte[] idx2b = new byte[]{ '2', 'b' };
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
mcTable.put(Bytes.toBytes("row2"), idxCol2, idx2b);
mcTable.put(Bytes.toBytes("row4"), idxCol2, idx2b);
}
});
// only rows 6 & 8 should be returned for idx2 now
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Scanner scanner = mcTable.readByIndex(idxCol2, idx2);
try {
Row row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row6"), allColumns, new byte[][]{idx1, idx2, zero, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row8"), allColumns, new byte[][]{idx1, idx2, two, valA});
assertEmpty(scanner);
} finally {
scanner.close();
}
scanner = mcTable.readByIndex(idxCol2, idx2b);
try {
Row row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row2"), allColumns, new byte[][]{idx1, idx2b, two, valA});
row = scanner.next();
TableAssert.assertRow(row, Bytes.toBytes("row4"), allColumns, new byte[][]{idx1, idx2b, one, valA});
assertEmpty(scanner);
} finally {
scanner.close();
}
}
});
// rows 2 & 4 should be returned for idx2b
} finally {
dsFrameworkUtil.deleteInstance(multiColumnTabInstance);
}
}
/**
* Test conditions where the indexed column name or column value may contain the key delimiter.
* @throws Exception
*/
@Test
public void testIndexKeyDelimiterHandling() throws Exception {
Id.DatasetInstance delimTabInstance = Id.DatasetInstance.from(DatasetFrameworkTestUtil.NAMESPACE_ID, "delimtab");
dsFrameworkUtil.createInstance("indexedTable", delimTabInstance, DatasetProperties.builder()
.add(IndexedTableDefinition.INDEX_COLUMNS_CONF_KEY, idxColString)
.build());
final IndexedTable iTable = dsFrameworkUtil.getInstance(delimTabInstance);
final byte[] delim = new byte[]{ 0 };
try {
final byte[] valueWithDelimiter = Bytes.concat(idx1, delim, idx2);
TransactionExecutor tx = dsFrameworkUtil.newTransactionExecutor(iTable);
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
iTable.put(keyA, idxCol, idx1);
iTable.put(keyB, idxCol, valueWithDelimiter);
iTable.put(keyC, idxCol, idx2);
}
});
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Scanner scanner = iTable.readByIndex(idxCol, idx1);
try {
Row row = scanner.next();
TableAssert.assertRow(row, keyA, new byte[][]{idxCol}, new byte[][]{idx1});
assertEmpty(scanner);
} finally {
scanner.close();
}
scanner = iTable.readByIndex(idxCol, idx2);
try {
Row row = scanner.next();
TableAssert.assertRow(row, keyC, new byte[][]{idxCol}, new byte[][]{idx2});
assertEmpty(scanner);
} finally {
scanner.close();
}
scanner = iTable.readByIndex(idxCol, valueWithDelimiter);
try {
Row row = scanner.next();
TableAssert.assertRow(row, keyB, new byte[][]{idxCol}, new byte[][]{valueWithDelimiter});
assertEmpty(scanner);
} finally {
scanner.close();
}
}
});
} finally {
dsFrameworkUtil.deleteInstance(delimTabInstance);
}
}
@Test
public void testIncrementIndexing() throws Exception {
Id.DatasetInstance incrTabInstance = Id.DatasetInstance.from(DatasetFrameworkTestUtil.NAMESPACE_ID, "incrtab");
dsFrameworkUtil.createInstance("indexedTable", incrTabInstance, DatasetProperties.builder()
.add(IndexedTableDefinition.INDEX_COLUMNS_CONF_KEY, "idx1,idx2,idx3")
.build());
final IndexedTable iTable = dsFrameworkUtil.getInstance(incrTabInstance);
final byte[] idxCol1 = Bytes.toBytes("idx1");
final byte[] idxCol2 = Bytes.toBytes("idx2");
final byte[] idxCol3 = Bytes.toBytes("idx3");
final byte[] row1 = Bytes.toBytes("row1");
try {
TransactionExecutor tx = dsFrameworkUtil.newTransactionExecutor(iTable);
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
long result = iTable.incrementAndGet(row1, idxCol1, 1);
assertEquals(1L, result);
}
});
final byte[] oneBytes = Bytes.toBytes(1L);
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Scanner scanner = iTable.readByIndex(idxCol1, oneBytes);
try {
Row row = scanner.next();
TableAssert.assertRow(row, row1, new byte[][]{idxCol1}, new byte[][]{oneBytes});
assertEmpty(scanner);
} finally {
scanner.close();
}
}
});
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
long result = iTable.incrementAndGet(row1, idxCol1, 1);
assertEquals(2L, result);
}
});
final byte[] twoBytes = Bytes.toBytes(2L);
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// previous index by value 1 should be gone
Scanner scanner = iTable.readByIndex(idxCol1, oneBytes);
try {
assertEmpty(scanner);
} finally {
scanner.close();
}
// should now be indexed by value 2
scanner = iTable.readByIndex(idxCol1, twoBytes);
try {
Row row = scanner.next();
TableAssert.assertRow(row, row1, new byte[][]{idxCol1}, new byte[][]{twoBytes});
assertEmpty(scanner);
} finally {
scanner.close();
}
}
});
final byte[] threeBytes = Bytes.toBytes(3L);
final byte[][] idxCols = new byte[][]{ idxCol1, idxCol2, idxCol3 };
final byte[][] expectedValues = new byte[][]{ threeBytes, oneBytes, oneBytes };
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Row result = iTable.incrementAndGet(row1, idxCols, new long[]{ 1, 1, 1 });
assertNotNull(result);
TableAssert.assertColumns(result, idxCols, expectedValues);
}
});
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
Scanner scanner = iTable.readByIndex(idxCol1, threeBytes);
try {
Row row = scanner.next();
TableAssert.assertRow(row, row1, idxCols, expectedValues);
assertEmpty(scanner);
} finally {
scanner.close();
}
scanner = iTable.readByIndex(idxCol2, oneBytes);
try {
Row row = scanner.next();
TableAssert.assertRow(row, row1, idxCols, expectedValues);
assertEmpty(scanner);
} finally {
scanner.close();
}
scanner = iTable.readByIndex(idxCol3, oneBytes);
try {
Row row = scanner.next();
TableAssert.assertRow(row, row1, idxCols, expectedValues);
assertEmpty(scanner);
} finally {
scanner.close();
}
}
});
final byte[] row2 = Bytes.toBytes("row2");
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
// read-less increment on an indexed column should throw an exception
try {
iTable.increment(row2, idxCol1, 1L);
fail("Expected IllegalArgumentException performing increment on indexed column");
} catch (IllegalArgumentException iae) {
// expected
}
// read-less increment on a non-indexed column should succeed
iTable.increment(row2, valCol, 1L);
byte[] result = iTable.get(row2, valCol);
assertArrayEquals(oneBytes, result);
}
});
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
iTable.put(row2, valCol, valA);
}
});
// increment against a column with non-long value should fail
tx.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
try {
iTable.incrementAndGet(row2, valCol, 1L);
fail("Expected NumberFormatException from increment on a column with non-long value");
} catch (NumberFormatException nfe) {
// expected
}
}
});
} finally {
dsFrameworkUtil.deleteInstance(incrTabInstance);
}
}
/**
* Asserts that the given scanner contains no more rows.
*/
private void assertEmpty(Scanner scanner) {
Row row = scanner.next();
Assert.assertNull(row);
}
private Row readFirst(Scanner scanner) {
try {
return scanner.next();
} finally {
scanner.close();
}
}
}