diff --git hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java
index b876fa7..1973997 100644
--- hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java
+++ hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java
@@ -103,7 +103,8 @@ public class Scan extends OperationWithAttributes {
// define this attribute with the appropriate table name by calling
// scan.setAttribute(Scan.SCAN_ATTRIBUTES_TABLE_NAME, Bytes.toBytes(tableName))
static public final String SCAN_ATTRIBUTES_TABLE_NAME = "scan.attributes.table.name";
-
+ /** Scan Hints */
+ static public final String HINT_NARROW_ROWS = "_hint_narrow_rows_";
/*
* -1 means no caching
*/
diff --git hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ExplicitScanReplacementFilter.java hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ExplicitScanReplacementFilter.java
index e69de29..40b79aa 100644
--- hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ExplicitScanReplacementFilter.java
+++ hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ExplicitScanReplacementFilter.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright 2013 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;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NavigableSet;
+
+import org.apache.hadoop.hbase.Cell;
+import org.apache.hadoop.hbase.filter.FilterBase;
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ *
+ * This filter is server-side filter and is not meant to be used in a HBase
+ * client application. It is used in a StoreScanner to replace explicit list of
+ * columns in a Scan instance when rows to be scanned in a table are
+ * small/medium size.
+ *
+ *
+ * See HBASE-9769 for more information. This filter is not a generic and this
+ * explains its naming convention - it is the replacement of an explicit column
+ * qualifiers list in a StoreScanner scan operation.
+ *
+ *
+ * It keeps list of column qualifiers hashed to a fixed bucket array (similar to
+ * HashMap) to improve performance with a large column qualifier lists.
+ *
+ */
+public class ExplicitScanReplacementFilter extends FilterBase {
+
+ /** Total number of buckets in a bucket array. 256 is OK. Its a good number. */
+ final int buckets = 256;
+ /** Array of List of size 'buckets'. */
+ Object[] hashedColumnArray = new Object[buckets];
+
+ /**
+ * Constructor of a filter.
+ * @param columns - list of column qualifiers
+ */
+ public ExplicitScanReplacementFilter(NavigableSet columns) {
+ buildColumnFamilies(columns);
+ }
+
+ /**
+ * Map list of a given column qualifiers into bucket array.
+ * @param cols - list of column qualifiers.
+ */
+ private void buildColumnFamilies(NavigableSet cols) {
+ if (cols != null) {
+ for (byte[] col : cols) {
+ hashedColumnAdd(col);
+ }
+ }
+ }
+
+ /**
+ * Add column qualifier to a bucket array.
+ * @param col - column qualifier
+ */
+ @SuppressWarnings("unchecked")
+ private void hashedColumnAdd(byte[] col) {
+
+ long hash = hashCode(col, 0, col.length);
+ int index = (int) (Math.abs(hash) & 0xff);
+
+ List list = (List) hashedColumnArray[index];
+ if (list == null) {
+ list = new ArrayList();
+ hashedColumnArray[index] = list;
+ }
+ list.add(col);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public ReturnCode filterKeyValue(Cell v) {
+
+ byte[] buf = v.getQualifierArray();
+ int colOffset = v.getQualifierOffset();
+ int colLen = v.getQualifierLength();
+
+ long hash = hashCode(buf, colOffset, colLen);
+ int index = (int) (Math.abs(hash) & 0xff);
+ List list = (List) hashedColumnArray[index];
+ if (list != null) {
+ for (byte[] col : list) {
+ if (Bytes.equals(col, 0, col.length, buf, colOffset, colLen)) {
+ return ReturnCode.INCLUDE;
+ }
+ }
+ }
+ return ReturnCode.SKIP;
+ }
+
+
+
+ /**
+ * Calculates hash function of a given byte sub-array.
+ * @param qualifierBuffer - byte buffer
+ * @param qualifierOff - qualifier offset
+ * @param qualifierLen - qualifier length
+ * @return hash code of a sub-array.
+ */
+ private final long hashCode(final byte[] buffer, final int offset,
+ final int length) {
+
+ long h = 0;
+ int off = offset;
+ for (int i = 0; i < length; i++) {
+ h = 31 * h + buffer[off++];
+ }
+ return h;
+ }
+}
diff --git hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java
index 6e7f936..5815b62 100644
--- hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java
+++ hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java
@@ -55,6 +55,9 @@ import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.RemoteExceptionHandler;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.filter.Filter;
+import org.apache.hadoop.hbase.filter.FilterList;
+import org.apache.hadoop.hbase.filter.FilterList.Operator;
import org.apache.hadoop.hbase.io.compress.Compression;
import org.apache.hadoop.hbase.io.hfile.CacheConfig;
import org.apache.hadoop.hbase.io.hfile.HFile;
@@ -1648,15 +1651,18 @@ public class HStore implements Store {
@Override
public KeyValueScanner getScanner(Scan scan,
- final NavigableSet targetCols) throws IOException {
+ final NavigableSet targetCols) throws IOException {
lock.readLock().lock();
try {
KeyValueScanner scanner = null;
- if (this.getCoprocessorHost() != null) {
- scanner = this.getCoprocessorHost().preStoreScannerOpen(this, scan, targetCols);
+ boolean useFilter = doesUseExplicitColumnFilter(scan);
+ if (getHRegion().getCoprocessorHost() != null) {
+ scanner = getHRegion().getCoprocessorHost().preStoreScannerOpen(this,
+ scan, useFilter ? null : targetCols);
}
if (scanner == null) {
- scanner = new StoreScanner(this, getScanInfo(), scan, targetCols);
+ scanner = new StoreScanner(this, getScanInfo(), scan, useFilter ? null
+ : targetCols);
}
return scanner;
} finally {
@@ -1664,6 +1670,37 @@ public class HStore implements Store {
}
}
+ /**
+ * Checks if we need replace explicit qualifier list in a scan object for thsi
+ * column family with a filter.
+ *
+ * @param scan
+ * @return true - if yes, false - otherwise
+ */
+ private boolean doesUseExplicitColumnFilter(Scan scan) {
+ byte[] narrowRows = scan.getAttribute(Scan.HINT_NARROW_ROWS);
+ if (narrowRows != null && new String(narrowRows).equalsIgnoreCase("true")) {
+ NavigableSet cols = scan.getFamilyMap().get(getFamily().getName());
+ if (cols != null && (cols.size() > 1 || cols.first() != null)) {
+ ExplicitScanReplacementFilter colFilter = new ExplicitScanReplacementFilter(
+ cols);
+ Filter f = scan.getFilter();
+ if (f != null) {
+ FilterList fl = new FilterList(Operator.MUST_PASS_ALL, colFilter, f);
+ scan.setFilter(fl);
+ } else {
+ scan.setFilter(colFilter);
+ }
+ // Update scan's family map and removes entry fo this columnFamily. Q:
+ // is it safe?
+ scan.getFamilyMap().remove(getFamily().getName());
+ return true;
+ }
+ }
+ return false;
+ }
+
+
@Override
public String toString() {
return this.getColumnFamilyName();