// Copyright (c) 2026 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 util import ( "fmt" index "github.com/blevesearch/bleve_index_api" ) // This file provides a mechanism for users of bleve to provide callbacks // that can process data before it is written to disk, and after it is read // from disk. This can be used for things like encryption, compression, etc. // The user is responsible for ensuring that the writer and reader callbacks // are compatible with each other, and that any state needed by the callbacks // is managed appropriately. For example, if the writer callback uses a // unique key or nonce per write, the reader callback must be able to // determine the correct key or nonce to use for each read. // The callbacks are identified by an id string, which is returned by the // WriterHook. The same id string is passed to the ReaderHook when creating a reader. // This allows the reader to determine which callback to use for a given file. // Support for identifying all callbacks used by a given index and to remove // selected callbacks associated with ids is provided via index.WriterIdsInUse() // and index.DropWriterIds(). const DefaultFileCallbackId = "" // FileWriter and FileReader interfaces are wrappers around the callback functions // provided by the user. They provide a convenient way to apply the callbacks to data // being written to or read from a file. They also store the id the callbacks, // which can be useful for managing state across multiple reads and writes. type FileWriter interface { Process(data []byte) []byte Id() string } type fileWriterImpl struct { id string processor func(data []byte) []byte } func NewFileWriter(context []byte) (FileWriter, error) { rv := &fileWriterImpl{} if index.WriterHook != nil { var err error rv.id, rv.processor, err = index.WriterHook(context) if err != nil { return nil, err } } return rv, nil } func (w *fileWriterImpl) Process(data []byte) []byte { if w.processor != nil { return w.processor(data) } return data } func (w *fileWriterImpl) Id() string { return w.id } type FileReader interface { Process(data []byte) ([]byte, error) Id() string } type fileReaderImpl struct { id string processor func(data []byte) ([]byte, error) } func NewFileReader(id string, context []byte) (FileReader, error) { rv := &fileReaderImpl{ id: id, } if index.ReaderHook != nil { var err error rv.processor, err = index.ReaderHook(id, context) if err != nil { return nil, err } } else if id != "" { return nil, fmt.Errorf("reader callback id %s provided but no ReaderHook is set", id) } return rv, nil } func (r *fileReaderImpl) Process(data []byte) ([]byte, error) { if r.processor != nil { return r.processor(data) } return data, nil } func (r *fileReaderImpl) Id() string { return r.id } // ----------------------------------------------------------------------- // set of bolt keys and bucket names that require processing by the reader // and writer callbacks. var boltKeysProcessed = map[string]struct{}{ string(BoltDeletedKey): {}, string(BoltInternalKey): {}, string(BoltStatsKey): {}, string(BoltUpdatedFieldsKey): {}, }