use of io.cdap.cdap.api.dataset.table.Row in project cdap by caskdata.
the class IndexedTable method incrementAndGet.
/**
* Increments (atomically) the specified row and columns by the specified amounts, and returns the new values.
* Note that performing this operation on an indexed column will generally have a negative impact on performance,
* since up to three writes will need to be performed for every increment (one removing the index for the previous,
* pre-increment value, one adding the index for the incremented value, and one for the increment itself).
*
* @see Table#incrementAndGet(byte[], byte[][], long[])
*/
@ReadWrite
@Override
public Row incrementAndGet(byte[] row, byte[][] columns, long[] amounts) {
if (columns.length != amounts.length) {
throw new IllegalArgumentException("Size of columns and amounts arguments must match");
}
Row existingRow = table.get(row, columns);
byte[][] updatedValues = new byte[columns.length][];
NavigableMap<byte[], byte[]> result = new TreeMap<>(Bytes.BYTES_COMPARATOR);
for (int i = 0; i < columns.length; i++) {
long existingValue = 0L;
byte[] existingBytes = existingRow.get(columns[i]);
if (existingBytes != null) {
if (existingBytes.length != Bytes.SIZEOF_LONG) {
throw new NumberFormatException("Attempted to increment a value that is not convertible to long," + " row: " + Bytes.toStringBinary(row) + " column: " + Bytes.toStringBinary(columns[i]));
}
existingValue = Bytes.toLong(existingBytes);
if (indexedColumns.contains(columns[i])) {
index.delete(createIndexKey(row, columns[i], existingBytes), IDX_COL);
}
}
updatedValues[i] = Bytes.toBytes(existingValue + amounts[i]);
result.put(columns[i], updatedValues[i]);
if (indexedColumns.contains(columns[i])) {
index.put(createIndexKey(row, columns[i], updatedValues[i]), IDX_COL, row);
}
}
table.put(row, columns, updatedValues);
return new Result(row, result);
}
use of io.cdap.cdap.api.dataset.table.Row in project cdap by caskdata.
the class IndexedTable method delete.
@WriteOnly
@Override
public void delete(byte[] row, byte[][] columns) {
Row existingRow = table.get(row, columns);
if (existingRow.isEmpty()) {
// no row to delete
return;
}
// delete all index entries
deleteIndexEntries(existingRow);
// delete the row's columns
table.delete(row, columns);
}
use of io.cdap.cdap.api.dataset.table.Row in project cdap by caskdata.
the class KeyValueTable method scan.
/**
* Scans table.
* @param startRow start row inclusive. {@code null} means start from first row of the table
* @param stopRow stop row exclusive. {@code null} means scan all rows to the end of the table
* @return {@link io.cdap.cdap.api.dataset.lib.CloseableIterator} of
* {@link KeyValue KeyValue<byte[], byte[]>}
*/
public CloseableIterator<KeyValue<byte[], byte[]>> scan(byte[] startRow, byte[] stopRow) {
final Scanner scanner = table.scan(startRow, stopRow);
return new AbstractCloseableIterator<KeyValue<byte[], byte[]>>() {
private boolean closed = false;
@Override
protected KeyValue<byte[], byte[]> computeNext() {
if (closed) {
return endOfData();
}
Row next = scanner.next();
if (next != null) {
return new KeyValue<>(next.getRow(), next.get(KEY_COLUMN));
}
close();
return null;
}
@Override
public void close() {
scanner.close();
endOfData();
closed = true;
}
};
}
use of io.cdap.cdap.api.dataset.table.Row in project cdap by caskdata.
the class IndexedObjectStore method write.
/**
* Writes to the dataset, deleting any existing secondaryKey corresponding to the key and updates the indexTable with
* the secondaryKey that is passed.
*
* @param key key for storing the object
* @param object object to be stored
* @param secondaryKeys indices that can be used to lookup the object
*/
@WriteOnly
public void write(byte[] key, T object, byte[][] secondaryKeys) {
writeToObjectStore(key, object);
// Update the secondaryKeys
// logic:
// - Get existing secondary keys
// - Compute diff between existing secondary keys and new secondary keys
// - Remove the secondaryKeys that are removed
// - Add the new keys that are added
Row row = index.get(getPrefixedPrimaryKey(key));
Set<byte[]> existingSecondaryKeys = new TreeSet<>(Bytes.BYTES_COMPARATOR);
if (!row.isEmpty()) {
existingSecondaryKeys = row.getColumns().keySet();
}
Set<byte[]> newSecondaryKeys = new TreeSet<>(Bytes.BYTES_COMPARATOR);
newSecondaryKeys.addAll(Arrays.asList(secondaryKeys));
List<byte[]> secondaryKeysDeleted = secondaryKeysToDelete(existingSecondaryKeys, newSecondaryKeys);
if (secondaryKeysDeleted.size() > 0) {
deleteSecondaryKeys(key, secondaryKeysDeleted.toArray(new byte[secondaryKeysDeleted.size()][]));
}
List<byte[]> secondaryKeysAdded = secondaryKeysToAdd(existingSecondaryKeys, newSecondaryKeys);
// for each key store the secondaryKey. This will be used while deleting old index values.
if (secondaryKeysAdded.size() > 0) {
byte[][] fooValues = new byte[secondaryKeysAdded.size()][];
Arrays.fill(fooValues, EMPTY_VALUE);
index.put(getPrefixedPrimaryKey(key), secondaryKeysAdded.toArray(new byte[secondaryKeysAdded.size()][]), fooValues);
}
for (byte[] secondaryKey : secondaryKeysAdded) {
// update the index.
index.put(secondaryKey, key, EMPTY_VALUE);
}
}
use of io.cdap.cdap.api.dataset.table.Row in project cdap by caskdata.
the class IndexedTableTest method testIncrementIndexing.
@Test
public void testIncrementIndexing() throws Exception {
DatasetId incrTabInstance = DatasetFrameworkTestUtil.NAMESPACE_ID.dataset("incrtab");
dsFrameworkUtil.createInstance("indexedTable", incrTabInstance, DatasetProperties.builder().add(IndexedTable.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 {
try (Scanner scanner = iTable.readByIndex(idxCol1, oneBytes)) {
Row row = scanner.next();
TableAssert.assertRow(row, row1, new byte[][] { idxCol1 }, new byte[][] { oneBytes });
assertEmpty(scanner);
}
}
});
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);
}
}
Aggregations