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