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.Comparator; 023import java.util.TreeSet; 024 025import org.apache.hadoop.hbase.Cell; 026import org.apache.hadoop.hbase.CellUtil; 027import org.apache.hadoop.hbase.PrivateCellUtil; 028import org.apache.yetus.audience.InterfaceAudience; 029import org.apache.hadoop.hbase.exceptions.DeserializationException; 030import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException; 031import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations; 032import org.apache.hadoop.hbase.shaded.protobuf.generated.FilterProtos; 033import org.apache.hadoop.hbase.util.Bytes; 034 035/** 036 * This filter is used for selecting only those keys with columns that matches 037 * a particular prefix. For example, if prefix is 'an', it will pass keys will 038 * columns like 'and', 'anti' but not keys with columns like 'ball', 'act'. 039 */ 040@InterfaceAudience.Public 041public class MultipleColumnPrefixFilter extends FilterBase { 042 protected byte [] hint = null; 043 protected TreeSet<byte []> sortedPrefixes = createTreeSet(); 044 private final static int MAX_LOG_PREFIXES = 5; 045 046 public MultipleColumnPrefixFilter(final byte [][] prefixes) { 047 if (prefixes != null) { 048 for (int i = 0; i < prefixes.length; i++) { 049 if (!sortedPrefixes.add(prefixes[i])) 050 throw new IllegalArgumentException ("prefixes must be distinct"); 051 } 052 } 053 } 054 055 public byte [][] getPrefix() { 056 int count = 0; 057 byte [][] temp = new byte [sortedPrefixes.size()][]; 058 for (byte [] prefixes : sortedPrefixes) { 059 temp [count++] = prefixes; 060 } 061 return temp; 062 } 063 064 @Override 065 public boolean filterRowKey(Cell cell) throws IOException { 066 // Impl in FilterBase might do unnecessary copy for Off heap backed Cells. 067 return false; 068 } 069 070 @Deprecated 071 @Override 072 public ReturnCode filterKeyValue(final Cell c) { 073 return filterCell(c); 074 } 075 076 @Override 077 public ReturnCode filterCell(final Cell c) { 078 if (sortedPrefixes.isEmpty()) { 079 return ReturnCode.INCLUDE; 080 } else { 081 return filterColumn(c); 082 } 083 } 084 085 public ReturnCode filterColumn(Cell cell) { 086 byte [] qualifier = CellUtil.cloneQualifier(cell); 087 TreeSet<byte []> lesserOrEqualPrefixes = 088 (TreeSet<byte []>) sortedPrefixes.headSet(qualifier, true); 089 090 if (lesserOrEqualPrefixes.size() != 0) { 091 byte [] largestPrefixSmallerThanQualifier = lesserOrEqualPrefixes.last(); 092 093 if (Bytes.startsWith(qualifier, largestPrefixSmallerThanQualifier)) { 094 return ReturnCode.INCLUDE; 095 } 096 097 if (lesserOrEqualPrefixes.size() == sortedPrefixes.size()) { 098 return ReturnCode.NEXT_ROW; 099 } else { 100 hint = sortedPrefixes.higher(largestPrefixSmallerThanQualifier); 101 return ReturnCode.SEEK_NEXT_USING_HINT; 102 } 103 } else { 104 hint = sortedPrefixes.first(); 105 return ReturnCode.SEEK_NEXT_USING_HINT; 106 } 107 } 108 109 public static Filter createFilterFromArguments(ArrayList<byte []> filterArguments) { 110 byte [][] prefixes = new byte [filterArguments.size()][]; 111 for (int i = 0 ; i < filterArguments.size(); i++) { 112 byte [] columnPrefix = ParseFilter.removeQuotesFromByteArray(filterArguments.get(i)); 113 prefixes[i] = columnPrefix; 114 } 115 return new MultipleColumnPrefixFilter(prefixes); 116 } 117 118 /** 119 * @return The filter serialized using pb 120 */ 121 @Override 122 public byte [] toByteArray() { 123 FilterProtos.MultipleColumnPrefixFilter.Builder builder = 124 FilterProtos.MultipleColumnPrefixFilter.newBuilder(); 125 for (byte [] element : sortedPrefixes) { 126 if (element != null) builder.addSortedPrefixes(UnsafeByteOperations.unsafeWrap(element)); 127 } 128 return builder.build().toByteArray(); 129 } 130 131 /** 132 * @param pbBytes A pb serialized {@link MultipleColumnPrefixFilter} instance 133 * @return An instance of {@link MultipleColumnPrefixFilter} made from <code>bytes</code> 134 * @throws DeserializationException 135 * @see #toByteArray 136 */ 137 public static MultipleColumnPrefixFilter parseFrom(final byte [] pbBytes) 138 throws DeserializationException { 139 FilterProtos.MultipleColumnPrefixFilter proto; 140 try { 141 proto = FilterProtos.MultipleColumnPrefixFilter.parseFrom(pbBytes); 142 } catch (InvalidProtocolBufferException e) { 143 throw new DeserializationException(e); 144 } 145 int numPrefixes = proto.getSortedPrefixesCount(); 146 byte [][] prefixes = new byte[numPrefixes][]; 147 for (int i = 0; i < numPrefixes; ++i) { 148 prefixes[i] = proto.getSortedPrefixes(i).toByteArray(); 149 } 150 151 return new MultipleColumnPrefixFilter(prefixes); 152 } 153 154 /** 155 * @param o the other filter to compare with 156 * @return true if and only if the fields of the filter that are serialized 157 * are equal to the corresponding fields in other. Used for testing. 158 */ 159 @Override 160 boolean areSerializedFieldsEqual(Filter o) { 161 if (o == this) return true; 162 if (!(o instanceof MultipleColumnPrefixFilter)) return false; 163 164 MultipleColumnPrefixFilter other = (MultipleColumnPrefixFilter)o; 165 return this.sortedPrefixes.equals(other.sortedPrefixes); 166 } 167 168 @Override 169 public Cell getNextCellHint(Cell cell) { 170 return PrivateCellUtil.createFirstOnRowCol(cell, hint, 0, hint.length); 171 } 172 173 public TreeSet<byte []> createTreeSet() { 174 return new TreeSet<>(new Comparator<Object>() { 175 @Override 176 public int compare (Object o1, Object o2) { 177 if (o1 == null || o2 == null) 178 throw new IllegalArgumentException ("prefixes can't be null"); 179 180 byte [] b1 = (byte []) o1; 181 byte [] b2 = (byte []) o2; 182 return Bytes.compareTo (b1, 0, b1.length, b2, 0, b2.length); 183 } 184 }); 185 } 186 187 @Override 188 public String toString() { 189 return toString(MAX_LOG_PREFIXES); 190 } 191 192 protected String toString(int maxPrefixes) { 193 StringBuilder prefixes = new StringBuilder(); 194 195 int count = 0; 196 for (byte[] ba : this.sortedPrefixes) { 197 if (count >= maxPrefixes) { 198 break; 199 } 200 ++count; 201 prefixes.append(Bytes.toStringBinary(ba)); 202 if (count < this.sortedPrefixes.size() && count < maxPrefixes) { 203 prefixes.append(", "); 204 } 205 } 206 207 return String.format("%s (%d/%d): [%s]", this.getClass().getSimpleName(), 208 count, this.sortedPrefixes.size(), prefixes.toString()); 209 } 210}