github.com/petermattis/pebble@v0.0.0-20190905164901-ab51a2166067/ptable/reader.go (about)

     1  // Copyright 2018 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package ptable
     6  
     7  import (
     8  	"encoding/binary"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"sort"
    13  
    14  	"github.com/golang/snappy"
    15  	"github.com/petermattis/pebble/cache"
    16  	"github.com/petermattis/pebble/internal/base"
    17  	"github.com/petermattis/pebble/internal/crc"
    18  	"github.com/petermattis/pebble/vfs"
    19  )
    20  
    21  // Iter ...
    22  type Iter struct {
    23  	reader *Reader
    24  	cmp    base.Compare
    25  	index  Block
    26  	data   Block
    27  	pos    int32
    28  	err    error
    29  }
    30  
    31  // Init ...
    32  func (i *Iter) Init(r *Reader) error {
    33  	i.reader = r
    34  	i.cmp = i.reader.cmp
    35  	i.err = r.err
    36  	if i.err != nil {
    37  		return i.err
    38  	}
    39  	i.index.init(r.index)
    40  	i.pos = -1
    41  	return nil
    42  }
    43  
    44  // SeekGE moves the iterator to the first block containing keys greater than or
    45  // equal to the given key.
    46  func (i *Iter) SeekGE(key []byte) {
    47  	keys := i.index.Column(0).Bytes()
    48  	index := sort.Search(int(i.index.rows-1), func(j int) bool {
    49  		return i.cmp(key, keys.At(j)) < 0
    50  	})
    51  	if index > 0 {
    52  		index--
    53  	}
    54  	i.pos = int32(index)
    55  	i.loadBlock()
    56  }
    57  
    58  // SeekLT moves the iterator to the last block containing keys less than the
    59  // given key.
    60  func (i *Iter) SeekLT(key []byte) {
    61  	panic("pebble/ptable: SeekLT unimplemented")
    62  }
    63  
    64  // First moves the iterator to the first block in the table.
    65  func (i *Iter) First() {
    66  	i.pos = 0
    67  	i.loadBlock()
    68  }
    69  
    70  // Last moves the iterator to the last block in the table.
    71  func (i *Iter) Last() {
    72  	// NB: the index block has 1 more row than there are data blocks in the
    73  	// table.
    74  	i.pos = i.index.rows - 2
    75  	i.loadBlock()
    76  }
    77  
    78  // Next moves the iterator to the next block in the table.
    79  func (i *Iter) Next() {
    80  	if i.pos+1 >= i.index.rows {
    81  		return
    82  	}
    83  	i.pos++
    84  	i.loadBlock()
    85  }
    86  
    87  // Prev moves the iterator to the previous block in the table.
    88  func (i *Iter) Prev() {
    89  	if i.pos < 0 {
    90  		return
    91  	}
    92  	i.pos--
    93  	i.loadBlock()
    94  }
    95  
    96  // Valid returns true if the iterator is positioned at a valid block and false
    97  // otherwise.
    98  func (i *Iter) Valid() bool {
    99  	return i.err == nil && i.pos >= 0 && i.pos+1 < i.index.rows
   100  }
   101  
   102  // Block returns the block the iterator is currently pointed out.
   103  func (i *Iter) Block() *Block {
   104  	return &i.data
   105  }
   106  
   107  func (i *Iter) loadBlock() {
   108  	if !i.Valid() {
   109  		return
   110  	}
   111  	offsets := i.index.Column(1).Int64()
   112  	bh := blockHandle{
   113  		offset: uint64(offsets[i.pos]),
   114  		length: uint64(offsets[i.pos+1]-offsets[i.pos]) - blockTrailerLen,
   115  	}
   116  	b, err := i.reader.readBlock(bh)
   117  	if err != nil {
   118  		i.err = err
   119  		return
   120  	}
   121  	i.data.init(b.Get())
   122  	b.Release()
   123  }
   124  
   125  // Reader ...
   126  type Reader struct {
   127  	file    vfs.File
   128  	dbNum   uint64
   129  	fileNum uint64
   130  	err     error
   131  	index   []byte
   132  	cache   *cache.Cache
   133  	cmp     base.Compare
   134  }
   135  
   136  // NewReader ...
   137  func NewReader(f vfs.File, fileNum uint64, o *base.Options) *Reader {
   138  	o = o.EnsureDefaults()
   139  	r := &Reader{
   140  		file:    f,
   141  		dbNum:   0, // TODO(peter): needed for block cache
   142  		fileNum: fileNum,
   143  		cache:   o.Cache,
   144  		cmp:     o.Comparer.Compare,
   145  	}
   146  
   147  	if f == nil {
   148  		r.err = errors.New("pebble/table: nil file")
   149  		return r
   150  	}
   151  	stat, err := f.Stat()
   152  	if err != nil {
   153  		r.err = fmt.Errorf("pebble/table: invalid table (could not stat file): %v", err)
   154  		return r
   155  	}
   156  
   157  	// legacy footer format:
   158  	//    metaindex handle (varint64 offset, varint64 size)
   159  	//    index handle     (varint64 offset, varint64 size)
   160  	//    <padding> to make the total size 2 * BlockHandle::kMaxEncodedLength
   161  	//    table_magic_number (8 bytes)
   162  	// new footer format:
   163  	//    checksum type (char, 1 byte)
   164  	//    metaindex handle (varint64 offset, varint64 size)
   165  	//    index handle     (varint64 offset, varint64 size)
   166  	//    <padding> to make the total size 2 * BlockHandle::kMaxEncodedLength + 1
   167  	//    footer version (4 bytes)
   168  	//    table_magic_number (8 bytes)
   169  	footer := make([]byte, footerLen)
   170  	if stat.Size() < int64(len(footer)) {
   171  		r.err = errors.New("pebble/table: invalid table (file size is too small)")
   172  		return r
   173  	}
   174  	_, err = f.ReadAt(footer, stat.Size()-int64(len(footer)))
   175  	if err != nil && err != io.EOF {
   176  		r.err = fmt.Errorf("pebble/table: invalid table (could not read footer): %v", err)
   177  		return r
   178  	}
   179  	if string(footer[magicOffset:footerLen]) != magic {
   180  		r.err = errors.New("pebble/table: invalid table (bad magic number)")
   181  		return r
   182  	}
   183  
   184  	version := binary.LittleEndian.Uint32(footer[versionOffset:magicOffset])
   185  	if version != formatVersion {
   186  		r.err = fmt.Errorf("pebble/table: unsupported format version %d", version)
   187  		return r
   188  	}
   189  
   190  	if footer[0] != checksumCRC32c {
   191  		r.err = fmt.Errorf("pebble/table: unsupported checksum type %d", footer[0])
   192  		return r
   193  	}
   194  	footer = footer[1:]
   195  
   196  	// TODO(peter): Read the metaindex.
   197  	_, n := decodeBlockHandle(footer)
   198  	if n == 0 {
   199  		r.err = errors.New("pebble/table: invalid table (bad metaindex block handle)")
   200  		return r
   201  	}
   202  	footer = footer[n:]
   203  
   204  	// Read the index into memory.
   205  	//
   206  	// TODO(peter): Allow the index block to be placed in the block cache.
   207  	indexBH, n := decodeBlockHandle(footer)
   208  	if n == 0 {
   209  		r.err = errors.New("pebble/table: invalid table (bad index block handle)")
   210  		return r
   211  	}
   212  	var h cache.Handle
   213  	h, r.err = r.readBlock(indexBH)
   214  	r.index = h.Get()
   215  	h.Release()
   216  	return r
   217  }
   218  
   219  // Close ...
   220  func (r *Reader) Close() error {
   221  	if r.err != nil {
   222  		if r.file != nil {
   223  			r.file.Close()
   224  			r.file = nil
   225  		}
   226  		return r.err
   227  	}
   228  	if r.file != nil {
   229  		r.err = r.file.Close()
   230  		r.file = nil
   231  		if r.err != nil {
   232  			return r.err
   233  		}
   234  	}
   235  	// Make any future calls to Get, NewIter or Close return an error.
   236  	r.err = errors.New("pebble/table: reader is closed")
   237  	return nil
   238  }
   239  
   240  // NewIter ...
   241  func (r *Reader) NewIter() *Iter {
   242  	// TODO(peter): Don't allow the Reader to be closed while a tableIter exists
   243  	// on it.
   244  	if r.err != nil {
   245  		return &Iter{err: r.err}
   246  	}
   247  	i := &Iter{}
   248  	_ = i.Init(r)
   249  	return i
   250  }
   251  
   252  // readBlock reads and decompresses a block from disk into memory.
   253  func (r *Reader) readBlock(bh blockHandle) (cache.Handle, error) {
   254  	if h := r.cache.Get(r.dbNum, r.fileNum, bh.offset); h.Get() != nil {
   255  		return h, nil
   256  	}
   257  
   258  	b := make([]byte, bh.length+blockTrailerLen)
   259  	if _, err := r.file.ReadAt(b, int64(bh.offset)); err != nil {
   260  		return cache.Handle{}, err
   261  	}
   262  	checksum0 := binary.LittleEndian.Uint32(b[bh.length+1:])
   263  	checksum1 := crc.New(b[:bh.length+1]).Value()
   264  	if checksum0 != checksum1 {
   265  		return cache.Handle{}, errors.New("pebble/table: invalid table (checksum mismatch)")
   266  	}
   267  	switch b[bh.length] {
   268  	case noCompressionBlockType:
   269  		b = b[:bh.length]
   270  		h := r.cache.Set(r.dbNum, r.fileNum, bh.offset, b)
   271  		return h, nil
   272  	case snappyCompressionBlockType:
   273  		b, err := snappy.Decode(nil, b[:bh.length])
   274  		if err != nil {
   275  			return cache.Handle{}, err
   276  		}
   277  		h := r.cache.Set(r.dbNum, r.fileNum, bh.offset, b)
   278  		return h, nil
   279  	}
   280  	return cache.Handle{}, fmt.Errorf("pebble/table: unknown block compression: %d", b[bh.length])
   281  }