001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.filter; 019 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.List; 024import org.apache.commons.codec.binary.Hex; 025import org.apache.hadoop.hbase.HBaseClassTestRule; 026import org.apache.hadoop.hbase.TableName; 027import org.apache.hadoop.hbase.client.Put; 028import org.apache.hadoop.hbase.client.Result; 029import org.apache.hadoop.hbase.client.ResultScanner; 030import org.apache.hadoop.hbase.client.Scan; 031import org.apache.hadoop.hbase.client.Table; 032import org.apache.hadoop.hbase.testclassification.FilterTests; 033import org.apache.hadoop.hbase.testclassification.MediumTests; 034import org.apache.hadoop.hbase.util.Bytes; 035import org.junit.Assert; 036import org.junit.ClassRule; 037import org.junit.Rule; 038import org.junit.Test; 039import org.junit.experimental.categories.Category; 040import org.junit.rules.TestName; 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044/** 045 * Test if Scan.setStartStopRowForPrefixScan works as intended. 046 */ 047@Category({ FilterTests.class, MediumTests.class }) 048public class TestScanRowPrefix extends FilterTestingCluster { 049 050 @ClassRule 051 public static final HBaseClassTestRule CLASS_RULE = 052 HBaseClassTestRule.forClass(TestScanRowPrefix.class); 053 054 private static final Logger LOG = LoggerFactory.getLogger(TestScanRowPrefix.class); 055 056 @Rule 057 public TestName name = new TestName(); 058 059 @Test 060 public void testPrefixScanning() throws IOException { 061 final TableName tableName = TableName.valueOf(name.getMethodName()); 062 createTable(tableName, "F"); 063 Table table = openTable(tableName); 064 065 /** 066 * Note that about half of these tests were relevant for a different implementation approach of 067 * setStartStopRowForPrefixScan. These test cases have been retained to ensure that also the 068 * edge cases found there are still covered. 069 */ 070 071 final byte[][] rowIds = { { (byte) 0x11 }, // 0 072 { (byte) 0x12 }, // 1 073 { (byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFE }, // 2 074 { (byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFF }, // 3 075 { (byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFF, (byte) 0x00 }, // 4 076 { (byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFF, (byte) 0x01 }, // 5 077 { (byte) 0x12, (byte) 0x24 }, // 6 078 { (byte) 0x12, (byte) 0x24, (byte) 0x00 }, // 7 079 { (byte) 0x12, (byte) 0x24, (byte) 0x00, (byte) 0x00 }, // 8 080 { (byte) 0x12, (byte) 0x25 }, // 9 081 { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }, // 10 082 }; 083 for (byte[] rowId : rowIds) { 084 Put p = new Put(rowId); 085 // Use the rowId as the column qualifier 086 p.addColumn(Bytes.toBytes("F"), rowId, Bytes.toBytes("Dummy value")); 087 table.put(p); 088 } 089 090 byte[] prefix0 = {}; 091 List<byte[]> expected0 = new ArrayList<>(16); 092 expected0.addAll(Arrays.asList(rowIds)); // Expect all rows 093 094 byte[] prefix1 = { (byte) 0x12, (byte) 0x23 }; 095 List<byte[]> expected1 = new ArrayList<>(16); 096 expected1.add(rowIds[2]); 097 expected1.add(rowIds[3]); 098 expected1.add(rowIds[4]); 099 expected1.add(rowIds[5]); 100 101 byte[] prefix2 = { (byte) 0x12, (byte) 0x23, (byte) 0xFF, (byte) 0xFF }; 102 List<byte[]> expected2 = new ArrayList<>(); 103 expected2.add(rowIds[3]); 104 expected2.add(rowIds[4]); 105 expected2.add(rowIds[5]); 106 107 byte[] prefix3 = { (byte) 0x12, (byte) 0x24 }; 108 List<byte[]> expected3 = new ArrayList<>(); 109 expected3.add(rowIds[6]); 110 expected3.add(rowIds[7]); 111 expected3.add(rowIds[8]); 112 113 byte[] prefix4 = { (byte) 0xFF, (byte) 0xFF }; 114 List<byte[]> expected4 = new ArrayList<>(); 115 expected4.add(rowIds[10]); 116 117 // ======== 118 // PREFIX 0 119 Scan scan = new Scan(); 120 scan.setStartStopRowForPrefixScan(prefix0); 121 verifyScanResult(table, scan, expected0, "Scan empty prefix failed"); 122 123 // ======== 124 // PREFIX 1 125 scan = new Scan(); 126 scan.setStartStopRowForPrefixScan(prefix1); 127 verifyScanResult(table, scan, expected1, "Scan normal prefix failed"); 128 129 scan.setStartStopRowForPrefixScan(null); 130 verifyScanResult(table, scan, expected0, "Scan after prefix reset failed"); 131 132 scan = new Scan(); 133 scan.setFilter(new ColumnPrefixFilter(prefix1)); 134 verifyScanResult(table, scan, expected1, "Double check on column prefix failed"); 135 136 // ======== 137 // PREFIX 2 138 scan = new Scan(); 139 scan.setStartStopRowForPrefixScan(prefix2); 140 verifyScanResult(table, scan, expected2, "Scan edge 0xFF prefix failed"); 141 142 scan.setStartStopRowForPrefixScan(null); 143 verifyScanResult(table, scan, expected0, "Scan after prefix reset failed"); 144 145 scan = new Scan(); 146 scan.setFilter(new ColumnPrefixFilter(prefix2)); 147 verifyScanResult(table, scan, expected2, "Double check on column prefix failed"); 148 149 // ======== 150 // PREFIX 3 151 scan = new Scan(); 152 scan.setStartStopRowForPrefixScan(prefix3); 153 verifyScanResult(table, scan, expected3, "Scan normal with 0x00 ends failed"); 154 155 scan.setStartStopRowForPrefixScan(null); 156 verifyScanResult(table, scan, expected0, "Scan after prefix reset failed"); 157 158 scan = new Scan(); 159 scan.setFilter(new ColumnPrefixFilter(prefix3)); 160 verifyScanResult(table, scan, expected3, "Double check on column prefix failed"); 161 162 // ======== 163 // PREFIX 4 164 scan = new Scan(); 165 scan.setStartStopRowForPrefixScan(prefix4); 166 verifyScanResult(table, scan, expected4, "Scan end prefix failed"); 167 168 scan.setStartStopRowForPrefixScan(null); 169 verifyScanResult(table, scan, expected0, "Scan after prefix reset failed"); 170 171 scan = new Scan(); 172 scan.setFilter(new ColumnPrefixFilter(prefix4)); 173 verifyScanResult(table, scan, expected4, "Double check on column prefix failed"); 174 175 // ======== 176 // COMBINED 177 // Prefix + Filter 178 scan = new Scan(); 179 scan.setStartStopRowForPrefixScan(prefix1); 180 verifyScanResult(table, scan, expected1, "Prefix filter failed"); 181 182 scan.setFilter(new ColumnPrefixFilter(prefix2)); 183 verifyScanResult(table, scan, expected2, "Combined Prefix + Filter failed"); 184 185 scan.setStartStopRowForPrefixScan(null); 186 verifyScanResult(table, scan, expected2, "Combined Prefix + Filter; removing Prefix failed"); 187 188 scan.setFilter(null); 189 verifyScanResult(table, scan, expected0, "Scan after Filter reset failed"); 190 191 // ======== 192 // Reversed: Filter + Prefix 193 scan = new Scan(); 194 scan.setFilter(new ColumnPrefixFilter(prefix2)); 195 verifyScanResult(table, scan, expected2, "Test filter failed"); 196 197 scan.setStartStopRowForPrefixScan(prefix1); 198 verifyScanResult(table, scan, expected2, "Combined Filter + Prefix failed"); 199 200 scan.setFilter(null); 201 verifyScanResult(table, scan, expected1, "Combined Filter + Prefix ; removing Filter failed"); 202 203 scan.setStartStopRowForPrefixScan(null); 204 verifyScanResult(table, scan, expected0, "Scan after prefix reset failed"); 205 } 206 207 private void verifyScanResult(Table table, Scan scan, List<byte[]> expectedKeys, String message) { 208 List<byte[]> actualKeys = new ArrayList<>(); 209 try { 210 ResultScanner scanner = table.getScanner(scan); 211 for (Result result : scanner) { 212 actualKeys.add(result.getRow()); 213 } 214 215 String fullMessage = message; 216 if (LOG.isDebugEnabled()) { 217 fullMessage = message + "\n" 218 + tableOfTwoListsOfByteArrays("Expected", expectedKeys, "Actual ", actualKeys); 219 } 220 221 Assert.assertArrayEquals(fullMessage, expectedKeys.toArray(), actualKeys.toArray()); 222 } catch (IOException e) { 223 e.printStackTrace(); 224 Assert.fail(); 225 } 226 } 227 228 private String printMultiple(char letter, int count) { 229 StringBuilder sb = new StringBuilder(count); 230 for (int i = 0; i < count; i++) { 231 sb.append(letter); 232 } 233 return sb.toString(); 234 } 235 236 private String tableOfTwoListsOfByteArrays(String label1, List<byte[]> listOfBytes1, 237 String label2, List<byte[]> listOfBytes2) { 238 int margin1 = calculateWidth(label1, listOfBytes1); 239 int margin2 = calculateWidth(label2, listOfBytes2); 240 241 StringBuilder sb = new StringBuilder(512); 242 String separator = '+' + printMultiple('-', margin1 + margin2 + 5) + '+' + '\n'; 243 sb.append(separator); 244 sb.append(printLine(label1, margin1, label2, margin2)).append('\n'); 245 sb.append(separator); 246 int maxLength = Math.max(listOfBytes1.size(), listOfBytes2.size()); 247 for (int offset = 0; offset < maxLength; offset++) { 248 String value1 = getStringFromList(listOfBytes1, offset); 249 String value2 = getStringFromList(listOfBytes2, offset); 250 sb.append(printLine(value1, margin1, value2, margin2)).append('\n'); 251 } 252 sb.append(separator).append('\n'); 253 return sb.toString(); 254 } 255 256 private String printLine(String leftValue, int leftWidth1, String rightValue, int rightWidth) { 257 return "| " + leftValue + printMultiple(' ', leftWidth1 - leftValue.length()) + " | " 258 + rightValue + printMultiple(' ', rightWidth - rightValue.length()) + " |"; 259 } 260 261 private int calculateWidth(String label1, List<byte[]> listOfBytes1) { 262 int longestList1 = label1.length(); 263 for (byte[] value : listOfBytes1) { 264 longestList1 = Math.max(value.length * 2, longestList1); 265 } 266 return longestList1 + 5; 267 } 268 269 private String getStringFromList(List<byte[]> listOfBytes, int offset) { 270 String value1; 271 if (listOfBytes.size() > offset) { 272 value1 = Hex.encodeHexString(listOfBytes.get(offset)); 273 } else { 274 value1 = "<missing>"; 275 } 276 return value1; 277 } 278 279}