Index: hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestGetEmptyRowKey.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestGetEmptyRowKey.java (revision 0) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestGetEmptyRowKey.java (revision 0) @@ -0,0 +1,90 @@ +/** + * + * 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 static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.MediumTests; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(MediumTests.class) +public class TestGetEmptyRowKey { + private final static HBaseTestingUtility UTIL = new HBaseTestingUtility(); + private final static byte[] FAMILY = Bytes.toBytes("f1"); + private final static byte[] COL_QUAL = Bytes.toBytes("f1"); + private final static byte[] VAL_BYTES = Bytes.toBytes("v1"); + private final static byte[] ROW_BYTES = Bytes.toBytes("r1"); + @BeforeClass + public static void beforeClass() throws Exception { + UTIL.startMiniCluster(); + } + + @AfterClass + public static void afterClass() throws Exception { + UTIL.shutdownMiniCluster(); + } + + @Test + public void test() throws Exception { + //Create a table and put in 1 row + HBaseAdmin admin = UTIL.getHBaseAdmin(); + HTableDescriptor desc = new HTableDescriptor(Bytes.toBytes("test")); + desc.addFamily(new HColumnDescriptor(FAMILY)); + admin.createTable(desc); + HTable table = new HTable(UTIL.getConfiguration(), "test"); + + Put put = new Put(ROW_BYTES); + put.add(FAMILY, COL_QUAL, VAL_BYTES); + table.put(put); + table.flushCommits(); + + //Try getting the row with an empty row key + Result res = table.get(new Get(new byte[0])); + assertTrue(res.isEmpty() == true); + res = table.get(new Get(Bytes.toBytes("r1-not-exist"))); + assertTrue(res.isEmpty() == true); + res = table.get(new Get(ROW_BYTES)); + assertTrue(Arrays.equals(res.getValue(FAMILY, COL_QUAL), VAL_BYTES)); + + //Now actually put in a row with an empty row key + put = new Put(new byte[0]); + put.add(FAMILY, COL_QUAL, VAL_BYTES); + table.put(put); + table.flushCommits(); + res = table.get(new Get(new byte[0])); + assertTrue(Arrays.equals(res.getValue(FAMILY, COL_QUAL), VAL_BYTES)); + + table.close(); + } +} Index: hbase-server/src/main/java/org/apache/hadoop/hbase/client/Scan.java =================================================================== --- hbase-server/src/main/java/org/apache/hadoop/hbase/client/Scan.java (revision 1435435) +++ hbase-server/src/main/java/org/apache/hadoop/hbase/client/Scan.java (working copy) @@ -92,6 +92,7 @@ private int storeLimit = -1; private int storeOffset = 0; + private boolean scanCreatedFromGet; // If application wants to collect scan metrics, it needs to // call scan.setAttribute(SCAN_ATTRIBUTES_ENABLE, Bytes.toBytes(Boolean.TRUE)) @@ -194,6 +195,7 @@ this.storeOffset = get.getRowOffsetPerColumnFamily(); this.tr = get.getTimeRange(); this.familyMap = get.getFamilyMap(); + this.scanCreatedFromGet = true; } public boolean isGetScan() { @@ -201,6 +203,10 @@ Bytes.equals(this.startRow, this.stopRow); } + public boolean isScanCreatedFromGet() { + return scanCreatedFromGet; + } + /** * Get all columns from the specified family. *

Index: hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java =================================================================== --- hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java (revision 1435435) +++ hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java (working copy) @@ -3434,12 +3434,14 @@ // KeyValue indicating that limit is reached when scanning private final KeyValue KV_LIMIT = new KeyValue(); private final byte [] stopRow; + private final byte [] stopRowFromScan; private Filter filter; private int batch; private int isScan; private boolean filterClosed = false; private long readPt; private long maxResultSize; + private boolean scanCreatedFromGet; public HRegionInfo getRegionInfo() { return regionInfo; @@ -3455,7 +3457,10 @@ } this.batch = scan.getBatch(); - if (Bytes.equals(scan.getStopRow(), HConstants.EMPTY_END_ROW)) { + this.stopRowFromScan = scan.getStopRow(); + this.scanCreatedFromGet = scan.isScanCreatedFromGet(); + if (Bytes.equals(scan.getStopRow(), HConstants.EMPTY_END_ROW) && + !this.scanCreatedFromGet) { this.stopRow = null; } else { this.stopRow = scan.getStopRow(); @@ -3670,7 +3675,7 @@ offset = current.getRowOffset(); length = current.getRowLength(); } - boolean stopRow = isStopRow(currentRow, offset, length); + boolean stopRow = isStopRowConsideringEmptyRows(currentRow, offset, length); // Check if we were getting data from the joinedHeap and hit the limit. // If not, then it's main path - getting results from storeHeap. if (joinedContinuationRow == null) { @@ -3760,6 +3765,34 @@ } } + /** + * + * @param currentRow + * @param offset + * @param length + * @return whether we should stop (scanning) + */ + private boolean isStopRowConsideringEmptyRows(byte[] currentRow, int offset, short length) { + if (isStopRow(currentRow, offset, length)) { + //for the case where the get-scan has an empty stop row key and an empty start + //row key, we will not want to stop yet since we want to return that row as result + if (this.scanCreatedFromGet && this.stopRowFromScan != null && currentRow != null && + (Bytes.equals(currentRow, offset, length, HConstants.EMPTY_START_ROW, 0, 0) && + Bytes.equals(this.stopRowFromScan, 0, this.stopRowFromScan.length, + HConstants.EMPTY_END_ROW, 0, 0))) { + return false; + } + return true; + } + if (scanCreatedFromGet && stopRowFromScan != null && + comparator.compareRows(stopRowFromScan, 0, stopRowFromScan.length, + currentRow, offset, length) <= -1) { + return true; + + } + return false; + } + private boolean filterRowKey(byte[] row, int offset, short length) { return filter != null && filter.filterRowKey(row, offset, length);