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