1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.regionserver;
20
21 import java.io.DataInput;
22 import java.io.IOException;
23 import java.net.InetSocketAddress;
24 import java.nio.ByteBuffer;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.Map;
30 import java.util.SortedSet;
31 import java.util.UUID;
32 import java.util.concurrent.atomic.AtomicBoolean;
33 import java.util.concurrent.atomic.AtomicInteger;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.apache.hadoop.conf.Configuration;
38 import org.apache.hadoop.fs.FileSystem;
39 import org.apache.hadoop.fs.Path;
40 import org.apache.hadoop.hbase.Cell;
41 import org.apache.hadoop.hbase.CellUtil;
42 import org.apache.hadoop.hbase.HConstants;
43 import org.apache.hadoop.hbase.HDFSBlocksDistribution;
44 import org.apache.hadoop.hbase.KeyValue;
45 import org.apache.hadoop.hbase.KeyValue.KVComparator;
46 import org.apache.hadoop.hbase.KeyValueUtil;
47 import org.apache.hadoop.hbase.classification.InterfaceAudience;
48 import org.apache.hadoop.hbase.client.Scan;
49 import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
50 import org.apache.hadoop.hbase.io.TimeRange;
51 import org.apache.hadoop.hbase.io.hfile.BlockType;
52 import org.apache.hadoop.hbase.io.hfile.CacheConfig;
53 import org.apache.hadoop.hbase.io.hfile.HFile;
54 import org.apache.hadoop.hbase.io.hfile.HFileContext;
55 import org.apache.hadoop.hbase.io.hfile.HFileScanner;
56 import org.apache.hadoop.hbase.io.hfile.HFileWriterV2;
57 import org.apache.hadoop.hbase.regionserver.compactions.Compactor;
58 import org.apache.hadoop.hbase.util.BloomFilter;
59 import org.apache.hadoop.hbase.util.BloomFilterFactory;
60 import org.apache.hadoop.hbase.util.BloomFilterWriter;
61 import org.apache.hadoop.hbase.util.Bytes;
62 import org.apache.hadoop.io.WritableUtils;
63
64 import com.google.common.annotations.VisibleForTesting;
65 import com.google.common.base.Function;
66 import com.google.common.base.Preconditions;
67 import com.google.common.collect.ImmutableList;
68 import com.google.common.collect.Ordering;
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 @InterfaceAudience.LimitedPrivate("Coprocessor")
84 public class StoreFile {
85 private static final Log LOG = LogFactory.getLog(StoreFile.class.getName());
86
87
88
89
90 public static final byte [] MAX_SEQ_ID_KEY = Bytes.toBytes("MAX_SEQ_ID_KEY");
91
92
93 public static final byte[] MAJOR_COMPACTION_KEY =
94 Bytes.toBytes("MAJOR_COMPACTION_KEY");
95
96
97 public static final byte[] EXCLUDE_FROM_MINOR_COMPACTION_KEY =
98 Bytes.toBytes("EXCLUDE_FROM_MINOR_COMPACTION");
99
100
101 public static final byte[] BLOOM_FILTER_TYPE_KEY =
102 Bytes.toBytes("BLOOM_FILTER_TYPE");
103
104
105 public static final byte[] DELETE_FAMILY_COUNT =
106 Bytes.toBytes("DELETE_FAMILY_COUNT");
107
108
109 private static final byte[] LAST_BLOOM_KEY = Bytes.toBytes("LAST_BLOOM_KEY");
110
111
112 public static final byte[] TIMERANGE_KEY = Bytes.toBytes("TIMERANGE");
113
114
115 public static final byte[] EARLIEST_PUT_TS = Bytes.toBytes("EARLIEST_PUT_TS");
116
117 private final StoreFileInfo fileInfo;
118 private final FileSystem fs;
119
120
121 private final CacheConfig cacheConf;
122
123
124
125 private long sequenceid = -1;
126
127
128
129 private long maxMemstoreTS = -1;
130
131
132 private byte[] firstKey;
133
134 private byte[] lastKey;
135
136 private KVComparator comparator;
137
138 CacheConfig getCacheConf() {
139 return cacheConf;
140 }
141
142 public byte[] getFirstKey() {
143 return firstKey;
144 }
145
146 public byte[] getLastKey() {
147 return lastKey;
148 }
149
150 public KVComparator getComparator() {
151 return comparator;
152 }
153
154 public long getMaxMemstoreTS() {
155 return maxMemstoreTS;
156 }
157
158 public void setMaxMemstoreTS(long maxMemstoreTS) {
159 this.maxMemstoreTS = maxMemstoreTS;
160 }
161
162
163
164 private AtomicBoolean majorCompaction = null;
165
166
167
168 private boolean excludeFromMinorCompaction = false;
169
170
171 public static final byte[] BULKLOAD_TASK_KEY =
172 Bytes.toBytes("BULKLOAD_SOURCE_TASK");
173 public static final byte[] BULKLOAD_TIME_KEY =
174 Bytes.toBytes("BULKLOAD_TIMESTAMP");
175
176
177
178
179 private Map<byte[], byte[]> metadataMap;
180
181
182 private volatile Reader reader;
183
184
185
186
187
188 private final BloomType cfBloomType;
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205 public StoreFile(final FileSystem fs, final Path p, final Configuration conf,
206 final CacheConfig cacheConf, final BloomType cfBloomType) throws IOException {
207 this(fs, new StoreFileInfo(conf, fs, p), conf, cacheConf, cfBloomType);
208 }
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 public StoreFile(final FileSystem fs, final StoreFileInfo fileInfo, final Configuration conf,
227 final CacheConfig cacheConf, final BloomType cfBloomType) throws IOException {
228 this.fs = fs;
229 this.fileInfo = fileInfo;
230 this.cacheConf = cacheConf;
231
232 if (BloomFilterFactory.isGeneralBloomEnabled(conf)) {
233 this.cfBloomType = cfBloomType;
234 } else {
235 LOG.info("Ignoring bloom filter check for file " + this.getPath() + ": " +
236 "cfBloomType=" + cfBloomType + " (disabled in config)");
237 this.cfBloomType = BloomType.NONE;
238 }
239 }
240
241
242
243
244
245 public StoreFile(final StoreFile other) {
246 this.fs = other.fs;
247 this.fileInfo = other.fileInfo;
248 this.cacheConf = other.cacheConf;
249 this.cfBloomType = other.cfBloomType;
250 }
251
252
253
254
255 public StoreFile cloneForReader() {
256 return new StoreFile(this);
257 }
258
259
260
261
262
263 public StoreFileInfo getFileInfo() {
264 return this.fileInfo;
265 }
266
267
268
269
270 public Path getPath() {
271 return this.fileInfo.getPath();
272 }
273
274
275
276
277 public Path getQualifiedPath() {
278 return this.fileInfo.getPath().makeQualified(fs);
279 }
280
281
282
283
284
285 public boolean isReference() {
286 return this.fileInfo.isReference();
287 }
288
289
290
291
292 public boolean isHFile() {
293 return StoreFileInfo.isHFile(this.fileInfo.getPath());
294 }
295
296
297
298
299 public boolean isMajorCompaction() {
300 if (this.majorCompaction == null) {
301 throw new NullPointerException("This has not been set yet");
302 }
303 return this.majorCompaction.get();
304 }
305
306
307
308
309 public boolean excludeFromMinorCompaction() {
310 return this.excludeFromMinorCompaction;
311 }
312
313
314
315
316 public long getMaxSequenceId() {
317 return this.sequenceid;
318 }
319
320 public long getModificationTimeStamp() throws IOException {
321 return (fileInfo == null) ? 0 : fileInfo.getModificationTime();
322 }
323
324
325
326
327
328
329 public byte[] getMetadataValue(byte[] key) {
330 return metadataMap.get(key);
331 }
332
333
334
335
336
337
338
339
340
341 public static long getMaxMemstoreTSInList(Collection<StoreFile> sfs) {
342 long max = 0;
343 for (StoreFile sf : sfs) {
344 if (!sf.isBulkLoadResult()) {
345 max = Math.max(max, sf.getMaxMemstoreTS());
346 }
347 }
348 return max;
349 }
350
351
352
353
354
355
356
357
358 public static long getMaxSequenceIdInList(Collection<StoreFile> sfs) {
359 long max = 0;
360 for (StoreFile sf : sfs) {
361 max = Math.max(max, sf.getMaxSequenceId());
362 }
363 return max;
364 }
365
366
367
368
369
370
371
372
373
374
375
376 public boolean isBulkLoadResult() {
377 boolean bulkLoadedHFile = false;
378 String fileName = this.getPath().getName();
379 int startPos = fileName.indexOf("SeqId_");
380 if (startPos != -1) {
381 bulkLoadedHFile = true;
382 }
383 return bulkLoadedHFile || metadataMap.containsKey(BULKLOAD_TIME_KEY);
384 }
385
386 @VisibleForTesting
387 public boolean isCompactedAway() {
388 if (this.reader != null) {
389 return this.reader.isCompactedAway();
390 }
391 return true;
392 }
393
394 @VisibleForTesting
395 public int getRefCount() {
396 return this.reader.refCount.get();
397 }
398
399
400
401
402 public long getBulkLoadTimestamp() {
403 byte[] bulkLoadTimestamp = metadataMap.get(BULKLOAD_TIME_KEY);
404 return (bulkLoadTimestamp == null) ? 0 : Bytes.toLong(bulkLoadTimestamp);
405 }
406
407
408
409
410
411 public HDFSBlocksDistribution getHDFSBlockDistribution() {
412 return this.fileInfo.getHDFSBlockDistribution();
413 }
414
415
416
417
418
419
420
421 private Reader open(boolean canUseDropBehind) throws IOException {
422 if (this.reader != null) {
423 throw new IllegalAccessError("Already open");
424 }
425
426
427 this.reader = fileInfo.open(this.fs, this.cacheConf, canUseDropBehind);
428
429
430 metadataMap = Collections.unmodifiableMap(this.reader.loadFileInfo());
431
432
433 byte [] b = metadataMap.get(MAX_SEQ_ID_KEY);
434 if (b != null) {
435
436
437
438
439
440 this.sequenceid = Bytes.toLong(b);
441 if (fileInfo.isTopReference()) {
442 this.sequenceid += 1;
443 }
444 }
445
446 if (isBulkLoadResult()){
447
448
449 String fileName = this.getPath().getName();
450
451 int startPos = fileName.lastIndexOf("SeqId_");
452 if (startPos != -1) {
453 this.sequenceid = Long.parseLong(fileName.substring(startPos + 6,
454 fileName.indexOf('_', startPos + 6)));
455
456 if (fileInfo.isTopReference()) {
457 this.sequenceid += 1;
458 }
459 }
460 this.reader.setBulkLoaded(true);
461 }
462 this.reader.setSequenceID(this.sequenceid);
463
464 b = metadataMap.get(HFileWriterV2.MAX_MEMSTORE_TS_KEY);
465 if (b != null) {
466 this.maxMemstoreTS = Bytes.toLong(b);
467 }
468
469 b = metadataMap.get(MAJOR_COMPACTION_KEY);
470 if (b != null) {
471 boolean mc = Bytes.toBoolean(b);
472 if (this.majorCompaction == null) {
473 this.majorCompaction = new AtomicBoolean(mc);
474 } else {
475 this.majorCompaction.set(mc);
476 }
477 } else {
478
479
480 this.majorCompaction = new AtomicBoolean(false);
481 }
482
483 b = metadataMap.get(EXCLUDE_FROM_MINOR_COMPACTION_KEY);
484 this.excludeFromMinorCompaction = (b != null && Bytes.toBoolean(b));
485
486 BloomType hfileBloomType = reader.getBloomFilterType();
487 if (cfBloomType != BloomType.NONE) {
488 reader.loadBloomfilter(BlockType.GENERAL_BLOOM_META);
489 if (hfileBloomType != cfBloomType) {
490 LOG.info("HFile Bloom filter type for "
491 + reader.getHFileReader().getName() + ": " + hfileBloomType
492 + ", but " + cfBloomType + " specified in column family "
493 + "configuration");
494 }
495 } else if (hfileBloomType != BloomType.NONE) {
496 LOG.info("Bloom filter turned off by CF config for "
497 + reader.getHFileReader().getName());
498 }
499
500
501 reader.loadBloomfilter(BlockType.DELETE_FAMILY_BLOOM_META);
502
503 try {
504 this.reader.timeRange = TimeRangeTracker.getTimeRange(metadataMap.get(TIMERANGE_KEY));
505 } catch (IllegalArgumentException e) {
506 LOG.error("Error reading timestamp range data from meta -- " +
507 "proceeding without", e);
508 this.reader.timeRange = null;
509 }
510
511 firstKey = reader.getFirstKey();
512 lastKey = reader.getLastKey();
513 comparator = reader.getComparator();
514 return this.reader;
515 }
516
517 public Reader createReader() throws IOException {
518 return createReader(false);
519 }
520
521
522
523
524
525 public Reader createReader(boolean canUseDropBehind) throws IOException {
526 if (this.reader == null) {
527 try {
528 this.reader = open(canUseDropBehind);
529 } catch (IOException e) {
530 try {
531 boolean evictOnClose =
532 cacheConf != null? cacheConf.shouldEvictOnClose(): true;
533 this.closeReader(evictOnClose);
534 } catch (IOException ee) {
535 }
536 throw e;
537 }
538
539 }
540 return this.reader;
541 }
542
543
544
545
546
547 public Reader getReader() {
548 return this.reader;
549 }
550
551
552
553
554
555 public synchronized void closeReader(boolean evictOnClose)
556 throws IOException {
557 if (this.reader != null) {
558 this.reader.close(evictOnClose);
559 this.reader = null;
560 }
561 }
562
563
564
565
566 public void markCompactedAway() {
567 if (this.reader != null) {
568 this.reader.markCompactedAway();
569 }
570 }
571
572
573
574
575
576 public void deleteReader() throws IOException {
577 boolean evictOnClose =
578 cacheConf != null? cacheConf.shouldEvictOnClose(): true;
579 closeReader(evictOnClose);
580 this.fs.delete(getPath(), true);
581 }
582
583 @Override
584 public String toString() {
585 return this.fileInfo.toString();
586 }
587
588
589
590
591 public String toStringDetailed() {
592 StringBuilder sb = new StringBuilder();
593 sb.append(this.getPath().toString());
594 sb.append(", isReference=").append(isReference());
595 sb.append(", isBulkLoadResult=").append(isBulkLoadResult());
596 if (isBulkLoadResult()) {
597 sb.append(", bulkLoadTS=").append(getBulkLoadTimestamp());
598 } else {
599 sb.append(", seqid=").append(getMaxSequenceId());
600 }
601 sb.append(", majorCompaction=").append(isMajorCompaction());
602
603 return sb.toString();
604 }
605
606 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="ICAST_INTEGER_MULTIPLY_CAST_TO_LONG",
607 justification="Will not overflow")
608 public static class WriterBuilder {
609 private final Configuration conf;
610 private final CacheConfig cacheConf;
611 private final FileSystem fs;
612
613 private KeyValue.KVComparator comparator = KeyValue.COMPARATOR;
614 private BloomType bloomType = BloomType.NONE;
615 private long maxKeyCount = 0;
616 private Path dir;
617 private Path filePath;
618 private InetSocketAddress[] favoredNodes;
619 private HFileContext fileContext;
620 private TimeRangeTracker trt;
621 private boolean shouldDropCacheBehind;
622
623 public WriterBuilder(Configuration conf, CacheConfig cacheConf,
624 FileSystem fs) {
625 this.conf = conf;
626 this.cacheConf = cacheConf;
627 this.fs = fs;
628 }
629
630
631
632
633
634
635 public WriterBuilder withTimeRangeTracker(final TimeRangeTracker trt) {
636 Preconditions.checkNotNull(trt);
637 this.trt = trt;
638 return this;
639 }
640
641
642
643
644
645
646
647
648 public WriterBuilder withOutputDir(Path dir) {
649 Preconditions.checkNotNull(dir);
650 this.dir = dir;
651 return this;
652 }
653
654
655
656
657
658
659 public WriterBuilder withFilePath(Path filePath) {
660 Preconditions.checkNotNull(filePath);
661 this.filePath = filePath;
662 return this;
663 }
664
665
666
667
668
669 public WriterBuilder withFavoredNodes(InetSocketAddress[] favoredNodes) {
670 this.favoredNodes = favoredNodes;
671 return this;
672 }
673
674 public WriterBuilder withComparator(KeyValue.KVComparator comparator) {
675 Preconditions.checkNotNull(comparator);
676 this.comparator = comparator;
677 return this;
678 }
679
680 public WriterBuilder withBloomType(BloomType bloomType) {
681 Preconditions.checkNotNull(bloomType);
682 this.bloomType = bloomType;
683 return this;
684 }
685
686
687
688
689
690 public WriterBuilder withMaxKeyCount(long maxKeyCount) {
691 this.maxKeyCount = maxKeyCount;
692 return this;
693 }
694
695 public WriterBuilder withFileContext(HFileContext fileContext) {
696 this.fileContext = fileContext;
697 return this;
698 }
699
700 public WriterBuilder withShouldDropCacheBehind(boolean shouldDropCacheBehind) {
701 this.shouldDropCacheBehind = shouldDropCacheBehind;
702 return this;
703 }
704
705
706
707
708
709
710 public Writer build() throws IOException {
711 if ((dir == null ? 0 : 1) + (filePath == null ? 0 : 1) != 1) {
712 throw new IllegalArgumentException("Either specify parent directory " +
713 "or file path");
714 }
715
716 if (dir == null) {
717 dir = filePath.getParent();
718 }
719
720 if (!fs.exists(dir)) {
721 fs.mkdirs(dir);
722 }
723
724 if (filePath == null) {
725 filePath = getUniqueFile(fs, dir);
726 if (!BloomFilterFactory.isGeneralBloomEnabled(conf)) {
727 bloomType = BloomType.NONE;
728 }
729 }
730
731 if (comparator == null) {
732 comparator = KeyValue.COMPARATOR;
733 }
734 return new Writer(fs, filePath,
735 conf, cacheConf, comparator, bloomType, maxKeyCount, favoredNodes, fileContext,
736 shouldDropCacheBehind, trt);
737 }
738 }
739
740
741
742
743
744
745 public static Path getUniqueFile(final FileSystem fs, final Path dir)
746 throws IOException {
747 if (!fs.getFileStatus(dir).isDirectory()) {
748 throw new IOException("Expecting " + dir.toString() +
749 " to be a directory");
750 }
751 return new Path(dir, UUID.randomUUID().toString().replaceAll("-", ""));
752 }
753
754 public Long getMinimumTimestamp() {
755 return getReader().timeRange == null? null: getReader().timeRange.getMin();
756 }
757
758 public Long getMaximumTimestamp() {
759 return getReader().timeRange == null? null: getReader().timeRange.getMax();
760 }
761
762
763
764
765
766
767
768 @SuppressWarnings("deprecation")
769 byte[] getFileSplitPoint(KVComparator comparator) throws IOException {
770 if (this.reader == null) {
771 LOG.warn("Storefile " + this + " Reader is null; cannot get split point");
772 return null;
773 }
774
775
776
777 byte [] midkey = this.reader.midkey();
778 if (midkey != null) {
779 KeyValue mk = KeyValue.createKeyValueFromKey(midkey, 0, midkey.length);
780 byte [] fk = this.reader.getFirstKey();
781 KeyValue firstKey = KeyValue.createKeyValueFromKey(fk, 0, fk.length);
782 byte [] lk = this.reader.getLastKey();
783 KeyValue lastKey = KeyValue.createKeyValueFromKey(lk, 0, lk.length);
784
785 if (comparator.compareRows(mk, firstKey) == 0 || comparator.compareRows(mk, lastKey) == 0) {
786 if (LOG.isDebugEnabled()) {
787 LOG.debug("cannot split because midkey is the same as first or last row");
788 }
789 return null;
790 }
791 return mk.getRow();
792 }
793 return null;
794 }
795
796
797
798
799
800 public static class Writer implements Compactor.CellSink {
801 private final BloomFilterWriter generalBloomFilterWriter;
802 private final BloomFilterWriter deleteFamilyBloomFilterWriter;
803 private final BloomType bloomType;
804 private byte[] lastBloomKey;
805 private int lastBloomKeyOffset, lastBloomKeyLen;
806 private KVComparator kvComparator;
807 private Cell lastCell = null;
808 private long earliestPutTs = HConstants.LATEST_TIMESTAMP;
809 private Cell lastDeleteFamilyCell = null;
810 private long deleteFamilyCnt = 0;
811
812
813
814
815
816
817
818
819 private final boolean timeRangeTrackerSet;
820 final TimeRangeTracker timeRangeTracker;
821
822 protected HFile.Writer writer;
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838 private Writer(FileSystem fs, Path path,
839 final Configuration conf,
840 CacheConfig cacheConf,
841 final KVComparator comparator, BloomType bloomType, long maxKeys,
842 InetSocketAddress[] favoredNodes, HFileContext fileContext, boolean shouldDropCacheBehind)
843 throws IOException {
844 this(fs, path, conf, cacheConf, comparator, bloomType, maxKeys, favoredNodes, fileContext,
845 shouldDropCacheBehind, null);
846 }
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863 private Writer(FileSystem fs, Path path,
864 final Configuration conf,
865 CacheConfig cacheConf,
866 final KVComparator comparator, BloomType bloomType, long maxKeys,
867 InetSocketAddress[] favoredNodes, HFileContext fileContext,
868 boolean shouldDropCacheBehind, final TimeRangeTracker trt)
869 throws IOException {
870
871
872
873 this.timeRangeTrackerSet = trt != null;
874 this.timeRangeTracker = this.timeRangeTrackerSet? trt: new TimeRangeTracker();
875 writer = HFile.getWriterFactory(conf, cacheConf)
876 .withPath(fs, path)
877 .withComparator(comparator)
878 .withFavoredNodes(favoredNodes)
879 .withFileContext(fileContext)
880 .withShouldDropCacheBehind(shouldDropCacheBehind)
881 .create();
882
883 this.kvComparator = comparator;
884
885 generalBloomFilterWriter = BloomFilterFactory.createGeneralBloomAtWrite(
886 conf, cacheConf, bloomType,
887 (int) Math.min(maxKeys, Integer.MAX_VALUE), writer);
888
889 if (generalBloomFilterWriter != null) {
890 this.bloomType = bloomType;
891 if (LOG.isTraceEnabled()) LOG.trace("Bloom filter type for " + path + ": " +
892 this.bloomType + ", " + generalBloomFilterWriter.getClass().getSimpleName());
893 } else {
894
895 this.bloomType = BloomType.NONE;
896 }
897
898
899
900 if (this.bloomType != BloomType.ROWCOL) {
901 this.deleteFamilyBloomFilterWriter = BloomFilterFactory
902 .createDeleteBloomAtWrite(conf, cacheConf,
903 (int) Math.min(maxKeys, Integer.MAX_VALUE), writer);
904 } else {
905 deleteFamilyBloomFilterWriter = null;
906 }
907 if (deleteFamilyBloomFilterWriter != null) {
908 if (LOG.isTraceEnabled()) LOG.trace("Delete Family Bloom filter type for " + path + ": "
909 + deleteFamilyBloomFilterWriter.getClass().getSimpleName());
910 }
911 }
912
913
914
915
916
917
918
919
920 public void appendMetadata(final long maxSequenceId, final boolean majorCompaction)
921 throws IOException {
922 writer.appendFileInfo(MAX_SEQ_ID_KEY, Bytes.toBytes(maxSequenceId));
923 writer.appendFileInfo(MAJOR_COMPACTION_KEY,
924 Bytes.toBytes(majorCompaction));
925 appendTrackedTimestampsToMetadata();
926 }
927
928
929
930
931 public void appendTrackedTimestampsToMetadata() throws IOException {
932 appendFileInfo(TIMERANGE_KEY,WritableUtils.toByteArray(timeRangeTracker));
933 appendFileInfo(EARLIEST_PUT_TS, Bytes.toBytes(earliestPutTs));
934 }
935
936
937
938
939
940
941
942
943 public void trackTimestamps(final Cell cell) {
944 if (KeyValue.Type.Put.getCode() == cell.getTypeByte()) {
945 earliestPutTs = Math.min(earliestPutTs, cell.getTimestamp());
946 }
947 if (!timeRangeTrackerSet) {
948 timeRangeTracker.includeTimestamp(cell);
949 }
950 }
951
952 private void appendGeneralBloomfilter(final Cell cell) throws IOException {
953 if (this.generalBloomFilterWriter != null) {
954
955 boolean newKey = true;
956 if (this.lastCell != null) {
957 switch(bloomType) {
958 case ROW:
959 newKey = ! kvComparator.matchingRows(cell, lastCell);
960 break;
961 case ROWCOL:
962 newKey = ! kvComparator.matchingRowColumn(cell, lastCell);
963 break;
964 case NONE:
965 newKey = false;
966 break;
967 default:
968 throw new IOException("Invalid Bloom filter type: " + bloomType +
969 " (ROW or ROWCOL expected)");
970 }
971 }
972 if (newKey) {
973
974
975
976
977
978
979
980
981 byte[] bloomKey;
982 int bloomKeyOffset, bloomKeyLen;
983
984 switch (bloomType) {
985 case ROW:
986 bloomKey = cell.getRowArray();
987 bloomKeyOffset = cell.getRowOffset();
988 bloomKeyLen = cell.getRowLength();
989 break;
990 case ROWCOL:
991
992
993
994 bloomKey = generalBloomFilterWriter.createBloomKey(cell.getRowArray(),
995 cell.getRowOffset(), cell.getRowLength(), cell.getQualifierArray(),
996 cell.getQualifierOffset(), cell.getQualifierLength());
997 bloomKeyOffset = 0;
998 bloomKeyLen = bloomKey.length;
999 break;
1000 default:
1001 throw new IOException("Invalid Bloom filter type: " + bloomType +
1002 " (ROW or ROWCOL expected)");
1003 }
1004 generalBloomFilterWriter.add(bloomKey, bloomKeyOffset, bloomKeyLen);
1005 if (lastBloomKey != null
1006 && generalBloomFilterWriter.getComparator().compareFlatKey(bloomKey,
1007 bloomKeyOffset, bloomKeyLen, lastBloomKey,
1008 lastBloomKeyOffset, lastBloomKeyLen) <= 0) {
1009 throw new IOException("Non-increasing Bloom keys: "
1010 + Bytes.toStringBinary(bloomKey, bloomKeyOffset, bloomKeyLen)
1011 + " after "
1012 + Bytes.toStringBinary(lastBloomKey, lastBloomKeyOffset,
1013 lastBloomKeyLen));
1014 }
1015 lastBloomKey = bloomKey;
1016 lastBloomKeyOffset = bloomKeyOffset;
1017 lastBloomKeyLen = bloomKeyLen;
1018 this.lastCell = cell;
1019 }
1020 }
1021 }
1022
1023 private void appendDeleteFamilyBloomFilter(final Cell cell)
1024 throws IOException {
1025 if (!CellUtil.isDeleteFamily(cell) && !CellUtil.isDeleteFamilyVersion(cell)) {
1026 return;
1027 }
1028
1029
1030 deleteFamilyCnt++;
1031 if (null != this.deleteFamilyBloomFilterWriter) {
1032 boolean newKey = true;
1033 if (lastDeleteFamilyCell != null) {
1034 newKey = !kvComparator.matchingRows(cell, lastDeleteFamilyCell);
1035 }
1036 if (newKey) {
1037 this.deleteFamilyBloomFilterWriter.add(cell.getRowArray(),
1038 cell.getRowOffset(), cell.getRowLength());
1039 this.lastDeleteFamilyCell = cell;
1040 }
1041 }
1042 }
1043
1044 public void append(final Cell cell) throws IOException {
1045 appendGeneralBloomfilter(cell);
1046 appendDeleteFamilyBloomFilter(cell);
1047 writer.append(cell);
1048 trackTimestamps(cell);
1049 }
1050
1051 public Path getPath() {
1052 return this.writer.getPath();
1053 }
1054
1055 boolean hasGeneralBloom() {
1056 return this.generalBloomFilterWriter != null;
1057 }
1058
1059
1060
1061
1062
1063
1064 BloomFilterWriter getGeneralBloomWriter() {
1065 return generalBloomFilterWriter;
1066 }
1067
1068 private boolean closeBloomFilter(BloomFilterWriter bfw) throws IOException {
1069 boolean haveBloom = (bfw != null && bfw.getKeyCount() > 0);
1070 if (haveBloom) {
1071 bfw.compactBloom();
1072 }
1073 return haveBloom;
1074 }
1075
1076 private boolean closeGeneralBloomFilter() throws IOException {
1077 boolean hasGeneralBloom = closeBloomFilter(generalBloomFilterWriter);
1078
1079
1080 if (hasGeneralBloom) {
1081 writer.addGeneralBloomFilter(generalBloomFilterWriter);
1082 writer.appendFileInfo(BLOOM_FILTER_TYPE_KEY,
1083 Bytes.toBytes(bloomType.toString()));
1084 if (lastBloomKey != null) {
1085 writer.appendFileInfo(LAST_BLOOM_KEY, Arrays.copyOfRange(
1086 lastBloomKey, lastBloomKeyOffset, lastBloomKeyOffset
1087 + lastBloomKeyLen));
1088 }
1089 }
1090 return hasGeneralBloom;
1091 }
1092
1093 private boolean closeDeleteFamilyBloomFilter() throws IOException {
1094 boolean hasDeleteFamilyBloom = closeBloomFilter(deleteFamilyBloomFilterWriter);
1095
1096
1097 if (hasDeleteFamilyBloom) {
1098 writer.addDeleteFamilyBloomFilter(deleteFamilyBloomFilterWriter);
1099 }
1100
1101
1102
1103 writer.appendFileInfo(DELETE_FAMILY_COUNT,
1104 Bytes.toBytes(this.deleteFamilyCnt));
1105
1106 return hasDeleteFamilyBloom;
1107 }
1108
1109 public void close() throws IOException {
1110 boolean hasGeneralBloom = this.closeGeneralBloomFilter();
1111 boolean hasDeleteFamilyBloom = this.closeDeleteFamilyBloomFilter();
1112
1113 writer.close();
1114
1115
1116
1117 if (StoreFile.LOG.isTraceEnabled()) {
1118 StoreFile.LOG.trace((hasGeneralBloom ? "" : "NO ") + "General Bloom and " +
1119 (hasDeleteFamilyBloom ? "" : "NO ") + "DeleteFamily" + " was added to HFile " +
1120 getPath());
1121 }
1122
1123 }
1124
1125 public void appendFileInfo(byte[] key, byte[] value) throws IOException {
1126 writer.appendFileInfo(key, value);
1127 }
1128
1129
1130
1131 HFile.Writer getHFileWriter() {
1132 return writer;
1133 }
1134 }
1135
1136
1137
1138
1139 public static class Reader {
1140 private static final Log LOG = LogFactory.getLog(Reader.class.getName());
1141
1142 protected BloomFilter generalBloomFilter = null;
1143 protected BloomFilter deleteFamilyBloomFilter = null;
1144 protected BloomType bloomFilterType;
1145 private final HFile.Reader reader;
1146 protected TimeRange timeRange;
1147 protected long sequenceID = -1;
1148 private byte[] lastBloomKey;
1149 private long deleteFamilyCnt = -1;
1150 private boolean bulkLoadResult = false;
1151
1152
1153
1154 private AtomicInteger refCount = new AtomicInteger(0);
1155
1156 private volatile boolean compactedAway = false;
1157
1158 public Reader(FileSystem fs, Path path, CacheConfig cacheConf, Configuration conf)
1159 throws IOException {
1160 reader = HFile.createReader(fs, path, cacheConf, conf);
1161 bloomFilterType = BloomType.NONE;
1162 }
1163
1164 void markCompactedAway() {
1165 this.compactedAway = true;
1166 }
1167
1168 public Reader(FileSystem fs, Path path, FSDataInputStreamWrapper in, long size,
1169 CacheConfig cacheConf, Configuration conf) throws IOException {
1170 reader = HFile.createReader(fs, path, in, size, cacheConf, conf);
1171 bloomFilterType = BloomType.NONE;
1172 }
1173
1174 public void setReplicaStoreFile(boolean isPrimaryReplicaStoreFile) {
1175 reader.setPrimaryReplicaReader(isPrimaryReplicaStoreFile);
1176 }
1177 public boolean isPrimaryReplicaReader() {
1178 return reader.isPrimaryReplicaReader();
1179 }
1180
1181
1182
1183
1184 Reader() {
1185 this.reader = null;
1186 }
1187
1188 public KVComparator getComparator() {
1189 return reader.getComparator();
1190 }
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200 public StoreFileScanner getStoreFileScanner(boolean cacheBlocks,
1201 boolean pread) {
1202 return getStoreFileScanner(cacheBlocks, pread, false,
1203
1204
1205 0);
1206 }
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216 public StoreFileScanner getStoreFileScanner(boolean cacheBlocks,
1217 boolean pread,
1218 boolean isCompaction, long readPt) {
1219
1220 refCount.incrementAndGet();
1221 return new StoreFileScanner(this,
1222 getScanner(cacheBlocks, pread, isCompaction),
1223 !isCompaction, reader.hasMVCCInfo(), readPt);
1224 }
1225
1226
1227
1228
1229
1230 void decrementRefCount() {
1231 refCount.decrementAndGet();
1232 }
1233
1234
1235
1236
1237 public boolean isReferencedInReads() {
1238 return refCount.get() != 0;
1239 }
1240
1241
1242
1243
1244 public boolean isCompactedAway() {
1245 return this.compactedAway;
1246 }
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257 @Deprecated
1258 public HFileScanner getScanner(boolean cacheBlocks, boolean pread) {
1259 return getScanner(cacheBlocks, pread, false);
1260 }
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275 @Deprecated
1276 public HFileScanner getScanner(boolean cacheBlocks, boolean pread,
1277 boolean isCompaction) {
1278 return reader.getScanner(cacheBlocks, pread, isCompaction);
1279 }
1280
1281 public void close(boolean evictOnClose) throws IOException {
1282 reader.close(evictOnClose);
1283 }
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293 boolean passesTimerangeFilter(TimeRange tr, long oldestUnexpiredTS) {
1294 return this.timeRange == null? true:
1295 this.timeRange.includesTimeRange(tr) && this.timeRange.getMax() >= oldestUnexpiredTS;
1296 }
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314 boolean passesBloomFilter(Scan scan,
1315 final SortedSet<byte[]> columns) {
1316
1317
1318 if (!scan.isGetScan()) {
1319 return true;
1320 }
1321
1322 byte[] row = scan.getStartRow();
1323 switch (this.bloomFilterType) {
1324 case ROW:
1325 return passesGeneralBloomFilter(row, 0, row.length, null, 0, 0);
1326
1327 case ROWCOL:
1328 if (columns != null && columns.size() == 1) {
1329 byte[] column = columns.first();
1330 return passesGeneralBloomFilter(row, 0, row.length, column, 0,
1331 column.length);
1332 }
1333
1334
1335
1336 return true;
1337
1338 default:
1339 return true;
1340 }
1341 }
1342
1343 public boolean passesDeleteFamilyBloomFilter(byte[] row, int rowOffset,
1344 int rowLen) {
1345
1346
1347 BloomFilter bloomFilter = this.deleteFamilyBloomFilter;
1348
1349
1350 if (reader.getTrailer().getEntryCount() == 0 || deleteFamilyCnt == 0) {
1351 return false;
1352 }
1353
1354 if (bloomFilter == null) {
1355 return true;
1356 }
1357
1358 try {
1359 if (!bloomFilter.supportsAutoLoading()) {
1360 return true;
1361 }
1362 return bloomFilter.contains(row, rowOffset, rowLen, null);
1363 } catch (IllegalArgumentException e) {
1364 LOG.error("Bad Delete Family bloom filter data -- proceeding without",
1365 e);
1366 setDeleteFamilyBloomFilterFaulty();
1367 }
1368
1369 return true;
1370 }
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384 public boolean passesGeneralBloomFilter(byte[] row, int rowOffset,
1385 int rowLen, byte[] col, int colOffset, int colLen) {
1386
1387
1388 BloomFilter bloomFilter = this.generalBloomFilter;
1389 if (bloomFilter == null) {
1390 return true;
1391 }
1392
1393 byte[] key;
1394 switch (bloomFilterType) {
1395 case ROW:
1396 if (col != null) {
1397 throw new RuntimeException("Row-only Bloom filter called with " +
1398 "column specified");
1399 }
1400 if (rowOffset != 0 || rowLen != row.length) {
1401 throw new AssertionError("For row-only Bloom filters the row "
1402 + "must occupy the whole array");
1403 }
1404 key = row;
1405 break;
1406
1407 case ROWCOL:
1408 key = bloomFilter.createBloomKey(row, rowOffset, rowLen, col,
1409 colOffset, colLen);
1410
1411 break;
1412
1413 default:
1414 return true;
1415 }
1416
1417
1418 if (reader.getTrailer().getEntryCount() == 0)
1419 return false;
1420
1421 try {
1422 boolean shouldCheckBloom;
1423 ByteBuffer bloom;
1424 if (bloomFilter.supportsAutoLoading()) {
1425 bloom = null;
1426 shouldCheckBloom = true;
1427 } else {
1428 bloom = reader.getMetaBlock(HFile.BLOOM_FILTER_DATA_KEY,
1429 true);
1430 shouldCheckBloom = bloom != null;
1431 }
1432
1433 if (shouldCheckBloom) {
1434 boolean exists;
1435
1436
1437
1438
1439 boolean keyIsAfterLast = lastBloomKey != null
1440 && bloomFilter.getComparator().compareFlatKey(key, lastBloomKey) > 0;
1441
1442 if (bloomFilterType == BloomType.ROWCOL) {
1443
1444
1445
1446
1447 byte[] rowBloomKey = bloomFilter.createBloomKey(row, rowOffset, rowLen,
1448 null, 0, 0);
1449
1450 if (keyIsAfterLast
1451 && bloomFilter.getComparator().compareFlatKey(rowBloomKey,
1452 lastBloomKey) > 0) {
1453 exists = false;
1454 } else {
1455 exists =
1456 bloomFilter.contains(key, 0, key.length, bloom) ||
1457 bloomFilter.contains(rowBloomKey, 0, rowBloomKey.length,
1458 bloom);
1459 }
1460 } else {
1461 exists = !keyIsAfterLast
1462 && bloomFilter.contains(key, 0, key.length, bloom);
1463 }
1464
1465 return exists;
1466 }
1467 } catch (IOException e) {
1468 LOG.error("Error reading bloom filter data -- proceeding without",
1469 e);
1470 setGeneralBloomFilterFaulty();
1471 } catch (IllegalArgumentException e) {
1472 LOG.error("Bad bloom filter data -- proceeding without", e);
1473 setGeneralBloomFilterFaulty();
1474 }
1475
1476 return true;
1477 }
1478
1479
1480
1481
1482
1483
1484 public boolean passesKeyRangeFilter(Scan scan) {
1485 if (this.getFirstKey() == null || this.getLastKey() == null) {
1486
1487 return false;
1488 }
1489 if (Bytes.equals(scan.getStartRow(), HConstants.EMPTY_START_ROW)
1490 && Bytes.equals(scan.getStopRow(), HConstants.EMPTY_END_ROW)) {
1491 return true;
1492 }
1493 KeyValue smallestScanKeyValue = scan.isReversed() ? KeyValueUtil
1494 .createFirstOnRow(scan.getStopRow()) : KeyValueUtil.createFirstOnRow(scan
1495 .getStartRow());
1496 KeyValue largestScanKeyValue = scan.isReversed() ? KeyValueUtil
1497 .createLastOnRow(scan.getStartRow()) : KeyValueUtil.createLastOnRow(scan
1498 .getStopRow());
1499 boolean nonOverLapping = (getComparator().compareFlatKey(
1500 this.getFirstKey(), largestScanKeyValue.getKey()) > 0 && !Bytes
1501 .equals(scan.isReversed() ? scan.getStartRow() : scan.getStopRow(),
1502 HConstants.EMPTY_END_ROW))
1503 || getComparator().compareFlatKey(this.getLastKey(),
1504 smallestScanKeyValue.getKey()) < 0;
1505 return !nonOverLapping;
1506 }
1507
1508 public Map<byte[], byte[]> loadFileInfo() throws IOException {
1509 Map<byte [], byte []> fi = reader.loadFileInfo();
1510
1511 byte[] b = fi.get(BLOOM_FILTER_TYPE_KEY);
1512 if (b != null) {
1513 bloomFilterType = BloomType.valueOf(Bytes.toString(b));
1514 }
1515
1516 lastBloomKey = fi.get(LAST_BLOOM_KEY);
1517 byte[] cnt = fi.get(DELETE_FAMILY_COUNT);
1518 if (cnt != null) {
1519 deleteFamilyCnt = Bytes.toLong(cnt);
1520 }
1521
1522 return fi;
1523 }
1524
1525 public void loadBloomfilter() {
1526 this.loadBloomfilter(BlockType.GENERAL_BLOOM_META);
1527 this.loadBloomfilter(BlockType.DELETE_FAMILY_BLOOM_META);
1528 }
1529
1530 private void loadBloomfilter(BlockType blockType) {
1531 try {
1532 if (blockType == BlockType.GENERAL_BLOOM_META) {
1533 if (this.generalBloomFilter != null)
1534 return;
1535
1536 DataInput bloomMeta = reader.getGeneralBloomFilterMetadata();
1537 if (bloomMeta != null) {
1538
1539 if (bloomFilterType == BloomType.NONE) {
1540 throw new IOException(
1541 "valid bloom filter type not found in FileInfo");
1542 } else {
1543 generalBloomFilter = BloomFilterFactory.createFromMeta(bloomMeta,
1544 reader);
1545 if (LOG.isTraceEnabled()) {
1546 LOG.trace("Loaded " + bloomFilterType.toString() + " "
1547 + generalBloomFilter.getClass().getSimpleName()
1548 + " metadata for " + reader.getName());
1549 }
1550 }
1551 }
1552 } else if (blockType == BlockType.DELETE_FAMILY_BLOOM_META) {
1553 if (this.deleteFamilyBloomFilter != null)
1554 return;
1555
1556 DataInput bloomMeta = reader.getDeleteBloomFilterMetadata();
1557 if (bloomMeta != null) {
1558 deleteFamilyBloomFilter = BloomFilterFactory.createFromMeta(
1559 bloomMeta, reader);
1560 LOG.info("Loaded Delete Family Bloom ("
1561 + deleteFamilyBloomFilter.getClass().getSimpleName()
1562 + ") metadata for " + reader.getName());
1563 }
1564 } else {
1565 throw new RuntimeException("Block Type: " + blockType.toString()
1566 + "is not supported for Bloom filter");
1567 }
1568 } catch (IOException e) {
1569 LOG.error("Error reading bloom filter meta for " + blockType
1570 + " -- proceeding without", e);
1571 setBloomFilterFaulty(blockType);
1572 } catch (IllegalArgumentException e) {
1573 LOG.error("Bad bloom filter meta " + blockType
1574 + " -- proceeding without", e);
1575 setBloomFilterFaulty(blockType);
1576 }
1577 }
1578
1579 private void setBloomFilterFaulty(BlockType blockType) {
1580 if (blockType == BlockType.GENERAL_BLOOM_META) {
1581 setGeneralBloomFilterFaulty();
1582 } else if (blockType == BlockType.DELETE_FAMILY_BLOOM_META) {
1583 setDeleteFamilyBloomFilterFaulty();
1584 }
1585 }
1586
1587
1588
1589
1590
1591
1592
1593
1594 public long getFilterEntries() {
1595 return generalBloomFilter != null ? generalBloomFilter.getKeyCount()
1596 : reader.getEntries();
1597 }
1598
1599 public void setGeneralBloomFilterFaulty() {
1600 generalBloomFilter = null;
1601 }
1602
1603 public void setDeleteFamilyBloomFilterFaulty() {
1604 this.deleteFamilyBloomFilter = null;
1605 }
1606
1607 public byte[] getLastKey() {
1608 return reader.getLastKey();
1609 }
1610
1611 public byte[] getLastRowKey() {
1612 return reader.getLastRowKey();
1613 }
1614
1615 public byte[] midkey() throws IOException {
1616 return reader.midkey();
1617 }
1618
1619 public long length() {
1620 return reader.length();
1621 }
1622
1623 public long getTotalUncompressedBytes() {
1624 return reader.getTrailer().getTotalUncompressedBytes();
1625 }
1626
1627 public long getEntries() {
1628 return reader.getEntries();
1629 }
1630
1631 public long getDeleteFamilyCnt() {
1632 return deleteFamilyCnt;
1633 }
1634
1635 public byte[] getFirstKey() {
1636 return reader.getFirstKey();
1637 }
1638
1639 public long indexSize() {
1640 return reader.indexSize();
1641 }
1642
1643 public BloomType getBloomFilterType() {
1644 return this.bloomFilterType;
1645 }
1646
1647 public long getSequenceID() {
1648 return sequenceID;
1649 }
1650
1651 public void setSequenceID(long sequenceID) {
1652 this.sequenceID = sequenceID;
1653 }
1654
1655 public void setBulkLoaded(boolean bulkLoadResult) {
1656 this.bulkLoadResult = bulkLoadResult;
1657 }
1658
1659 public boolean isBulkLoaded() {
1660 return this.bulkLoadResult;
1661 }
1662
1663 BloomFilter getGeneralBloomFilter() {
1664 return generalBloomFilter;
1665 }
1666
1667 long getUncompressedDataIndexSize() {
1668 return reader.getTrailer().getUncompressedDataIndexSize();
1669 }
1670
1671 public long getTotalBloomSize() {
1672 if (generalBloomFilter == null)
1673 return 0;
1674 return generalBloomFilter.getByteSize();
1675 }
1676
1677 public int getHFileVersion() {
1678 return reader.getTrailer().getMajorVersion();
1679 }
1680
1681 public int getHFileMinorVersion() {
1682 return reader.getTrailer().getMinorVersion();
1683 }
1684
1685 public HFile.Reader getHFileReader() {
1686 return reader;
1687 }
1688
1689 void disableBloomFilterForTesting() {
1690 generalBloomFilter = null;
1691 this.deleteFamilyBloomFilter = null;
1692 }
1693
1694 public long getMaxTimestamp() {
1695 return timeRange == null ? TimeRange.INITIAL_MAX_TIMESTAMP: timeRange.getMax();
1696 }
1697 }
1698
1699
1700
1701
1702 public abstract static class Comparators {
1703
1704
1705
1706
1707
1708
1709
1710
1711 public static final Comparator<StoreFile> SEQ_ID =
1712 Ordering.compound(ImmutableList.of(
1713 Ordering.natural().onResultOf(new GetSeqId()),
1714 Ordering.natural().onResultOf(new GetFileSize()).reverse(),
1715 Ordering.natural().onResultOf(new GetBulkTime()),
1716 Ordering.natural().onResultOf(new GetPathName())
1717 ));
1718
1719
1720
1721
1722
1723 public static final Comparator<StoreFile> SEQ_ID_MAX_TIMESTAMP =
1724 Ordering.compound(ImmutableList.of(
1725 Ordering.natural().onResultOf(new GetSeqId()),
1726 Ordering.natural().onResultOf(new GetMaxTimestamp()),
1727 Ordering.natural().onResultOf(new GetFileSize()).reverse(),
1728 Ordering.natural().onResultOf(new GetBulkTime()),
1729 Ordering.natural().onResultOf(new GetPathName())
1730 ));
1731
1732 private static class GetSeqId implements Function<StoreFile, Long> {
1733 @Override
1734 public Long apply(StoreFile sf) {
1735 return sf.getMaxSequenceId();
1736 }
1737 }
1738
1739 private static class GetFileSize implements Function<StoreFile, Long> {
1740 @Override
1741 public Long apply(StoreFile sf) {
1742 if (sf.getReader() != null) {
1743 return sf.getReader().length();
1744 } else {
1745
1746
1747 return -1L;
1748 }
1749 }
1750 }
1751
1752 private static class GetBulkTime implements Function<StoreFile, Long> {
1753 @Override
1754 public Long apply(StoreFile sf) {
1755 if (!sf.isBulkLoadResult()) return Long.MAX_VALUE;
1756 return sf.getBulkLoadTimestamp();
1757 }
1758 }
1759
1760 private static class GetPathName implements Function<StoreFile, String> {
1761 @Override
1762 public String apply(StoreFile sf) {
1763 return sf.getPath().getName();
1764 }
1765 }
1766
1767 private static class GetMaxTimestamp implements Function<StoreFile, Long> {
1768 @Override
1769 public Long apply(StoreFile sf) {
1770 return sf.getMaximumTimestamp() == null? (Long)Long.MAX_VALUE : sf.getMaximumTimestamp();
1771 }
1772 }
1773 }
1774 }