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.hbase.classification.InterfaceAudience;
27  import org.apache.hadoop.hbase.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.ipc.RpcControllerFactory;
32  import org.apache.hadoop.hbase.util.Bytes;
33  import org.apache.hadoop.hbase.util.ExceptionUtil;
34  
35  /**
36   * A reversed client scanner which support backward scanning
37   */
38  @InterfaceAudience.Public
39  @InterfaceStability.Evolving
40  public class ReversedClientScanner extends ClientScanner {
41    private static final Log LOG = LogFactory.getLog(ReversedClientScanner.class);
42    // A byte array in which all elements are the max byte, and it is used to
43    // construct closest front row
44    static byte[] MAX_BYTE_ARRAY = Bytes.createMaxByteArray(9);
45  
46    /**
47     * Create a new ReversibleClientScanner for the specified table Note that the
48     * passed {@link Scan}'s start row maybe changed.
49     * @param conf The {@link Configuration} to use.
50     * @param scan {@link Scan} to use in this scanner
51     * @param tableName The table that we wish to scan
52     * @param connection Connection identifying the cluster
53     * @throws IOException
54     */
55    public ReversedClientScanner(Configuration conf, Scan scan,
56        TableName tableName, HConnection connection) throws IOException {
57      super(conf, scan, tableName, connection);
58    }
59  
60    @Override
61    protected boolean nextScanner(int nbRows, final boolean done)
62        throws IOException {
63      // Close the previous scanner if it's open
64      if (this.callable != null) {
65        this.callable.setClose();
66        this.caller.callWithRetries(callable);
67        this.callable = null;
68      }
69  
70      // Where to start the next scanner
71      byte[] localStartKey;
72      boolean locateTheClosestFrontRow = true;
73      // if we're at start of table, close and return false to stop iterating
74      if (this.currentRegion != null) {
75        byte[] startKey = this.currentRegion.getStartKey();
76        if (startKey == null
77            || Bytes.equals(startKey, HConstants.EMPTY_BYTE_ARRAY)
78            || checkScanStopRow(startKey) || done) {
79          close();
80          if (LOG.isDebugEnabled()) {
81            LOG.debug("Finished " + this.currentRegion);
82          }
83          return false;
84        }
85        localStartKey = startKey;
86        if (LOG.isDebugEnabled()) {
87          LOG.debug("Finished " + this.currentRegion);
88        }
89      } else {
90        localStartKey = this.scan.getStartRow();
91        if (!Bytes.equals(localStartKey, HConstants.EMPTY_BYTE_ARRAY)) {
92          locateTheClosestFrontRow = false;
93        }
94      }
95  
96      if (LOG.isDebugEnabled() && this.currentRegion != null) {
97        // Only worth logging if NOT first region in scan.
98        LOG.debug("Advancing internal scanner to startKey at '"
99            + Bytes.toStringBinary(localStartKey) + "'");
100     }
101     try {
102       // In reversed scan, we want to locate the previous region through current
103       // region's start key. In order to get that previous region, first we
104       // create a closest row before the start key of current region, then
105       // locate all the regions from the created closest row to start key of
106       // current region, thus the last one of located regions should be the
107       // previous region of current region. The related logic of locating
108       // regions is implemented in ReversedScannerCallable
109       byte[] locateStartRow = locateTheClosestFrontRow ? createClosestRowBefore(localStartKey)
110           : null;
111       callable = getScannerCallable(localStartKey, nbRows, locateStartRow);
112       // Open a scanner on the region server starting at the
113       // beginning of the region
114       this.caller.callWithRetries(callable);
115       this.currentRegion = callable.getHRegionInfo();
116       if (this.scanMetrics != null) {
117         this.scanMetrics.countOfRegions.incrementAndGet();
118       }
119     } catch (IOException e) {
120       ExceptionUtil.rethrowIfInterrupt(e);
121       close();
122       throw e;
123     }
124     return true;
125   }
126   
127   protected ScannerCallable getScannerCallable(byte[] localStartKey,
128       int nbRows, byte[] locateStartRow) {
129     scan.setStartRow(localStartKey);
130     ScannerCallable s =
131         new ReversedScannerCallable(getConnection(), getTable(), scan, this.scanMetrics,
132             locateStartRow, rpcControllerFactory.newController());
133     s.setCaching(nbRows);
134     return s;
135   }
136 
137   @Override
138   // returns true if stopRow >= passed region startKey
139   protected boolean checkScanStopRow(final byte[] startKey) {
140     if (this.scan.getStopRow().length > 0) {
141       // there is a stop row, check to see if we are past it.
142       byte[] stopRow = scan.getStopRow();
143       int cmp = Bytes.compareTo(stopRow, 0, stopRow.length, startKey, 0,
144           startKey.length);
145       if (cmp >= 0) {
146         // stopRow >= startKey (stopRow is equals to or larger than endKey)
147         // This is a stop.
148         return true;
149       }
150     }
151     return false; // unlikely.
152   }
153 
154   /**
155    * Create the closest row before the specified row
156    * @param row
157    * @return a new byte array which is the closest front row of the specified one
158    */
159   protected byte[] createClosestRowBefore(byte[] row) {
160     if (row == null) {
161       throw new IllegalArgumentException("The passed row is empty");
162     }
163     if (Bytes.equals(row, HConstants.EMPTY_BYTE_ARRAY)) {
164       return MAX_BYTE_ARRAY;
165     }
166     if (row[row.length - 1] == 0) {
167       return Arrays.copyOf(row, row.length - 1);
168     } else {
169       byte[] closestFrontRow = Arrays.copyOf(row, row.length);
170       closestFrontRow[row.length - 1] = (byte) ((closestFrontRow[row.length - 1] & 0xff) - 1);
171       closestFrontRow = Bytes.add(closestFrontRow, MAX_BYTE_ARRAY);
172       return closestFrontRow;
173     }
174   }
175 
176 }