use of io.cdap.cdap.api.annotation.ReadWrite 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.annotation.ReadWrite in project cdap by caskdata.
the class PartitionedFileSetDataset method consumePartitions.
// PartitionConsumerState consists of two things:
// 1) A list of transaction IDs representing the list of transactions in progress during the previous call.
// Each of these transaction IDs need to be checked for new partitions because there may be partitions created by
// those partitions since the previous call.
// 2) A transaction ID from which to start scanning for new partitions. This is an exclusive end range that the
// previous call stopped scanning partitions at.
// Note that each of the transactions IDs in (1) will be smaller than the transactionId in (2).
@ReadWrite
@Override
public PartitionConsumerResult consumePartitions(PartitionConsumerState partitionConsumerState, int limit, Predicate<PartitionDetail> predicate) {
List<Long> previousInProgress = partitionConsumerState.getVersionsToCheck();
Set<Long> noLongerInProgress = setDiff(previousInProgress, tx.getInProgress());
List<PartitionDetail> partitions = Lists.newArrayList();
Iterator<Long> iter = noLongerInProgress.iterator();
while (iter.hasNext()) {
Long txId = iter.next();
if (partitions.size() >= limit) {
break;
}
try (Scanner scanner = partitionsTable.readByIndex(WRITE_PTR_COL, Bytes.toBytes(txId))) {
scannerToPartitions(scanner, partitions, limit, predicate);
}
// remove the txIds as they are added to the partitions list already
// if they're not removed, they will be persisted in the state for the next scan
iter.remove();
}
// exclusive scan end, to be used as the start for a next call to consumePartitions
long scanUpTo;
if (partitions.size() < limit) {
// no read your own writes (partitions)
scanUpTo = Math.min(tx.getWritePointer(), tx.getReadPointer() + 1);
Long endTxId;
try (Scanner scanner = partitionsTable.scanByIndex(WRITE_PTR_COL, Bytes.toBytes(partitionConsumerState.getStartVersion()), Bytes.toBytes(scanUpTo))) {
endTxId = scannerToPartitions(scanner, partitions, limit, predicate);
}
if (endTxId != null) {
// nonnull means that the scanner was not exhausted
scanUpTo = endTxId;
}
} else {
// if we have already hit the limit, don't scan; instead, use the startVersion as the startVersion to the next
// call to consumePartitions
scanUpTo = partitionConsumerState.getStartVersion();
}
List<Long> inProgressBeforeScanEnd = Lists.newArrayList(noLongerInProgress);
for (long txId : tx.getInProgress()) {
if (txId >= scanUpTo) {
break;
}
inProgressBeforeScanEnd.add(txId);
}
return new PartitionConsumerResult(new PartitionConsumerState(scanUpTo, inProgressBeforeScanEnd), partitions);
}
use of io.cdap.cdap.api.annotation.ReadWrite in project cdap by caskdata.
the class BufferingTable method compareAndSwap.
@ReadWrite
@Override
public boolean compareAndSwap(byte[] row, byte[] column, byte[] expectedValue, byte[] newValue) {
ensureTransactionIsStarted();
// TODO: add support for empty values; see https://issues.cask.co/browse/TEPHRA-45 for details.
if (newValue != null && newValue.length == 0) {
warnAboutEmptyValue(column);
}
// NOTE: there is more efficient way to do it, but for now we want more simple implementation, not over-optimizing
byte[][] columns = new byte[][] { column };
try {
byte[] currentValue = getRowMap(row, columns).get(column);
reportRead(1);
if (Arrays.equals(expectedValue, currentValue)) {
putInternal(row, columns, new byte[][] { newValue });
reportWrite(1, getSize(row) + getSize(column) + getSize(newValue));
return true;
}
} catch (Exception e) {
LOG.debug("compareAndSwap failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("compareAndSwap failed", e);
}
return false;
}
use of io.cdap.cdap.api.annotation.ReadWrite in project cdap by caskdata.
the class IndexedTable method compareAndSwap.
/**
* Perform a swap operation by primary key.
* Parameters are as if they were on a non-indexed table.
* Note that if the swap is on the secondary key column,
* then the index must be updated; otherwise, this is a
* pass-through to the underlying table.
*/
@ReadWrite
@Override
public boolean compareAndSwap(byte[] row, byte[] column, byte[] expected, byte[] newValue) {
// is the same as the new value, then the index is not affected either.
if (!indexedColumns.contains(column) || Arrays.equals(expected, newValue)) {
return table.compareAndSwap(row, column, expected, newValue);
}
// the swap is on the index column. it will only succeed if the current
// value matches the expected value of the swap. if that value is not null,
// then we must remove the row key from the index for that value.
Delete idxDelete = null;
if (expected != null) {
idxDelete = new Delete(createIndexKey(row, column, expected), IDX_COL);
}
// if the new value is not null, then we must add the rowkey to the index
// for that value.
Put idxPut = null;
if (newValue != null) {
idxPut = new Put(createIndexKey(row, column, newValue), IDX_COL, row);
}
// apply all operations to both tables
boolean success = table.compareAndSwap(row, column, expected, newValue);
if (!success) {
// do nothing: no changes
return false;
}
if (idxDelete != null) {
index.delete(idxDelete);
}
if (idxPut != null) {
index.put(idxPut);
}
return true;
}
use of io.cdap.cdap.api.annotation.ReadWrite in project cdap by caskdata.
the class BufferingTable method internalIncrementAndGet.
@ReadWrite
protected Row internalIncrementAndGet(byte[] row, byte[][] columns, long[] amounts) {
// Logic:
// * fetching current values
// * updating values
// * updating in-memory store
// * returning updated values as result
// NOTE: there is more efficient way to do it, but for now we want more simple implementation, not over-optimizing
Map<byte[], byte[]> rowMap;
try {
rowMap = getRowMap(row, columns);
reportRead(1);
} catch (Exception e) {
LOG.debug("incrementAndGet failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("incrementAndGet failed", e);
}
byte[][] updatedValues = new byte[columns.length][];
NavigableMap<byte[], byte[]> result = Maps.newTreeMap(Bytes.BYTES_COMPARATOR);
for (int i = 0; i < columns.length; i++) {
byte[] column = columns[i];
byte[] val = rowMap.get(column);
// converting to long
long longVal;
if (val == null) {
longVal = 0L;
} else {
if (val.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(column));
}
longVal = Bytes.toLong(val);
}
longVal += amounts[i];
updatedValues[i] = Bytes.toBytes(longVal);
result.put(column, updatedValues[i]);
}
putInternal(row, columns, updatedValues);
reportWrite(1, getSize(row) + getSize(columns) + getSize(amounts));
return new Result(row, result);
}
Aggregations