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