mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-07-13 17:12:05 -04:00
Bumps [github.com/blevesearch/bleve/v2](https://github.com/blevesearch/bleve) from 2.5.7 to 2.6.0. - [Release notes](https://github.com/blevesearch/bleve/releases) - [Commits](https://github.com/blevesearch/bleve/compare/v2.5.7...v2.6.0) --- updated-dependencies: - dependency-name: github.com/blevesearch/bleve/v2 dependency-version: 2.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
933 lines
27 KiB
Go
933 lines
27 KiB
Go
// Copyright (c) 2017 Couchbase, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package zap
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
|
|
"github.com/RoaringBitmap/roaring/v2"
|
|
index "github.com/blevesearch/bleve_index_api"
|
|
mmap "github.com/blevesearch/mmap-go"
|
|
segment "github.com/blevesearch/scorch_segment_api/v2"
|
|
"github.com/golang/snappy"
|
|
)
|
|
|
|
var reflectStaticSizeSegmentBase int
|
|
|
|
func init() {
|
|
var sb SegmentBase
|
|
reflectStaticSizeSegmentBase = int(unsafe.Sizeof(sb))
|
|
}
|
|
|
|
// OpenUsing returns a zap impl of a segment which tracks some config values during
|
|
// the its lifetime.
|
|
func (z *ZapPlugin) OpenUsing(path string, config map[string]interface{}) (segment.Segment, error) {
|
|
return z.open(path, config)
|
|
}
|
|
|
|
// Open returns a zap impl of a segment
|
|
func (z *ZapPlugin) Open(path string) (segment.Segment, error) {
|
|
return z.open(path, nil)
|
|
}
|
|
|
|
func (*ZapPlugin) open(path string, config map[string]interface{}) (segment.Segment, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mm, err := mmap.Map(f, mmap.RDONLY, 0)
|
|
if err != nil {
|
|
// mmap failed, try to close the file
|
|
_ = f.Close()
|
|
return nil, err
|
|
}
|
|
|
|
rv := &Segment{
|
|
SegmentBase: SegmentBase{
|
|
fieldsMap: make(map[string]uint16),
|
|
fieldsOptions: make(map[string]index.FieldIndexingOptions),
|
|
invIndexCache: newInvertedIndexCache(),
|
|
vecIndexCache: newVectorIndexCache(),
|
|
synIndexCache: newSynonymIndexCache(),
|
|
nstIndexCache: newNestedIndexCache(),
|
|
fieldDvReaders: make([][]*docValueReader, len(segmentSections)),
|
|
config: config,
|
|
},
|
|
f: f,
|
|
mm: mm,
|
|
path: path,
|
|
refs: 1,
|
|
}
|
|
rv.SegmentBase.updateSize()
|
|
|
|
err = rv.loadConfig()
|
|
if err != nil {
|
|
_ = rv.Close()
|
|
return nil, err
|
|
}
|
|
|
|
err = rv.loadFields()
|
|
if err != nil {
|
|
_ = rv.Close()
|
|
return nil, err
|
|
}
|
|
|
|
err = rv.loadDvReaders()
|
|
if err != nil {
|
|
_ = rv.Close()
|
|
return nil, err
|
|
}
|
|
|
|
// initialize any of the caches if needed
|
|
err = rv.nstIndexCache.initialize(rv.numDocs, rv.getEdgeListOffset(), rv.mem)
|
|
if err != nil {
|
|
_ = rv.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return rv, nil
|
|
}
|
|
|
|
// SegmentBase is a memory only, read-only implementation of the
|
|
// segment.Segment interface, using zap's data representation.
|
|
type SegmentBase struct {
|
|
// atomic access to these variables, moved to top to correct alignment issues on ARM, 386 and 32-bit MIPS.
|
|
bytesRead uint64
|
|
bytesWritten uint64
|
|
|
|
mem []byte
|
|
memCRC uint32
|
|
chunkMode uint32
|
|
fieldsMap map[string]uint16 // fieldName -> fieldID+1
|
|
fieldsOptions map[string]index.FieldIndexingOptions // fieldName -> fieldOptions
|
|
fieldsInv []string // fieldID -> fieldName
|
|
fieldsSectionsMap [][]uint64 // fieldID -> section -> address
|
|
numDocs uint64
|
|
storedIndexOffset uint64
|
|
sectionsIndexOffset uint64
|
|
fieldDvReaders [][]*docValueReader // naive chunk cache per field; section->fieldID->reader
|
|
fieldDvNames []string // field names cached in fieldDvReaders
|
|
size uint64
|
|
|
|
// file reader initialised with the writer callback id used by the segment
|
|
fileReader *FileReader
|
|
|
|
// index update specific tracking
|
|
updatedFields map[string]*index.UpdateFieldInfo
|
|
config map[string]interface{} // config for the segment
|
|
|
|
// section-specific caches
|
|
invIndexCache *invertedIndexCache
|
|
vecIndexCache *vectorIndexCache
|
|
synIndexCache *synonymIndexCache
|
|
nstIndexCache *nestedIndexCache
|
|
}
|
|
|
|
func (sb *SegmentBase) Size() int {
|
|
return int(sb.size)
|
|
}
|
|
|
|
func (sb *SegmentBase) updateSize() {
|
|
sizeInBytes := reflectStaticSizeSegmentBase +
|
|
cap(sb.mem)
|
|
|
|
// fieldsMap
|
|
for k := range sb.fieldsMap {
|
|
sizeInBytes += (len(k) + SizeOfString) + SizeOfUint16
|
|
}
|
|
|
|
// fieldsOptions
|
|
for k := range sb.fieldsOptions {
|
|
sizeInBytes += (len(k) + SizeOfString) + SizeOfUint64
|
|
}
|
|
|
|
// fieldsInv
|
|
for _, entry := range sb.fieldsInv {
|
|
sizeInBytes += len(entry) + SizeOfString
|
|
}
|
|
|
|
// fieldDvReaders
|
|
for _, secDvReaders := range sb.fieldDvReaders {
|
|
for _, v := range secDvReaders {
|
|
sizeInBytes += SizeOfUint16 + SizeOfPtr
|
|
if v != nil {
|
|
sizeInBytes += v.size()
|
|
}
|
|
}
|
|
}
|
|
|
|
sb.size = uint64(sizeInBytes)
|
|
}
|
|
|
|
func (sb *SegmentBase) AddRef() {}
|
|
func (sb *SegmentBase) DecRef() (err error) { return nil }
|
|
func (sb *SegmentBase) Close() (err error) {
|
|
sb.invIndexCache.Clear()
|
|
sb.vecIndexCache.Clear()
|
|
sb.synIndexCache.Clear()
|
|
sb.nstIndexCache.Clear()
|
|
return nil
|
|
}
|
|
|
|
// Segment implements a persisted segment.Segment interface, by
|
|
// embedding an mmap()'ed SegmentBase.
|
|
type Segment struct {
|
|
SegmentBase
|
|
|
|
f *os.File
|
|
mm mmap.MMap
|
|
path string
|
|
version uint32
|
|
crc uint32
|
|
|
|
m sync.Mutex // Protects the fields that follow.
|
|
refs int64
|
|
}
|
|
|
|
func (s *Segment) Size() int {
|
|
// 8 /* size of file pointer */
|
|
// 4 /* size of version -> uint32 */
|
|
// 4 /* size of crc -> uint32 */
|
|
sizeOfUints := 16
|
|
|
|
sizeInBytes := (len(s.path) + SizeOfString) + sizeOfUints
|
|
|
|
// mutex, refs -> int64
|
|
sizeInBytes += 16
|
|
|
|
// do not include the mmap'ed part
|
|
return sizeInBytes + s.SegmentBase.Size() - cap(s.mem)
|
|
}
|
|
|
|
func (s *Segment) AddRef() {
|
|
s.m.Lock()
|
|
s.refs++
|
|
s.m.Unlock()
|
|
}
|
|
|
|
func (s *Segment) DecRef() (err error) {
|
|
s.m.Lock()
|
|
s.refs--
|
|
if s.refs == 0 {
|
|
err = s.closeActual()
|
|
}
|
|
s.m.Unlock()
|
|
return err
|
|
}
|
|
|
|
func (s *Segment) loadConfig() error {
|
|
// read offsets of 32 bit values - crc, ver, chunk
|
|
crcOffset := len(s.mm) - 4
|
|
verOffset := crcOffset - 4
|
|
chunkOffset := verOffset - 4
|
|
|
|
// read offsets of 64 bit values - sectionsIndexOffset, storedIndexOffset, numDocsOffset
|
|
sectionsIndexOffset := chunkOffset - 8
|
|
storedIndexOffset := sectionsIndexOffset - 8
|
|
numDocsOffset := storedIndexOffset - 8
|
|
|
|
// read offsets for the writer id length
|
|
idLenOffset := numDocsOffset - 4
|
|
|
|
// read 32-bit crc
|
|
s.crc = binary.BigEndian.Uint32(s.mm[crcOffset : crcOffset+4])
|
|
|
|
// read 32-bit version
|
|
s.version = binary.BigEndian.Uint32(s.mm[verOffset : verOffset+4])
|
|
if s.version != Version {
|
|
return fmt.Errorf("unsupported version %d != %d", s.version, Version)
|
|
}
|
|
|
|
// read 32-bit chunk mode
|
|
s.chunkMode = binary.BigEndian.Uint32(s.mm[chunkOffset : chunkOffset+4])
|
|
|
|
// read 64-bit sections index offset
|
|
s.sectionsIndexOffset = binary.BigEndian.Uint64(s.mm[sectionsIndexOffset : sectionsIndexOffset+8])
|
|
|
|
// read 64-bit stored index offset
|
|
s.storedIndexOffset = binary.BigEndian.Uint64(s.mm[storedIndexOffset : storedIndexOffset+8])
|
|
|
|
// read 64-bit num docs
|
|
s.numDocs = binary.BigEndian.Uint64(s.mm[numDocsOffset : numDocsOffset+8])
|
|
|
|
// read the length of the id
|
|
idLen := binary.BigEndian.Uint32(s.mm[idLenOffset : idLenOffset+4])
|
|
idOffset := idLenOffset - int(idLen)
|
|
|
|
// read the file writer callback id and initialize the file reader with the same id
|
|
fileWriterID := string(s.mm[idOffset : idOffset+int(idLen)])
|
|
var err error
|
|
s.fileReader, err = NewFileReader(fileWriterID, []byte(s.path))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
footerSize := FooterSize + int(idLen)
|
|
s.incrementBytesRead(uint64(footerSize))
|
|
s.SegmentBase.mem = s.mm[:len(s.mm)-footerSize]
|
|
return nil
|
|
}
|
|
|
|
// Implements the segment.DiskStatsReporter interface
|
|
// Only the persistedSegment type implments the
|
|
// interface, as the intention is to retrieve the bytes
|
|
// read from the on-disk segment as part of the current
|
|
// query.
|
|
func (s *Segment) ResetBytesRead(val uint64) {
|
|
atomic.StoreUint64(&s.SegmentBase.bytesRead, val)
|
|
}
|
|
|
|
func (s *Segment) BytesRead() uint64 {
|
|
return atomic.LoadUint64(&s.bytesRead)
|
|
}
|
|
|
|
func (s *Segment) BytesWritten() uint64 {
|
|
return 0
|
|
}
|
|
|
|
func (s *Segment) incrementBytesRead(val uint64) {
|
|
atomic.AddUint64(&s.bytesRead, val)
|
|
}
|
|
|
|
func (sb *SegmentBase) BytesWritten() uint64 {
|
|
return atomic.LoadUint64(&sb.bytesWritten)
|
|
}
|
|
|
|
func (sb *SegmentBase) setBytesWritten(val uint64) {
|
|
atomic.AddUint64(&sb.bytesWritten, val)
|
|
}
|
|
|
|
func (sb *SegmentBase) BytesRead() uint64 {
|
|
return 0
|
|
}
|
|
|
|
func (sb *SegmentBase) ResetBytesRead(val uint64) {}
|
|
|
|
func (sb *SegmentBase) incrementBytesRead(val uint64) {
|
|
atomic.AddUint64(&sb.bytesRead, val)
|
|
}
|
|
|
|
func (sb *SegmentBase) loadFields() error {
|
|
pos := sb.sectionsIndexOffset
|
|
|
|
if pos == 0 {
|
|
return fmt.Errorf("no sections index present")
|
|
}
|
|
|
|
seek := pos + binary.MaxVarintLen64
|
|
if seek > uint64(len(sb.mem)) {
|
|
// handling a buffer overflow case.
|
|
// a rare case where the backing buffer is not large enough to be read directly via
|
|
// a pos+binary.MaxVarintLen64 seek. For eg, this can happen when there is only
|
|
// one field to be indexed in the entire batch of data and while writing out
|
|
// these fields metadata, you write 1 + 8 bytes whereas the MaxVarintLen64 = 10.
|
|
seek = uint64(len(sb.mem))
|
|
}
|
|
|
|
// read the number of fields
|
|
numFields, sz := binary.Uvarint(sb.mem[pos:seek])
|
|
// here, the pos is incremented by the valid number bytes read from the buffer
|
|
// so in the edge case pointed out above the numFields = 1, the sz = 1 as well.
|
|
pos += uint64(sz)
|
|
sb.incrementBytesRead(uint64(sz))
|
|
|
|
// the following loop will be executed only once in the edge case pointed out above
|
|
// since there is only field's offset store which occupies 8 bytes.
|
|
// the pointer then seeks to a position preceding the sectionsIndexOffset, at
|
|
// which point the responsibility of handling the out-of-bounds cases shifts to
|
|
// the specific section's parsing logic.
|
|
var fieldID uint64
|
|
for fieldID < numFields {
|
|
addr := binary.BigEndian.Uint64(sb.mem[pos : pos+8])
|
|
sb.incrementBytesRead(8)
|
|
|
|
fieldSectionMap, err := sb.loadField(uint16(fieldID), addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sb.fieldsSectionsMap = append(sb.fieldsSectionsMap, fieldSectionMap)
|
|
|
|
fieldID++
|
|
pos += 8
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// loadField loads the field metadata for the given fieldID at the given position
|
|
func (sb *SegmentBase) loadField(fieldID uint16, pos uint64) ([]uint64, error) {
|
|
if pos == 0 {
|
|
// there is no indexing structure present for this field/section
|
|
return nil, nil
|
|
}
|
|
|
|
fieldStartPos := pos // to track the number of bytes read
|
|
fieldNameLen, sz := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
|
pos += uint64(sz)
|
|
|
|
fieldName, err := sb.fileReader.process(sb.mem[pos : pos+fieldNameLen])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pos += fieldNameLen
|
|
|
|
// read field options
|
|
fieldOptions, sz := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
|
pos += uint64(sz)
|
|
|
|
sb.fieldsInv = append(sb.fieldsInv, string(fieldName))
|
|
sb.fieldsMap[string(fieldName)] = fieldID + 1
|
|
sb.fieldsOptions[string(fieldName)] = index.FieldIndexingOptions(fieldOptions)
|
|
|
|
fieldNumSections, sz := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
|
pos += uint64(sz)
|
|
// create an address mapping array for each of the segment sections
|
|
// if the field has a valid section index, then the address will be non-zero
|
|
// else it will be zero.
|
|
fieldSectionMap := make([]uint64, NumSections)
|
|
for sectionIdx := uint64(0); sectionIdx < fieldNumSections; sectionIdx++ {
|
|
// read section id
|
|
fieldSectionType := binary.BigEndian.Uint16(sb.mem[pos : pos+2])
|
|
pos += 2
|
|
fieldSectionAddr := binary.BigEndian.Uint64(sb.mem[pos : pos+8])
|
|
pos += 8
|
|
fieldSectionMap[fieldSectionType] = fieldSectionAddr
|
|
}
|
|
|
|
// account the bytes read while parsing the sections field index.
|
|
sb.incrementBytesRead((pos - uint64(fieldStartPos)) + fieldNameLen)
|
|
return fieldSectionMap, nil
|
|
}
|
|
|
|
// Dictionary returns the term dictionary for the specified field
|
|
func (sb *SegmentBase) Dictionary(field string) (segment.TermDictionary, error) {
|
|
dict, err := sb.dictionary(field)
|
|
if err == nil && dict == nil {
|
|
return emptyDictionary, nil
|
|
}
|
|
return dict, err
|
|
}
|
|
|
|
func (sb *SegmentBase) dictionary(field string) (rv *Dictionary, err error) {
|
|
fieldIDPlus1 := sb.fieldsMap[field]
|
|
if fieldIDPlus1 == 0 {
|
|
return nil, nil
|
|
}
|
|
pos := sb.fieldsSectionsMap[fieldIDPlus1-1][SectionInvertedTextIndex]
|
|
if pos > 0 {
|
|
rv = &Dictionary{
|
|
sb: sb,
|
|
field: field,
|
|
fieldID: fieldIDPlus1 - 1,
|
|
}
|
|
// skip the doc value offsets to get to the dictionary portion
|
|
for i := 0; i < 2; i++ {
|
|
_, n := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
|
pos += uint64(n)
|
|
}
|
|
dictLoc, n := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
|
pos += uint64(n)
|
|
fst, bytesRead, err := sb.invIndexCache.loadOrCreate(rv.fieldID, sb.mem[dictLoc:], sb.fileReader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("dictionary for field %s err: %v", field, err)
|
|
}
|
|
rv.fst = fst
|
|
rv.fstReader, err = rv.fst.Reader()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("dictionary for field %s, vellum reader err: %v", field, err)
|
|
}
|
|
rv.bytesRead += bytesRead
|
|
}
|
|
|
|
return rv, nil
|
|
}
|
|
|
|
// Thesaurus returns the thesaurus with the specified name, or an empty thesaurus if not found.
|
|
func (sb *SegmentBase) Thesaurus(name string) (segment.Thesaurus, error) {
|
|
thesaurus, err := sb.thesaurus(name)
|
|
if err == nil && thesaurus == nil {
|
|
return emptyThesaurus, nil
|
|
}
|
|
return thesaurus, err
|
|
}
|
|
|
|
func (sb *SegmentBase) thesaurus(name string) (rv *Thesaurus, err error) {
|
|
fieldIDPlus1 := sb.fieldsMap[name]
|
|
if fieldIDPlus1 == 0 {
|
|
return nil, nil
|
|
}
|
|
pos := sb.fieldsSectionsMap[fieldIDPlus1-1][SectionSynonymIndex]
|
|
if pos > 0 {
|
|
rv = &Thesaurus{
|
|
sb: sb,
|
|
name: name,
|
|
fieldID: fieldIDPlus1 - 1,
|
|
}
|
|
// skip the doc value offsets as doc values are not supported in thesaurus
|
|
for i := 0; i < 2; i++ {
|
|
_, n := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
|
pos += uint64(n)
|
|
}
|
|
thesLoc, n := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
|
pos += uint64(n)
|
|
fst, synTermMap, bytesRead, err := sb.synIndexCache.loadOrCreate(rv.fieldID, sb.mem[thesLoc:], sb.fileReader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("thesaurus name %s err: %v", name, err)
|
|
}
|
|
rv.fst = fst
|
|
rv.synIDTermMap = synTermMap
|
|
rv.fstReader, err = rv.fst.Reader()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("thesaurus name %s vellum reader err: %v", name, err)
|
|
}
|
|
rv.bytesRead += bytesRead
|
|
}
|
|
return rv, nil
|
|
}
|
|
|
|
// visitDocumentCtx holds data structures that are reusable across
|
|
// multiple VisitDocument() calls to avoid memory allocations
|
|
type visitDocumentCtx struct {
|
|
buf []byte
|
|
reader bytes.Reader
|
|
arrayPos []uint64
|
|
}
|
|
|
|
var visitDocumentCtxPool = sync.Pool{
|
|
New: func() interface{} {
|
|
reuse := &visitDocumentCtx{}
|
|
return reuse
|
|
},
|
|
}
|
|
|
|
// VisitStoredFields invokes the StoredFieldValueVisitor for each stored field
|
|
// for the specified doc number
|
|
func (sb *SegmentBase) VisitStoredFields(num uint64, visitor segment.StoredFieldValueVisitor) error {
|
|
vdc := visitDocumentCtxPool.Get().(*visitDocumentCtx)
|
|
defer visitDocumentCtxPool.Put(vdc)
|
|
return sb.visitStoredFields(vdc, num, visitor)
|
|
}
|
|
|
|
func (sb *SegmentBase) visitStoredFields(vdc *visitDocumentCtx, num uint64,
|
|
visitor segment.StoredFieldValueVisitor) error {
|
|
// first make sure this is a valid number in this segment
|
|
if num < sb.numDocs {
|
|
meta, compressed, err := sb.getDocStoredMetaAndCompressed(num)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vdc.reader.Reset(meta)
|
|
|
|
// handle _id field special case
|
|
idFieldValLen, err := binary.ReadUvarint(&vdc.reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
idFieldVal := compressed[:idFieldValLen]
|
|
|
|
keepGoing := visitor("_id", byte('t'), idFieldVal, nil)
|
|
if !keepGoing {
|
|
visitDocumentCtxPool.Put(vdc)
|
|
return nil
|
|
}
|
|
|
|
// handle non-"_id" fields
|
|
compressed = compressed[idFieldValLen:]
|
|
|
|
uncompressed, err := snappy.Decode(vdc.buf[:cap(vdc.buf)], compressed)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for keepGoing {
|
|
field, err := binary.ReadUvarint(&vdc.reader)
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
typ, err := binary.ReadUvarint(&vdc.reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
offset, err := binary.ReadUvarint(&vdc.reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
l, err := binary.ReadUvarint(&vdc.reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
numap, err := binary.ReadUvarint(&vdc.reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var arrayPos []uint64
|
|
if numap > 0 {
|
|
if cap(vdc.arrayPos) < int(numap) {
|
|
vdc.arrayPos = make([]uint64, numap)
|
|
}
|
|
arrayPos = vdc.arrayPos[:numap]
|
|
for i := 0; i < int(numap); i++ {
|
|
ap, err := binary.ReadUvarint(&vdc.reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
arrayPos[i] = ap
|
|
}
|
|
}
|
|
value := uncompressed[offset : offset+l]
|
|
keepGoing = visitor(sb.fieldsInv[field], byte(typ), value, arrayPos)
|
|
}
|
|
|
|
vdc.buf = uncompressed
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DocID returns the value of the _id field for the given docNum
|
|
func (sb *SegmentBase) DocID(num uint64) ([]byte, error) {
|
|
if num >= sb.numDocs {
|
|
return nil, nil
|
|
}
|
|
|
|
vdc := visitDocumentCtxPool.Get().(*visitDocumentCtx)
|
|
|
|
meta, compressed, err := sb.getDocStoredMetaAndCompressed(num)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vdc.reader.Reset(meta)
|
|
|
|
// handle _id field special case
|
|
idFieldValLen, err := binary.ReadUvarint(&vdc.reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
idFieldVal := compressed[:idFieldValLen]
|
|
|
|
visitDocumentCtxPool.Put(vdc)
|
|
|
|
return idFieldVal, nil
|
|
}
|
|
|
|
// Count returns the number of documents in this segment.
|
|
func (sb *SegmentBase) Count() uint64 {
|
|
return sb.numDocs
|
|
}
|
|
|
|
// DocNumbers returns a bitset corresponding to the doc numbers of all the
|
|
// provided _id strings
|
|
func (sb *SegmentBase) DocNumbers(ids []string) (*roaring.Bitmap, error) {
|
|
rv := roaring.New()
|
|
|
|
if len(sb.fieldsMap) > 0 {
|
|
idDict, err := sb.dictionary("_id")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
postingsList := emptyPostingsList
|
|
|
|
sMax, err := idDict.fst.GetMaxKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sMaxStr := string(sMax)
|
|
for _, id := range ids {
|
|
if id <= sMaxStr {
|
|
postingsList, err = idDict.postingsList([]byte(id), nil, postingsList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
postingsList.OrInto(rv)
|
|
}
|
|
}
|
|
}
|
|
|
|
return rv, nil
|
|
}
|
|
|
|
// Fields returns the field names used in this segment
|
|
func (sb *SegmentBase) Fields() []string {
|
|
return sb.fieldsInv
|
|
}
|
|
|
|
// Path returns the path of this segment on disk
|
|
func (s *Segment) Path() string {
|
|
return s.path
|
|
}
|
|
|
|
// Close releases all resources associated with this segment
|
|
func (s *Segment) Close() (err error) {
|
|
return s.DecRef()
|
|
}
|
|
|
|
func (s *Segment) closeActual() (err error) {
|
|
// clear contents from all caches before un-mmapping
|
|
s.invIndexCache.Clear()
|
|
s.vecIndexCache.Clear()
|
|
s.synIndexCache.Clear()
|
|
s.nstIndexCache.Clear()
|
|
|
|
if s.mm != nil {
|
|
err = s.mm.Unmap()
|
|
}
|
|
// try to close file even if unmap failed
|
|
if s.f != nil {
|
|
err2 := s.f.Close()
|
|
if err == nil {
|
|
// try to return first error
|
|
err = err2
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// some helpers i started adding for the command-line utility
|
|
|
|
// Data returns the underlying mmaped data slice
|
|
func (s *Segment) Data() []byte {
|
|
return s.mm
|
|
}
|
|
|
|
// CRC returns the CRC value stored in the file footer
|
|
func (s *Segment) CRC() uint32 {
|
|
return s.crc
|
|
}
|
|
|
|
// Version returns the file version in the file footer
|
|
func (s *Segment) Version() uint32 {
|
|
return s.version
|
|
}
|
|
|
|
// ChunkFactor returns the chunk factor in the file footer
|
|
func (s *Segment) ChunkMode() uint32 {
|
|
return s.chunkMode
|
|
}
|
|
|
|
// SectionsIndexOffset returns the sections index offset in the file footer
|
|
func (s *Segment) SectionsIndexOffset() uint64 {
|
|
return s.sectionsIndexOffset
|
|
}
|
|
|
|
// StoredIndexOffset returns the stored value index offset in the file footer
|
|
func (s *Segment) StoredIndexOffset() uint64 {
|
|
return s.storedIndexOffset
|
|
}
|
|
|
|
// NumDocs returns the number of documents in the file footer
|
|
func (s *Segment) NumDocs() uint64 {
|
|
return s.numDocs
|
|
}
|
|
|
|
// DictAddr is a helper function to compute the file offset where the
|
|
// dictionary is stored for the specified field.
|
|
func (s *Segment) DictAddr(field string) (uint64, error) {
|
|
fieldIDPlus1, ok := s.fieldsMap[field]
|
|
if !ok {
|
|
return 0, fmt.Errorf("no such field '%s'", field)
|
|
}
|
|
dictStart := s.fieldsSectionsMap[fieldIDPlus1-1][SectionInvertedTextIndex]
|
|
if dictStart == 0 {
|
|
return 0, fmt.Errorf("no dictionary for field '%s'", field)
|
|
}
|
|
for i := 0; i < 2; i++ {
|
|
_, n := binary.Uvarint(s.mem[dictStart : dictStart+binary.MaxVarintLen64])
|
|
dictStart += uint64(n)
|
|
}
|
|
dictLoc, _ := binary.Uvarint(s.mem[dictStart : dictStart+binary.MaxVarintLen64])
|
|
return dictLoc, nil
|
|
}
|
|
|
|
// VectorAddr is a helper function to compute the file offset where the
|
|
// vector index is stored for the specified field.
|
|
func (s *Segment) VectorAddr(name string) (uint64, error) {
|
|
fieldIDPlus1, ok := s.fieldsMap[name]
|
|
if !ok {
|
|
return 0, fmt.Errorf("no such field '%s'", name)
|
|
}
|
|
vectorStart := s.fieldsSectionsMap[fieldIDPlus1-1][SectionFaissVectorIndex]
|
|
if vectorStart == 0 {
|
|
return 0, fmt.Errorf("no vector index for field '%s'", name)
|
|
}
|
|
for i := 0; i < 2; i++ {
|
|
_, n := binary.Uvarint(s.mem[vectorStart : vectorStart+binary.MaxVarintLen64])
|
|
vectorStart += uint64(n)
|
|
}
|
|
vectorLoc, _ := binary.Uvarint(s.mem[vectorStart : vectorStart+binary.MaxVarintLen64])
|
|
return vectorLoc, nil
|
|
}
|
|
|
|
// ThesaurusAddr is a helper function to compute the file offset where the
|
|
// thesaurus is stored with the specified name.
|
|
func (s *Segment) ThesaurusAddr(name string) (uint64, error) {
|
|
fieldIDPlus1, ok := s.fieldsMap[name]
|
|
if !ok {
|
|
return 0, fmt.Errorf("no such thesaurus '%s'", name)
|
|
}
|
|
thesaurusStart := s.fieldsSectionsMap[fieldIDPlus1-1][SectionSynonymIndex]
|
|
if thesaurusStart == 0 {
|
|
return 0, fmt.Errorf("no such thesaurus '%s'", name)
|
|
}
|
|
for i := 0; i < 2; i++ {
|
|
_, n := binary.Uvarint(s.mem[thesaurusStart : thesaurusStart+binary.MaxVarintLen64])
|
|
thesaurusStart += uint64(n)
|
|
}
|
|
thesLoc, _ := binary.Uvarint(s.mem[thesaurusStart : thesaurusStart+binary.MaxVarintLen64])
|
|
return thesLoc, nil
|
|
}
|
|
|
|
// EdgeListAddr is the exported helper function to compute the
|
|
// file offset where the edge list is stored.
|
|
func (s *Segment) EdgeListAddr() (uint64, error) {
|
|
return s.getEdgeListOffset(), nil
|
|
}
|
|
|
|
func (sb *SegmentBase) loadDvReaders() error {
|
|
if sb.numDocs == 0 {
|
|
return nil
|
|
}
|
|
for fieldID, sections := range sb.fieldsSectionsMap {
|
|
for secID, secOffset := range sections {
|
|
if secOffset > 0 {
|
|
pos := secOffset
|
|
var read uint64
|
|
fieldLocStart, n := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
|
if n <= 0 {
|
|
return fmt.Errorf("loadDvReaders: failed to read the docvalue offset start for field %v", sb.fieldsInv[fieldID])
|
|
}
|
|
pos += uint64(n)
|
|
read += uint64(n)
|
|
fieldLocEnd, n := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
|
if n <= 0 {
|
|
return fmt.Errorf("loadDvReaders: failed to read the docvalue offset end for field %v", sb.fieldsInv[fieldID])
|
|
}
|
|
pos += uint64(n)
|
|
read += uint64(n)
|
|
|
|
sb.incrementBytesRead(read)
|
|
|
|
fieldDvReader, err := sb.loadFieldDocValueReader(sb.fieldsInv[fieldID], fieldLocStart, fieldLocEnd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if fieldDvReader != nil {
|
|
if sb.fieldDvReaders[secID] == nil {
|
|
sb.fieldDvReaders[secID] = make([]*docValueReader, len(sb.fieldsInv))
|
|
}
|
|
sb.fieldDvReaders[secID][uint16(fieldID)] = fieldDvReader
|
|
sb.fieldDvNames = append(sb.fieldDvNames, sb.fieldsInv[fieldID])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Getter method to retrieve updateFieldInfo within segment base
|
|
func (s *SegmentBase) GetUpdatedFields() map[string]*index.UpdateFieldInfo {
|
|
return s.updatedFields
|
|
}
|
|
|
|
// Setter method to store updateFieldInfo within segment base
|
|
func (s *SegmentBase) SetUpdatedFields(updatedFields map[string]*index.UpdateFieldInfo) {
|
|
s.updatedFields = updatedFields
|
|
}
|
|
|
|
// Ancestors returns a slice of document numbers representing the ancestors of the
|
|
// specified document (docNum) within the segment. If the document has no ancestors,
|
|
// a slice containing only the document number itself is returned. The prealloc
|
|
// parameter allows for reusing a preallocated slice to avoid additional allocations.
|
|
func (sb *SegmentBase) Ancestors(docNum uint64, prealloc []index.AncestorID) []index.AncestorID {
|
|
return sb.nstIndexCache.ancestry(docNum, prealloc)
|
|
}
|
|
|
|
// CountRoot returns the number of root documents in the segment, excluding any
|
|
// documents that are marked as deleted in the provided bitmap. The deleted bitmap
|
|
// may contain both root and sub-document numbers, and the method ensures that
|
|
// only root documents are counted.
|
|
func (sb *SegmentBase) CountRoot(deleted *roaring.Bitmap) uint64 {
|
|
// the formula is as follows:
|
|
// Total Docs (T) = Root Docs (R) + Sub Docs (S)
|
|
// R = T - S
|
|
// Now if we have D deleted docs, some of which may be sub-docs, we need to exclude
|
|
// those from the root doc count. Let D = dR + dS, where dR is the number of deleted
|
|
// root docs and dS is the number of deleted sub docs.
|
|
// dR = D - dS
|
|
// Therefore, the count of root docs excluding deleted ones is:
|
|
// R - dR = (T - S) - (D - dS)
|
|
return (sb.Count() - sb.countNested()) - (sb.nstIndexCache.countRoot(deleted))
|
|
}
|
|
|
|
// AddNestedDocuments returns a bitmap containing the original document numbers in drops,
|
|
// plus any descendant document numbers for each dropped document. The drops
|
|
// parameter represents a set of document numbers to be dropped, and the returned
|
|
// bitmap includes both the original drops and all their descendants (if any).
|
|
func (sb *SegmentBase) AddNestedDocuments(drops *roaring.Bitmap) *roaring.Bitmap {
|
|
// If no drops or no subDocs, nothing to do
|
|
if drops == nil || drops.GetCardinality() == 0 || sb.countNested() == 0 {
|
|
return drops
|
|
}
|
|
// Get the edge list for this segment
|
|
el := sb.EdgeList()
|
|
// Algorithm => iterate through each child->parent mapping in the edge list,
|
|
// and for each pair, check if the parent is in the drops bitmap.
|
|
// If it is, and the child is also not already in the drops bitmap,
|
|
// add the child to the drops. Repeat this process until no
|
|
// new additions are made in an iteration.
|
|
changed := true
|
|
for changed {
|
|
changed = false
|
|
el.Iterate(func(child uint64, parent uint64) bool {
|
|
if drops.Contains(uint32(parent)) && !drops.Contains(uint32(child)) {
|
|
drops.Add(uint32(child))
|
|
changed = true
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
return drops
|
|
}
|
|
|
|
// EdgeList returns an EdgeList interface representing the parent-child relationships between documents in the segment.
|
|
// The EdgeList interface allows iteration over child-parent document pairs, enabling navigation of document hierarchies.
|
|
// The underlying implementation may use a map or a slice, but callers should rely on the interface methods.
|
|
func (sb *SegmentBase) EdgeList() EdgeList {
|
|
return sb.nstIndexCache.edgeList()
|
|
}
|
|
|
|
// Utility method to count the number of nested documents in the segment, not exported.
|
|
func (sb *SegmentBase) countNested() uint64 {
|
|
return sb.nstIndexCache.countNested()
|
|
}
|
|
|
|
func (sb *SegmentBase) CallbackId() string {
|
|
return sb.fileReader.id
|
|
}
|