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.client;
019
020import org.apache.hadoop.hbase.CompareOperator;
021import org.apache.hadoop.hbase.filter.Filter;
022import org.apache.hadoop.hbase.io.TimeRange;
023import org.apache.hadoop.hbase.util.Bytes;
024import org.apache.yetus.audience.InterfaceAudience;
025import org.apache.yetus.audience.InterfaceStability;
026
027import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
028
029/**
030 * Used to perform CheckAndMutate operations.
031 * <p>
032 * Use the builder class to instantiate a CheckAndMutate object. This builder class is fluent style
033 * APIs, the code are like:
034 *
035 * <pre>
036 * <code>
037 * // A CheckAndMutate operation where do the specified action if the column (specified by the
038 * // family and the qualifier) of the row equals to the specified value
039 * CheckAndMutate checkAndMutate = CheckAndMutate.newBuilder(row)
040 *   .ifEquals(family, qualifier, value)
041 *   .build(put);
042 *
043 * // A CheckAndMutate operation where do the specified action if the column (specified by the
044 * // family and the qualifier) of the row doesn't exist
045 * CheckAndMutate checkAndMutate = CheckAndMutate.newBuilder(row)
046 *   .ifNotExists(family, qualifier)
047 *   .build(put);
048 *
049 * // A CheckAndMutate operation where do the specified action if the row matches the filter
050 * CheckAndMutate checkAndMutate = CheckAndMutate.newBuilder(row)
051 *   .ifMatches(filter)
052 *   .build(delete);
053 * </code>
054 * </pre>
055 */
056@InterfaceAudience.Public
057@InterfaceStability.Evolving
058public final class CheckAndMutate implements Row {
059
060  /**
061   * A builder class for building a CheckAndMutate object.
062   */
063  @InterfaceAudience.Public
064  @InterfaceStability.Evolving
065  public static final class Builder {
066    private final byte[] row;
067    private byte[] family;
068    private byte[] qualifier;
069    private CompareOperator op;
070    private byte[] value;
071    private Filter filter;
072    private TimeRange timeRange;
073    private boolean queryMetricsEnabled = false;
074
075    private Builder(byte[] row) {
076      this.row = Preconditions.checkNotNull(row, "row is null");
077    }
078
079    /**
080     * Check for lack of column
081     * @param family    family to check
082     * @param qualifier qualifier to check
083     * @return the CheckAndMutate object
084     */
085    public Builder ifNotExists(byte[] family, byte[] qualifier) {
086      return ifEquals(family, qualifier, null);
087    }
088
089    /**
090     * Check for equality
091     * @param family    family to check
092     * @param qualifier qualifier to check
093     * @param value     the expected value
094     * @return the CheckAndMutate object
095     */
096    public Builder ifEquals(byte[] family, byte[] qualifier, byte[] value) {
097      return ifMatches(family, qualifier, CompareOperator.EQUAL, value);
098    }
099
100    /**
101     * Check for match
102     * @param family    family to check
103     * @param qualifier qualifier to check
104     * @param compareOp comparison operator to use
105     * @param value     the expected value
106     * @return the CheckAndMutate object
107     */
108    public Builder ifMatches(byte[] family, byte[] qualifier, CompareOperator compareOp,
109      byte[] value) {
110      this.family = Preconditions.checkNotNull(family, "family is null");
111      this.qualifier = qualifier;
112      this.op = Preconditions.checkNotNull(compareOp, "compareOp is null");
113      this.value = value;
114      return this;
115    }
116
117    /**
118     * Check for match
119     * @param filter filter to check
120     * @return the CheckAndMutate object
121     */
122    public Builder ifMatches(Filter filter) {
123      this.filter = Preconditions.checkNotNull(filter, "filter is null");
124      return this;
125    }
126
127    /**
128     * Specify a timerange
129     * @param timeRange time range to check
130     * @return the CheckAndMutate object
131     */
132    public Builder timeRange(TimeRange timeRange) {
133      this.timeRange = timeRange;
134      return this;
135    }
136
137    /**
138     * Enables the return of {@link QueryMetrics} alongside the corresponding result for this query
139     * <p>
140     * This is intended for advanced users who need result-granular, server-side metrics
141     * <p>
142     * Does not work
143     * @param queryMetricsEnabled {@code true} to enable collection of per-result query metrics
144     *                            {@code false} to disable metrics collection (resulting in
145     *                            {@code null} metrics)
146     */
147    public Builder queryMetricsEnabled(boolean queryMetricsEnabled) {
148      this.queryMetricsEnabled = queryMetricsEnabled;
149      return this;
150    }
151
152    private void preCheck(Row action) {
153      Preconditions.checkNotNull(action, "action is null");
154      if (!Bytes.equals(row, action.getRow())) {
155        throw new IllegalArgumentException(
156          "The row of the action <" + Bytes.toStringBinary(action.getRow())
157            + "> doesn't match the original one <" + Bytes.toStringBinary(this.row) + ">");
158      }
159      Preconditions.checkState(op != null || filter != null,
160        "condition is null. You need to"
161          + " specify the condition by calling ifNotExists/ifEquals/ifMatches before building a"
162          + " CheckAndMutate object");
163    }
164
165    /**
166     * Build the CheckAndMutate object
167     * @param put data to put if check succeeds
168     * @return a CheckAndMutate object
169     */
170    public CheckAndMutate build(Put put) {
171      preCheck(put);
172      if (filter != null) {
173        return new CheckAndMutate(row, filter, timeRange, put, queryMetricsEnabled);
174      } else {
175        return new CheckAndMutate(row, family, qualifier, op, value, timeRange, put,
176          queryMetricsEnabled);
177      }
178    }
179
180    /**
181     * Build the CheckAndMutate object
182     * @param delete data to delete if check succeeds
183     * @return a CheckAndMutate object
184     */
185    public CheckAndMutate build(Delete delete) {
186      preCheck(delete);
187      if (filter != null) {
188        return new CheckAndMutate(row, filter, timeRange, delete, queryMetricsEnabled);
189      } else {
190        return new CheckAndMutate(row, family, qualifier, op, value, timeRange, delete,
191          queryMetricsEnabled);
192      }
193    }
194
195    /**
196     * Build the CheckAndMutate object with an Increment to commit if the check succeeds.
197     * @param increment data to increment if check succeeds
198     * @return a CheckAndMutate object
199     */
200    public CheckAndMutate build(Increment increment) {
201      preCheck(increment);
202      if (filter != null) {
203        return new CheckAndMutate(row, filter, timeRange, increment, queryMetricsEnabled);
204      } else {
205        return new CheckAndMutate(row, family, qualifier, op, value, timeRange, increment,
206          queryMetricsEnabled);
207      }
208    }
209
210    /**
211     * Build the CheckAndMutate object with an Append to commit if the check succeeds.
212     * @param append data to append if check succeeds
213     * @return a CheckAndMutate object
214     */
215    public CheckAndMutate build(Append append) {
216      preCheck(append);
217      if (filter != null) {
218        return new CheckAndMutate(row, filter, timeRange, append, queryMetricsEnabled);
219      } else {
220        return new CheckAndMutate(row, family, qualifier, op, value, timeRange, append,
221          queryMetricsEnabled);
222      }
223    }
224
225    /**
226     * Build the CheckAndMutate object with a RowMutations to commit if the check succeeds.
227     * @param mutations mutations to perform if check succeeds
228     * @return a CheckAndMutate object
229     */
230    public CheckAndMutate build(RowMutations mutations) {
231      preCheck(mutations);
232      if (filter != null) {
233        return new CheckAndMutate(row, filter, timeRange, mutations, queryMetricsEnabled);
234      } else {
235        return new CheckAndMutate(row, family, qualifier, op, value, timeRange, mutations,
236          queryMetricsEnabled);
237      }
238    }
239  }
240
241  /**
242   * returns a builder object to build a CheckAndMutate object
243   * @param row row
244   * @return a builder object
245   */
246  public static Builder newBuilder(byte[] row) {
247    return new Builder(row);
248  }
249
250  private final byte[] row;
251  private final byte[] family;
252  private final byte[] qualifier;
253  private final CompareOperator op;
254  private final byte[] value;
255  private final Filter filter;
256  private final TimeRange timeRange;
257  private final Row action;
258  private final boolean queryMetricsEnabled;
259
260  private CheckAndMutate(byte[] row, byte[] family, byte[] qualifier, final CompareOperator op,
261    byte[] value, TimeRange timeRange, Row action, boolean queryMetricsEnabled) {
262    this.row = row;
263    this.family = family;
264    this.qualifier = qualifier;
265    this.op = op;
266    this.value = value;
267    this.filter = null;
268    this.timeRange = timeRange != null ? timeRange : TimeRange.allTime();
269    this.action = action;
270    this.queryMetricsEnabled = queryMetricsEnabled;
271  }
272
273  private CheckAndMutate(byte[] row, Filter filter, TimeRange timeRange, Row action,
274    boolean queryMetricsEnabled) {
275    this.row = row;
276    this.family = null;
277    this.qualifier = null;
278    this.op = null;
279    this.value = null;
280    this.filter = filter;
281    this.timeRange = timeRange != null ? timeRange : TimeRange.allTime();
282    this.action = action;
283    this.queryMetricsEnabled = queryMetricsEnabled;
284  }
285
286  /** Returns the row */
287  @Override
288  public byte[] getRow() {
289    return row;
290  }
291
292  /** Returns the family to check */
293  public byte[] getFamily() {
294    return family;
295  }
296
297  /** Returns the qualifier to check */
298  public byte[] getQualifier() {
299    return qualifier;
300  }
301
302  /** Returns the comparison operator */
303  public CompareOperator getCompareOp() {
304    return op;
305  }
306
307  /** Returns the expected value */
308  public byte[] getValue() {
309    return value;
310  }
311
312  /** Returns the filter to check */
313  public Filter getFilter() {
314    return filter;
315  }
316
317  /** Returns whether this has a filter or not */
318  public boolean hasFilter() {
319    return filter != null;
320  }
321
322  /** Returns the time range to check */
323  public TimeRange getTimeRange() {
324    return timeRange;
325  }
326
327  /** Returns the action done if check succeeds */
328  public Row getAction() {
329    return action;
330  }
331
332  /** Returns whether query metrics are enabled */
333  public boolean isQueryMetricsEnabled() {
334    return queryMetricsEnabled;
335  }
336}