mirror of
https://github.com/luxfi/database.git
synced 2026-10-30 15:01:18 +00:00
334 lines
7.5 KiB
Go
334 lines
7.5 KiB
Go
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package prefixdb
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"sync"
|
|
|
|
"github.com/luxfi/database"
|
|
)
|
|
|
|
// Database partitions a database into a sub-database by prefixing all keys with
|
|
// a unique value.
|
|
type Database struct {
|
|
lock sync.RWMutex
|
|
db database.Database
|
|
prefix []byte
|
|
closed bool
|
|
|
|
bufferPool sync.Pool
|
|
}
|
|
|
|
// New returns a new prefixed database.
|
|
func New(prefix []byte, db database.Database) *Database {
|
|
return &Database{
|
|
db: db,
|
|
prefix: prefix,
|
|
bufferPool: sync.Pool{
|
|
New: func() any {
|
|
return make([]byte, 0, len(prefix)+256)
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Deprecated: Use [New] instead.
|
|
func NewNested(prefix []byte, db database.Database) *Database {
|
|
return New(prefix, db)
|
|
}
|
|
|
|
// Close implements the database.Database interface.
|
|
// Closing a prefixed view releases only the view; the parent store's owner is
|
|
// responsible for closing the underlying database.
|
|
func (p *Database) Close() error {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
if p.closed {
|
|
return database.ErrClosed
|
|
}
|
|
p.closed = true
|
|
return nil
|
|
}
|
|
|
|
func (p *Database) isClosed() bool {
|
|
p.lock.RLock()
|
|
defer p.lock.RUnlock()
|
|
|
|
return p.closed
|
|
}
|
|
|
|
// HealthCheck implements the database.Database interface.
|
|
func (p *Database) HealthCheck(ctx context.Context) (any, error) {
|
|
p.lock.RLock()
|
|
defer p.lock.RUnlock()
|
|
|
|
if p.closed {
|
|
return nil, database.ErrClosed
|
|
}
|
|
return p.db.HealthCheck(ctx)
|
|
}
|
|
|
|
func (p *Database) Backup(w io.Writer, since uint64) (uint64, error) {
|
|
p.lock.RLock()
|
|
defer p.lock.RUnlock()
|
|
|
|
if p.closed {
|
|
return 0, database.ErrClosed
|
|
}
|
|
return p.db.Backup(w, since)
|
|
}
|
|
|
|
func (p *Database) Load(r io.Reader) error {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
if p.closed {
|
|
return database.ErrClosed
|
|
}
|
|
return p.db.Load(r)
|
|
}
|
|
|
|
// Has implements the database.Database interface.
|
|
func (p *Database) Has(key []byte) (bool, error) {
|
|
p.lock.RLock()
|
|
defer p.lock.RUnlock()
|
|
|
|
if p.closed {
|
|
return false, database.ErrClosed
|
|
}
|
|
return p.db.Has(p.prefixKey(key))
|
|
}
|
|
|
|
// Get implements the database.Database interface.
|
|
func (p *Database) Get(key []byte) ([]byte, error) {
|
|
p.lock.RLock()
|
|
defer p.lock.RUnlock()
|
|
|
|
if p.closed {
|
|
return nil, database.ErrClosed
|
|
}
|
|
return p.db.Get(p.prefixKey(key))
|
|
}
|
|
|
|
// Put implements the database.Database interface.
|
|
func (p *Database) Put(key, value []byte) error {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
if p.closed {
|
|
return database.ErrClosed
|
|
}
|
|
return p.db.Put(p.prefixKey(key), value)
|
|
}
|
|
|
|
// Delete implements the database.Database interface.
|
|
func (p *Database) Delete(key []byte) error {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
if p.closed {
|
|
return database.ErrClosed
|
|
}
|
|
return p.db.Delete(p.prefixKey(key))
|
|
}
|
|
|
|
// NewBatch implements the database.Database interface.
|
|
func (p *Database) NewBatch() database.Batch {
|
|
return &batch{
|
|
Batch: p.db.NewBatch(),
|
|
prefix: p,
|
|
}
|
|
}
|
|
|
|
// NewIterator implements the database.Database interface.
|
|
func (p *Database) NewIterator() database.Iterator {
|
|
return p.NewIteratorWithStartAndPrefix(nil, nil)
|
|
}
|
|
|
|
// NewIteratorWithStart implements the database.Database interface.
|
|
func (p *Database) NewIteratorWithStart(start []byte) database.Iterator {
|
|
return p.NewIteratorWithStartAndPrefix(start, nil)
|
|
}
|
|
|
|
// NewIteratorWithPrefix implements the database.Database interface.
|
|
func (p *Database) NewIteratorWithPrefix(prefix []byte) database.Iterator {
|
|
return p.NewIteratorWithStartAndPrefix(nil, prefix)
|
|
}
|
|
|
|
// NewIteratorWithStartAndPrefix implements the database.Database interface.
|
|
//
|
|
// prefixedStart MUST be derived only when the caller supplied a start: a nil
|
|
// start means "begin at the prefix", and the underlying store seeks to the
|
|
// prefix itself. Synthesizing start = p.prefix for a nil start would seek the
|
|
// underlying iterator to the FRONT of this partition (p.prefix), which sorts
|
|
// BEFORE p.prefix+prefix; with intervening keys under other sub-prefixes a
|
|
// store that stops at the first non-matching key after a start-seek (zapdb)
|
|
// then returns zero rows for a prefix that does exist. Each prefixed key is
|
|
// also built in its OWN buffer (prefixKey) so the two never alias p.prefix's
|
|
// backing array.
|
|
func (p *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
|
|
p.lock.RLock()
|
|
defer p.lock.RUnlock()
|
|
|
|
if p.closed {
|
|
return &database.IteratorError{
|
|
Err: database.ErrClosed,
|
|
}
|
|
}
|
|
|
|
var prefixedStart []byte
|
|
if len(start) > 0 {
|
|
prefixedStart = p.prefixKey(start)
|
|
}
|
|
prefixedPrefix := p.prefixKey(prefix)
|
|
return &iterator{
|
|
Iterator: p.db.NewIteratorWithStartAndPrefix(prefixedStart, prefixedPrefix),
|
|
prefix: p,
|
|
}
|
|
}
|
|
|
|
// Compact implements the database.Database interface.
|
|
func (p *Database) Compact(start, limit []byte) error {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
if p.closed {
|
|
return database.ErrClosed
|
|
}
|
|
if start != nil {
|
|
start = p.prefixKey(start)
|
|
}
|
|
if limit != nil {
|
|
limit = p.prefixKey(limit)
|
|
}
|
|
return p.db.Compact(start, limit)
|
|
}
|
|
|
|
// Sync implements the database.Syncer interface by delegating to the underlying database.
|
|
func (p *Database) Sync() error {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
if p.closed {
|
|
return database.ErrClosed
|
|
}
|
|
return p.db.Sync()
|
|
}
|
|
|
|
// prefixKey returns a key with the prefix appended.
|
|
func (p *Database) prefixKey(key []byte) []byte {
|
|
buf := p.bufferPool.Get().([]byte)[:0]
|
|
buf = append(buf, p.prefix...)
|
|
buf = append(buf, key...)
|
|
|
|
// Create a new slice to avoid keeping reference to the buffer
|
|
result := make([]byte, len(buf))
|
|
copy(result, buf)
|
|
|
|
p.bufferPool.Put(buf)
|
|
return result
|
|
}
|
|
|
|
// removePrefix returns a key with the prefix removed.
|
|
// The returned key shares memory with the input key.
|
|
func (p *Database) removePrefix(key []byte) []byte {
|
|
if len(key) < len(p.prefix) {
|
|
return nil
|
|
}
|
|
return key[len(p.prefix):]
|
|
}
|
|
|
|
// batch wraps a database.Batch to add a prefix to all keys.
|
|
type batch struct {
|
|
database.Batch
|
|
prefix *Database
|
|
}
|
|
|
|
// Put implements the database.Batch interface.
|
|
func (b *batch) Put(key, value []byte) error {
|
|
return b.Batch.Put(b.prefix.prefixKey(key), value)
|
|
}
|
|
|
|
// Delete implements the database.Batch interface.
|
|
func (b *batch) Delete(key []byte) error {
|
|
return b.Batch.Delete(b.prefix.prefixKey(key))
|
|
}
|
|
|
|
// Write implements the database.Batch interface.
|
|
func (b *batch) Write() error {
|
|
b.prefix.lock.RLock()
|
|
defer b.prefix.lock.RUnlock()
|
|
|
|
if b.prefix.closed {
|
|
return database.ErrClosed
|
|
}
|
|
return b.Batch.Write()
|
|
}
|
|
|
|
// Inner implements the database.Batch interface.
|
|
func (b *batch) Inner() database.Batch {
|
|
return b.Batch
|
|
}
|
|
|
|
// iterator wraps a database.Iterator to remove the prefix from all keys.
|
|
type iterator struct {
|
|
database.Iterator
|
|
prefix *Database
|
|
|
|
key, val []byte
|
|
err error
|
|
}
|
|
|
|
// Next moves the iterator to the next key/value pair.
|
|
func (it *iterator) Next() bool {
|
|
if it.prefix.isClosed() {
|
|
it.key = nil
|
|
it.val = nil
|
|
it.err = database.ErrClosed
|
|
return false
|
|
}
|
|
|
|
next := it.Iterator.Next()
|
|
if next {
|
|
if rawKey := it.Iterator.Key(); rawKey != nil {
|
|
it.key = it.prefix.removePrefix(rawKey)
|
|
} else {
|
|
it.key = nil
|
|
}
|
|
it.val = it.Iterator.Value()
|
|
} else {
|
|
it.key = nil
|
|
it.val = nil
|
|
}
|
|
return next
|
|
}
|
|
|
|
// Key implements the database.Iterator interface.
|
|
func (it *iterator) Key() []byte {
|
|
return it.key
|
|
}
|
|
|
|
// Value implements the database.Iterator interface.
|
|
func (it *iterator) Value() []byte {
|
|
return it.val
|
|
}
|
|
|
|
// Error implements the database.Iterator interface.
|
|
func (it *iterator) Error() error {
|
|
if it.err != nil {
|
|
return it.err
|
|
}
|
|
return it.Iterator.Error()
|
|
}
|
|
|
|
// Release implements the database.Iterator interface.
|
|
func (it *iterator) Release() {
|
|
it.key = nil
|
|
it.val = nil
|
|
it.Iterator.Release()
|
|
}
|