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 }