diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java index afc1117cfd2..ae56dc9dac1 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java @@ -21,6 +21,7 @@ package org.apache.hadoop.hbase.client; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -298,7 +299,7 @@ public class Scan extends Query { * Get versions of columns only within the specified timestamp range, * [minStamp, maxStamp). Note, default maximum versions to return is 1. If * your time range spans more than one version and you want all versions - * returned, up the number of versions beyond the defaut. + * returned, up the number of versions beyond the default. * @param minStamp minimum timestamp value, inclusive * @param maxStamp maximum timestamp value, exclusive * @throws IOException if invalid time range @@ -348,7 +349,10 @@ public class Scan extends Query { /** * Set the stop row. * @param stopRow row to end at (exclusive) - * Note: In order to make stopRow inclusive add a trailing 0 byte + *

Note: In order to make stopRow inclusive add a trailing 0 byte

+ *

Note: When doing a filter for a rowKey Prefix + * use {@link #setRowPrefixFilter(byte[])}. + * The 'trailing 0' will not yield the desired result.

* @return this */ public Scan setStopRow(byte [] stopRow) { @@ -356,6 +360,70 @@ public class Scan extends Query { return this; } + /** + *

Set a filter (using stopRow and startRow) so the result set only contains rows where the + * rowKey starts with the specified prefix.

+ *

This is a utility method that converts the desired rowPrefix into the appropriate values + * for the startRow and stopRow to achieve the desired result.

+ *

This can safely be used in combination with setFilter.

+ *

NOTE: Doing a {@link #setStartRow(byte[])} and/or {@link #setStopRow(byte[])} + * after this method will yield undefined results.

+ * @param rowPrefix the prefix all rows must start with. (Set null to remove the filter.) + * @return this + */ + public Scan setRowPrefixFilter(byte[] rowPrefix) { + if (rowPrefix == null) { + setStartRow(HConstants.EMPTY_START_ROW); + setStopRow(HConstants.EMPTY_END_ROW); + } else { + this.setStartRow(rowPrefix); + this.setStopRow(calculateTheClosestNextRowKeyForPrefix(rowPrefix)); + } + return this; + } + + /** + *

When scanning for a prefix the scan should stop immediately after the the last row that + * has the specified prefix. This method calculates the closest next rowKey immediately following + * the given rowKeyPrefix.

+ *

IMPORTANT: This converts a rowKeyPrefix into a rowKey.

+ *

If the prefix is an 'ASCII' string put into a byte[] then this is easy because you can + * simply increment the last byte of the array. + * But if your application uses real binary rowids you may run into the scenario that your + * prefix is something like:

+ *    { 0x12, 0x23, 0xFF, 0xFF }
+ * Then this stopRow needs to be fed into the actual scan
+ *    { 0x12, 0x24 } (Notice that it is shorter now)
+ * This method calculates the correct stop row value for this usecase. + * + * @param rowKeyPrefix the rowKeyPrefix. + * @return the closest next rowKey immediately following the given rowKeyPrefix. + */ + private byte[] calculateTheClosestNextRowKeyForPrefix(byte[] rowKeyPrefix) { + // Essentially we are treating it like an 'unsigned very very long' and doing +1 manually. + // Search for the place where the trailing 0xFFs start + int offset = rowKeyPrefix.length; + while (offset > 0) { + if (rowKeyPrefix[offset - 1] != (byte) 0xFF) { + break; + } + offset--; + } + + if (offset == 0) { + // We got an 0xFFFF... (only FFs) stopRow value which is + // the last possible prefix before the end of the table. + // So set it to stop at the 'end of the table' + return HConstants.EMPTY_END_ROW; + } + + // Copy the right length of the original + byte[] newStopRow = Arrays.copyOfRange(rowKeyPrefix, 0, offset); + // And increment the last one + newStopRow[newStopRow.length - 1]++; + return newStopRow; + } + /** * Get all available versions. * @return this diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/FilterTestingCluster.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/FilterTestingCluster.java new file mode 100644 index 00000000000..7e04eb78f87 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/FilterTestingCluster.java @@ -0,0 +1,129 @@ +/* + * Copyright 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.filter; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.impl.Log4JLogger; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.MasterNotRunningException; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.ZooKeeperConnectionException; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.ScannerCallable; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.ipc.RpcClient; +import org.apache.hadoop.hbase.ipc.RpcServer; +import org.apache.hadoop.hbase.MediumTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.log4j.Level; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.experimental.categories.Category; + +/** + * By using this class as the super class of a set of tests you will have a HBase testing + * cluster available that is very suitable for writing tests for scanning and filtering against. + */ +@Category({MediumTests.class}) +public class FilterTestingCluster { + private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); + private static Configuration conf = null; + private static HBaseAdmin admin = null; + private static List createdTables = new ArrayList<>(); + + protected static void createTable(String tableName, String columnFamilyName) { + assertNotNull("HBaseAdmin is not initialized successfully.", admin); + HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName)); + HColumnDescriptor colDef = new HColumnDescriptor(Bytes.toBytes(columnFamilyName)); + desc.addFamily(colDef); + + try { + admin.createTable(desc); + createdTables.add(tableName); + assertTrue("Fail to create the table", admin.tableExists(tableName)); + } catch (IOException e) { + assertNull("Exception found while creating table", e); + } + } + + protected static Table openTable(String tableName) throws IOException { + Table table = new HTable(conf, tableName); + assertTrue("Fail to create the table", admin.tableExists(tableName)); + return table; + } + + private static void deleteTables() { + if (admin != null) { + for (String tableName: createdTables){ + try { + if (admin.tableExists(tableName)) { + admin.disableTable(tableName); + admin.deleteTable(tableName); + } + } catch (IOException e) { + assertNull("Exception found deleting the table", e); + } + } + } + } + + private static void initialize(Configuration conf) { + FilterTestingCluster.conf = HBaseConfiguration.create(conf); + FilterTestingCluster.conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 1); + try { + admin = new HBaseAdmin(conf); + } catch (MasterNotRunningException e) { + assertNull("Master is not running", e); + } catch (ZooKeeperConnectionException e) { + assertNull("Cannot connect to Zookeeper", e); + } catch (IOException e) { + assertNull("IOException", e); + } + } + + @BeforeClass + public static void setUp() throws Exception { + ((Log4JLogger)RpcServer.LOG).getLogger().setLevel(Level.ALL); + ((Log4JLogger)RpcClient.LOG).getLogger().setLevel(Level.ALL); + ((Log4JLogger)ScannerCallable.LOG).getLogger().setLevel(Level.ALL); + TEST_UTIL.startMiniCluster(1); + initialize(TEST_UTIL.getConfiguration()); + } + + @AfterClass + public static void tearDown() throws Exception { + deleteTables(); + TEST_UTIL.shutdownMiniCluster(); + } + +} diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilterWithScanLimits.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilterWithScanLimits.java index 567bd694a85..97216e6823a 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilterWithScanLimits.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFilterWithScanLimits.java @@ -22,7 +22,6 @@ package org.apache.hadoop.hbase.filter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.ArrayList; @@ -30,8 +29,6 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.commons.logging.impl.Log4JLogger; -import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HBaseTestingUtility; @@ -48,13 +45,11 @@ import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Scan; -import org.apache.hadoop.hbase.client.ScannerCallable; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.ipc.RpcClient; import org.apache.hadoop.hbase.ipc.RpcServer; +import org.apache.hadoop.hbase.MediumTests; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.log4j.Level; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -63,30 +58,27 @@ import org.junit.experimental.categories.Category; * Test if Filter is incompatible with scan-limits */ @Category(MediumTests.class) -public class TestFilterWithScanLimits { +public class TestFilterWithScanLimits extends FilterTestingCluster { private static final Log LOG = LogFactory .getLog(TestFilterWithScanLimits.class); - private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); - private static Configuration conf = null; - private static HBaseAdmin admin = null; - private static TableName name = TableName.valueOf("test"); + private static final String tableName = "scanWithLimit"; + private static final String columnFamily = "f1"; @Test public void testScanWithLimit() { int kv_number = 0; try { Scan scan = new Scan(); - // set batch number as 2, which means each Result should contain 2 KVs at - // most + // set batch number as 2, which means each Result should contain 2 KVs at most scan.setBatch(2); SingleColumnValueFilter filter = new SingleColumnValueFilter( - Bytes.toBytes("f1"), Bytes.toBytes("c5"), + Bytes.toBytes(columnFamily), Bytes.toBytes("c5"), CompareFilter.CompareOp.EQUAL, new SubstringComparator("2_c5")); // add filter after batch defined scan.setFilter(filter); - Table table = new HTable(conf, name); + Table table = openTable(tableName); ResultScanner scanner = table.getScanner(scan); // Expect to get following row // row2 => , , @@ -110,10 +102,11 @@ public class TestFilterWithScanLimits { assertEquals("We should not get result(s) returned.", 0, kv_number); } - private static void prepareData() { + @BeforeClass + public static void prepareData() { try { - Table table = new HTable(TestFilterWithScanLimits.conf, name); - assertTrue("Fail to create the table", admin.tableExists(name)); + createTable(tableName, columnFamily); + Table table = openTable(tableName); List puts = new ArrayList(); // row1 => , , , , @@ -136,64 +129,4 @@ public class TestFilterWithScanLimits { } } - private static void createTable() { - assertNotNull("HBaseAdmin is not initialized successfully.", admin); - if (admin != null) { - - HTableDescriptor desc = new HTableDescriptor(name); - HColumnDescriptor coldef = new HColumnDescriptor(Bytes.toBytes("f1")); - desc.addFamily(coldef); - - try { - admin.createTable(desc); - assertTrue("Fail to create the table", admin.tableExists(name)); - } catch (IOException e) { - assertNull("Exception found while creating table", e); - } - - } - } - - private static void deleteTable() { - if (admin != null) { - try { - admin.disableTable(name); - admin.deleteTable(name); - } catch (IOException e) { - assertNull("Exception found deleting the table", e); - } - } - } - - private static void initialize(Configuration conf) { - TestFilterWithScanLimits.conf = HBaseConfiguration.create(conf); - TestFilterWithScanLimits.conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 1); - try { - admin = new HBaseAdmin(conf); - } catch (MasterNotRunningException e) { - assertNull("Master is not running", e); - } catch (ZooKeeperConnectionException e) { - assertNull("Cannot connect to Zookeeper", e); - } catch (IOException e) { - assertNull("IOException", e); - } - createTable(); - prepareData(); - } - - @BeforeClass - public static void setUp() throws Exception { - ((Log4JLogger)RpcServer.LOG).getLogger().setLevel(Level.ALL); - ((Log4JLogger)RpcClient.LOG).getLogger().setLevel(Level.ALL); - ((Log4JLogger)ScannerCallable.LOG).getLogger().setLevel(Level.ALL); - TEST_UTIL.startMiniCluster(1); - initialize(TEST_UTIL.getConfiguration()); - } - - @AfterClass - public static void tearDown() throws Exception { - deleteTable(); - TEST_UTIL.shutdownMiniCluster(); - } - } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestScanRowPrefix.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestScanRowPrefix.java new file mode 100644 index 00000000000..ef4d897e548 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestScanRowPrefix.java @@ -0,0 +1,277 @@ +/* + * Copyright 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.filter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.codec.binary.Hex; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.MediumTests; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Test if Scan.setRowPrefixFilter works as intended. + */ +@Category({MediumTests.class}) +public class TestScanRowPrefix extends FilterTestingCluster { + private static final Log LOG = LogFactory + .getLog(TestScanRowPrefix.class); + + @Test + public void testPrefixScanning() throws IOException { + String tableName = "prefixScanning"; + createTable(tableName,"F"); + Table table = openTable(tableName); + + /** + * Note that about half of these tests were relevant for an different implementation approach + * of setRowPrefixFilter. These test cases have been retained to ensure that also the + * edge cases found there are still covered. + */ + + final byte[][] rowIds = { + {(byte) 0x11}, // 0 + {(byte) 0x12}, // 1 + {(byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFE}, // 2 + {(byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFF}, // 3 + {(byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFF, (byte) 0x00}, // 4 + {(byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFF, (byte) 0x01}, // 5 + {(byte) 0x12, (byte) 0x24}, // 6 + {(byte) 0x12, (byte) 0x24, (byte) 0x00}, // 7 + {(byte) 0x12, (byte) 0x24, (byte) 0x00, (byte) 0x00}, // 8 + {(byte) 0x12, (byte) 0x25}, // 9 + {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}, // 10 + }; + for (byte[] rowId: rowIds) { + Put p = new Put(rowId); + // Use the rowId as the column qualifier + p.add("F".getBytes(), rowId, "Dummy value".getBytes()); + table.put(p); + } + + byte[] prefix0 = {}; + List expected0 = new ArrayList<>(16); + expected0.addAll(Arrays.asList(rowIds)); // Expect all rows + + byte[] prefix1 = {(byte) 0x12, (byte) 0x23}; + List expected1 = new ArrayList<>(16); + expected1.add(rowIds[2]); + expected1.add(rowIds[3]); + expected1.add(rowIds[4]); + expected1.add(rowIds[5]); + + byte[] prefix2 = {(byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFF}; + List expected2 = new ArrayList<>(); + expected2.add(rowIds[3]); + expected2.add(rowIds[4]); + expected2.add(rowIds[5]); + + byte[] prefix3 = {(byte) 0x12, (byte) 0x24}; + List expected3 = new ArrayList<>(); + expected3.add(rowIds[6]); + expected3.add(rowIds[7]); + expected3.add(rowIds[8]); + + byte[] prefix4 = {(byte) 0xFF, (byte) 0xFF}; + List expected4 = new ArrayList<>(); + expected4.add(rowIds[10]); + + // ======== + // PREFIX 0 + Scan scan = new Scan(); + scan.setRowPrefixFilter(prefix0); + verifyScanResult(table, scan, expected0, "Scan empty prefix failed"); + + // ======== + // PREFIX 1 + scan = new Scan(); + scan.setRowPrefixFilter(prefix1); + verifyScanResult(table, scan, expected1, "Scan normal prefix failed"); + + scan.setRowPrefixFilter(null); + verifyScanResult(table, scan, expected0, "Scan after prefix reset failed"); + + scan = new Scan(); + scan.setFilter(new ColumnPrefixFilter(prefix1)); + verifyScanResult(table, scan, expected1, "Double check on column prefix failed"); + + // ======== + // PREFIX 2 + scan = new Scan(); + scan.setRowPrefixFilter(prefix2); + verifyScanResult(table, scan, expected2, "Scan edge 0xFF prefix failed"); + + scan.setRowPrefixFilter(null); + verifyScanResult(table, scan, expected0, "Scan after prefix reset failed"); + + scan = new Scan(); + scan.setFilter(new ColumnPrefixFilter(prefix2)); + verifyScanResult(table, scan, expected2, "Double check on column prefix failed"); + + // ======== + // PREFIX 3 + scan = new Scan(); + scan.setRowPrefixFilter(prefix3); + verifyScanResult(table, scan, expected3, "Scan normal with 0x00 ends failed"); + + scan.setRowPrefixFilter(null); + verifyScanResult(table, scan, expected0, "Scan after prefix reset failed"); + + scan = new Scan(); + scan.setFilter(new ColumnPrefixFilter(prefix3)); + verifyScanResult(table, scan, expected3, "Double check on column prefix failed"); + + // ======== + // PREFIX 4 + scan = new Scan(); + scan.setRowPrefixFilter(prefix4); + verifyScanResult(table, scan, expected4, "Scan end prefix failed"); + + scan.setRowPrefixFilter(null); + verifyScanResult(table, scan, expected0, "Scan after prefix reset failed"); + + scan = new Scan(); + scan.setFilter(new ColumnPrefixFilter(prefix4)); + verifyScanResult(table, scan, expected4, "Double check on column prefix failed"); + + // ======== + // COMBINED + // Prefix + Filter + scan = new Scan(); + scan.setRowPrefixFilter(prefix1); + verifyScanResult(table, scan, expected1, "Prefix filter failed"); + + scan.setFilter(new ColumnPrefixFilter(prefix2)); + verifyScanResult(table, scan, expected2, "Combined Prefix + Filter failed"); + + scan.setRowPrefixFilter(null); + verifyScanResult(table, scan, expected2, "Combined Prefix + Filter; removing Prefix failed"); + + scan.setFilter(null); + verifyScanResult(table, scan, expected0, "Scan after Filter reset failed"); + + // ======== + // Reversed: Filter + Prefix + scan = new Scan(); + scan.setFilter(new ColumnPrefixFilter(prefix2)); + verifyScanResult(table, scan, expected2, "Test filter failed"); + + scan.setRowPrefixFilter(prefix1); + verifyScanResult(table, scan, expected2, "Combined Filter + Prefix failed"); + + scan.setFilter(null); + verifyScanResult(table, scan, expected1, "Combined Filter + Prefix ; removing Filter failed"); + + scan.setRowPrefixFilter(null); + verifyScanResult(table, scan, expected0, "Scan after prefix reset failed"); + } + + private void verifyScanResult(Table table, Scan scan, List expectedKeys, String message) { + List actualKeys = new ArrayList<>(); + try { + ResultScanner scanner = table.getScanner(scan); + for (Result result : scanner) { + actualKeys.add(result.getRow()); + } + + String fullMessage = message; + if (LOG.isDebugEnabled()) { + fullMessage = message + "\n" + tableOfTwoListsOfByteArrays( + "Expected", expectedKeys, + "Actual ", actualKeys); + } + + Assert.assertArrayEquals( + fullMessage, + expectedKeys.toArray(), + actualKeys.toArray()); + } catch (IOException e) { + e.printStackTrace(); + Assert.fail(); + } + } + + private String printMultiple(char letter, int count) { + StringBuilder sb = new StringBuilder(count); + for (int i = 0; i < count; i++) { + sb.append(letter); + } + return sb.toString(); + } + + private String tableOfTwoListsOfByteArrays( + String label1, List listOfBytes1, + String label2, List listOfBytes2) { + int margin1 = calculateWidth(label1, listOfBytes1); + int margin2 = calculateWidth(label2, listOfBytes2); + + StringBuilder sb = new StringBuilder(512); + String separator = '+' + printMultiple('-', margin1 + margin2 + 5) + '+' + '\n'; + sb.append(separator); + sb.append(printLine(label1, margin1, label2, margin2)).append('\n'); + sb.append(separator); + int maxLength = Math.max(listOfBytes1.size(), listOfBytes2.size()); + for (int offset = 0; offset < maxLength; offset++) { + String value1 = getStringFromList(listOfBytes1, offset); + String value2 = getStringFromList(listOfBytes2, offset); + sb.append(printLine(value1, margin1, value2, margin2)).append('\n'); + } + sb.append(separator).append('\n'); + return sb.toString(); + } + + private String printLine(String leftValue, int leftWidth1, String rightValue, int rightWidth) { + return "| " + + leftValue + printMultiple(' ', leftWidth1 - leftValue.length() ) + + " | " + + rightValue + printMultiple(' ', rightWidth - rightValue.length()) + + " |"; + } + + private int calculateWidth(String label1, List listOfBytes1) { + int longestList1 = label1.length(); + for (byte[] value : listOfBytes1) { + longestList1 = Math.max(value.length * 2, longestList1); + } + return longestList1 + 5; + } + + private String getStringFromList(List listOfBytes, int offset) { + String value1; + if (listOfBytes.size() > offset) { + value1 = Hex.encodeHexString(listOfBytes.get(offset)); + } else { + value1 = ""; + } + return value1; + } + +} diff --git a/src/main/docbkx/book.xml b/src/main/docbkx/book.xml index fe0cd6c1ffc..181d0d9ab26 100644 --- a/src/main/docbkx/book.xml +++ b/src/main/docbkx/book.xml @@ -576,8 +576,7 @@ HTable htable = ... // instantiate HTable Scan scan = new Scan(); scan.addColumn(CF, ATTR); -scan.setStartRow(Bytes.toBytes("row")); // start key is inclusive -scan.setStopRow(Bytes.toBytes("rox")); // stop key is exclusive +scan.setRowPrefixFilter(Bytes.toBytes("row")); ResultScanner rs = htable.getScanner(scan); try { for (Result r = rs.next(); r != null; r = rs.next()) {