arnaucube
arnaucube/arboPublic Self-hosted repository
arbo/db/prefixeddb/prefixeddb.go
db/prefixeddb/prefixeddb.goRaw
  1// Package prefixeddb wraps database operations with a shared key prefix.
  2package prefixeddb
  3
  4import (
  5	"bytes"
  6	"slices"
  7
  8	"github.com/arnaucube/arbo/db"
  9)
 10
 11// PrefixedDatabase wraps a db.Database prefixing all keys with `prefix`.
 12type PrefixedDatabase struct {
 13	prefix []byte
 14	db     db.Database
 15}
 16
 17// check that PrefixedDatabase implements the db.Database interface
 18var _ db.Database = (*PrefixedDatabase)(nil)
 19
 20func prefixSlice(prefix, v []byte) []byte {
 21	// Copy prefix to ensure we don't reuse the slice's backing array.
 22	jointPrefix := make([]byte, 0, len(prefix)+len(v))
 23	jointPrefix = append(jointPrefix, prefix...)
 24	jointPrefix = append(jointPrefix, v...)
 25	// Make sure the prefix's capacity is fixed, to prevent future appends from sharing memory.
 26	return slices.Clip(jointPrefix)
 27}
 28
 29// NewPrefixedDatabase creates a new PrefixedDatabase.  If the db is already a
 30// PrefixedDatabase, instead of wrapping again, the prefixes are appended to
 31// avoid unnecessary layers.
 32func NewPrefixedDatabase(db db.Database, prefix []byte) *PrefixedDatabase {
 33	if pdb, ok := db.(*PrefixedDatabase); ok {
 34		return &PrefixedDatabase{prefixSlice(pdb.prefix, prefix), pdb.db}
 35	}
 36	return &PrefixedDatabase{prefix, db}
 37}
 38
 39// Close implements the db.Database.Close interface method.  Notice that this
 40// method also closes the wrapped db.Database.
 41func (d *PrefixedDatabase) Close() error {
 42	return d.db.Close()
 43}
 44
 45// Compact implements the db.Database.Compact interface method.
 46func (d *PrefixedDatabase) Compact() error {
 47	return d.db.Compact()
 48}
 49
 50// WriteTx returns a db.WriteTx
 51func (d *PrefixedDatabase) WriteTx() db.WriteTx {
 52	return NewPrefixedWriteTx(d.db.WriteTx(), d.prefix)
 53}
 54
 55// Get implements the db.Database.Get interface method
 56func (d *PrefixedDatabase) Get(key []byte) ([]byte, error) {
 57	return d.db.Get(prefixSlice(d.prefix, key))
 58}
 59
 60// Iterate implements the db.Database.Iterate interface method
 61func (d *PrefixedDatabase) Iterate(prefix []byte, callback func(key, value []byte) bool) error {
 62	return d.db.Iterate(prefixSlice(d.prefix, prefix), func(key, value []byte) bool {
 63		return callback(bytes.TrimPrefix(key, d.prefix), value)
 64	})
 65}
 66
 67// PrefixedReader wraps a Reader tx prefixing all keys with `prefix`.
 68type PrefixedReader struct {
 69	prefix []byte
 70	rd     db.Reader
 71}
 72
 73// check that PrefixedDatabase implements the db.Database interface
 74var _ db.Reader = (*PrefixedReader)(nil)
 75
 76// NewPrefixedReader creates a new PrefixedReader.  If the rd is already a
 77// PrefixedReader, instead of wrapping again, the prefixes are appended to
 78// avoid unnecessary layers.
 79func NewPrefixedReader(rd db.Reader, prefix []byte) *PrefixedReader {
 80	if pdb, ok := rd.(*PrefixedReader); ok {
 81		return &PrefixedReader{prefixSlice(pdb.prefix, prefix), pdb.rd}
 82	}
 83	return &PrefixedReader{prefix, rd}
 84}
 85
 86// Get retrieves a value using the configured key prefix.
 87func (d *PrefixedReader) Get(key []byte) ([]byte, error) {
 88	return d.rd.Get(prefixSlice(d.prefix, key))
 89}
 90
 91// Iterate implements the db.Database.Iterate interface method
 92func (d *PrefixedReader) Iterate(prefix []byte, callback func(key, value []byte) bool) error {
 93	return d.rd.Iterate(prefixSlice(d.prefix, prefix), func(key, value []byte) bool {
 94		return callback(bytes.TrimPrefix(key, d.prefix), value)
 95	})
 96}
 97
 98// PrefixedWriteTx wraps a WriteTx prefixing all keys with `prefix`.
 99type PrefixedWriteTx struct {
100	prefix []byte
101	tx     db.WriteTx
102}
103
104// check that PrefixedWriteTx implements the db.WriteTx interface
105var _ db.WriteTx = (*PrefixedWriteTx)(nil)
106
107// NewPrefixedWriteTx creates a new db.WriteTx.  If the tx is already a
108// PrefixedWriteTx, instead of wrapping again, the prefixes are appended to avoid
109// unnecessary layers.
110func NewPrefixedWriteTx(tx db.WriteTx, prefix []byte) *PrefixedWriteTx {
111	if ptx, ok := tx.(*PrefixedWriteTx); ok {
112		return &PrefixedWriteTx{prefixSlice(ptx.prefix, prefix), ptx.tx}
113	}
114	return &PrefixedWriteTx{prefix, tx}
115}
116
117// Get implements the db.WriteTx.Get interface method
118func (t *PrefixedWriteTx) Get(key []byte) ([]byte, error) {
119	return t.tx.Get(prefixSlice(t.prefix, key))
120}
121
122// Iterate visits matching entries with the configured prefix removed.
123func (t *PrefixedWriteTx) Iterate(prefix []byte, callback func(key, value []byte) bool) error {
124	return t.tx.Iterate(prefixSlice(t.prefix, prefix), func(key, value []byte) bool {
125		return callback(bytes.TrimPrefix(key, t.prefix), value)
126	})
127}
128
129// Discard implements the db.WriteTx.Discard interface method.  Notice that this
130// method also discards the wrapped db.WriteTx.
131func (t *PrefixedWriteTx) Discard() {
132	t.tx.Discard()
133}
134
135// Set implements the db.WriteTx.Set interface method
136func (t *PrefixedWriteTx) Set(key, value []byte) error {
137	return t.tx.Set(prefixSlice(t.prefix, key), value)
138}
139
140// Delete implements the db.WriteTx.Delete interface method
141func (t *PrefixedWriteTx) Delete(key []byte) error {
142	return t.tx.Delete(prefixSlice(t.prefix, key))
143}
144
145// Apply implements the db.WriteTx.Apply interface method
146func (t *PrefixedWriteTx) Apply(other db.WriteTx) error {
147	return t.tx.Apply(other)
148}
149
150// Unwrap returns the wrapped WriteTx
151func (t *PrefixedWriteTx) Unwrap() db.WriteTx {
152	return t.tx
153}
154
155// Commit implements the db.WriteTx.Commit interface method.  Notice that this
156// method also commits the wrapped db.WriteTx.
157func (t *PrefixedWriteTx) Commit() error {
158	return t.tx.Commit()
159}