All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.cloud.bigtable.hbase.adapters.CheckAndMutateUtil Maven / Gradle / Ivy

Go to download

This project contains artifacts that adapt bigtable client to work with hbase.

There is a newer version: 2.14.8
Show newest version
/*
 * Copyright 2018 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.cloud.bigtable.hbase.adapters;

import com.google.api.core.InternalApi;
import com.google.bigtable.v2.CheckAndMutateRowRequest;
import com.google.bigtable.v2.RowFilter;
import com.google.cloud.bigtable.data.v2.internal.RequestContext;
import com.google.cloud.bigtable.data.v2.models.ConditionalRowMutation;
import com.google.cloud.bigtable.data.v2.models.Mutation;
import com.google.cloud.bigtable.data.v2.models.Query;
import com.google.cloud.bigtable.hbase.adapters.read.ReadHooks;
import com.google.cloud.bigtable.hbase.filter.TimestampRangeFilter;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.protobuf.ByteString;
import java.util.Arrays;
import javax.annotation.Nullable;
import org.apache.hadoop.hbase.DoNotRetryIOException;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.RowMutations;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.BinaryComparator;
import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.ValueFilter;

/** For internal use only - public for technical reasons. */
@InternalApi("For internal usage only")
public class CheckAndMutateUtil {

  // ReadHooks don't make sense from conditional mutations. If any filter attempts to make use of
  // them (which they shouldn't since we built the filter), throw an exception.
  private static final ReadHooks UNSUPPORTED_READ_HOOKS =
      new ReadHooks() {
        @Override
        public void composePreSendHook(Function newHook) {
          throw new IllegalStateException("We built a bad Filter for conditional mutation.");
        }

        @Override
        public void applyPreSendHook(Query query) {
          throw new UnsupportedOperationException(
              "We built a bad Filter for conditional mutation.");
        }
      };

  /**
   * wasMutationApplied.
   *
   * @param request a {@link ConditionalRowMutation} object.
   * @param predicateMatched a {@link Boolean} object.
   * @return a boolean.
   */
  public static boolean wasMutationApplied(
      ConditionalRowMutation request, Boolean predicateMatched) {

    // TODO: ConditionalRowMutation should have methods to check if it has true/false mutations
    CheckAndMutateRowRequest proto =
        request.toProto(RequestContext.create("SomeProject", "Some Instance", ""));
    // If we have true mods, we want the predicate to have matched.
    // If we have false mods, we did not want the predicate to have matched.
    return (proto.getTrueMutationsCount() > 0 && predicateMatched)
        || (proto.getFalseMutationsCount() > 0 && !predicateMatched);
  }

  /**
   * This class can be used to convert HBase checkAnd* operations to Bigtable {@link
   * ConditionalRowMutation}s.
   */
  public static class RequestBuilder {
    private final HBaseRequestAdapter hbaseAdapter;

    private final Mutation mutations = Mutation.createUnsafe();

    private final byte[] row;
    private final byte[] family;
    private byte[] qualifier;
    private CompareOp compareOp;
    private byte[] value;
    private boolean checkNonExistence = false;
    private Filter timeFilter = null;

    /**
     * RequestBuilder.
     *
     * @param hbaseAdapter a {@link HBaseRequestAdapter} used to convert HBase {@link
     *     org.apache.hadoop.hbase.client.Mutation} to Cloud Bigtable {@link
     *     com.google.cloud.bigtable.data.v2.models.Mutation}
     * @param row the RowKey in which to to check value matching
     * @param family the family in which to check value matching.
     */
    public RequestBuilder(HBaseRequestAdapter hbaseAdapter, byte[] row, byte[] family) {
      this.row = Preconditions.checkNotNull(row, "row is null");
      this.family = Preconditions.checkNotNull(family, "family is null");

      // The hbaseAdapter used here should not set client-side timestamps, since that may cause
      // strange contention
      // issues.  See issue #1709.
      this.hbaseAdapter = hbaseAdapter.withServerSideTimestamps();
    }

    public RequestBuilder qualifier(byte[] qualifier) {
      this.qualifier =
          Preconditions.checkNotNull(
              qualifier,
              "qualifier is null. Consider using"
                  + " an empty byte array, or just do not call this method if you want a null qualifier");
      return this;
    }

    public RequestBuilder ifNotExists() {
      Preconditions.checkState(
          compareOp == null, "ifNotExists and ifMatches are mutually exclusive");
      this.checkNonExistence = true;
      return this;
    }

