blob: c5a291e941796d19ecdf257320873bbeb7382df4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.omid.transaction;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.omid.HBaseShims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Charsets;
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
@SuppressWarnings("all")
public final class CellUtils {
private static final Logger LOG = LoggerFactory.getLogger(CellUtils.class);
static final byte[] SHADOW_CELL_SUFFIX = "\u0080".getBytes(Charsets.UTF_8); // Non printable char (128 ASCII)
//Prefix starts with 0 to apear before other cells in TransactionVisibilityFilter
static final byte[] SHADOW_CELL_PREFIX = "\u0000\u0080".getBytes(Charsets.UTF_8);
static byte[] DELETE_TOMBSTONE = HConstants.EMPTY_BYTE_ARRAY;
static byte[] LEGACY_DELETE_TOMBSTONE = Bytes.toBytes("__OMID_TOMBSTONE__");
public static final byte[] FAMILY_DELETE_QUALIFIER = HConstants.EMPTY_BYTE_ARRAY;
public static final String TRANSACTION_ATTRIBUTE = "__OMID_TRANSACTION__";
/**/
public static final String CLIENT_GET_ATTRIBUTE = "__OMID_CLIENT_GET__";
public static final String LL_ATTRIBUTE = "__OMID_LL__";
/**
* Utility interface to get rid of the dependency on HBase server package
*/
interface CellGetter {
Result get(Get get) throws IOException;
}
/**
* Returns true if the particular cell passed exists in the datastore.
* @param row row
* @param family column family
* @param qualifier columnn name
* @param version version
* @param cellGetter an instance of CellGetter
* @return true if the cell specified exists. false otherwise
* @throws IOException
*/
public static boolean hasCell(byte[] row,
byte[] family,
byte[] qualifier,
long version,
CellGetter cellGetter)
throws IOException {
Get get = new Get(row);
get.addColumn(family, qualifier);
get.setTimeStamp(version);
Result result = cellGetter.get(get);
return result.containsColumn(family, qualifier);
}
/**
* Returns true if the particular cell passed has a corresponding shadow cell in the datastore
* @param row row
* @param family column family
* @param qualifier columnn name
* @param version version
* @param cellGetter an instance of CellGetter
* @return true if it has a shadow cell. false otherwise.
* @throws IOException
*/
public static boolean hasShadowCell(byte[] row,
byte[] family,
byte[] qualifier,
long version,
CellGetter cellGetter) throws IOException {
return hasCell(row, family, addShadowCellSuffixPrefix(qualifier),
version, cellGetter);
}
/**
* Builds a new qualifier composed of the HBase qualifier passed + the shadow cell suffix.
* @param qualifierArray the qualifier to be suffixed
* @param qualOffset the offset where the qualifier starts
* @param qualLength the qualifier length
* @return the suffixed qualifier
*/
public static byte[] addShadowCellSuffixPrefix(byte[] qualifierArray, int qualOffset, int qualLength) {
byte[] result = new byte[qualLength + SHADOW_CELL_SUFFIX.length + SHADOW_CELL_PREFIX.length];
System.arraycopy(SHADOW_CELL_PREFIX, 0, result,0 , SHADOW_CELL_PREFIX.length);
System.arraycopy(qualifierArray, qualOffset, result, SHADOW_CELL_PREFIX.length, qualLength);
System.arraycopy(SHADOW_CELL_SUFFIX, 0, result, qualLength + SHADOW_CELL_PREFIX.length,
SHADOW_CELL_SUFFIX.length);
return result;
}
/**
* Builds a new qualifier composed of the HBase qualifier passed + the shadow cell suffix.
* Contains a reduced signature to avoid boilerplate code in client side.
* @param qualifier
* the qualifier to be suffixed
* @return the suffixed qualifier
*/
public static byte[] addShadowCellSuffixPrefix(byte[] qualifier) {
return addShadowCellSuffixPrefix(qualifier, 0, qualifier.length);
}
/**
* Builds a new qualifier removing the shadow cell suffix from the
* passed HBase qualifier.
* @param qualifier the qualifier to remove the suffix from
* @param qualOffset the offset where the qualifier starts
* @param qualLength the qualifier length
* @return the new qualifier without the suffix
*/
public static byte[] removeShadowCellSuffixPrefix(byte[] qualifier, int qualOffset, int qualLength) {
if (endsWith(qualifier, qualOffset, qualLength, SHADOW_CELL_SUFFIX)) {
if (startsWith(qualifier, qualOffset,qualLength, SHADOW_CELL_PREFIX)) {
return Arrays.copyOfRange(qualifier,
qualOffset + SHADOW_CELL_PREFIX.length,
qualOffset + (qualLength - SHADOW_CELL_SUFFIX.length));
} else {
//support backward competatbiliy
return Arrays.copyOfRange(qualifier,
qualOffset,qualOffset + (qualLength - SHADOW_CELL_SUFFIX.length));
}
}
throw new IllegalArgumentException(
"Can't find shadow cell suffix in qualifier "
+ Bytes.toString(qualifier));
}
/**
* Returns the qualifier length removing the shadow cell suffix and prefix. In case that que suffix is not found,
* just returns the length of the qualifier passed.
* @param qualifier the qualifier to remove the suffix from
* @param qualOffset the offset where the qualifier starts
* @param qualLength the qualifier length
* @return the qualifier length without the suffix
*/
public static int qualifierLengthFromShadowCellQualifier(byte[] qualifier, int qualOffset, int qualLength) {
if (endsWith(qualifier, qualOffset, qualLength, SHADOW_CELL_SUFFIX)) {
if (startsWith(qualifier,qualOffset, qualLength, SHADOW_CELL_PREFIX)) {
return qualLength - SHADOW_CELL_SUFFIX.length - SHADOW_CELL_PREFIX.length;
} else {
return qualLength - SHADOW_CELL_SUFFIX.length;
}
}
return qualLength;
}
/**
* Returns the qualifier length removing the shadow cell suffix and prefix. In case that que suffix is not found,
* just returns the length of the qualifier passed.
* @param qualifier the qualifier to remove the suffix from
* @param qualOffset the offset where the qualifier starts
* @param qualLength the qualifier length
* @return the qualifier length without the suffix
*/
public static int qualifierOffsetFromShadowCellQualifier(byte[] qualifier, int qualOffset, int qualLength) {
if (startsWith(qualifier, qualOffset, qualLength, SHADOW_CELL_PREFIX)) {
return qualOffset + SHADOW_CELL_PREFIX.length;
}
return qualOffset;
}
/**
* Complement to matchingQualifier() methods in HBase's CellUtil.class
* @param left the cell to compare the qualifier
* @param qualArray the explicit qualifier array passed
* @param qualOffset the explicit qualifier offset passed
* @param qualLen the explicit qualifier length passed
* @return whether the qualifiers are equal or not
*/
public static boolean matchingQualifier(final Cell left, final byte[] qualArray, int qualOffset, int qualLen) {
return Bytes.equals(left.getQualifierArray(), left.getQualifierOffset(), left.getQualifierLength(),
qualArray, qualOffset, qualLen);
}
/**
* Check that the cell passed meets the requirements for a valid cell identifier with Omid. Basically, users can't:
* 1) specify a timestamp
* 2) use a particular suffix in the qualifier
*/
public static void validateCell(Cell cell, long startTimestamp) {
// Throw exception if timestamp is set by the user
if (cell.getTimestamp() != HConstants.LATEST_TIMESTAMP
&& cell.getTimestamp() != startTimestamp) {
throw new IllegalArgumentException(
"Timestamp not allowed in transactional user operations");
}
// Throw exception if using a non-allowed qualifier
if (isShadowCell(cell)) {
throw new IllegalArgumentException(
"Reserved string used in column qualifier");
}
}
/**
* Returns whether a cell contains a qualifier that is a delete cell
* column qualifier or not.
* @param cell the cell to check if contains the delete cell qualifier
* @return whether the cell passed contains a delete cell qualifier or not
*/
public static boolean isFamilyDeleteCell(Cell cell) {
return CellUtil.matchingQualifier(cell, CellUtils.FAMILY_DELETE_QUALIFIER) &&
CellUtil.matchingValue(cell, HConstants.EMPTY_BYTE_ARRAY);
}
/**
* Returns whether a cell contains a qualifier that is a shadow cell
* column qualifier or not.
* @param cell the cell to check if contains the shadow cell qualifier
* @return whether the cell passed contains a shadow cell qualifier or not
*/
public static boolean isShadowCell(Cell cell) {
byte[] qualifier = cell.getQualifierArray();
int qualOffset = cell.getQualifierOffset();
int qualLength = cell.getQualifierLength();
return endsWith(qualifier, qualOffset, qualLength, SHADOW_CELL_SUFFIX);
}
private static boolean endsWith(byte[] value, int offset, int length, byte[] suffix) {
if (length <= suffix.length) {
return false;
}
int suffixOffset = offset + length - suffix.length;
int result = Bytes.compareTo(value, suffixOffset, suffix.length,
suffix, 0, suffix.length);
return result == 0;
}
private static boolean startsWith(byte[] value, int offset, int length, byte[] prefix) {
if (length <= prefix.length) {
return false;
}
int result = Bytes.compareTo(value, offset, prefix.length,
prefix, 0, prefix.length);
return result == 0;
}
/**
* Returns if a cell is marked as a tombstone.
* @param cell the cell to check
* @return whether the cell is marked as a tombstone or not
*/
public static boolean isTombstone(Cell cell) {
return CellUtil.matchingValue(cell, DELETE_TOMBSTONE) ||
CellUtil.matchingValue(cell, LEGACY_DELETE_TOMBSTONE);
}
/**
* Returns a new shadow cell created from a particular cell.
* @param cell
* the cell to reconstruct the shadow cell from.
* @param shadowCellValue
* the value for the new shadow cell created
* @return the brand-new shadow cell
*/
public static Cell buildShadowCellFromCell(Cell cell, byte[] shadowCellValue) {
byte[] shadowCellQualifier = addShadowCellSuffixPrefix(cell.getQualifierArray(),
cell.getQualifierOffset(),
cell.getQualifierLength());
return new KeyValue(
cell.getRowArray(), cell.getRowOffset(), cell.getRowLength(),
cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength(),
shadowCellQualifier, 0, shadowCellQualifier.length,
cell.getTimestamp(), KeyValue.Type.codeToType(cell.getTypeByte()),
shadowCellValue, 0, shadowCellValue.length);
}
/**
* Analyzes a list of cells, associating the corresponding shadow cell if present.
*
* @param cells the list of cells to classify
* @return a sorted map associating each cell with its shadow cell
*/
public static SortedMap<Cell, Optional<Cell>> mapCellsToShadowCells(List<Cell> cells) {
// Move CellComparator to HBaseSims for 2.0 support
// Need to access through CellComparatorImpl.COMPARATOR
SortedMap<Cell, Optional<Cell>> cellToShadowCellMap
= new TreeMap<Cell, Optional<Cell>>(HBaseShims.cellComparatorInstance());
Map<CellId, Cell> cellIdToCellMap = new HashMap<CellId, Cell>();
Map<CellId, Cell> cellIdToSCCellMap = new HashMap<CellId, Cell>();
for (Cell cell : cells) {
if (!isShadowCell(cell)) {
CellId key = new CellId(cell, false);
// Get the current cell and compare the values
Cell storedCell = cellIdToCellMap.get(key);
if (storedCell != null) {
if (CellUtil.matchingValue(cell, storedCell)) {
// TODO: Should we check also here the MVCC and swap if its greater???
// Values are the same, ignore
} else {
if (cell.getSequenceId() > storedCell.getSequenceId()) { // Swap values
Optional<Cell> previousValue = cellToShadowCellMap.remove(storedCell);
Preconditions.checkNotNull(previousValue, "Should contain an Optional<Cell> value");
cellIdToCellMap.put(key, cell);
cellToShadowCellMap.put(cell, previousValue);
} else {
LOG.warn("Cell {} with an earlier MVCC found. Ignoring...", cell);
}
}
} else {
cellIdToCellMap.put(key, cell);
Cell sc = cellIdToSCCellMap.get(key);
if (sc != null) {
cellToShadowCellMap.put(cell, Optional.of(sc));
} else {
cellToShadowCellMap.put(cell, Optional.<Cell>absent());
}
}
} else {
CellId key = new CellId(cell, true);
Cell savedCell = cellIdToCellMap.get(key);
if (savedCell != null) {
Cell originalCell = savedCell;
cellToShadowCellMap.put(originalCell, Optional.of(cell));
} else {
cellIdToSCCellMap.put(key, cell);
}
}
}
return cellToShadowCellMap;
}
private static class CellId {
private static final int MIN_BITS = 32;
private final Cell cell;
private final boolean isShadowCell;
CellId(Cell cell, boolean isShadowCell) {
this.cell = cell;
this.isShadowCell = isShadowCell;
}
Cell getCell() {
return cell;
}
boolean isShadowCell() {
return isShadowCell;
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof CellId))
return false;
CellId otherCellId = (CellId) o;
Cell otherCell = otherCellId.getCell();
// Row comparison
if (!CellUtil.matchingRow(otherCell, cell)) {
return false;
}
// Family comparison
if (!CellUtil.matchingFamily(otherCell, cell)) {
return false;
}
// Qualifier comparison
int qualifierLength = cell.getQualifierLength();
int qualifierOffset = cell.getQualifierOffset();
int otherQualifierLength = otherCell.getQualifierLength();
int otherQualifierOffset = otherCell.getQualifierOffset();
if (isShadowCell()) {
qualifierLength = qualifierLengthFromShadowCellQualifier(cell.getQualifierArray(),
cell.getQualifierOffset(),
cell.getQualifierLength());
qualifierOffset = qualifierOffsetFromShadowCellQualifier(cell.getQualifierArray(), cell.getQualifierOffset(),
cell.getQualifierLength());
}
if (otherCellId.isShadowCell()) {
otherQualifierLength = qualifierLengthFromShadowCellQualifier(otherCell.getQualifierArray(),
otherCell.getQualifierOffset(),
otherCell.getQualifierLength());
otherQualifierOffset = qualifierOffsetFromShadowCellQualifier(otherCell.getQualifierArray(), otherCell.getQualifierOffset(),
otherCell.getQualifierLength());
}
if (!Bytes.equals(cell.getQualifierArray(), qualifierOffset, qualifierLength,
otherCell.getQualifierArray(), otherQualifierOffset, otherQualifierLength)) {
return false;
}
// Timestamp comparison
return otherCell.getTimestamp() == cell.getTimestamp();
}
@Override
public int hashCode() {
Hasher hasher = Hashing.goodFastHash(MIN_BITS).newHasher();
hasher.putBytes(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
hasher.putBytes(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
int qualifierLength = cell.getQualifierLength();
int qualifierOffset = cell.getQualifierOffset();
if (isShadowCell()) {
qualifierLength = qualifierLengthFromShadowCellQualifier(cell.getQualifierArray(),
cell.getQualifierOffset(),
cell.getQualifierLength());
if (startsWith(cell.getQualifierArray(), cell.getQualifierOffset(),
cell.getQualifierLength(), SHADOW_CELL_PREFIX)) {
qualifierOffset = qualifierOffset + SHADOW_CELL_PREFIX.length;
}
}
hasher.putBytes(cell.getQualifierArray(),qualifierOffset , qualifierLength);
hasher.putLong(cell.getTimestamp());
return hasher.hash().asInt();
}
@Override
public String toString() {
ToStringHelper helper = Objects.toStringHelper(this);
helper.add("row", Bytes.toStringBinary(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength()));
helper.add("family", Bytes.toString(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength()));
helper.add("is shadow cell?", isShadowCell);
helper.add("qualifier",
Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength()));
if (isShadowCell()) {
int qualifierLength = qualifierLengthFromShadowCellQualifier(cell.getQualifierArray(),
cell.getQualifierOffset(),
cell.getQualifierLength());
byte[] cellWithoutSc = removeShadowCellSuffixPrefix(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength());
helper.add("qualifier whithout shadow cell suffix", Bytes.toString(cellWithoutSc));
}
helper.add("ts", cell.getTimestamp());
return helper.toString();
}
}
}