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