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