1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  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,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.io;
20  
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
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.fs.FSDataInputStream;
28  import org.apache.hadoop.fs.FileSystem;
29  import org.apache.hadoop.fs.Path;
30  import org.apache.hadoop.hbase.KeyValue;
31  import org.apache.hadoop.hbase.client.Scan;
32  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
33  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
34  import org.apache.hadoop.hbase.io.hfile.HFileScanner;
35  import org.apache.hadoop.hbase.regionserver.StoreFile;
36  import org.apache.hadoop.hbase.util.Bytes;
37  
38  /**
39   * A facade for a {@link org.apache.hadoop.hbase.io.hfile.HFile.Reader} that serves up
40   * either the top or bottom half of a HFile where 'bottom' is the first half
41   * of the file containing the keys that sort lowest and 'top' is the second half
42   * of the file with keys that sort greater than those of the bottom half.
43   * The top includes the split files midkey, of the key that follows if it does
44   * not exist in the file.
45   *
46   * <p>This type works in tandem with the {@link Reference} type.  This class
47   * is used reading while Reference is used writing.
48   *
49   * <p>This file is not splitable.  Calls to {@link #midkey()} return null.
50   */
51  @InterfaceAudience.Private
52  public class HalfStoreFileReader extends StoreFile.Reader {
53    final Log LOG = LogFactory.getLog(HalfStoreFileReader.class);
54    final boolean top;
55    // This is the key we split around.  Its the first possible entry on a row:
56    // i.e. empty column and a timestamp of LATEST_TIMESTAMP.
57    protected final byte [] splitkey;
58    
59    private byte[] firstKey = null;
60    
61    private boolean firstKeySeeked = false;
62  
63    /**
64     * Creates a half file reader for a normal hfile.
65     * @param fs fileystem to read from
66     * @param p path to hfile
67     * @param cacheConf
68     * @param r original reference file (contains top or bottom)
69     * @param preferredEncodingInCache
70     * @throws IOException
71     */
72    public HalfStoreFileReader(final FileSystem fs, final Path p,
73        final CacheConfig cacheConf, final Reference r,
74        DataBlockEncoding preferredEncodingInCache) throws IOException {
75      super(fs, p, cacheConf, preferredEncodingInCache);
76      // This is not actual midkey for this half-file; its just border
77      // around which we split top and bottom.  Have to look in files to find
78      // actual last and first keys for bottom and top halves.  Half-files don't
79      // have an actual midkey themselves. No midkey is how we indicate file is
80      // not splittable.
81      this.splitkey = r.getSplitKey();
82      // Is it top or bottom half?
83      this.top = Reference.isTopFileRegion(r.getFileRegion());
84    }
85  
86    /**
87     * Creates a half file reader for a hfile referred to by an hfilelink.
88     * @param fs fileystem to read from
89     * @param p path to hfile
90     * @param in {@link FSDataInputStreamWrapper}
91     * @param size Full size of the hfile file
92     * @param cacheConf
93     * @param r original reference file (contains top or bottom)
94     * @param preferredEncodingInCache
95     * @throws IOException
96     */
97    public HalfStoreFileReader(final FileSystem fs, final Path p, final FSDataInputStreamWrapper in,
98        long size, final CacheConfig cacheConf,  final Reference r,
99        final DataBlockEncoding preferredEncodingInCache) throws IOException {
100     super(fs, p, in, size, cacheConf, preferredEncodingInCache);
101     // This is not actual midkey for this half-file; its just border
102     // around which we split top and bottom.  Have to look in files to find
103     // actual last and first keys for bottom and top halves.  Half-files don't
104     // have an actual midkey themselves. No midkey is how we indicate file is
105     // not splittable.
106     this.splitkey = r.getSplitKey();
107     // Is it top or bottom half?
108     this.top = Reference.isTopFileRegion(r.getFileRegion());
109   }
110 
111   protected boolean isTop() {
112     return this.top;
113   }
114 
115   @Override
116   public HFileScanner getScanner(final boolean cacheBlocks,
117       final boolean pread, final boolean isCompaction) {
118     final HFileScanner s = super.getScanner(cacheBlocks, pread, isCompaction);
119     return new HFileScanner() {
120       final HFileScanner delegate = s;
121       public boolean atEnd = false;
122 
123       public ByteBuffer getKey() {
124         if (atEnd) return null;
125         return delegate.getKey();
126       }
127 
128       public String getKeyString() {
129         if (atEnd) return null;
130 
131         return delegate.getKeyString();
132       }
133 
134       public ByteBuffer getValue() {
135         if (atEnd) return null;
136 
137         return delegate.getValue();
138       }
139 
140       public String getValueString() {
141         if (atEnd) return null;
142 
143         return delegate.getValueString();
144       }
145 
146       public KeyValue getKeyValue() {
147         if (atEnd) return null;
148 
149         return delegate.getKeyValue();
150       }
151 
152       public boolean next() throws IOException {
153         if (atEnd) return false;
154 
155         boolean b = delegate.next();
156         if (!b) {
157           return b;
158         }
159         // constrain the bottom.
160         if (!top) {
161           ByteBuffer bb = getKey();
162           if (getComparator().compare(bb.array(), bb.arrayOffset(), bb.limit(),
163               splitkey, 0, splitkey.length) >= 0) {
164             atEnd = true;
165             return false;
166           }
167         }
168         return true;
169       }
170 
171       public boolean seekBefore(byte[] key) throws IOException {
172         return seekBefore(key, 0, key.length);
173       }
174 
175       public boolean seekBefore(byte [] key, int offset, int length)
176       throws IOException {
177         if (top) {
178           byte[] fk = getFirstKey();
179           // This will be null when the file is empty in which we can not seekBefore to any key
180           if (fk == null) return false;
181           if (getComparator().compare(key, offset, length, fk, 0,
182               fk.length) <= 0) {
183             return false;
184           }
185         } else {
186           // The equals sign isn't strictly necessary just here to be consistent with seekTo
187           if (getComparator().compare(key, offset, length, splitkey, 0,
188               splitkey.length) >= 0) {
189             return this.delegate.seekBefore(splitkey, 0, splitkey.length);
190           }
191         }
192         return this.delegate.seekBefore(key, offset, length);
193       }
194 
195       public boolean seekTo() throws IOException {
196         if (top) {
197           int r = this.delegate.seekTo(splitkey);
198           if (r < 0) {
199             // midkey is < first key in file
200             return this.delegate.seekTo();
201           }
202           if (r > 0) {
203             return this.delegate.next();
204           }
205           return true;
206         }
207 
208         boolean b = delegate.seekTo();
209         if (!b) {
210           return b;
211         }
212         // Check key.
213         ByteBuffer k = this.delegate.getKey();
214         return this.delegate.getReader().getComparator().
215           compare(k.array(), k.arrayOffset(), k.limit(),
216             splitkey, 0, splitkey.length) < 0;
217       }
218 
219       public int seekTo(byte[] key) throws IOException {
220         return seekTo(key, 0, key.length);
221       }
222 
223       public int seekTo(byte[] key, int offset, int length) throws IOException {
224         if (top) {
225           if (getComparator().compare(key, offset, length, splitkey, 0,
226               splitkey.length) < 0) {
227             return -1;
228           }
229         } else {
230           if (getComparator().compare(key, offset, length, splitkey, 0,
231               splitkey.length) >= 0) {
232             // we would place the scanner in the second half.
233             // it might be an error to return false here ever...
234             boolean res = delegate.seekBefore(splitkey, 0, splitkey.length);
235             if (!res) {
236               throw new IOException("Seeking for a key in bottom of file, but key exists in top of file, failed on seekBefore(midkey)");
237             }
238             return 1;
239           }
240         }
241         return delegate.seekTo(key, offset, length);
242       }
243 
244       @Override
245       public int reseekTo(byte[] key) throws IOException {
246         return reseekTo(key, 0, key.length);
247       }
248 
249       @Override
250       public int reseekTo(byte[] key, int offset, int length)
251       throws IOException {
252         //This function is identical to the corresponding seekTo function except
253         //that we call reseekTo (and not seekTo) on the delegate.
254         if (top) {
255           if (getComparator().compare(key, offset, length, splitkey, 0,
256               splitkey.length) < 0) {
257             return -1;
258           }
259         } else {
260           if (getComparator().compare(key, offset, length, splitkey, 0,
261               splitkey.length) >= 0) {
262             // we would place the scanner in the second half.
263             // it might be an error to return false here ever...
264             boolean res = delegate.seekBefore(splitkey, 0, splitkey.length);
265             if (!res) {
266               throw new IOException("Seeking for a key in bottom of file, but" +
267                   " key exists in top of file, failed on seekBefore(midkey)");
268             }
269             return 1;
270           }
271         }
272         if (atEnd) {
273           // skip the 'reseek' and just return 1.
274           return 1;
275         }
276         return delegate.reseekTo(key, offset, length);
277       }
278 
279       public org.apache.hadoop.hbase.io.hfile.HFile.Reader getReader() {
280         return this.delegate.getReader();
281       }
282 
283       public boolean isSeeked() {
284         return this.delegate.isSeeked();
285       }
286     };
287   }
288   
289   @Override
290   public boolean passesKeyRangeFilter(Scan scan) {
291     return true;
292   }
293   
294   @Override
295   public byte[] getLastKey() {
296     if (top) {
297       return super.getLastKey();
298     }
299     // Get a scanner that caches the block and that uses pread.
300     HFileScanner scanner = getScanner(true, true);
301     try {
302       if (scanner.seekBefore(this.splitkey)) {
303         return Bytes.toBytes(scanner.getKey());
304       }
305     } catch (IOException e) {
306       LOG.warn("Failed seekBefore " + Bytes.toStringBinary(this.splitkey), e);
307     }
308     return null;
309   }
310 
311   @Override
312   public byte[] midkey() throws IOException {
313     // Returns null to indicate file is not splitable.
314     return null;
315   }
316   
317   @Override
318   public byte[] getFirstKey() {
319     if (!firstKeySeeked) {
320       HFileScanner scanner = getScanner(true, true, false);
321       try {
322         if (scanner.seekTo()) {
323           this.firstKey = Bytes.toBytes(scanner.getKey());
324         }
325         firstKeySeeked = true;
326       } catch (IOException e) {
327         LOG.warn("Failed seekTo first KV in the file", e);
328       }
329     }
330     return this.firstKey;
331   }
332 }