Refactor: add and use db.NamespacedKV

This commit is contained in:
Jakob Borg
2015-01-17 20:53:33 +01:00
parent 6f2b4b96cf
commit 6f8ac2b61c
7 changed files with 267 additions and 185 deletions

View File

@@ -199,7 +199,7 @@ func (f *BlockFinder) Fix(folder, file string, index int32, oldHash, newHash []b
// file name (variable size)
func toBlockKey(hash []byte, folder, file string) []byte {
o := make([]byte, 1+64+32+len(file))
o[0] = keyTypeBlock
o[0] = KeyTypeBlock
copy(o[1:], []byte(folder))
copy(o[1+64:], []byte(hash))
copy(o[1+64+32:], []byte(file))
@@ -210,7 +210,7 @@ func fromBlockKey(data []byte) (string, string) {
if len(data) < 1+64+32+1 {
panic("Incorrect key length")
}
if data[0] != keyTypeBlock {
if data[0] != KeyTypeBlock {
panic("Incorrect key type")
}

View File

@@ -50,9 +50,11 @@ func clock(v int64) int64 {
}
const (
keyTypeDevice = iota
keyTypeGlobal
keyTypeBlock
KeyTypeDevice = iota
KeyTypeGlobal
KeyTypeBlock
KeyTypeDeviceStatistic
KeyTypeFolderStatistic
)
type fileVersion struct {
@@ -112,7 +114,7 @@ const batchFlushSize = 64
// name (variable size)
func deviceKey(folder, device, file []byte) []byte {
k := make([]byte, 1+64+32+len(file))
k[0] = keyTypeDevice
k[0] = KeyTypeDevice
if len(folder) > 64 {
panic("folder name too long")
}
@@ -145,7 +147,7 @@ func deviceKeyDevice(key []byte) []byte {
// name (variable size)
func globalKey(folder, file []byte) []byte {
k := make([]byte, 1+64+len(file))
k[0] = keyTypeGlobal
k[0] = KeyTypeGlobal
if len(folder) > 64 {
panic("folder name too long")
}
@@ -915,7 +917,7 @@ func ldbListFolders(db *leveldb.DB) []string {
snap.Release()
}()
dbi := snap.NewIterator(util.BytesPrefix([]byte{keyTypeGlobal}), nil)
dbi := snap.NewIterator(util.BytesPrefix([]byte{KeyTypeGlobal}), nil)
defer dbi.Release()
folderExists := make(map[string]bool)
@@ -953,7 +955,7 @@ func ldbDropFolder(db *leveldb.DB, folder []byte) {
}()
// Remove all items related to the given folder from the device->file bucket
dbi := snap.NewIterator(util.BytesPrefix([]byte{keyTypeDevice}), nil)
dbi := snap.NewIterator(util.BytesPrefix([]byte{KeyTypeDevice}), nil)
for dbi.Next() {
itemFolder := deviceKeyFolder(dbi.Key())
if bytes.Compare(folder, itemFolder) == 0 {
@@ -963,7 +965,7 @@ func ldbDropFolder(db *leveldb.DB, folder []byte) {
dbi.Release()
// Remove all items related to the given folder from the global bucket
dbi = snap.NewIterator(util.BytesPrefix([]byte{keyTypeGlobal}), nil)
dbi = snap.NewIterator(util.BytesPrefix([]byte{KeyTypeGlobal}), nil)
for dbi.Next() {
itemFolder := globalKeyFolder(dbi.Key())
if bytes.Compare(folder, itemFolder) == 0 {

106
internal/db/namespaced.go Normal file
View File

@@ -0,0 +1,106 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package db
import (
"encoding/binary"
"time"
"github.com/syndtr/goleveldb/leveldb"
)
// NamespacedKV is a simple key-value store using a specific namespace within
// a leveldb.
type NamespacedKV struct {
db *leveldb.DB
prefix []byte
}
// NewNamespacedKV returns a new NamespacedKV that lives in the namespace
// specified by the prefix.
func NewNamespacedKV(db *leveldb.DB, prefix string) *NamespacedKV {
return &NamespacedKV{
db: db,
prefix: []byte(prefix),
}
}
// PutInt64 stores a new int64. Any existing value (even if of another type)
// is overwritten.
func (n *NamespacedKV) PutInt64(key string, val int64) {
keyBs := append(n.prefix, []byte(key)...)
var valBs [8]byte
binary.BigEndian.PutUint64(valBs[:], uint64(val))
n.db.Put(keyBs, valBs[:], nil)
}
// Int64 returns the stored value interpreted as an int64 and a boolean that
// is false if no value was stored at the key.
func (n *NamespacedKV) Int64(key string) (int64, bool) {
keyBs := append(n.prefix, []byte(key)...)
valBs, err := n.db.Get(keyBs, nil)
if err != nil {
return 0, false
}
val := binary.BigEndian.Uint64(valBs)
return int64(val), true
}
// PutTime stores a new time.Time. Any existing value (even if of another
// type) is overwritten.
func (n *NamespacedKV) PutTime(key string, val time.Time) {
keyBs := append(n.prefix, []byte(key)...)
valBs, _ := val.MarshalBinary() // never returns an error
n.db.Put(keyBs, valBs, nil)
}
// Time returns the stored value interpreted as a time.Time and a boolean
// that is false if no value was stored at the key.
func (n NamespacedKV) Time(key string) (time.Time, bool) {
var t time.Time
keyBs := append(n.prefix, []byte(key)...)
valBs, err := n.db.Get(keyBs, nil)
if err != nil {
return t, false
}
err = t.UnmarshalBinary(valBs)
return t, err == nil
}
// PutString stores a new string. Any existing value (even if of another type)
// is overwritten.
func (n *NamespacedKV) PutString(key, val string) {
keyBs := append(n.prefix, []byte(key)...)
n.db.Put(keyBs, []byte(val), nil)
}
// String returns the stored value interpreted as a string and a boolean that
// is false if no value was stored at the key.
func (n NamespacedKV) String(key string) (string, bool) {
keyBs := append(n.prefix, []byte(key)...)
valBs, err := n.db.Get(keyBs, nil)
if err != nil {
return "", false
}
return string(valBs), true
}
// Delete deletes the specified key. It is allowed to delete a nonexistent
// key.
func (n NamespacedKV) Delete(key string) {
keyBs := append(n.prefix, []byte(key)...)
n.db.Delete(keyBs, nil)
}

View File

@@ -0,0 +1,101 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package db
import (
"testing"
"time"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/storage"
)
func TestNamespacedInt(t *testing.T) {
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
n1 := NewNamespacedKV(ldb, "foo")
n2 := NewNamespacedKV(ldb, "bar")
// Key is missing to start with
if v, ok := n1.Int64("test"); v != 0 || ok {
t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok)
}
n1.PutInt64("test", 42)
// It should now exist in n1
if v, ok := n1.Int64("test"); v != 42 || !ok {
t.Errorf("Incorrect return v %v != 42 || ok %v != true", v, ok)
}
// ... but not in n2, which is in a different namespace
if v, ok := n2.Int64("test"); v != 0 || ok {
t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok)
}
n1.Delete("test")
// It should no longer exist
if v, ok := n1.Int64("test"); v != 0 || ok {
t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok)
}
}
func TestNamespacedTime(t *testing.T) {
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
n1 := NewNamespacedKV(ldb, "foo")
if v, ok := n1.Time("test"); v != (time.Time{}) || ok {
t.Errorf("Incorrect return v %v != %v || ok %v != false", v, time.Time{}, ok)
}
now := time.Now()
n1.PutTime("test", now)
if v, ok := n1.Time("test"); v != now || !ok {
t.Errorf("Incorrect return v %v != %v || ok %v != true", v, now, ok)
}
}
func TestNamespacedString(t *testing.T) {
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
n1 := NewNamespacedKV(ldb, "foo")
if v, ok := n1.String("test"); v != "" || ok {
t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok)
}
n1.PutString("test", "yo")
if v, ok := n1.String("test"); v != "yo" || !ok {
t.Errorf("Incorrect return v %q != \"yo\" || ok %v != true", v, ok)
}
}