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 java.io.IOException;
021import java.nio.ByteBuffer;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.NavigableSet;
027import java.util.Set;
028import java.util.TreeMap;
029import java.util.TreeSet;
030import java.util.stream.Collectors;
031import org.apache.hadoop.hbase.HConstants;
032import org.apache.hadoop.hbase.filter.Filter;
033import org.apache.hadoop.hbase.io.TimeRange;
034import org.apache.hadoop.hbase.security.access.Permission;
035import org.apache.hadoop.hbase.security.visibility.Authorizations;
036import org.apache.hadoop.hbase.util.Bytes;
037import org.apache.yetus.audience.InterfaceAudience;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041/**
042 * Used to perform Get operations on a single row.
043 * <p>
044 * To get everything for a row, instantiate a Get object with the row to get. To further narrow the
045 * scope of what to Get, use the methods below.
046 * <p>
047 * To get all columns from specific families, execute {@link #addFamily(byte[]) addFamily} for each
048 * family to retrieve.
049 * <p>
050 * To get specific columns, execute {@link #addColumn(byte[], byte[]) addColumn} for each column to
051 * retrieve.
052 * <p>
053 * To only retrieve columns within a specific range of version timestamps, execute
054 * {@link #setTimeRange(long, long) setTimeRange}.
055 * <p>
056 * To only retrieve columns with a specific timestamp, execute {@link #setTimestamp(long)
057 * setTimestamp}.
058 * <p>
059 * To limit the number of versions of each column to be returned, execute {@link #readVersions(int)
060 * readVersions}.
061 * <p>
062 * To add a filter, call {@link #setFilter(Filter) setFilter}.
063 */
064@InterfaceAudience.Public
065public class Get extends Query implements Row {
066  private static final Logger LOG = LoggerFactory.getLogger(Get.class);
067
068  private byte[] row = null;
069  private int maxVersions = 1;
070  private boolean cacheBlocks = true;
071  private int storeLimit = -1;
072  private int storeOffset = 0;
073  private TimeRange tr = TimeRange.allTime();
074  private boolean checkExistenceOnly = false;
075  private Map<byte[], NavigableSet<byte[]>> familyMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
076
077  /**
078   * Create a Get operation for the specified row.
079   * <p>
080   * If no further operations are done, this will get the latest version of all columns in all
081   * families of the specified row.
082   * @param row row key
083   */
084  public Get(byte[] row) {
085    Mutation.checkRow(row);
086    this.row = row;
087  }
088
089  /**
090   * Copy-constructor
091   */
092  public Get(Get get) {
093    this(get.getRow());
094    // from Query
095    this.setFilter(get.getFilter());
096    this.setReplicaId(get.getReplicaId());
097    this.setConsistency(get.getConsistency());
098    this.setQueryMetricsEnabled(get.isQueryMetricsEnabled());
099    // from Get
100    this.cacheBlocks = get.getCacheBlocks();
101    this.maxVersions = get.getMaxVersions();
102    this.storeLimit = get.getMaxResultsPerColumnFamily();
103    this.storeOffset = get.getRowOffsetPerColumnFamily();
104    this.tr = get.getTimeRange();
105    this.checkExistenceOnly = get.isCheckExistenceOnly();
106    this.loadColumnFamiliesOnDemand = get.getLoadColumnFamiliesOnDemandValue();
107    Map<byte[], NavigableSet<byte[]>> fams = get.getFamilyMap();
108    for (Map.Entry<byte[], NavigableSet<byte[]>> entry : fams.entrySet()) {
109      byte[] fam = entry.getKey();
110      NavigableSet<byte[]> cols = entry.getValue();
111      if (cols != null && cols.size() > 0) {
112        for (byte[] col : cols) {
113          addColumn(fam, col);
114        }
115      } else {
116        addFamily(fam);
117      }
118    }
119    for (Map.Entry<String, byte[]> attr : get.getAttributesMap().entrySet()) {
120      setAttribute(attr.getKey(), attr.getValue());
121    }
122    for (Map.Entry<byte[], TimeRange> entry : get.getColumnFamilyTimeRange().entrySet()) {
123      TimeRange tr = entry.getValue();
124      setColumnFamilyTimeRange(entry.getKey(), tr.getMin(), tr.getMax());
125    }
126    super.setPriority(get.getPriority());
127  }
128
129  /**
130   * Create a Get operation for the specified row.
131   */
132  public Get(byte[] row, int rowOffset, int rowLength) {
133    Mutation.checkRow(row, rowOffset, rowLength);
134    this.row = Bytes.copy(row, rowOffset, rowLength);
135  }
136
137  /**
138   * Create a Get operation for the specified row.
139   */
140  public Get(ByteBuffer row) {
141    Mutation.checkRow(row);
142    this.row = new byte[row.remaining()];
143    row.get(this.row);
144  }
145
146  public boolean isCheckExistenceOnly() {
147    return checkExistenceOnly;
148  }
149
150  public Get setCheckExistenceOnly(boolean checkExistenceOnly) {
151    this.checkExistenceOnly = checkExistenceOnly;
152    return this;
153  }
154
155  /**
156   * Get all columns from the specified family.
157   * <p>
158   * Overrides previous calls to addColumn for this family.
159   * @param family family name
160   * @return the Get object
161   */
162  public Get addFamily(byte[] family) {
163    familyMap.remove(family);
164    familyMap.put(family, null);
165    return this;
166  }
167
168  /**
169   * Get the column from the specific family with the specified qualifier.
170   * <p>
171   * Overrides previous calls to addFamily for this family.
172   * @param family    family name
173   * @param qualifier column qualifier
174   * @return the Get objec
175   */
176  public Get addColumn(byte[] family, byte[] qualifier) {
177    NavigableSet<byte[]> set = familyMap.get(family);
178    if (set == null) {
179      set = new TreeSet<>(Bytes.BYTES_COMPARATOR);
180      familyMap.put(family, set);
181    }
182    if (qualifier == null) {
183      qualifier = HConstants.EMPTY_BYTE_ARRAY;
184    }
185    set.add(qualifier);
186    return this;
187  }
188
189  /**
190   * Get versions of columns only within the specified timestamp range, [minStamp, maxStamp).
191   * @param minStamp minimum timestamp value, inclusive
192   * @param maxStamp maximum timestamp value, exclusive
193   * @return this for invocation chaining
194   */
195  public Get setTimeRange(long minStamp, long maxStamp) throws IOException {
196    tr = TimeRange.between(minStamp, maxStamp);
197    return this;
198  }
199
200  /**
201   * Get versions of columns with the specified timestamp.
202   * @param timestamp version timestamp
203   * @return this for invocation chaining
204   */
205  public Get setTimestamp(long timestamp) {
206    try {
207      tr = TimeRange.at(timestamp);
208    } catch (Exception e) {
209      // This should never happen, unless integer overflow or something extremely wrong...
210      LOG.error("TimeRange failed, likely caused by integer overflow. ", e);
211      throw e;
212    }
213    return this;
214  }
215
216  @Override
217  public Get setColumnFamilyTimeRange(byte[] cf, long minStamp, long maxStamp) {
218    super.setColumnFamilyTimeRange(cf, minStamp, maxStamp);
219    return this;
220  }
221
222  /**
223   * Get all available versions.
224   * @return this for invocation chaining
225   */
226  public Get readAllVersions() {
227    this.maxVersions = Integer.MAX_VALUE;
228    return this;
229  }
230
231  /**
232   * Get up to the specified number of versions of each column.
233   * @param versions specified number of versions for each column
234   * @throws IOException if invalid number of versions
235   * @return this for invocation chaining
236   */
237  public Get readVersions(int versions) throws IOException {
238    if (versions <= 0) {
239      throw new IOException("versions must be positive");
240    }
241    this.maxVersions = versions;
242    return this;
243  }
244
245  @Override
246  public Get setLoadColumnFamiliesOnDemand(boolean value) {
247    super.setLoadColumnFamiliesOnDemand(value);
248    return this;
249  }
250
251  /**
252   * Set the maximum number of values to return per row per Column Family
253   * @param limit the maximum number of values returned / row / CF
254   * @return this for invocation chaining
255   */
256  public Get setMaxResultsPerColumnFamily(int limit) {
257    this.storeLimit = limit;
258    return this;
259  }
260
261  /**
262   * Set offset for the row per Column Family. This offset is only within a particular row/CF
263   * combination. It gets reset back to zero when we move to the next row or CF.
264   * @param offset is the number of kvs that will be skipped.
265   * @return this for invocation chaining
266   */
267  public Get setRowOffsetPerColumnFamily(int offset) {
268    this.storeOffset = offset;
269    return this;
270  }
271
272  @Override
273  public Get setFilter(Filter filter) {
274    super.setFilter(filter);
275    return this;
276  }
277
278  /* Accessors */
279
280  /**
281   * Set whether blocks should be cached for this Get.
282   * <p>
283   * This is true by default. When true, default settings of the table and family are used (this
284   * will never override caching blocks if the block cache is disabled for that family or entirely).
285   * @param cacheBlocks if false, default settings are overridden and blocks will not be cached
286   */
287  public Get setCacheBlocks(boolean cacheBlocks) {
288    this.cacheBlocks = cacheBlocks;
289    return this;
290  }
291
292  /**
293   * Get whether blocks should be cached for this Get.
294   * @return true if default caching should be used, false if blocks should not be cached
295   */
296  public boolean getCacheBlocks() {
297    return cacheBlocks;
298  }
299
300  /**
301   * Method for retrieving the get's row
302   */
303  @Override
304  public byte[] getRow() {
305    return this.row;
306  }
307
308  /**
309   * Method for retrieving the get's maximum number of version
310   * @return the maximum number of version to fetch for this get
311   */
312  public int getMaxVersions() {
313    return this.maxVersions;
314  }
315
316  /**
317   * Method for retrieving the get's maximum number of values to return per Column Family
318   * @return the maximum number of values to fetch per CF
319   */
320  public int getMaxResultsPerColumnFamily() {
321    return this.storeLimit;
322  }
323
324  /**
325   * Method for retrieving the get's offset per row per column family (#kvs to be skipped)
326   * @return the row offset
327   */
328  public int getRowOffsetPerColumnFamily() {
329    return this.storeOffset;
330  }
331
332  /**
333   * Method for retrieving the get's TimeRange
334   */
335  public TimeRange getTimeRange() {
336    return this.tr;
337  }
338
339  /**
340   * Method for retrieving the keys in the familyMap
341   * @return keys in the current familyMap
342   */
343  public Set<byte[]> familySet() {
344    return this.familyMap.keySet();
345  }
346
347  /**
348   * Method for retrieving the number of families to get from
349   * @return number of families
350   */
351  public int numFamilies() {
352    return this.familyMap.size();
353  }
354
355  /**
356   * Method for checking if any families have been inserted into this Get
357   * @return true if familyMap is non empty false otherwise
358   */
359  public boolean hasFamilies() {
360    return !this.familyMap.isEmpty();
361  }
362
363  /**
364   * Method for retrieving the get's familyMap
365   */
366  public Map<byte[], NavigableSet<byte[]>> getFamilyMap() {
367    return this.familyMap;
368  }
369
370  /**
371   * Compile the table and column family (i.e. schema) information into a String. Useful for parsing
372   * and aggregation by debugging, logging, and administration tools.
373   */
374  @Override
375  public Map<String, Object> getFingerprint() {
376    Map<String, Object> map = new HashMap<>();
377    List<String> families = new ArrayList<>(this.familyMap.entrySet().size());
378    map.put("families", families);
379    for (Map.Entry<byte[], NavigableSet<byte[]>> entry : this.familyMap.entrySet()) {
380      families.add(Bytes.toStringBinary(entry.getKey()));
381    }
382    return map;
383  }
384
385  /**
386   * Compile the details beyond the scope of getFingerprint (row, columns, timestamps, etc.) into a
387   * Map along with the fingerprinted information. Useful for debugging, logging, and administration
388   * tools.
389   * @param maxCols a limit on the number of columns output prior to truncation
390   */
391  @Override
392  public Map<String, Object> toMap(int maxCols) {
393    // we start with the fingerprint map and build on top of it.
394    Map<String, Object> map = getFingerprint();
395    // replace the fingerprint's simple list of families with a
396    // map from column families to lists of qualifiers and kv details
397    Map<String, List<String>> columns = new HashMap<>();
398    map.put("families", columns);
399    // add scalar information first
400    map.put("row", Bytes.toStringBinary(this.row));
401    map.put("maxVersions", this.maxVersions);
402    map.put("cacheBlocks", this.cacheBlocks);
403    List<Long> timeRange = new ArrayList<>(2);
404    timeRange.add(this.tr.getMin());
405    timeRange.add(this.tr.getMax());
406    map.put("timeRange", timeRange);
407    int colCount = 0;
408    // iterate through affected families and add details
409    for (Map.Entry<byte[], NavigableSet<byte[]>> entry : this.familyMap.entrySet()) {
410      List<String> familyList = new ArrayList<>();
411      columns.put(Bytes.toStringBinary(entry.getKey()), familyList);
412      if (entry.getValue() == null) {
413        colCount++;
414        --maxCols;
415        familyList.add("ALL");
416      } else {
417        colCount += entry.getValue().size();
418        if (maxCols <= 0) {
419          continue;
420        }
421        for (byte[] column : entry.getValue()) {
422          if (--maxCols <= 0) {
423            continue;
424          }
425          familyList.add(Bytes.toStringBinary(column));
426        }
427      }
428    }
429    map.put("totalColumns", colCount);
430    if (this.filter != null) {
431      map.put("filter", this.filter.toString());
432    }
433    // add the id if set
434    if (getId() != null) {
435      map.put("id", getId());
436    }
437    map.put("storeLimit", this.storeLimit);
438    map.put("storeOffset", this.storeOffset);
439    map.put("checkExistenceOnly", this.checkExistenceOnly);
440
441    map.put("targetReplicaId", this.targetReplicaId);
442    map.put("consistency", this.consistency);
443    map.put("loadColumnFamiliesOnDemand", this.loadColumnFamiliesOnDemand);
444    if (!colFamTimeRangeMap.isEmpty()) {
445      Map<String, List<Long>> colFamTimeRangeMapStr = colFamTimeRangeMap.entrySet().stream()
446        .collect(Collectors.toMap((e) -> Bytes.toStringBinary(e.getKey()), e -> {
447          TimeRange value = e.getValue();
448          List<Long> rangeList = new ArrayList<>();
449          rangeList.add(value.getMin());
450          rangeList.add(value.getMax());
451          return rangeList;
452        }));
453
454      map.put("colFamTimeRangeMap", colFamTimeRangeMapStr);
455    }
456    map.put("priority", getPriority());
457    map.put("queryMetricsEnabled", queryMetricsEnabled);
458    return map;
459  }
460
461  @Override
462  public int hashCode() {
463    // TODO: This is wrong. Can't have two gets the same just because on same row. But it
464    // matches how equals works currently and gets rid of the findbugs warning.
465    return Bytes.hashCode(this.getRow());
466  }
467
468  @Override
469  public boolean equals(Object obj) {
470    if (this == obj) {
471      return true;
472    }
473    if (!(obj instanceof Row)) {
474      return false;
475    }
476    Row other = (Row) obj;
477    // TODO: This is wrong. Can't have two gets the same just because on same row.
478    return Row.COMPARATOR.compare(this, other) == 0;
479  }
480
481  @Override
482  public Get setAttribute(String name, byte[] value) {
483    super.setAttribute(name, value);
484    return this;
485  }
486
487  @Override
488  public Get setId(String id) {
489    super.setId(id);
490    return this;
491  }
492
493  @Override
494  public Get setAuthorizations(Authorizations authorizations) {
495    super.setAuthorizations(authorizations);
496    return this;
497  }
498
499  @Override
500  public Get setACL(Map<String, Permission> perms) {
501    super.setACL(perms);
502    return this;
503  }
504
505  @Override
506  public Get setACL(String user, Permission perms) {
507    super.setACL(user, perms);
508    return this;
509  }
510
511  @Override
512  public Get setConsistency(Consistency consistency) {
513    super.setConsistency(consistency);
514    return this;
515  }
516
517  @Override
518  public Get setReplicaId(int Id) {
519    super.setReplicaId(Id);
520    return this;
521  }
522
523  @Override
524  public Get setIsolationLevel(IsolationLevel level) {
525    super.setIsolationLevel(level);
526    return this;
527  }
528
529  @Override
530  public Get setPriority(int priority) {
531    super.setPriority(priority);
532    return this;
533  }
534}