View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements. See the NOTICE file distributed with this
6    * work for additional information regarding copyright ownership. The ASF
7    * licenses this file to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   * http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16   * License for the specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.hadoop.hbase.client;
20  
21  import java.io.IOException;
22  import java.util.Arrays;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.apache.hadoop.classification.InterfaceAudience;
27  import org.apache.hadoop.classification.InterfaceStability;
28  import org.apache.hadoop.conf.Configuration;
29  import org.apache.hadoop.hbase.HConstants;
30  import org.apache.hadoop.hbase.TableName;
31  import org.apache.hadoop.hbase.util.Bytes;
32  
33  /**
34   * A reversed client scanner which support backward scanning
35   */
36  @InterfaceAudience.Public
37  @InterfaceStability.Evolving
38  public class ReversedClientScanner extends ClientScanner {
39    private static final Log LOG = LogFactory.getLog(ReversedClientScanner.class);
40    // A byte array in which all elements are the max byte, and it is used to
41    // construct closest front row
42    static byte[] MAX_BYTE_ARRAY = Bytes.createMaxByteArray(9);
43    /**
44     * Create a new ReversibleClientScanner for the specified table Note that the
45     * passed {@link Scan}'s start row maybe changed.
46     * @param conf The {@link Configuration} to use.
47     * @param scan {@link Scan} to use in this scanner
48     * @param tableName The table that we wish to scan
49     * @param connection Connection identifying the cluster
50     * @throws IOException
51     */
52    public ReversedClientScanner(Configuration conf, Scan scan,
53        TableName tableName, HConnection connection) throws IOException {
54      super(conf, scan, tableName, connection);
55    }
56  
57    @Override
58    protected boolean nextScanner(int nbRows, final boolean done)
59        throws IOException {
60      // Close the previous scanner if it's open
61      if (this.callable != null) {
62        this.callable.setClose();
63        this.caller.callWithRetries(callable);
64        this.callable = null;
65      }
66  
67      // Where to start the next scanner
68      byte[] localStartKey;
69      boolean locateTheClosestFrontRow = true;
70      // if we're at start of table, close and return false to stop iterating
71      if (this.currentRegion != null) {
72        byte[] startKey = this.currentRegion.getStartKey();
73        if (startKey == null
74            || Bytes.equals(startKey, HConstants.EMPTY_BYTE_ARRAY)
75            || checkScanStopRow(startKey) || done) {
76          close();
77          if (LOG.isDebugEnabled()) {
78            LOG.debug("Finished " + this.currentRegion);
79          }
80          return false;
81        }
82        localStartKey = startKey;
83        if (LOG.isDebugEnabled()) {
84          LOG.debug("Finished " + this.currentRegion);
85        }
86      } else {
87        localStartKey = this.scan.getStartRow();
88        if (!Bytes.equals(localStartKey, HConstants.EMPTY_BYTE_ARRAY)) {
89          locateTheClosestFrontRow = false;
90        }
91      }
92  
93      if (LOG.isDebugEnabled() && this.currentRegion != null) {
94        // Only worth logging if NOT first region in scan.
95        LOG.debug("Advancing internal scanner to startKey at '"
96            + Bytes.toStringBinary(localStartKey) + "'");
97      }
98      try {
99        // In reversed scan, we want to locate the previous region through current
100       // region's start key. In order to get that previous region, first we
101       // create a closest row before the start key of current region, then
102       // locate all the regions from the created closest row to start key of
103       // current region, thus the last one of located regions should be the
104       // previous region of current region. The related logic of locating
105       // regions is implemented in ReversedScannerCallable
106       byte[] locateStartRow = locateTheClosestFrontRow ? createClosestRowBefore(localStartKey)
107           : null;
108       callable = getScannerCallable(localStartKey, nbRows, locateStartRow);
109       // Open a scanner on the region server starting at the
110       // beginning of the region
111       this.caller.callWithRetries(callable);
112       this.currentRegion = callable.getHRegionInfo();
113       if (this.scanMetrics != null) {
114         this.scanMetrics.countOfRegions.incrementAndGet();
115       }
116     } catch (IOException e) {
117       close();
118       throw e;
119     }
120     return true;
121   }
122   
123   protected ScannerCallable getScannerCallable(byte[] localStartKey,
124       int nbRows, byte[] locateStartRow) {
125     scan.setStartRow(localStartKey);
126     ScannerCallable s = new ReversedScannerCallable(getConnection(),
127         getTable(), scan, this.scanMetrics, locateStartRow);
128     s.setCaching(nbRows);
129     return s;
130   }
131 
132   @Override
133   // returns true if stopRow >= passed region startKey
134   protected boolean checkScanStopRow(final byte[] startKey) {
135     if (this.scan.getStopRow().length > 0) {
136       // there is a stop row, check to see if we are past it.
137       byte[] stopRow = scan.getStopRow();
138       int cmp = Bytes.compareTo(stopRow, 0, stopRow.length, startKey, 0,
139           startKey.length);
140       if (cmp >= 0) {
141         // stopRow >= startKey (stopRow is equals to or larger than endKey)
142         // This is a stop.
143         return true;
144       }
145     }
146     return false; // unlikely.
147   }
148 
149   /**
150    * Create the closest row before the specified row
151    * @param row
152    * @return a new byte array which is the closest front row of the specified one
153    */
154   private byte[] createClosestRowBefore(byte[] row) {
155     if (row == null) {
156       throw new IllegalArgumentException("The passed row is empty");
157     }
158     if (Bytes.equals(row, HConstants.EMPTY_BYTE_ARRAY)) {
159       return MAX_BYTE_ARRAY;
160     }
161     if (row[row.length - 1] == 0) {
162       return Arrays.copyOf(row, row.length - 1);
163     } else {
164       byte[] closestFrontRow = Arrays.copyOf(row, row.length);
165       closestFrontRow[row.length - 1] = (byte) ((closestFrontRow[row.length - 1] & 0xff) - 1);
166       closestFrontRow = Bytes.add(closestFrontRow, MAX_BYTE_ARRAY);
167       return closestFrontRow;
168     }
169   }
170 
171 }