/* * 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.phoenix.filter; import java.io.IOException; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.filter.Filter; import org.apache.hadoop.hbase.filter.Filter.ReturnCode; import org.apache.hadoop.hbase.util.Bytes; import org.apache.phoenix.schema.PDatum; import org.apache.phoenix.schema.RowKeySchema.RowKeySchemaBuilder; import org.apache.phoenix.schema.SortOrder; import org.apache.phoenix.schema.types.PChar; import org.apache.phoenix.schema.types.PDataType; import org.apache.phoenix.schema.types.PVarchar; import org.apache.phoenix.util.ByteUtil; import junit.framework.TestCase; public class DistinctPrefixFilterTest extends TestCase { private DistinctPrefixFilter createFilter(int[] widths, int prefixLength) { RowKeySchemaBuilder builder = new RowKeySchemaBuilder(widths.length); for (final int width : widths) { builder.addField( new PDatum() { @Override public boolean isNullable() { return width <= 0; } @Override public PDataType<?> getDataType() { return width <= 0 ? PVarchar.INSTANCE : PChar.INSTANCE; } @Override public Integer getMaxLength() { return width <= 0 ? null : width; } @Override public Integer getScale() { return null; } @Override public SortOrder getSortOrder() { return SortOrder.getDefault(); } }, width <= 0, SortOrder.getDefault()); } return new DistinctPrefixFilter(builder.build(), prefixLength); } private void assertInclude(String next, Filter f) throws IOException { assertInclude(Bytes.toBytes(next), f); } private void assertInclude(byte[] next, Filter f) throws IOException { Cell c = new KeyValue(next, ByteUtil.EMPTY_BYTE_ARRAY, ByteUtil.EMPTY_BYTE_ARRAY, 0, ByteUtil.EMPTY_BYTE_ARRAY); assertTrue(f.filterKeyValue(c) == ReturnCode.INCLUDE); assertFalse(f.filterAllRemaining()); } private void assertSeekAndHint(String next, Filter f, String rowHint) throws IOException { assertSeekAndHint(next, f, rowHint, false); } private void assertSeekAndHint(String next, Filter f, String rowHint, boolean filterAll) throws IOException { assertSeekAndHint(Bytes.toBytes(next), f, Bytes.toBytes(rowHint), filterAll); } private void assertSeekAndHint(byte[] next, Filter f, byte[] rowHint, boolean filterAll) throws IOException { Cell c = new KeyValue(next, ByteUtil.EMPTY_BYTE_ARRAY, ByteUtil.EMPTY_BYTE_ARRAY, 0, ByteUtil.EMPTY_BYTE_ARRAY); assertTrue(f.filterKeyValue(c) == ReturnCode.SEEK_NEXT_USING_HINT); Cell h = f.getNextCellHint(c); byte[] hintBytes = rowHint; assertTrue(Bytes.equals(hintBytes, 0, hintBytes.length, h.getRowArray(), h.getRowOffset(), h.getRowLength())); assertEquals(filterAll, f.filterAllRemaining()); } public void testSingleFixedWidth() throws Exception { Filter f = createFilter(new int[]{3}, 1); assertInclude("000", f); assertInclude("001", f); assertSeekAndHint("001", f, "002"); assertInclude("003", f); assertInclude("004", f); assertInclude("005", f); assertSeekAndHint("005", f, "006"); f = createFilter(new int[]{3}, 1); f.setReversed(true); assertInclude("005", f); assertInclude("004", f); assertSeekAndHint(new byte[]{'0','0','4'}, f, new byte[]{'0','0','4'}, false); assertInclude("003", f); assertInclude("002", f); assertInclude("001", f); assertSeekAndHint(new byte[]{'0','0','1'}, f, new byte[]{'0','0','1'}, false); } public void testMultiFixedWidth() throws Exception { Filter f = createFilter(new int[]{5,4}, 1); assertInclude("00000aaaa", f); assertInclude("00001aaaa", f); assertSeekAndHint("00001aaaa", f, "00002"); assertInclude("00003aaaa", f); assertInclude("00004aaaa", f); assertInclude("00005aaaa", f); assertSeekAndHint("00005aaaa", f, "00006"); f = createFilter(new int[]{5,4}, 2); assertInclude("00000aaaa", f); assertInclude("00001aaaa", f); assertSeekAndHint("00001aaaa", f, "00001aaab"); assertInclude("00003aaaa", f); assertInclude("00004aaaa", f); assertInclude("00005aaaa", f); assertSeekAndHint("00005aaaa", f, "00005aaab"); f = createFilter(new int[]{3,2}, 1); f.setReversed(true); assertInclude("005aa", f); assertInclude("004aa", f); assertSeekAndHint(new byte[]{'0','0','4','a','a'}, f, new byte[]{'0','0','4'}, false); assertInclude("003aa", f); assertInclude("002aa", f); assertInclude("001aa", f); assertSeekAndHint(new byte[]{'0','0','1','a','a'}, f, new byte[]{'0','0','1'}, false); f = createFilter(new int[]{3,2}, 2); f.setReversed(true); assertInclude("005bb", f); assertInclude("004bb", f); assertInclude("003bb", f); assertSeekAndHint(new byte[]{'0','0','3','b','b'}, f, new byte[]{'0','0','3','b','b'}, false); assertInclude("003ba", f); assertInclude("002bb", f); assertInclude("001bb", f); assertSeekAndHint(new byte[]{'0','0','1','b','b'}, f, new byte[]{'0','0','1','b','b'}, false); } public void testSingleVariableWidth() throws Exception { Filter f = createFilter(new int[]{-5}, 1); assertInclude("00000", f); assertInclude("00001", f); assertSeekAndHint("00001", f, "00001\01"); assertInclude("00003", f); assertInclude("00004", f); assertInclude("00005", f); assertSeekAndHint("00005", f, "00005\01"); } public void testVariableWithNull() throws Exception { Filter f = createFilter(new int[]{-2,-2}, 1); assertInclude("\00aa", f); assertSeekAndHint("\00aa", f, "\01"); assertSeekAndHint("\00aa", f, "\01"); f = createFilter(new int[]{-2,-2}, 2); assertInclude("\00\00", f); assertSeekAndHint("\00\00", f, "\00\00\01"); assertSeekAndHint("\00\00", f, "\00\00\01"); } public void testMultiVariableWidth() throws Exception { Filter f = createFilter(new int[]{-5,-4}, 1); assertInclude("00000\00aaaa", f); assertInclude("00001\00aaaa", f); assertSeekAndHint("00001\00aaaa", f, "00001\01"); assertInclude("00003\00aaaa", f); assertInclude("00004\00aaaa", f); assertInclude("00005\00aaaa", f); assertSeekAndHint("00005\00aaaa", f, "00005\01"); f = createFilter(new int[]{-5,-4}, 2); assertInclude("00000\00aaaa", f); assertInclude("00001\00aaaa", f); assertSeekAndHint("00001\00aaaa", f, "00001\00aaaa\01"); assertInclude("00003\00aaaa", f); assertInclude("00004\00aaaa", f); assertInclude("00005\00aaaa", f); assertSeekAndHint("00005\00aaaa", f, "00005\00aaaa\01"); f = createFilter(new int[]{-3,-2}, 1); f.setReversed(true); assertInclude("005\00aa", f); assertInclude("004\00aa", f); assertSeekAndHint(new byte[]{'0','0','4', 0, 'a', 'a'}, f, new byte[] {'0','0','4'}, false); f = createFilter(new int[]{-3,-2}, 2); f.setReversed(true); assertInclude("005\00bb", f); assertInclude("004\00bb", f); assertSeekAndHint(new byte[]{'0','0','4', 0, 'b', 'b'}, f, new byte[]{'0','0','4', 0, 'b', 'b'}, false); } public void testFixedAfterVariable() throws Exception { Filter f = createFilter(new int[]{-5,4}, 1); assertInclude("00000\00aaaa", f); assertInclude("00001\00aaaa", f); assertSeekAndHint("00001\00aaaa", f, "00001\01"); assertInclude("00003\00aaaa", f); assertInclude("00004\00aaaa", f); assertInclude("00005\00aaaa", f); assertSeekAndHint("00005\00aaaa", f, "00005\01"); f = createFilter(new int[]{-5,4}, 2); assertInclude("00000\00aaaa", f); assertInclude("00001\00aaaa", f); assertSeekAndHint("00001\00aaaa", f, "00001\00aaab"); assertInclude("00003\00aaaa", f); assertInclude("00004\00aaaa", f); assertInclude("00005\00aaaa", f); assertSeekAndHint("00005\00aaaa", f, "00005\00aaab"); } public void testVariableAfterFixed() throws Exception { Filter f = createFilter(new int[]{5,-4}, 1); assertInclude("00000aaaa", f); assertInclude("00001aaaa", f); assertSeekAndHint("00001aaaa", f, "00002"); assertInclude("00003aaaa", f); assertInclude("00004aaaa", f); assertInclude("00005aaaa", f); assertSeekAndHint("00005aaaa", f, "00006"); f = createFilter(new int[]{5,-4}, 2); assertInclude("00000aaaa", f); assertInclude("00001aaaa", f); assertSeekAndHint("00001aaaa", f, "00001aaaa\01"); assertInclude("00003aaaa", f); assertInclude("00004aaaa", f); assertInclude("00005aaaa", f); assertSeekAndHint("00005aaaa", f, "00005aaaa\01"); } public void testNoNextKey() throws Exception { Filter f = createFilter(new int[]{2,2}, 1); assertInclude("00cc", f); assertInclude(new byte[]{-1,-1,20,20}, f); // make sure we end the scan when we cannot increase a fixed length prefix assertSeekAndHint(new byte[]{-1,-1,20,20}, f, new byte[]{-1,-1}, true); assertSeekAndHint(new byte[]{-1,-1,20,20}, f, new byte[]{-1,-1}, true); f = createFilter(new int[]{2,2}, 1); f.setReversed(true); assertInclude(new byte[]{0,0,1,1}, f); assertSeekAndHint(new byte[]{0,0,1,1}, f, new byte[]{0,0}, false); assertSeekAndHint(new byte[]{0,0,1,1}, f, new byte[]{0,0}, false); } }