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.HashSet; 023import java.util.List; 024import java.util.Objects; 025import java.util.Set; 026import org.apache.hadoop.hbase.Cell; 027import org.apache.hadoop.hbase.CellUtil; 028import org.apache.hadoop.hbase.CompareOperator; 029import org.apache.hadoop.hbase.exceptions.DeserializationException; 030import org.apache.hadoop.hbase.util.Bytes; 031import org.apache.yetus.audience.InterfaceAudience; 032 033import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; 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.ProtobufUtil; 038import org.apache.hadoop.hbase.shaded.protobuf.generated.FilterProtos; 039 040/** 041 * A filter for adding inter-column timestamp matching Only cells with a correspondingly timestamped 042 * entry in the target column will be retained Not compatible with Scan.setBatch as operations need 043 * full rows for correct filtering 044 */ 045@InterfaceAudience.Public 046public class DependentColumnFilter extends CompareFilter { 047 048 protected byte[] columnFamily; 049 protected byte[] columnQualifier; 050 protected boolean dropDependentColumn; 051 052 protected Set<Long> stampSet = new HashSet<>(); 053 054 /** 055 * Build a dependent column filter with value checking dependent column varies will be compared 056 * using the supplied compareOp and comparator, for usage of which refer to {@link CompareFilter} 057 * @param family dependent column family 058 * @param qualifier dependent column qualifier 059 * @param dropDependentColumn whether the column should be discarded after 060 * @param valueCompareOp comparison op 061 * @param valueComparator comparator 062 * @deprecated Since 2.0.0. Will be removed in 3.0.0. Use 063 * {@link #DependentColumnFilter(byte[], byte[], boolean, CompareOperator, ByteArrayComparable)} 064 * instead. 065 */ 066 @Deprecated 067 public DependentColumnFilter(final byte[] family, final byte[] qualifier, 068 final boolean dropDependentColumn, final CompareOp valueCompareOp, 069 final ByteArrayComparable valueComparator) { 070 this(family, qualifier, dropDependentColumn, CompareOperator.valueOf(valueCompareOp.name()), 071 valueComparator); 072 } 073 074 /** 075 * Build a dependent column filter with value checking dependent column varies will be compared 076 * using the supplied compareOp and comparator, for usage of which refer to {@link CompareFilter} 077 * @param family dependent column family 078 * @param qualifier dependent column qualifier 079 * @param dropDependentColumn whether the column should be discarded after 080 * @param op Value comparison op 081 * @param valueComparator comparator 082 */ 083 public DependentColumnFilter(final byte[] family, final byte[] qualifier, 084 final boolean dropDependentColumn, final CompareOperator op, 085 final ByteArrayComparable valueComparator) { 086 // set up the comparator 087 super(op, valueComparator); 088 this.columnFamily = family; 089 this.columnQualifier = qualifier; 090 this.dropDependentColumn = dropDependentColumn; 091 } 092 093 /** 094 * Constructor for DependentColumn filter. Cells where a Cell from target column with the same 095 * timestamp do not exist will be dropped. 096 * @param family name of target column family 097 * @param qualifier name of column qualifier 098 */ 099 public DependentColumnFilter(final byte[] family, final byte[] qualifier) { 100 this(family, qualifier, false); 101 } 102 103 /** 104 * Constructor for DependentColumn filter. Cells where a Cell from target column with the same 105 * timestamp do not exist will be dropped. 106 * @param family name of dependent column family 107 * @param qualifier name of dependent qualifier 108 * @param dropDependentColumn whether the dependent columns Cells should be discarded 109 */ 110 public DependentColumnFilter(final byte[] family, final byte[] qualifier, 111 final boolean dropDependentColumn) { 112 this(family, qualifier, dropDependentColumn, CompareOp.NO_OP, null); 113 } 114 115 /** Returns the column family */ 116 public byte[] getFamily() { 117 return this.columnFamily; 118 } 119 120 /** Returns the column qualifier */ 121 public byte[] getQualifier() { 122 return this.columnQualifier; 123 } 124 125 /** Returns true if we should drop the dependent column, false otherwise */ 126 public boolean dropDependentColumn() { 127 return this.dropDependentColumn; 128 } 129 130 public boolean getDropDependentColumn() { 131 return this.dropDependentColumn; 132 } 133 134 @Override 135 public boolean filterAllRemaining() { 136 return false; 137 } 138 139 @Deprecated 140 @Override 141 public ReturnCode filterKeyValue(final Cell c) { 142 return filterCell(c); 143 } 144 145 @Override 146 public ReturnCode filterCell(final Cell c) { 147 // Check if the column and qualifier match 148 if (!CellUtil.matchingColumn(c, this.columnFamily, this.columnQualifier)) { 149 // include non-matches for the time being, they'll be discarded afterwards 150 return ReturnCode.INCLUDE; 151 } 152 // If it doesn't pass the op, skip it 153 if (comparator != null && compareValue(getCompareOperator(), comparator, c)) 154 return ReturnCode.SKIP; 155 156 stampSet.add(c.getTimestamp()); 157 if (dropDependentColumn) { 158 return ReturnCode.SKIP; 159 } 160 return ReturnCode.INCLUDE; 161 } 162 163 @Override 164 public void filterRowCells(List<Cell> kvs) { 165 kvs.removeIf(kv -> !stampSet.contains(kv.getTimestamp())); 166 } 167 168 @Override 169 public boolean hasFilterRow() { 170 return true; 171 } 172 173 @Override 174 public boolean filterRow() { 175 return false; 176 } 177 178 @Override 179 public boolean filterRowKey(byte[] buffer, int offset, int length) { 180 return false; 181 } 182 183 @Override 184 public void reset() { 185 stampSet.clear(); 186 } 187 188 public static Filter createFilterFromArguments(ArrayList<byte[]> filterArguments) { 189 Preconditions.checkArgument( 190 filterArguments.size() == 2 || filterArguments.size() == 3 || filterArguments.size() == 5, 191 "Expected 2, 3 or 5 but got: %s", filterArguments.size()); 192 if (filterArguments.size() == 2) { 193 byte[] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0)); 194 byte[] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1)); 195 return new DependentColumnFilter(family, qualifier); 196 197 } else if (filterArguments.size() == 3) { 198 byte[] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0)); 199 byte[] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1)); 200 boolean dropDependentColumn = ParseFilter.convertByteArrayToBoolean(filterArguments.get(2)); 201 return new DependentColumnFilter(family, qualifier, dropDependentColumn); 202 203 } else if (filterArguments.size() == 5) { 204 byte[] family = ParseFilter.removeQuotesFromByteArray(filterArguments.get(0)); 205 byte[] qualifier = ParseFilter.removeQuotesFromByteArray(filterArguments.get(1)); 206 boolean dropDependentColumn = ParseFilter.convertByteArrayToBoolean(filterArguments.get(2)); 207 CompareOperator op = ParseFilter.createCompareOperator(filterArguments.get(3)); 208 ByteArrayComparable comparator = 209 ParseFilter.createComparator(ParseFilter.removeQuotesFromByteArray(filterArguments.get(4))); 210 return new DependentColumnFilter(family, qualifier, dropDependentColumn, op, comparator); 211 } else { 212 throw new IllegalArgumentException("Expected 2, 3 or 5 but got: " + filterArguments.size()); 213 } 214 } 215 216 /** Returns The filter serialized using pb */ 217 @Override 218 public byte[] toByteArray() { 219 FilterProtos.DependentColumnFilter.Builder builder = 220 FilterProtos.DependentColumnFilter.newBuilder(); 221 builder.setCompareFilter(super.convert()); 222 if (this.columnFamily != null) { 223 builder.setColumnFamily(UnsafeByteOperations.unsafeWrap(this.columnFamily)); 224 } 225 if (this.columnQualifier != null) { 226 builder.setColumnQualifier(UnsafeByteOperations.unsafeWrap(this.columnQualifier)); 227 } 228 builder.setDropDependentColumn(this.dropDependentColumn); 229 return builder.build().toByteArray(); 230 } 231 232 /** 233 * Parse a seralized representation of {@link DependentColumnFilter} 234 * @param pbBytes A pb serialized {@link DependentColumnFilter} instance 235 * @return An instance of {@link DependentColumnFilter} made from <code>bytes</code> 236 * @throws DeserializationException if an error occurred 237 * @see #toByteArray 238 */ 239 public static DependentColumnFilter parseFrom(final byte[] pbBytes) 240 throws DeserializationException { 241 FilterProtos.DependentColumnFilter proto; 242 try { 243 proto = FilterProtos.DependentColumnFilter.parseFrom(pbBytes); 244 } catch (InvalidProtocolBufferException e) { 245 throw new DeserializationException(e); 246 } 247 final CompareOperator valueCompareOp = 248 CompareOperator.valueOf(proto.getCompareFilter().getCompareOp().name()); 249 ByteArrayComparable valueComparator = null; 250 try { 251 if (proto.getCompareFilter().hasComparator()) { 252 valueComparator = ProtobufUtil.toComparator(proto.getCompareFilter().getComparator()); 253 } 254 } catch (IOException ioe) { 255 throw new DeserializationException(ioe); 256 } 257 return new DependentColumnFilter( 258 proto.hasColumnFamily() ? proto.getColumnFamily().toByteArray() : null, 259 proto.hasColumnQualifier() ? proto.getColumnQualifier().toByteArray() : null, 260 proto.getDropDependentColumn(), valueCompareOp, valueComparator); 261 } 262 263 /** 264 * Returns true if and only if the fields of the filter that are serialized are equal to the 265 * corresponding fields in other. Used for testing. 266 */ 267 @edu.umd.cs.findbugs.annotations.SuppressWarnings( 268 value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") 269 @Override 270 boolean areSerializedFieldsEqual(Filter o) { 271 if (o == this) { 272 return true; 273 } 274 if (!(o instanceof DependentColumnFilter)) { 275 return false; 276 } 277 DependentColumnFilter other = (DependentColumnFilter) o; 278 return other != null && super.areSerializedFieldsEqual(other) 279 && Bytes.equals(this.getFamily(), other.getFamily()) 280 && Bytes.equals(this.getQualifier(), other.getQualifier()) 281 && this.dropDependentColumn() == other.dropDependentColumn(); 282 } 283 284 @Override 285 public String toString() { 286 return String.format("%s (%s, %s, %s, %s, %s)", this.getClass().getSimpleName(), 287 Bytes.toStringBinary(this.columnFamily), Bytes.toStringBinary(this.columnQualifier), 288 this.dropDependentColumn, this.op.name(), 289 this.comparator != null ? Bytes.toStringBinary(this.comparator.getValue()) : "null"); 290 } 291 292 @Override 293 public boolean equals(Object obj) { 294 return obj instanceof Filter && areSerializedFieldsEqual((Filter) obj); 295 } 296 297 @Override 298 public int hashCode() { 299 return Objects.hash(Bytes.hashCode(getFamily()), Bytes.hashCode(getQualifier()), 300 dropDependentColumn(), getComparator(), getCompareOperator()); 301 } 302}