arnaucube
arnaucube/arboPublic Self-hosted repository
arbo/db/pebbledb/pebledb.go
db/pebbledb/pebledb.goRaw
  1// Package pebbledb implements arbo database interfaces using Pebble.
  2package pebbledb
  3
  4import (
  5	"bytes"
  6	"errors"
  7	"fmt"
  8	"os"
  9
 10	"github.com/arnaucube/arbo/db"
 11	"github.com/cockroachdb/pebble"
 12)
 13
 14// WriteTx implements the interface db.WriteTx
 15type WriteTx struct {
 16	batch *pebble.Batch
 17}
 18
 19// check that WriteTx implements the db.WriteTx interface
 20var _ db.WriteTx = (*WriteTx)(nil)
 21
 22func get(reader pebble.Reader, k []byte) ([]byte, error) {
 23	v, closer, err := reader.Get(k)
 24	if errors.Is(err, pebble.ErrNotFound) {
 25		return nil, db.ErrKeyNotFound
 26	}
 27	if err != nil {
 28		return nil, err
 29	}
 30
 31	// Note that the returned value slice is only valid until Close is called.
 32	// Make a copy so we can return it.
 33	// TODO(mvdan): write a dbtest test to ensure this property on all DBs.
 34	v2 := bytes.Clone(v)
 35
 36	if err := closer.Close(); err != nil {
 37		return nil, err
 38	}
 39	return v2, nil
 40}
 41
 42func iterate(reader pebble.Reader, prefix []byte, callback func(k, v []byte) bool) (err error) {
 43	iterOptions := &pebble.IterOptions{
 44		LowerBound: prefix,
 45		UpperBound: keyUpperBound(prefix),
 46	}
 47	iter, err := reader.NewIter(iterOptions)
 48	if err != nil {
 49		return err
 50	}
 51	defer func() {
 52		errC := iter.Close()
 53		if err != nil {
 54			return
 55		}
 56		err = errC
 57	}()
 58
 59	for iter.First(); iter.Valid(); iter.Next() {
 60		localKey := iter.Key()[len(prefix):]
 61		if cont := callback(localKey, iter.Value()); !cont {
 62			break
 63		}
 64	}
 65	return iter.Error()
 66}
 67
 68// Get implements the db.WriteTx.Get interface method
 69func (tx *WriteTx) Get(k []byte) ([]byte, error) {
 70	return get(tx.batch, k)
 71}
 72
 73// Iterate implements the db.WriteTx.Iterate interface method.
 74func (tx *WriteTx) Iterate(prefix []byte, callback func(k, v []byte) bool) (err error) {
 75	return iterate(tx.batch, prefix, callback)
 76}
 77
 78// Set implements the db.WriteTx.Set interface method
 79func (tx *WriteTx) Set(k, v []byte) error {
 80	return tx.batch.Set(k, v, nil)
 81}
 82
 83// Delete implements the db.WriteTx.Delete interface method
 84func (tx *WriteTx) Delete(k []byte) error {
 85	return tx.batch.Delete(k, nil)
 86}
 87
 88// Apply implements the db.WriteTx.Apply interface method
 89func (tx *WriteTx) Apply(other db.WriteTx) (err error) {
 90	otherPebble := db.UnwrapWriteTx(other).(*WriteTx)
 91	return tx.batch.Apply(otherPebble.batch, nil)
 92}
 93
 94// Commit implements the db.WriteTx.Commit interface method
 95func (tx *WriteTx) Commit() error {
 96	if tx.batch == nil {
 97		return fmt.Errorf("cannot commit pebble tx: already committed or discarded")
 98	}
 99	err := tx.batch.Commit(nil)
100	tx.batch = nil
101	return err
102}
103
104// Discard implements the db.WriteTx.Discard interface method
105func (tx *WriteTx) Discard() {
106	if tx.batch == nil {
107		// Silently allow discarding twice or after a commit,
108		// since it can help for the sake of defers.
109		// Note that upstream pebbledb doesn't really allow calling Close twice;
110		// the object gets put in a shared pool, so a later call to Close races
111		// with another goroutine having already reused the same object from the pool.
112		return
113	}
114	// Discard cannot return errors; closing the batch only releases its resources.
115	_ = tx.batch.Close()
116	tx.batch = nil
117}
118
119// PebbleDB implements db.Database interface
120type PebbleDB struct {
121	db *pebble.DB
122}
123
124// check that PebbleDB implements the db.Database interface
125var _ db.Database = (*PebbleDB)(nil)
126
127// New returns a PebbleDB using the given Options, which implements the
128// db.Database interface
129func New(opts db.Options) (*PebbleDB, error) {
130	if err := os.MkdirAll(opts.Path, os.ModePerm); err != nil {
131		return nil, err
132	}
133	o := &pebble.Options{
134		Levels: []pebble.LevelOptions{
135			{
136				Compression: pebble.SnappyCompression,
137			},
138		},
139	}
140	db, err := pebble.Open(opts.Path, o)
141	if err != nil {
142		return nil, err
143	}
144
145	return &PebbleDB{
146		db: db,
147	}, nil
148}
149
150// Get implements the db.WriteTx.Get interface method
151func (db *PebbleDB) Get(k []byte) ([]byte, error) {
152	return get(db.db, k)
153}
154
155// WriteTx returns a db.WriteTx
156func (db *PebbleDB) WriteTx() db.WriteTx {
157	return &WriteTx{
158		batch: db.db.NewIndexedBatch(),
159	}
160}
161
162// Close closes the PebbleDB
163func (db *PebbleDB) Close() error {
164	return db.db.Close()
165}
166
167func keyUpperBound(b []byte) []byte {
168	// See keyUpperBound in Pebble's iterator_example_test.go.
169	end := bytes.Clone(b)
170	for i := len(end) - 1; i >= 0; i-- {
171		end[i] = end[i] + 1
172		if end[i] != 0 {
173			return end[:i+1]
174		}
175	}
176	return nil // no upper-bound
177}
178
179// Iterate implements the db.Database.Iterate interface method
180func (db *PebbleDB) Iterate(prefix []byte, callback func(k, v []byte) bool) (err error) {
181	return iterate(db.db, prefix, callback)
182}
183
184// Compact implements the db.Database.Compact interface method
185func (db *PebbleDB) Compact() error {
186	// from https://github.com/cockroachdb/pebble/issues/1474#issuecomment-1022313365
187	iter, err := db.db.NewIter(nil)
188	if err != nil {
189		return err
190	}
191	var first, last []byte
192	if iter.First() {
193		first = append(first, iter.Key()...)
194	}
195	if iter.Last() {
196		last = append(last, iter.Key()...)
197	}
198	if err := iter.Close(); err != nil {
199		return err
200	}
201	return db.db.Compact(first, last, true)
202}