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