177 lines
5.4 KiB
Go
177 lines
5.4 KiB
Go
// Copyright 2022 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
|
// of this source code is governed by a BSD-style license that can be found in
|
|
// the LICENSE file.
|
|
|
|
package pebble
|
|
|
|
import (
|
|
"github.com/cockroachdb/pebble/internal/base"
|
|
"github.com/cockroachdb/pebble/internal/keyspan"
|
|
"github.com/cockroachdb/pebble/internal/manifest"
|
|
"github.com/cockroachdb/pebble/internal/rangekey"
|
|
"github.com/cockroachdb/pebble/sstable"
|
|
)
|
|
|
|
// NewExternalIter takes an input set of sstable files which may overlap
|
|
// arbitrarily and returns an Iterator over the merged contents of the sstables.
|
|
// Input sstables may contain point keys, range keys, range deletions, etc. The
|
|
// input files slice must be sorted in reverse chronological ordering. A key in
|
|
// a file at a lower index will shadow a key with an identical user key
|
|
// contained within a file at a higher index.
|
|
//
|
|
// Input sstables must only contain keys with the zero sequence number.
|
|
func NewExternalIter(
|
|
o *Options,
|
|
iterOpts *IterOptions,
|
|
files []sstable.ReadableFile,
|
|
extraReaderOpts ...sstable.ReaderOption,
|
|
) (it *Iterator, err error) {
|
|
var readers []*sstable.Reader
|
|
|
|
// Ensure we close all the opened readers if we error out.
|
|
closeReaders := func() {
|
|
for i := range readers {
|
|
_ = readers[i].Close()
|
|
}
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
closeReaders()
|
|
}
|
|
}()
|
|
readers, err = openExternalTables(o, files, o.MakeReaderOptions(), extraReaderOpts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buf := iterAllocPool.Get().(*iterAlloc)
|
|
dbi := &buf.dbi
|
|
*dbi = Iterator{
|
|
alloc: buf,
|
|
cmp: o.Comparer.Compare,
|
|
equal: o.equal(),
|
|
merge: o.Merger.Merge,
|
|
split: o.Comparer.Split,
|
|
readState: nil,
|
|
keyBuf: buf.keyBuf,
|
|
prefixOrFullSeekKey: buf.prefixOrFullSeekKey,
|
|
boundsBuf: buf.boundsBuf,
|
|
batch: nil,
|
|
newIters: func(f *manifest.FileMetadata, opts *IterOptions, bytesIterated *uint64) (internalIterator, keyspan.FragmentIterator, error) {
|
|
// NB: External iterators are currently constructed without any
|
|
// `levelIters`. newIters should never be called. When we support
|
|
// organizing multiple non-overlapping files into a single level
|
|
// (see TODO below), we'll need to adjust this tableNewIters
|
|
// implementation to open iterators by looking up f in a map
|
|
// of readers indexed by *fileMetadata.
|
|
panic("unreachable")
|
|
},
|
|
seqNum: base.InternalKeySeqNumMax,
|
|
}
|
|
if iterOpts != nil {
|
|
dbi.opts = *iterOpts
|
|
dbi.saveBounds(iterOpts.LowerBound, iterOpts.UpperBound)
|
|
}
|
|
|
|
// TODO(jackson): In some instances we could generate fewer levels by using
|
|
// L0Sublevels code to organize nonoverlapping files into the same level.
|
|
// This would allow us to use levelIters and keep a smaller set of data and
|
|
// files in-memory. However, it would also require us to identify the bounds
|
|
// of all the files upfront.
|
|
|
|
// Ensure we close all iters if error out early.
|
|
mlevels := buf.mlevels[:0]
|
|
var rangeKeyIters []keyspan.FragmentIterator
|
|
defer func() {
|
|
if err != nil {
|
|
for i := range rangeKeyIters {
|
|
_ = rangeKeyIters[i].Close()
|
|
}
|
|
for i := range mlevels {
|
|
if mlevels[i].iter != nil {
|
|
_ = mlevels[i].iter.Close()
|
|
}
|
|
if mlevels[i].rangeDelIter != nil {
|
|
_ = mlevels[i].rangeDelIter.Close()
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
if iterOpts.pointKeys() {
|
|
if len(files) > cap(mlevels) {
|
|
mlevels = make([]mergingIterLevel, 0, len(files))
|
|
}
|
|
for _, r := range readers {
|
|
pointIter, err := r.NewIter(dbi.opts.LowerBound, dbi.opts.UpperBound)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rangeDelIter, err := r.NewRawRangeDelIter()
|
|
if err != nil {
|
|
_ = pointIter.Close()
|
|
return nil, err
|
|
}
|
|
mlevels = append(mlevels, mergingIterLevel{
|
|
iter: base.WrapIterWithStats(pointIter),
|
|
rangeDelIter: rangeDelIter,
|
|
})
|
|
}
|
|
}
|
|
buf.merging.init(&dbi.opts, dbi.cmp, dbi.split, mlevels...)
|
|
buf.merging.snapshot = base.InternalKeySeqNumMax
|
|
buf.merging.elideRangeTombstones = true
|
|
dbi.pointIter = &buf.merging
|
|
dbi.iter = dbi.pointIter
|
|
|
|
if dbi.opts.rangeKeys() {
|
|
for _, r := range readers {
|
|
rki, err := r.NewRawRangeKeyIter()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if rki != nil {
|
|
rangeKeyIters = append(rangeKeyIters, rki)
|
|
}
|
|
}
|
|
|
|
dbi.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
|
|
dbi.rangeKey.rangeKeyIter = rangekey.InitUserIteration(
|
|
o.Comparer.Compare,
|
|
base.InternalKeySeqNumMax,
|
|
&dbi.rangeKey.alloc.merging,
|
|
&dbi.rangeKey.alloc.defraging,
|
|
rangeKeyIters...,
|
|
)
|
|
|
|
dbi.rangeKey.iter.Init(dbi.cmp, &buf.merging, dbi.rangeKey.rangeKeyIter, keyspan.Hooks{
|
|
SpanChanged: dbi.rangeKeySpanChanged,
|
|
SkipPoint: dbi.rangeKeySkipPoint,
|
|
}, dbi.opts.LowerBound, dbi.opts.UpperBound)
|
|
dbi.iter = &dbi.rangeKey.iter
|
|
}
|
|
|
|
// Close all the opened sstable.Readers when the Iterator is closed.
|
|
dbi.closeHook = closeReaders
|
|
return dbi, nil
|
|
}
|
|
|
|
func openExternalTables(
|
|
o *Options,
|
|
files []sstable.ReadableFile,
|
|
readerOpts sstable.ReaderOptions,
|
|
extraReaderOpts ...sstable.ReaderOption,
|
|
) (readers []*sstable.Reader, err error) {
|
|
readers = make([]*sstable.Reader, 0, len(files))
|
|
for i := range files {
|
|
r, err := sstable.NewReader(files[i], readerOpts, extraReaderOpts...)
|
|
if err != nil {
|
|
return readers, err
|
|
}
|
|
// Use the index of the file in files as the sequence number for all of
|
|
// its keys.
|
|
r.Properties.GlobalSeqNum = uint64(len(files) - i)
|
|
readers = append(readers, r)
|
|
}
|
|
return readers, err
|
|
}
|