    /**
     * For non-null values, this produces a {@link RowFilter} equivalent to:
     *
     * 
     *   new ValueFilter(reverseCompareOp(compareOp), new BinaryComparator(value)
     * 
* *

Null values are bit tricky. In HBase 1.* style check and mutate, value == null always * means `check non-existence` regardless of compareOp. That's semantically confusing for * CompareOperators other than EQUALS. It's even more confusing if value == null and compareOp = * NOT_EQUALS. * *

HBase 2.* APIs introduced an explicit method of checking for "Non-Existence" as an * independent concept from compareOp, which is the inspiration for the {@link #ifNotExists()} * method. * *

Checking for existence in HBase can be expressed as follows: * *

     * compareOp = CompareOp.GREATER_OR_EQUAL, value = new byte[]{Byte.MIN_VALUE})
     * 
* *

Bigtable decided that value == null with a compareOp of NOT_EQUALS actually means check * for existence, even though HBase will still treat it as non-existence. * * @param compareOp a {@link CompareOp} * @param value a byte array. * @return a {@link RequestBuilder} object with compareOp and byte array value. */ public RequestBuilder ifMatches(CompareOp compareOp, @Nullable byte[] value) { Preconditions.checkState( checkNonExistence == false, "ifNotExists and ifMatches are mutually exclusive"); this.compareOp = Preconditions.checkNotNull(compareOp, "compareOp is null"); // TODO (issue #1704): only NOT_EQUALS should allow null. Everything else should use // ifNotExists(); this.value = value; return this; } public RequestBuilder timeRange(long start, long end) { if (start == 0L && end == Long.MAX_VALUE) { return this; } Preconditions.checkArgument((0L <= start) && (start < end), "Invalid start/end"); this.timeFilter = new TimestampRangeFilter(start, end); return this; } public RequestBuilder withPut(Put put) throws DoNotRetryIOException { validateRow(put.getRow()); hbaseAdapter.adapt(put, mutations); return this; } public RequestBuilder withDelete(Delete delete) throws DoNotRetryIOException { validateRow(delete.getRow()); hbaseAdapter.adapt(delete, mutations); return this; } public RequestBuilder withMutations(RowMutations rm) throws DoNotRetryIOException { validateRow(rm.getRow()); hbaseAdapter.adapt(rm, mutations); return this; } private void validateRow(byte[] actionRow) throws DoNotRetryIOException { if (!Arrays.equals(actionRow, row)) { // The following odd exception message is for compatibility with HBase. throw new DoNotRetryIOException("Action's getRow must match the passed row"); } } public ConditionalRowMutation build() { Preconditions.checkState( checkNonExistence || compareOp != null, "condition is null. You need to specify the condition by" + " calling ifNotExists/ifEquals/ifMatches before executing the request"); ConditionalRowMutation conditionalRowMutation = ConditionalRowMutation.create(hbaseAdapter.getTableId(), ByteString.copyFrom(row)); Scan scan = new Scan(); scan.setMaxVersions(1); scan.addColumn(family, qualifier); if (value == null || value.length == 0 || checkNonExistence) { // See ifMatches javadoc for more information on this if (CompareOp.NOT_EQUAL.equals(compareOp)) { // check for existence conditionalRowMutation.then(mutations); } else { // check for non-existence conditionalRowMutation.otherwise(mutations); } if (timeFilter != null) { scan.setFilter(timeFilter); } } else { ValueFilter valueFilter = new ValueFilter(reverseCompareOp(compareOp), new BinaryComparator(value)); if (timeFilter != null) { scan.setFilter(new FilterList(timeFilter, valueFilter)); } else { scan.setFilter(valueFilter); } conditionalRowMutation.then(mutations); } conditionalRowMutation.condition( Adapters.SCAN_ADAPTER.buildFilter(scan, UNSUPPORTED_READ_HOOKS)); return conditionalRowMutation; } } /** * For some reason, the ordering of CheckAndMutate operations is the inverse order of normal * {@link ValueFilter} operations. * * @param compareOp * @return the inverse of compareOp */ private static CompareOp reverseCompareOp(CompareOp compareOp) { switch (compareOp) { case EQUAL: case NOT_EQUAL: case NO_OP: return compareOp; case LESS: return CompareOp.GREATER; case LESS_OR_EQUAL: return CompareOp.GREATER_OR_EQUAL; case GREATER: return CompareOp.LESS; case GREATER_OR_EQUAL: return CompareOp.LESS_OR_EQUAL; default: return CompareOp.NO_OP; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy