/**
* Copyright 2009 The Apache Software Foundation 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.hadoop.hbase.regionserver.tableindexed;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HTableInterface;
import org.apache.hadoop.hbase.client.HTablePool;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.tableindexed.IndexSpecification;
import org.apache.hadoop.hbase.client.tableindexed.IndexedTableDescriptor;
import org.apache.hadoop.hbase.regionserver.FlushRequester;
import org.apache.hadoop.hbase.regionserver.OperationStatus;
import org.apache.hadoop.hbase.regionserver.transactional.TransactionalRegion;
import org.apache.hadoop.hbase.regionserver.wal.HLog;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.GetUtil;
import org.apache.hadoop.hbase.util.Pair;
public class IndexedRegion extends TransactionalRegion {
private static final Log LOG = LogFactory.getLog(IndexedRegion.class);
@SuppressWarnings("unused")
private final Configuration conf;
private final IndexedTableDescriptor indexTableDescriptor;
private final HTablePool tablePool;
@SuppressWarnings("deprecation")
public IndexedRegion(final Path basedir, final HLog log, final FileSystem fs,
final Configuration conf, final HRegionInfo regionInfo,
final FlushRequester flushListener) throws IOException {
super(basedir, log, fs, conf, regionInfo, flushListener);
this.indexTableDescriptor = new IndexedTableDescriptor(
regionInfo.getTableDesc());
this.conf = conf;
this.tablePool = new HTablePool();
}
@SuppressWarnings("deprecation")
private HTableInterface getIndexTable(final IndexSpecification index)
throws IOException {
return tablePool.getTable(index.getIndexedTableName(super.getRegionInfo()
.getTableDesc().getName()));
}
@SuppressWarnings("deprecation")
private void putTable(final HTableInterface t) {
if (t == null) {
return;
}
try {
tablePool.putTable(t);
} catch (IOException e) {
e.printStackTrace();
}
}
private Collection<IndexSpecification> getIndexes() {
return indexTableDescriptor.getIndexes();
}
/**
* {@inheritDoc}
*/
@Override
public void put(final Put put, final Integer lockId, final boolean writeToWAL)
throws IOException {
updateIndexes(put, lockId); // Do this first because will want to see the
// old row
super.put(put, lockId, writeToWAL);
}
/**
* {@inheritDoc}
*/
@Override
public OperationStatus[] put(Pair<Put, Integer>[] putsAndLocks)
throws IOException {
for (Pair<Put, Integer> pair : putsAndLocks) {
updateIndexes(pair.getFirst(), pair.getSecond());
}
return super.put(putsAndLocks);
}
private void updateIndexes(final Put put, final Integer lockId)
throws IOException {
List<IndexSpecification> indexesToUpdate = new LinkedList<IndexSpecification>();
// Find the indexes we need to update
for (IndexSpecification index : getIndexes()) {
if (possiblyAppliesToIndex(index, put)) {
indexesToUpdate.add(index);
}
}
if (indexesToUpdate.size() == 0) {
return;
}
NavigableSet<byte[]> neededColumns = getColumnsForIndexes(indexesToUpdate);
NavigableMap<byte[], byte[]> newColumnValues = getColumnsFromPut(put);
Get oldGet = new Get(put.getRow());
for (byte[] neededCol : neededColumns) {
// oldGet.addColumn(neededCol);
GetUtil.addColumn(oldGet, neededCol);
}
Result oldResult = super.get(oldGet, lockId);
// Add the old values to the new if they are not there
if (oldResult != null && oldResult.raw() != null) {
for (KeyValue oldKV : oldResult.raw()) {
byte[] column = KeyValue.makeColumn(oldKV.getFamily(),
oldKV.getQualifier());
if (!newColumnValues.containsKey(column)) {
newColumnValues.put(column, oldKV.getValue());
}
}
}
Iterator<IndexSpecification> indexIterator = indexesToUpdate.iterator();
while (indexIterator.hasNext()) {
IndexSpecification indexSpec = indexIterator.next();
if (!IndexMaintenanceUtils.doesApplyToIndex(indexSpec, newColumnValues)) {
indexIterator.remove();
}
}
SortedMap<byte[], byte[]> oldColumnValues = convertToValueMap(oldResult);
for (IndexSpecification indexSpec : indexesToUpdate) {
updateIndex(indexSpec, put, newColumnValues, oldColumnValues);
}
}
// FIXME: This call takes place in an RPC, and requires an RPC. This makes for
// a likely deadlock if the number of RPCs we are trying to serve is >= the
// number of handler threads.
private void updateIndex(final IndexSpecification indexSpec, final Put put,
final NavigableMap<byte[], byte[]> newColumnValues,
final SortedMap<byte[], byte[]> oldColumnValues) throws IOException {
Delete indexDelete = makeDeleteToRemoveOldIndexEntry(indexSpec,
put.getRow(), oldColumnValues);
Put indexPut = makeIndexUpdate(indexSpec, put.getRow(), newColumnValues);
HTableInterface indexTable = getIndexTable(indexSpec);
try {
if (indexDelete != null
&& !Bytes.equals(indexDelete.getRow(), indexPut.getRow())) {
// Only do the delete if the row changed. This way we save the put after
// delete issues in HBASE-2256
LOG.debug("Deleting old index row ["
+ Bytes.toString(indexDelete.getRow()) + "]. New row is ["
+ Bytes.toString(indexPut.getRow()) + "].");
indexTable.delete(indexDelete);
} else if (indexDelete != null) {
LOG.debug("Skipping deleting index row ["
+ Bytes.toString(indexDelete.getRow())
+ "] because it has not changed.");
}
indexTable.put(indexPut);
} finally {
putTable(indexTable);
}
}
/** Return the columns needed for the update. */
private NavigableSet<byte[]> getColumnsForIndexes(
final Collection<IndexSpecification> indexes) {
NavigableSet<byte[]> neededColumns = new TreeSet<byte[]>(
Bytes.BYTES_COMPARATOR);
for (IndexSpecification indexSpec : indexes) {
for (byte[] col : indexSpec.getAllColumns()) {
neededColumns.add(col);
}
}
return neededColumns;
}
private Delete makeDeleteToRemoveOldIndexEntry(
final IndexSpecification indexSpec, final byte[] row,
final SortedMap<byte[], byte[]> oldColumnValues) throws IOException {
for (byte[] indexedCol : indexSpec.getIndexedColumns()) {
if (!oldColumnValues.containsKey(indexedCol)) {
LOG.debug("Index [" + indexSpec.getIndexId()
+ "] not trying to remove old entry for row ["
+ Bytes.toString(row) + "] because col ["
+ Bytes.toString(indexedCol) + "] is missing");
return null;
}
}
byte[] oldIndexRow = indexSpec.getKeyGenerator().createIndexKey(row,
oldColumnValues);
LOG.debug("Index [" + indexSpec.getIndexId() + "] removing old entry ["
+ Bytes.toString(oldIndexRow) + "]");
return new Delete(oldIndexRow);
}
private NavigableMap<byte[], byte[]> getColumnsFromPut(final Put put) {
NavigableMap<byte[], byte[]> columnValues = new TreeMap<byte[], byte[]>(
Bytes.BYTES_COMPARATOR);
for (List<KeyValue> familyPuts : put.getFamilyMap().values()) {
for (KeyValue kv : familyPuts) {
columnValues.put(
KeyValue.makeColumn(kv.getFamily(), kv.getQualifier()),
kv.getValue());
}
}
return columnValues;
}
/**
* Ask if this put *could* apply to the index. It may actually apply if some
* of the columns needed are missing.
*
* @param indexSpec
* @param put
* @return true if possibly apply.
*/
private boolean possiblyAppliesToIndex(final IndexSpecification indexSpec,
final Put put) {
for (List<KeyValue> familyPuts : put.getFamilyMap().values()) {
for (KeyValue kv : familyPuts) {
if (indexSpec.containsColumn(KeyValue.makeColumn(kv.getFamily(),
kv.getQualifier()))) {
return true;
}
}
}
return false;
}
private Put makeIndexUpdate(final IndexSpecification indexSpec,
final byte[] row, final SortedMap<byte[], byte[]> columnValues)
throws IOException {
Put indexUpdate = IndexMaintenanceUtils.createIndexUpdate(indexSpec, row,
columnValues);
LOG.debug("Index [" + indexSpec.getIndexId() + "] adding new entry ["
+ Bytes.toString(indexUpdate.getRow()) + "] for row ["
+ Bytes.toString(row) + "]");
return indexUpdate;
}
// FIXME we can be smarter about this and avoid the base gets and index
// maintenance in many cases.
@Override
public void delete(final Delete delete, final Integer lockid,
final boolean writeToWAL) throws IOException {
// First look at the current (to be the old) state.
SortedMap<byte[], byte[]> oldColumnValues = null;
if (!getIndexes().isEmpty()) {
// Need all columns
NavigableSet<byte[]> neededColumns = getColumnsForIndexes(getIndexes());
Get get = new Get(delete.getRow());
for (byte[] col : neededColumns) {
// get.addColumn(col);
GetUtil.addColumn(get, col);
}
Result oldRow = super.get(get, lockid);
oldColumnValues = convertToValueMap(oldRow);
}
super.delete(delete, lockid, writeToWAL);
if (!getIndexes().isEmpty()) {
Get get = new Get(delete.getRow());
// Rebuild index if there is still a version visible.
Result currentRow = super.get(get, lockid);
SortedMap<byte[], byte[]> currentColumnValues = convertToValueMap(currentRow);
for (IndexSpecification indexSpec : getIndexes()) {
Delete indexDelete = null;
if (IndexMaintenanceUtils.doesApplyToIndex(indexSpec, oldColumnValues)) {
indexDelete = makeDeleteToRemoveOldIndexEntry(indexSpec,
delete.getRow(), oldColumnValues);
}
Put indexPut = null;
if (IndexMaintenanceUtils.doesApplyToIndex(indexSpec,
currentColumnValues)) {
indexPut = makeIndexUpdate(indexSpec, delete.getRow(),
currentColumnValues);
}
if (indexPut == null && indexDelete == null) {
continue;
}
HTableInterface indexTable = getIndexTable(indexSpec);
try {
if (indexDelete != null
&& (indexPut == null || !Bytes.equals(indexDelete.getRow(),
indexPut.getRow()))) {
// Only do the delete if the row changed. This way we save the put
// after delete issues in HBASE-2256
LOG.debug("Deleting old index row ["
+ Bytes.toString(indexDelete.getRow()) + "].");
indexTable.delete(indexDelete);
} else if (indexDelete != null) {
LOG.debug("Skipping deleting index row ["
+ Bytes.toString(indexDelete.getRow())
+ "] because it has not changed.");
for (byte[] indexCol : indexSpec.getAdditionalColumns()) {
byte[][] parsed = KeyValue.parseColumn(indexCol);
List<KeyValue> famDeletes = delete.getFamilyMap().get(parsed[0]);
if (famDeletes != null) {
for (KeyValue kv : famDeletes) {
if (Bytes.equals(indexCol,
KeyValue.makeColumn(kv.getFamily(), kv.getQualifier()))) {
LOG.debug("Need to delete this specific column: "
+ Bytes.toString(KeyValue.makeColumn(kv.getFamily(),
kv.getQualifier())));
Delete columnDelete = new Delete(indexDelete.getRow());
columnDelete.deleteColumns(kv.getFamily(),
kv.getQualifier());
indexTable.delete(columnDelete);
}
}
}
}
}
if (indexPut != null) {
indexTable.put(indexPut);
}
} finally {
putTable(indexTable);
}
}
}
}
private SortedMap<byte[], byte[]> convertToValueMap(final Result result) {
SortedMap<byte[], byte[]> currentColumnValues = new TreeMap<byte[], byte[]>(
Bytes.BYTES_COMPARATOR);
if (result == null || result.raw() == null) {
return currentColumnValues;
}
List<KeyValue> list = result.list();
if (list != null) {
for (KeyValue kv : result.list()) {
currentColumnValues.put(
KeyValue.makeColumn(kv.getFamily(), kv.getQualifier()),
kv.getValue());
}
}
return currentColumnValues;
}
}