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.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertNull; 022 023import java.io.IOException; 024import java.util.Arrays; 025import org.apache.hadoop.hbase.Cell; 026import org.apache.hadoop.hbase.KeyValue; 027import org.apache.hadoop.hbase.KeyValueUtil; 028import org.apache.hadoop.hbase.testclassification.FilterTests; 029import org.apache.hadoop.hbase.testclassification.SmallTests; 030import org.apache.hadoop.hbase.util.Bytes; 031import org.apache.hadoop.hbase.util.Pair; 032import org.junit.jupiter.api.Tag; 033import org.junit.jupiter.api.Test; 034 035@Tag(FilterTests.TAG) 036@Tag(SmallTests.TAG) 037public class TestFuzzyRowFilter { 038 039 @Test 040 public void testSatisfiesNoUnsafeForward() { 041 042 assertEquals(FuzzyRowFilter.SatisfiesCode.YES, 043 FuzzyRowFilter.satisfiesNoUnsafe(false, new byte[] { 1, (byte) -128, 1, 0, 1 }, 0, 5, 044 new byte[] { 1, 0, 1 }, new byte[] { 0, 1, 0 })); 045 046 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, 047 FuzzyRowFilter.satisfiesNoUnsafe(false, new byte[] { 1, (byte) -128, 2, 0, 1 }, 0, 5, 048 new byte[] { 1, 0, 1 }, new byte[] { 0, 1, 0 })); 049 050 assertEquals(FuzzyRowFilter.SatisfiesCode.YES, FuzzyRowFilter.satisfiesNoUnsafe(false, 051 new byte[] { 1, 2, 1, 3, 3 }, 0, 5, new byte[] { 1, 2, 0, 3 }, new byte[] { 0, 0, 1, 0 })); 052 053 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, 054 FuzzyRowFilter.satisfiesNoUnsafe(false, new byte[] { 1, 1, 1, 3, 0 }, // row to check 055 0, 5, new byte[] { 1, 2, 0, 3 }, // fuzzy row 056 new byte[] { 0, 0, 1, 0 })); // mask 057 058 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, 059 FuzzyRowFilter.satisfiesNoUnsafe(false, new byte[] { 1, 1, 1, 3, 0 }, 0, 5, 060 new byte[] { 1, (byte) 245, 0, 3 }, new byte[] { 0, 0, 1, 0 })); 061 062 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfiesNoUnsafe(false, 063 new byte[] { 1, 2, 1, 0, 1 }, 0, 5, new byte[] { 0, 1, 2 }, new byte[] { 1, 0, 0 })); 064 } 065 066 @Test 067 public void testSatisfiesForward() { 068 069 assertEquals(FuzzyRowFilter.SatisfiesCode.YES, FuzzyRowFilter.satisfies(false, 070 new byte[] { 1, (byte) -128, 1, 0, 1 }, new byte[] { 1, 0, 1 }, new byte[] { -1, 0, -1 })); 071 072 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfies(false, 073 new byte[] { 1, (byte) -128, 2, 0, 1 }, new byte[] { 1, 0, 1 }, new byte[] { -1, 0, -1 })); 074 075 assertEquals(FuzzyRowFilter.SatisfiesCode.YES, FuzzyRowFilter.satisfies(false, 076 new byte[] { 1, 2, 1, 3, 3 }, new byte[] { 1, 2, 0, 3 }, new byte[] { -1, -1, 0, -1 })); 077 078 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, 079 FuzzyRowFilter.satisfies(false, new byte[] { 1, 1, 1, 3, 0 }, // row to check 080 new byte[] { 1, 2, 0, 3 }, // fuzzy row 081 new byte[] { -1, -1, 0, -1 })); // mask 082 083 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, 084 FuzzyRowFilter.satisfies(false, new byte[] { 1, 1, 1, 3, 0 }, 085 new byte[] { 1, (byte) 245, 0, 3 }, new byte[] { -1, -1, 0, -1 })); 086 087 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfies(false, 088 new byte[] { 1, 2, 1, 0, 1 }, new byte[] { 0, 1, 2 }, new byte[] { 0, -1, -1 })); 089 } 090 091 @Test 092 public void testSatisfiesReverse() { 093 assertEquals(FuzzyRowFilter.SatisfiesCode.YES, FuzzyRowFilter.satisfies(true, 094 new byte[] { 1, (byte) -128, 1, 0, 1 }, new byte[] { 1, 0, 1 }, new byte[] { -1, 0, -1 })); 095 096 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfies(true, 097 new byte[] { 1, (byte) -128, 2, 0, 1 }, new byte[] { 1, 0, 1 }, new byte[] { -1, 0, -1 })); 098 099 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfies(true, 100 new byte[] { 2, 3, 1, 1, 1 }, new byte[] { 1, 0, 1 }, new byte[] { -1, 0, -1 })); 101 102 assertEquals(FuzzyRowFilter.SatisfiesCode.YES, FuzzyRowFilter.satisfies(true, 103 new byte[] { 1, 2, 1, 3, 3 }, new byte[] { 1, 2, 0, 3 }, new byte[] { -1, -1, 0, -1 })); 104 105 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, 106 FuzzyRowFilter.satisfies(true, new byte[] { 1, (byte) 245, 1, 3, 0 }, 107 new byte[] { 1, 1, 0, 3 }, new byte[] { -1, -1, 0, -1 })); 108 109 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfies(true, 110 new byte[] { 1, 3, 1, 3, 0 }, new byte[] { 1, 2, 0, 3 }, new byte[] { -1, -1, 0, -1 })); 111 112 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfies(true, 113 new byte[] { 2, 1, 1, 1, 0 }, new byte[] { 1, 2, 0, 3 }, new byte[] { -1, -1, 0, -1 })); 114 115 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfies(true, 116 new byte[] { 1, 2, 1, 0, 1 }, new byte[] { 0, 1, 2 }, new byte[] { 0, -1, -1 })); 117 } 118 119 @Test 120 public void testSatisfiesNoUnsafeReverse() { 121 assertEquals(FuzzyRowFilter.SatisfiesCode.YES, 122 FuzzyRowFilter.satisfiesNoUnsafe(true, new byte[] { 1, (byte) -128, 1, 0, 1 }, 0, 5, 123 new byte[] { 1, 0, 1 }, new byte[] { 0, 1, 0 })); 124 125 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, 126 FuzzyRowFilter.satisfiesNoUnsafe(true, new byte[] { 1, (byte) -128, 2, 0, 1 }, 0, 5, 127 new byte[] { 1, 0, 1 }, new byte[] { 0, 1, 0 })); 128 129 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfiesNoUnsafe(true, 130 new byte[] { 2, 3, 1, 1, 1 }, 0, 5, new byte[] { 1, 0, 1 }, new byte[] { 0, 1, 0 })); 131 132 assertEquals(FuzzyRowFilter.SatisfiesCode.YES, FuzzyRowFilter.satisfiesNoUnsafe(true, 133 new byte[] { 1, 2, 1, 3, 3 }, 0, 5, new byte[] { 1, 2, 0, 3 }, new byte[] { 0, 0, 1, 0 })); 134 135 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, 136 FuzzyRowFilter.satisfiesNoUnsafe(true, new byte[] { 1, (byte) 245, 1, 3, 0 }, 0, 5, 137 new byte[] { 1, 1, 0, 3 }, new byte[] { 0, 0, 1, 0 })); 138 139 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfiesNoUnsafe(true, 140 new byte[] { 1, 3, 1, 3, 0 }, 0, 5, new byte[] { 1, 2, 0, 3 }, new byte[] { 0, 0, 1, 0 })); 141 142 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfiesNoUnsafe(true, 143 new byte[] { 2, 1, 1, 1, 0 }, 0, 5, new byte[] { 1, 2, 0, 3 }, new byte[] { 0, 0, 1, 0 })); 144 145 assertEquals(FuzzyRowFilter.SatisfiesCode.NEXT_EXISTS, FuzzyRowFilter.satisfiesNoUnsafe(true, 146 new byte[] { 1, 2, 1, 0, 1 }, 0, 5, new byte[] { 0, 1, 2 }, new byte[] { 1, 0, 0 })); 147 } 148 149 @Test 150 public void testGetNextForFuzzyRuleForward() { 151 assertNext(false, new byte[] { 0, 1, 2 }, // fuzzy row 152 new byte[] { 0, -1, -1 }, // mask 153 new byte[] { 1, 2, 1, 0, 1 }, // current 154 new byte[] { 2, 1, 2 }); // expected next 155 156 assertNext(false, new byte[] { 0, 1, 2 }, // fuzzy row 157 new byte[] { 0, -1, -1 }, // mask 158 new byte[] { 1, 1, 2, 0, 1 }, // current 159 new byte[] { 1, 1, 2, 0, 2 }); // expected next 160 161 assertNext(false, new byte[] { 0, 1, 0, 2, 0 }, // fuzzy row 162 new byte[] { 0, -1, 0, -1, 0 }, // mask 163 new byte[] { 1, 0, 2, 0, 1 }, // current 164 new byte[] { 1, 1, 0, 2 }); // expected next 165 166 assertNext(false, new byte[] { 1, 0, 1 }, // fuzzy row 167 new byte[] { -1, 0, -1 }, // mask 168 new byte[] { 1, (byte) 128, 2, 0, 1 }, // current 169 new byte[] { 1, (byte) 129, 1 }); // expected next 170 171 assertNext(false, new byte[] { 0, 1, 0, 1 }, // fuzzy row 172 new byte[] { 0, -1, 0, -1 }, // mask 173 new byte[] { 5, 1, 0, 1 }, // current 174 new byte[] { 5, 1, 1, 1 }); // expected next 175 176 assertNext(false, new byte[] { 0, 1, 0, 1 }, // fuzzy row 177 new byte[] { 0, -1, 0, -1 }, // mask 178 new byte[] { 5, 1, 0, 1, 1 }, // current 179 new byte[] { 5, 1, 0, 1, 2 }); // expected next 180 181 assertNext(false, new byte[] { 0, 1, 0, 0 }, // fuzzy row 182 new byte[] { 0, -1, 0, 0 }, // mask 183 new byte[] { 5, 1, (byte) 255, 1 }, // current 184 new byte[] { 5, 1, (byte) 255, 2 }); // expected next 185 186 assertNext(false, new byte[] { 0, 1, 0, 1 }, // fuzzy row 187 new byte[] { 0, -1, 0, -1 }, // mask 188 new byte[] { 5, 1, (byte) 255, 1 }, // current 189 new byte[] { 6, 1, 0, 1 }); // expected next 190 191 assertNext(false, new byte[] { 0, 1, 0, 1 }, // fuzzy row 192 new byte[] { 0, -1, 0, -1 }, // mask 193 new byte[] { 5, 1, (byte) 255, 0 }, // current 194 new byte[] { 5, 1, (byte) 255, 1 }); // expected next 195 196 assertNext(false, new byte[] { 5, 1, 1, 0 }, // fuzzy row 197 new byte[] { -1, -1, 0, 0 }, // mask 198 new byte[] { 5, 1, (byte) 255, 1 }, // current 199 new byte[] { 5, 1, (byte) 255, 2 }); // expected next 200 201 assertNext(false, new byte[] { 1, 1, 1, 1 }, // fuzzy row 202 new byte[] { -1, -1, 0, 0 }, // mask 203 new byte[] { 1, 1, 2, 2 }, // current 204 new byte[] { 1, 1, 2, 3 }); // expected next 205 206 assertNext(false, new byte[] { 1, 1, 1, 1 }, // fuzzy row 207 new byte[] { -1, -1, 0, 0 }, // mask 208 new byte[] { 1, 1, 3, 2 }, // current 209 new byte[] { 1, 1, 3, 3 }); // expected next 210 211 assertNext(false, new byte[] { 1, 1, 1, 1 }, // fuzzy row 212 new byte[] { 0, 0, 0, 0 }, // mask 213 new byte[] { 1, 1, 2, 3 }, // current 214 new byte[] { 1, 1, 2, 4 }); // expected next 215 216 assertNext(false, new byte[] { 1, 1, 1, 1 }, // fuzzy row 217 new byte[] { 0, 0, 0, 0 }, // mask 218 new byte[] { 1, 1, 3, 2 }, // current 219 new byte[] { 1, 1, 3, 3 }); // expected next 220 221 assertNext(false, new byte[] { 1, 1, 0, 0 }, // fuzzy row 222 new byte[] { -1, -1, 0, 0 }, // mask 223 new byte[] { 0, 1, 3, 2 }, // current 224 new byte[] { 1, 1 }); // expected next 225 226 // No next for this one 227 assertNull(FuzzyRowFilter.getNextForFuzzyRule(new byte[] { 2, 3, 1, 1, 1 }, // row to 228 // check 229 new byte[] { 1, 0, 1 }, // fuzzy row 230 new byte[] { -1, 0, -1 })); // mask 231 assertNull(FuzzyRowFilter.getNextForFuzzyRule(new byte[] { 1, (byte) 245, 1, 3, 0 }, 232 new byte[] { 1, 1, 0, 3 }, new byte[] { -1, -1, 0, -1 })); 233 assertNull(FuzzyRowFilter.getNextForFuzzyRule(new byte[] { 1, 3, 1, 3, 0 }, 234 new byte[] { 1, 2, 0, 3 }, new byte[] { -1, -1, 0, -1 })); 235 assertNull(FuzzyRowFilter.getNextForFuzzyRule(new byte[] { 2, 1, 1, 1, 0 }, 236 new byte[] { 1, 2, 0, 3 }, new byte[] { -1, -1, 0, -1 })); 237 } 238 239 @Test 240 public void testGetNextForFuzzyRuleReverse() { 241 // In these reverse cases for the next row key the last non-max byte should be increased 242 // to make sure that the proper row is selected next by the scanner. 243 // For example: 244 // fuzzy row: 0,1,2 245 // mask: 0,-1,-1 246 // current: 1,2,1,0,1 247 // next would be: 1,1,2 248 // this has to be increased to 1,1,3 to make sure that the proper row is selected next. 249 assertNext(true, new byte[] { 0, 1, 2 }, // fuzzy row 250 new byte[] { 0, -1, -1 }, // mask 251 new byte[] { 1, 2, 1, 0, 1 }, // current 252 new byte[] { 1, 1, 3 }); // expected next 253 254 // Preserve HBASE-28634 boundary hints. Lowering 1115 to 1114 would skip rows such as 111444. 255 assertNext(true, Bytes.toBytes("111433"), // fuzzy row 256 new byte[] { -1, -1, -1, -1, 0, 0 }, // mask 257 Bytes.toBytes("1115"), // current 258 Bytes.toBytes("1115")); // expected next 259 260 assertNext(true, new byte[] { 0, 1, 0, 2, 0 }, // fuzzy row 261 new byte[] { 0, -1, 0, -1, 0 }, // mask 262 new byte[] { 1, 2, 1, 3, 1 }, // current 263 new byte[] { 1, 1, (byte) 255, 3 }); // expected next 264 265 assertNext(true, new byte[] { 1, 0, 1 }, // fuzzy row 266 new byte[] { -1, 0, -1 }, // mask 267 new byte[] { 1, (byte) 128, 2, 0, 1 }, // current 268 new byte[] { 1, (byte) 128, 2 }); // expected next 269 270 assertNext(true, new byte[] { 0, 1, 0, 1 }, // fuzzy row 271 new byte[] { 0, -1, 0, -1 }, // mask 272 new byte[] { 5, 1, 0, 2, 1 }, // current 273 new byte[] { 5, 1, 0, 2 }); // expected next 274 275 assertNext(true, new byte[] { 0, 1, 0, 1 }, // fuzzy row 276 new byte[] { 0, -1, 0, -1 }, // mask 277 new byte[] { 5, 1, 0, 1 }, // current 278 new byte[] { 4, 1, (byte) 255, 2 }); // expected next 279 280 assertNext(true, new byte[] { 0, 1, 0, 1 }, // fuzzy row 281 new byte[] { 0, -1, 0, -1 }, // mask 282 new byte[] { 5, 1, (byte) 255, 0 }, // current 283 new byte[] { 5, 1, (byte) 254, 2 }); // expected next 284 285 assertNext(true, new byte[] { 1, 1, 0, 0 }, // fuzzy row 286 new byte[] { -1, -1, 0, 0 }, // mask 287 new byte[] { 2, 1, 3, 2 }, // current 288 new byte[] { 1, 2 }); // expected next 289 290 assertNext(true, new byte[] { 1, 0, 1 }, // fuzzy row 291 new byte[] { -1, 0, -1 }, // mask 292 new byte[] { 2, 3, 1, 1, 1 }, // row to check 293 new byte[] { 1, (byte) 255, 2 }); // expected next 294 295 assertNext(true, new byte[] { 1, 1, 0, 3 }, // fuzzy row 296 new byte[] { -1, -1, 0, -1 }, // mask 297 new byte[] { 1, (byte) 245, 1, 3, 0 }, // row to check 298 new byte[] { 1, 1, (byte) 255, 4 }); // expected next 299 300 assertNext(true, new byte[] { 1, 2, 0, 3 }, // fuzzy row 301 new byte[] { -1, -1, 0, -1 }, // mask 302 new byte[] { 1, 3, 1, 3, 0 }, // row to check 303 new byte[] { 1, 2, (byte) 255, 4 }); // expected next 304 305 assertNext(true, new byte[] { 1, 2, 0, 3 }, // fuzzy row 306 new byte[] { -1, -1, 0, -1 }, // mask 307 new byte[] { 2, 1, 1, 1, 0 }, // row to check 308 new byte[] { 1, 2, (byte) 255, 4 }); // expected next 309 310 // No cell before the current one satisfies the fuzzy row -> null. 311 assertNull(FuzzyRowFilter.getNextForFuzzyRule(true, new byte[] { 1, 1, 1, 3, 0 }, 312 new byte[] { 1, 2, 0, 3 }, new byte[] { -1, -1, 0, -1 })); 313 } 314 315 @Test 316 public void testGetNextForFuzzyRuleReverseCanReturnCurrentRow() { 317 // HBASE-28634 reverse adjustment can intentionally return the current row as a boundary hint. 318 assertNext(true, new byte[] { 'a', 'a', 'a' }, // fuzzy row 319 new byte[] { -1, 0, -1 }, // mask 320 new byte[] { 'a', 'b', 'b' }, // current 321 new byte[] { 'a', 'b', 'b' }); // expected next 322 323 assertNext(true, new byte[] { 0, 1, 0, 0 }, // fuzzy row 324 new byte[] { 0, -1, 0, 0 }, // mask 325 new byte[] { 5, 1, (byte) 255, 1 }, // current 326 new byte[] { 5, 1, (byte) 255, 1 }); // expected next 327 328 assertNext(true, new byte[] { 1, 0, 1 }, // fuzzy row 329 new byte[] { -1, 0, -1 }, // mask 330 new byte[] { 1, (byte) 128, 2 }, // row to check 331 new byte[] { 1, (byte) 128, 2 }); // expected next 332 333 assertNext(true, new byte[] { 0, 1, 0, 1 }, // fuzzy row 334 new byte[] { 0, -1, 0, -1 }, // mask 335 new byte[] { 5, 1, 0, 2 }, // row to check 336 new byte[] { 5, 1, 0, 2 }); // expected next 337 338 assertNext(true, new byte[] { 5, 1, 1, 0 }, // fuzzy row 339 new byte[] { -1, -1, 0, 0 }, // mask 340 new byte[] { 5, 1, (byte) 0xFF, 1 }, // row to check 341 new byte[] { 5, 1, (byte) 0xFF, 1 }); // expected next 342 343 assertNext(true, new byte[] { 1, 1, 1, 1 }, // fuzzy row 344 new byte[] { -1, -1, 0, 0 }, // mask 345 new byte[] { 1, 1, 2, 2 }, // row to check 346 new byte[] { 1, 1, 2, 2 }); // expected next 347 348 assertNext(true, new byte[] { 1, 1, 1, 1 }, // fuzzy row 349 new byte[] { 0, 0, 0, 0 }, // mask 350 new byte[] { 1, 1, 2, 3 }, // row to check 351 new byte[] { 1, 1, 2, 3 }); // expected next 352 } 353 354 private static void assertNext(boolean reverse, byte[] fuzzyRow, byte[] mask, byte[] current, 355 byte[] expected) { 356 KeyValue kv = KeyValueUtil.createFirstOnRow(current); 357 byte[] nextForFuzzyRule = FuzzyRowFilter.getNextForFuzzyRule(reverse, kv.getRowArray(), 358 kv.getRowOffset(), kv.getRowLength(), fuzzyRow, mask); 359 assertEquals(Bytes.toStringBinary(expected), Bytes.toStringBinary(nextForFuzzyRule)); 360 } 361 362 @Test 363 public void testReverseFilterCellSkipsSameRowHint() { 364 // The first non-matching row can seek to abb, but abb would recreate abb as its reverse hint. 365 // The scanner should skip that non-matching row instead of seeking to the same row again. 366 FuzzyRowFilter filter = newReverseFuzzyRowFilter(); 367 368 KeyValue abc = KeyValueUtil.createFirstOnRow(Bytes.toBytes("abc")); 369 assertEquals(Filter.ReturnCode.SEEK_NEXT_USING_HINT, filter.filterCell(abc)); 370 Cell hint = filter.getNextCellHint(abc); 371 assertRow("abb", hint); 372 373 KeyValue abb = KeyValueUtil.createFirstOnRow(Bytes.toBytes("abb")); 374 // filterCell handles the same-row hint by skipping the current non-matching row. 375 assertEquals(Filter.ReturnCode.NEXT_ROW, filter.filterCell(abb)); 376 } 377 378 @Test 379 public void testReverseFilterListSkipsSameRowFuzzyHint() throws IOException { 380 for (FilterList.Operator operator : Arrays.asList(FilterList.Operator.MUST_PASS_ALL, 381 FilterList.Operator.MUST_PASS_ONE)) { 382 FilterList filterList = 383 new FilterList(operator, newReverseFuzzyRowFilter(), newReverseFuzzyRowFilter()); 384 385 KeyValue abc = KeyValueUtil.createFirstOnRow(Bytes.toBytes("abc")); 386 assertEquals(Filter.ReturnCode.SEEK_NEXT_USING_HINT, filterList.filterCell(abc)); 387 assertRow("abb", filterList.getNextCellHint(abc)); 388 389 KeyValue abb = KeyValueUtil.createFirstOnRow(Bytes.toBytes("abb")); 390 assertEquals(Filter.ReturnCode.NEXT_ROW, filterList.filterCell(abb)); 391 } 392 } 393 394 /** 395 * Serializing a filter must not leak the internal mask encoding. On the unsafe path the stored 396 * mask is {-1 (fixed), 0 (non-fixed)}; {@code toByteArray} must emit the public constructor {0, 397 * 1} form so the round-tripped filter behaves identically (otherwise the leaked {-1, 0} is 398 * reparsed as an all-fixed mask). Processing a cell first must not change this. 399 */ 400 @Test 401 public void testSerializationAfterFilterCellPreservesBehavior() throws Exception { 402 FuzzyRowFilter original = 403 new FuzzyRowFilter(Arrays.asList(new Pair<>(new byte[] { 1, 2, 3 }, new byte[] { 0, 1, 0 }))); 404 // Process a cell first to prove serialization is unaffected by scanning. 405 original.filterCell(KeyValueUtil.createFirstOnRow(new byte[] { 1, 50, 3 })); 406 407 FuzzyRowFilter parsed = FuzzyRowFilter.parseFrom(original.toByteArray()); 408 // A row matching only via the wildcard position must still be INCLUDED after the round-trip. 409 assertEquals(Filter.ReturnCode.INCLUDE, 410 parsed.filterCell(KeyValueUtil.createFirstOnRow(new byte[] { 1, 99, 3 }))); 411 } 412 413 /** 414 * Two filters built from the same rule (distinct array instances) must be equal and hash equally, 415 * i.e. {@code equals}/{@code hashCode} must be content-based, not identity-based, and consistent 416 * with each other and with the serialized ({0, 1}) form. This must also hold after one of them 417 * has processed a cell. 418 */ 419 @Test 420 public void testEqualsConsistentAfterFilterCell() { 421 FuzzyRowFilter fresh = 422 new FuzzyRowFilter(Arrays.asList(new Pair<>(new byte[] { 1, 2, 3 }, new byte[] { 0, 1, 0 }))); 423 FuzzyRowFilter scanned = 424 new FuzzyRowFilter(Arrays.asList(new Pair<>(new byte[] { 1, 2, 3 }, new byte[] { 0, 1, 0 }))); 425 scanned.filterCell(KeyValueUtil.createFirstOnRow(new byte[] { 1, 50, 3 })); 426 427 assertEquals(fresh, scanned); 428 assertEquals(fresh.hashCode(), scanned.hashCode()); 429 } 430 431 private static FuzzyRowFilter newReverseFuzzyRowFilter() { 432 FuzzyRowFilter filter = 433 new FuzzyRowFilter(Arrays.asList(new Pair<>(Bytes.toBytes("aaa"), new byte[] { 0, 1, 0 }))); 434 filter.setReversed(true); 435 return filter; 436 } 437 438 private static void assertRow(String expected, Cell cell) { 439 assertEquals(expected, 440 Bytes.toString(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength())); 441 } 442}