Skip to content

Commit c207edf

Browse files
janosnonsense
authored andcommitted
swarm: add database abstractions (shed package) (#18183)
1 parent 4f0d978 commit c207edf

18 files changed

+2287
-0
lines changed

swarm/shed/db.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2018 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
// Package shed provides a simple abstraction components to compose
18+
// more complex operations on storage data organized in fields and indexes.
19+
//
20+
// Only type which holds logical information about swarm storage chunks data
21+
// and metadata is IndexItem. This part is not generalized mostly for
22+
// performance reasons.
23+
package shed
24+
25+
import (
26+
"github.com/ethereum/go-ethereum/metrics"
27+
"github.com/syndtr/goleveldb/leveldb"
28+
"github.com/syndtr/goleveldb/leveldb/iterator"
29+
"github.com/syndtr/goleveldb/leveldb/opt"
30+
)
31+
32+
// The limit for LevelDB OpenFilesCacheCapacity.
33+
const openFileLimit = 128
34+
35+
// DB provides abstractions over LevelDB in order to
36+
// implement complex structures using fields and ordered indexes.
37+
// It provides a schema functionality to store fields and indexes
38+
// information about naming and types.
39+
type DB struct {
40+
ldb *leveldb.DB
41+
}
42+
43+
// NewDB constructs a new DB and validates the schema
44+
// if it exists in database on the given path.
45+
func NewDB(path string) (db *DB, err error) {
46+
ldb, err := leveldb.OpenFile(path, &opt.Options{
47+
OpenFilesCacheCapacity: openFileLimit,
48+
})
49+
if err != nil {
50+
return nil, err
51+
}
52+
db = &DB{
53+
ldb: ldb,
54+
}
55+
56+
if _, err = db.getSchema(); err != nil {
57+
if err == leveldb.ErrNotFound {
58+
// save schema with initialized default fields
59+
if err = db.putSchema(schema{
60+
Fields: make(map[string]fieldSpec),
61+
Indexes: make(map[byte]indexSpec),
62+
}); err != nil {
63+
return nil, err
64+
}
65+
} else {
66+
return nil, err
67+
}
68+
}
69+
return db, nil
70+
}
71+
72+
// Put wraps LevelDB Put method to increment metrics counter.
73+
func (db *DB) Put(key []byte, value []byte) (err error) {
74+
err = db.ldb.Put(key, value, nil)
75+
if err != nil {
76+
metrics.GetOrRegisterCounter("DB.putFail", nil).Inc(1)
77+
return err
78+
}
79+
metrics.GetOrRegisterCounter("DB.put", nil).Inc(1)
80+
return nil
81+
}
82+
83+
// Get wraps LevelDB Get method to increment metrics counter.
84+
func (db *DB) Get(key []byte) (value []byte, err error) {
85+
value, err = db.ldb.Get(key, nil)
86+
if err != nil {
87+
if err == leveldb.ErrNotFound {
88+
metrics.GetOrRegisterCounter("DB.getNotFound", nil).Inc(1)
89+
} else {
90+
metrics.GetOrRegisterCounter("DB.getFail", nil).Inc(1)
91+
}
92+
return nil, err
93+
}
94+
metrics.GetOrRegisterCounter("DB.get", nil).Inc(1)
95+
return value, nil
96+
}
97+
98+
// Delete wraps LevelDB Delete method to increment metrics counter.
99+
func (db *DB) Delete(key []byte) (err error) {
100+
err = db.ldb.Delete(key, nil)
101+
if err != nil {
102+
metrics.GetOrRegisterCounter("DB.deleteFail", nil).Inc(1)
103+
return err
104+
}
105+
metrics.GetOrRegisterCounter("DB.delete", nil).Inc(1)
106+
return nil
107+
}
108+
109+
// NewIterator wraps LevelDB NewIterator method to increment metrics counter.
110+
func (db *DB) NewIterator() iterator.Iterator {
111+
metrics.GetOrRegisterCounter("DB.newiterator", nil).Inc(1)
112+
113+
return db.ldb.NewIterator(nil, nil)
114+
}
115+
116+
// WriteBatch wraps LevelDB Write method to increment metrics counter.
117+
func (db *DB) WriteBatch(batch *leveldb.Batch) (err error) {
118+
err = db.ldb.Write(batch, nil)
119+
if err != nil {
120+
metrics.GetOrRegisterCounter("DB.writebatchFail", nil).Inc(1)
121+
return err
122+
}
123+
metrics.GetOrRegisterCounter("DB.writebatch", nil).Inc(1)
124+
return nil
125+
}
126+
127+
// Close closes LevelDB database.
128+
func (db *DB) Close() (err error) {
129+
return db.ldb.Close()
130+
}

swarm/shed/db_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright 2018 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package shed
18+
19+
import (
20+
"io/ioutil"
21+
"os"
22+
"testing"
23+
)
24+
25+
// TestNewDB constructs a new DB
26+
// and validates if the schema is initialized properly.
27+
func TestNewDB(t *testing.T) {
28+
db, cleanupFunc := newTestDB(t)
29+
defer cleanupFunc()
30+
31+
s, err := db.getSchema()
32+
if err != nil {
33+
t.Fatal(err)
34+
}
35+
if s.Fields == nil {
36+
t.Error("schema fields are empty")
37+
}
38+
if len(s.Fields) != 0 {
39+
t.Errorf("got schema fields length %v, want %v", len(s.Fields), 0)
40+
}
41+
if s.Indexes == nil {
42+
t.Error("schema indexes are empty")
43+
}
44+
if len(s.Indexes) != 0 {
45+
t.Errorf("got schema indexes length %v, want %v", len(s.Indexes), 0)
46+
}
47+
}
48+
49+
// TestDB_persistence creates one DB, saves a field and closes that DB.
50+
// Then, it constructs another DB and trues to retrieve the saved value.
51+
func TestDB_persistence(t *testing.T) {
52+
dir, err := ioutil.TempDir("", "shed-test-persistence")
53+
if err != nil {
54+
t.Fatal(err)
55+
}
56+
defer os.RemoveAll(dir)
57+
58+
db, err := NewDB(dir)
59+
if err != nil {
60+
t.Fatal(err)
61+
}
62+
stringField, err := db.NewStringField("preserve-me")
63+
if err != nil {
64+
t.Fatal(err)
65+
}
66+
want := "persistent value"
67+
err = stringField.Put(want)
68+
if err != nil {
69+
t.Fatal(err)
70+
}
71+
err = db.Close()
72+
if err != nil {
73+
t.Fatal(err)
74+
}
75+
76+
db2, err := NewDB(dir)
77+
if err != nil {
78+
t.Fatal(err)
79+
}
80+
stringField2, err := db2.NewStringField("preserve-me")
81+
if err != nil {
82+
t.Fatal(err)
83+
}
84+
got, err := stringField2.Get()
85+
if err != nil {
86+
t.Fatal(err)
87+
}
88+
if got != want {
89+
t.Errorf("got string %q, want %q", got, want)
90+
}
91+
}
92+
93+
// newTestDB is a helper function that constructs a
94+
// temporary database and returns a cleanup function that must
95+
// be called to remove the data.
96+
func newTestDB(t *testing.T) (db *DB, cleanupFunc func()) {
97+
t.Helper()
98+
99+
dir, err := ioutil.TempDir("", "shed-test")
100+
if err != nil {
101+
t.Fatal(err)
102+
}
103+
cleanupFunc = func() { os.RemoveAll(dir) }
104+
db, err = NewDB(dir)
105+
if err != nil {
106+
cleanupFunc()
107+
t.Fatal(err)
108+
}
109+
return db, cleanupFunc
110+
}

0 commit comments

Comments
 (0)