/*
* 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.data2.dataset2.lib.table;
import co.cask.cdap.api.annotation.Beta;
import co.cask.cdap.api.common.Bytes;
import co.cask.cdap.api.data.batch.Split;
import co.cask.cdap.api.data.batch.SplitReader;
import co.cask.cdap.api.dataset.DataSetException;
import co.cask.cdap.api.dataset.lib.AbstractDataset;
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.OrderedTable;
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.Scanner;
import co.cask.cdap.api.dataset.table.Table;
import co.cask.cdap.api.dataset.table.TableSplit;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/**
*
*/
class TableDataset extends AbstractDataset implements Table {
public static final Logger LOG = LoggerFactory.getLogger(TableDataset.class);
private final OrderedTable table;
TableDataset(String instanceName, OrderedTable table) {
super(instanceName, table);
this.table = table;
}
@Override
public Row get(byte[] row, byte[][] columns) {
try {
return new Result(row, table.get(row, columns));
} catch (Exception e) {
LOG.debug("get failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("get failed", e);
}
}
@Override
public Row get(byte[] row) {
try {
return new Result(row, table.get(row));
} catch (Exception e) {
LOG.debug("get failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("get failed", e);
}
}
@Override
public byte[] get(byte[] row, byte[] column) {
try {
return table.get(row, column);
} catch (Exception e) {
LOG.debug("get failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("get failed", e);
}
}
@Override
public Row get(byte[] row, byte[] startColumn, byte[] stopColumn, int limit) {
try {
return new Result(row, table.get(row, startColumn, stopColumn, limit));
} catch (Exception e) {
LOG.debug("get failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("get failed", e);
}
}
@Override
public Row get(Get get) {
return get.getColumns().isEmpty() ?
get(get.getRow()) :
get(get.getRow(), get.getColumns().toArray(new byte[get.getColumns().size()][]));
}
@Override
public void put(byte[] row, byte[][] columns, byte[][] values) {
try {
table.put(row, columns, values);
} catch (Exception e) {
LOG.debug("put failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("put failed", e);
}
}
@Override
public void put(byte[] row, byte[] column, byte[] value) {
try {
table.put(row, column, value);
} catch (Exception e) {
LOG.debug("put failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("put failed", e);
}
}
@Override
public void put(Put put) {
Preconditions.checkArgument(!put.getValues().isEmpty(), "Put must have at least one value");
byte[][] columns = new byte[put.getValues().size()][];
byte[][] values = new byte[put.getValues().size()][];
int i = 0;
for (Map.Entry<byte[], byte[]> columnValue : put.getValues().entrySet()) {
columns[i] = columnValue.getKey();
values[i] = columnValue.getValue();
i++;
}
put(put.getRow(), columns, values);
}
@Override
public void delete(byte[] row) {
try {
table.delete(row);
} catch (Exception e) {
LOG.debug("delete failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("delete failed", e);
}
}
@Override
public void delete(byte[] row, byte[] column) {
try {
table.delete(row, column);
} catch (Exception e) {
LOG.debug("delete failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("delete failed", e);
}
}
@Override
public void delete(byte[] row, byte[][] columns) {
try {
table.delete(row, columns);
} catch (Exception e) {
LOG.debug("delete failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("delete failed", e);
}
}
@Override
public void delete(Delete delete) {
if (delete.getColumns().isEmpty()) {
delete(delete.getRow());
} else {
delete(delete.getRow(), delete.getColumns().toArray(new byte[delete.getColumns().size()][]));
}
}
@Override
public long incrementAndGet(byte[] row, byte[] column, long amount) {
try {
return table.incrementAndGet(row, column, amount);
} catch (NumberFormatException e) {
LOG.debug("increment failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw e;
} catch (Exception e) {
LOG.debug("increment failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("increment failed", e);
}
}
@Override
public Row incrementAndGet(byte[] row, byte[][] columns, long[] amounts) {
Map<byte[], Long> incResult;
try {
incResult = table.incrementAndGet(row, columns, amounts);
} catch (NumberFormatException e) {
LOG.debug("increment failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw e;
} catch (Exception e) {
LOG.debug("increment failed for table: " + getTransactionAwareName() + ", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("increment failed", e);
}
// todo: define IncrementResult to make it more efficient
return new Result(row, Maps.transformValues(incResult, new Function<Long, byte[]>() {
@Nullable
@Override
public byte[] apply(@Nullable Long input) {
return input == null ? null : Bytes.toBytes(input);
}
}));
}
@Override
public Row incrementAndGet(Increment increment) {
Preconditions.checkArgument(!increment.getValues().isEmpty(), "Increment must have at least one value");
byte[][] columns = new byte[increment.getValues().size()][];
long[] values = new long[increment.getValues().size()];
int i = 0;
for (Map.Entry<byte[], Long> columnValue : increment.getValues().entrySet()) {
columns[i] = columnValue.getKey();
values[i] = columnValue.getValue();
i++;
}
return incrementAndGet(increment.getRow(), columns, values);
}
@Override
public void increment(byte[] row, byte[] column, long amount) {
try {
table.increment(row, column, amount);
} catch (Exception e) {
LOG.debug("increment failed for table: " + getTransactionAwareName() +
", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("increment failed", e);
}
}
@Override
public void increment(byte[] row, byte[][] columns, long[] amounts) {
try {
table.increment(row, columns, amounts);
} catch (Exception e) {
LOG.debug("increment failed for table: " + getTransactionAwareName() +
", row: " + Bytes.toStringBinary(row), e);
throw new DataSetException("increment failed", e);
}
}
@Override
public void increment(Increment increment) {
Preconditions.checkArgument(!increment.getValues().isEmpty(), "Increment must have at least one value");
byte[][] columns = new byte[increment.getValues().size()][];
long[] values = new long[increment.getValues().size()];
int i = 0;
for (Map.Entry<byte[], Long> columnValue : increment.getValues().entrySet()) {
columns[i] = columnValue.getKey();
values[i] = columnValue.getValue();
i++;
}
increment(increment.getRow(), columns, values);
}
@Override
public boolean compareAndSwap(byte[] row, byte[] column, byte[] expectedValue, byte[] newValue) {
try {
return table.compareAndSwap(row, column, expectedValue, newValue);
} catch (Exception e) {
String msg = "compareAndSwap failed for table: " + getTransactionAwareName();
LOG.debug(msg, e);
throw new DataSetException(msg, e);
}
}
@Override
public Scanner scan(byte[] startRow, byte[] stopRow) {
try {
return table.scan(startRow, stopRow);
} catch (Exception e) {
LOG.debug("scan failed for table: " + getTransactionAwareName(), e);
throw new DataSetException("scan failed", e);
}
}
@Override
public void write(byte[] key, Put put) {
put(put);
}
/**
* Returns splits for a range of keys in the table.
*
* @param numSplits Desired number of splits. If greater than zero, at most this many splits will be returned.
* If less or equal to zero, any number of splits can be returned.
* @param start If non-null, the returned splits will only cover keys that are greater or equal.
* @param stop If non-null, the returned splits will only cover keys that are less.
* @return list of {@link Split}
*/
@Beta
@Override
public List<Split> getSplits(int numSplits, byte[] start, byte[] stop) {
try {
return table.getSplits(numSplits, start, stop);
} catch (Exception e) {
LOG.error("getSplits failed for table: " + getTransactionAwareName(), e);
throw new DataSetException("getSplits failed", e);
}
}
@Override
public List<Split> getSplits() {
return getSplits(-1, null, null);
}
@Override
public SplitReader<byte[], Row> createSplitReader(Split split) {
return new TableScanner();
}
/**
* Implements a split reader for a key range of a table, based on the Scanner implementation of the underlying
* table implementation.
*/
public class TableScanner extends SplitReader<byte[], Row> {
// the underlying scanner
private Scanner scanner;
// the current key
private byte[] key = null;
// the current row, that is, a map from column key to value
private Map<byte[], byte[]> row = null;
@Override
public void initialize(Split split) throws InterruptedException {
TableSplit tableSplit = (TableSplit) split;
try {
this.scanner = table.scan(tableSplit.getStart(), tableSplit.getStop());
} catch (Exception e) {
LOG.debug("scan failed for table: " + getTransactionAwareName(), e);
throw new DataSetException("scan failed", e);
}
}
@Override
public boolean nextKeyValue() throws InterruptedException {
// call the underlying scanner, and depending on whether there it returns something, set current key and row.
Row next = this.scanner.next();
if (next == null) {
this.key = null;
this.row = null;
return false;
} else {
this.key = next.getRow();
this.row = next.getColumns();
return true;
}
}
@Override
public byte[] getCurrentKey() throws InterruptedException {
return this.key;
}
@Override
public Row getCurrentValue() throws InterruptedException {
return new Result(this.key, this.row);
}
@Override
public void close() {
this.scanner.close();
}
}
}