mirror of
https://github.com/taigrr/bitcask
synced 2025-01-18 04:03:17 -08:00
Add List, Hash and SortedSet data types
This commit is contained in:
parent
4aa4d1f198
commit
c3bda39067
4
go.mod
4
go.mod
@ -3,6 +3,10 @@ module github.com/prologic/bitcask
|
|||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/boltdb/bolt v1.3.1
|
||||||
|
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c
|
||||||
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||||
|
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect
|
||||||
github.com/gofrs/flock v0.7.1
|
github.com/gofrs/flock v0.7.1
|
||||||
github.com/pelletier/go-toml v1.6.0 // indirect
|
github.com/pelletier/go-toml v1.6.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
8
go.sum
8
go.sum
@ -25,6 +25,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
|||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||||
|
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||||
|
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
@ -40,6 +42,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
|
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0=
|
||||||
|
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
||||||
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
||||||
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||||
|
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk=
|
||||||
|
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
114
hash.go
Normal file
114
hash.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package bitcask
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *Bitcask) Hash(key []byte) *Hash {
|
||||||
|
return &Hash{db: b, key: key}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash ...
|
||||||
|
// +key,h = ""
|
||||||
|
// h[key]name = "latermoon"
|
||||||
|
// h[key]age = "27"
|
||||||
|
// h[key]sex = "Male"
|
||||||
|
type Hash struct {
|
||||||
|
db *Bitcask
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hash) Get(field []byte) ([]byte, error) {
|
||||||
|
return h.db.Get(h.fieldKey(field))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hash) MGet(fields ...[]byte) ([][]byte, error) {
|
||||||
|
vals := make([][]byte, 0, len(fields))
|
||||||
|
for _, field := range fields {
|
||||||
|
val, err := h.db.Get(h.fieldKey(field))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vals = append(vals, val)
|
||||||
|
}
|
||||||
|
return vals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll ...
|
||||||
|
func (h *Hash) GetAll() (map[string][]byte, error) {
|
||||||
|
keyVals := map[string][]byte{}
|
||||||
|
prefix := h.fieldPrefix()
|
||||||
|
err := h.db.Scan(prefix, func(key []byte) error {
|
||||||
|
val, err := h.db.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keyVals[string(h.fieldInKey(key))] = val
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return keyVals, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hash) Set(field, value []byte) error {
|
||||||
|
return h.MSet(field, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hash) MSet(fieldVals ...[]byte) error {
|
||||||
|
if len(fieldVals) == 0 || len(fieldVals)%2 != 0 {
|
||||||
|
return errors.New("invalid field value pairs")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(fieldVals); i += 2 {
|
||||||
|
field, val := fieldVals[i], fieldVals[i+1]
|
||||||
|
if err := h.db.Put(h.fieldKey(field), val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h.db.Put(h.rawKey(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hash) Remove(fields ...[]byte) error {
|
||||||
|
for _, field := range fields {
|
||||||
|
if err := h.db.Delete(h.fieldKey(field)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// clean up
|
||||||
|
prefix := h.fieldPrefix()
|
||||||
|
return h.db.Scan(prefix, func(key []byte) error {
|
||||||
|
return h.db.Delete(key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hash) Drop() error {
|
||||||
|
prefix := h.fieldPrefix()
|
||||||
|
err := h.db.Scan(prefix, func(key []byte) error {
|
||||||
|
return h.db.Delete(key)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.db.Delete(h.rawKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
// +key,h
|
||||||
|
func (h *Hash) rawKey() []byte {
|
||||||
|
return rawKey(h.key, HASH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// h[key]field
|
||||||
|
func (h *Hash) fieldKey(field []byte) []byte {
|
||||||
|
return bytes.Join([][]byte{h.fieldPrefix(), field}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// h[key]
|
||||||
|
func (h *Hash) fieldPrefix() []byte {
|
||||||
|
return bytes.Join([][]byte{[]byte{byte(HASH)}, SOK, h.key, EOK}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// split h[key]field into field
|
||||||
|
func (h *Hash) fieldInKey(fieldKey []byte) []byte {
|
||||||
|
right := bytes.Index(fieldKey, EOK)
|
||||||
|
return fieldKey[right+1:]
|
||||||
|
}
|
200
list.go
Normal file
200
list.go
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
package bitcask
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *Bitcask) List(key []byte) *List {
|
||||||
|
return &List{db: b, key: key}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List ...
|
||||||
|
// +key,l = ""
|
||||||
|
// l[key]0 = "a"
|
||||||
|
// l[key]1 = "b"
|
||||||
|
// l[key]2 = "c"
|
||||||
|
type List struct {
|
||||||
|
db *Bitcask
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) Index(i int64) ([]byte, error) {
|
||||||
|
x, err := l.leftIndex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return l.db.Get(l.indexKey(x + i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range enumerate value by index
|
||||||
|
// <start> must >= 0
|
||||||
|
// <stop> should equal to -1 or lager than <start>
|
||||||
|
func (l *List) Range(start, stop int64, fn func(i int64, value []byte, quit *bool)) error {
|
||||||
|
if start < 0 || (stop != -1 && start > stop) {
|
||||||
|
return errors.New("bad start/stop index")
|
||||||
|
}
|
||||||
|
x, y, err := l.rangeIndex()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if stop == -1 {
|
||||||
|
stop = (y - x + 1) - 1 // (size) - 1
|
||||||
|
}
|
||||||
|
min := l.indexKey(x + int64(start))
|
||||||
|
max := l.indexKey(x + int64(stop))
|
||||||
|
var i int64 // 0
|
||||||
|
ErrStopIteration := errors.New("err: stop iteration")
|
||||||
|
err = l.db.Scan(min, func(key []byte) error {
|
||||||
|
if key != nil && bytes.Compare(key, max) <= 0 {
|
||||||
|
val, err := l.db.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
quit := false
|
||||||
|
if fn(start+i, val, &quit); quit {
|
||||||
|
return ErrStopIteration
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrStopIteration
|
||||||
|
})
|
||||||
|
if err == ErrStopIteration {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append ...
|
||||||
|
func (l *List) Append(vals ...[]byte) error {
|
||||||
|
x, y, err := l.rangeIndex()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if x == 0 && y == -1 {
|
||||||
|
if err := l.db.Put(l.rawKey(), nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, val := range vals {
|
||||||
|
if err := l.db.Put(l.indexKey(y+int64(i)+1), val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop ...
|
||||||
|
func (l *List) Pop() ([]byte, error) {
|
||||||
|
x, y, err := l.rangeIndex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
size := y - x + 1
|
||||||
|
if size == 0 {
|
||||||
|
return nil, nil
|
||||||
|
} else if size < 0 { // double check
|
||||||
|
return nil, errors.New("bad list struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
idxkey := l.indexKey(y)
|
||||||
|
|
||||||
|
val, err := l.db.Get(idxkey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := l.db.Delete(idxkey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if size == 1 { // clean up
|
||||||
|
return nil, l.db.Delete(l.rawKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len ...
|
||||||
|
func (l *List) Len() (int64, error) {
|
||||||
|
x, y, err := l.rangeIndex()
|
||||||
|
return y - x + 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) rangeIndex() (int64, int64, error) {
|
||||||
|
left, err := l.leftIndex()
|
||||||
|
if err != nil {
|
||||||
|
return 0, -1, err
|
||||||
|
}
|
||||||
|
right, err := l.rightIndex()
|
||||||
|
if err != nil {
|
||||||
|
return 0, -1, err
|
||||||
|
}
|
||||||
|
log.Printf("left: %d\n", left)
|
||||||
|
log.Printf("right: %d\n", right)
|
||||||
|
return left, right, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) leftIndex() (int64, error) {
|
||||||
|
log.Println("leftIndex:")
|
||||||
|
idx := int64(0) // default 0
|
||||||
|
prefix := l.keyPrefix()
|
||||||
|
log.Printf(" prefix: %s\n", prefix)
|
||||||
|
ErrStopIteration := errors.New("err: stop iteration")
|
||||||
|
err := l.db.Scan(prefix, func(key []byte) error {
|
||||||
|
log.Printf(" key: %v\n", key)
|
||||||
|
if bytes.HasPrefix(key, prefix) {
|
||||||
|
idx = l.indexInKey(key)
|
||||||
|
log.Printf(" idx: %d\n", idx)
|
||||||
|
}
|
||||||
|
return ErrStopIteration
|
||||||
|
})
|
||||||
|
if err == ErrStopIteration {
|
||||||
|
return idx, nil
|
||||||
|
}
|
||||||
|
return idx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) rightIndex() (int64, error) {
|
||||||
|
log.Println("rightIndex:")
|
||||||
|
idx := int64(-1) // default -1
|
||||||
|
prefix := l.keyPrefix()
|
||||||
|
log.Printf(" prefix: %s\n", prefix)
|
||||||
|
err := l.db.Scan(prefix, func(key []byte) error {
|
||||||
|
log.Printf(" key: %v\n", key)
|
||||||
|
if bytes.HasPrefix(key, prefix) {
|
||||||
|
idx = l.indexInKey(key)
|
||||||
|
log.Printf(" idx: %d\n", idx)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return idx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// +key,l = ""
|
||||||
|
func (l *List) rawKey() []byte {
|
||||||
|
return rawKey(l.key, ElemType(LIST))
|
||||||
|
}
|
||||||
|
|
||||||
|
// l[key]
|
||||||
|
func (l *List) keyPrefix() []byte {
|
||||||
|
return bytes.Join([][]byte{[]byte{byte(LIST)}, SOK, l.key, EOK}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// l[key]0 = "a"
|
||||||
|
func (l *List) indexKey(i int64) []byte {
|
||||||
|
sign := []byte{0}
|
||||||
|
if i >= 0 {
|
||||||
|
sign = []byte{1}
|
||||||
|
}
|
||||||
|
b := bytes.Join([][]byte{l.keyPrefix(), sign, itob(i)}, nil)
|
||||||
|
log.Printf("indexKeu: %x\n", b)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// split l[key]index into index
|
||||||
|
func (l *List) indexInKey(key []byte) int64 {
|
||||||
|
idxbuf := bytes.TrimPrefix(key, l.keyPrefix())
|
||||||
|
return btoi(idxbuf[1:]) // skip sign "0/1"
|
||||||
|
}
|
63
score.go
Normal file
63
score.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package bitcask
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Score indicated that a number can be encoded to sorted []byte
|
||||||
|
// Score is use in SortedSet
|
||||||
|
// you can implement your own decode & encode function just like below
|
||||||
|
type Score []byte
|
||||||
|
|
||||||
|
// Int64ToScore ...
|
||||||
|
func Int64ToScore(i int64) Score {
|
||||||
|
b := make([]byte, 9)
|
||||||
|
// store sign in the first byte to keep the score order
|
||||||
|
if i < 0 {
|
||||||
|
b[0] = byte(0)
|
||||||
|
} else {
|
||||||
|
b[0] = byte(1)
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint64(b[1:], uint64(i))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScoreToInt64 ...
|
||||||
|
func ScoreToInt64(b Score) int64 {
|
||||||
|
return int64(binary.LittleEndian.Uint64(b[1:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64ToScore ...
|
||||||
|
func Float64ToScore(f float64) Score {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(b, float64ToUint64(f))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScoreToFloat64 ...
|
||||||
|
func ScoreToFloat64(b Score) float64 {
|
||||||
|
return uint64ToFloat64(binary.LittleEndian.Uint64(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy from https://github.com/reborndb/qdb/blob/master/pkg/store/util.go
|
||||||
|
// We can not use lexicographically bytes comparison for negative and positive float directly.
|
||||||
|
// so here we will do a trick below.
|
||||||
|
func float64ToUint64(f float64) uint64 {
|
||||||
|
u := math.Float64bits(f)
|
||||||
|
if f >= 0 {
|
||||||
|
u |= 0x8000000000000000
|
||||||
|
} else {
|
||||||
|
u = ^u
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func uint64ToFloat64(u uint64) float64 {
|
||||||
|
if u&0x8000000000000000 > 0 {
|
||||||
|
u &= ^uint64(0x8000000000000000)
|
||||||
|
} else {
|
||||||
|
u = ^u
|
||||||
|
}
|
||||||
|
return math.Float64frombits(u)
|
||||||
|
}
|
163
sortedset.go
Normal file
163
sortedset.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package bitcask
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *Bitcask) SortedSet(key []byte) *SortedSet {
|
||||||
|
return &SortedSet{db: b, key: key}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortedSet ...
|
||||||
|
// +key,z = ""
|
||||||
|
// z[key]m member = score
|
||||||
|
// z[key]s score member = ""
|
||||||
|
type SortedSet struct {
|
||||||
|
db *Bitcask
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add add score & member pairs
|
||||||
|
// SortedSet.Add(Score, []byte, Score, []byte ...)
|
||||||
|
func (s *SortedSet) Add(scoreMembers ...[]byte) (int, error) {
|
||||||
|
count := len(scoreMembers)
|
||||||
|
if count < 2 || count%2 != 0 {
|
||||||
|
return 0, errors.New("invalid score/member pairs")
|
||||||
|
}
|
||||||
|
added := 0
|
||||||
|
for i := 0; i < count; i += 2 {
|
||||||
|
score, member := scoreMembers[i], scoreMembers[i+1]
|
||||||
|
skey, mkey := s.scoreKey(score, member), s.memberKey(member)
|
||||||
|
oldscore, err := s.db.Get(mkey)
|
||||||
|
if err != nil && err != ErrKeyNotFound {
|
||||||
|
return added, err
|
||||||
|
}
|
||||||
|
// remove old score key
|
||||||
|
if oldscore != nil {
|
||||||
|
oldskey := s.scoreKey(oldscore, member)
|
||||||
|
if err := s.db.Delete(oldskey); err != nil {
|
||||||
|
return added, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
added++
|
||||||
|
}
|
||||||
|
if err := s.db.Put(mkey, score); err != nil {
|
||||||
|
return added, err
|
||||||
|
}
|
||||||
|
if err := s.db.Put(skey, nil); err != nil {
|
||||||
|
return added, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.db.Put(s.rawKey(), nil); err != nil {
|
||||||
|
return added, err
|
||||||
|
}
|
||||||
|
return added, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SortedSet) Score(member []byte) (Score, error) {
|
||||||
|
return s.db.Get(s.memberKey(member))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SortedSet) Remove(members ...[]byte) (int, error) {
|
||||||
|
removed := 0 // not including non existing members
|
||||||
|
for _, member := range members {
|
||||||
|
score, err := s.db.Get(s.memberKey(member))
|
||||||
|
if err != nil {
|
||||||
|
return removed, err
|
||||||
|
}
|
||||||
|
if score == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := s.db.Delete(s.scoreKey(score, member)); err != nil {
|
||||||
|
return removed, err
|
||||||
|
}
|
||||||
|
if err := s.db.Delete(s.memberKey(member)); err != nil {
|
||||||
|
return removed, err
|
||||||
|
}
|
||||||
|
removed++
|
||||||
|
}
|
||||||
|
// clean up
|
||||||
|
prefix := s.keyPrefix()
|
||||||
|
ErrStopIteration := errors.New("err: stop iteration")
|
||||||
|
err := s.db.Scan(prefix, func(key []byte) error {
|
||||||
|
if !bytes.HasPrefix(key, prefix) {
|
||||||
|
if err := s.db.Delete(s.rawKey()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ErrStopIteration
|
||||||
|
})
|
||||||
|
if err != ErrStopIteration {
|
||||||
|
return removed, err
|
||||||
|
}
|
||||||
|
return removed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range ...
|
||||||
|
// <fr> is less than <to>
|
||||||
|
func (s *SortedSet) Range(fr, to Score, fn func(i int64, score Score, member []byte, quit *bool)) error {
|
||||||
|
min := s.scorePrefix(fr)
|
||||||
|
max := append(s.scorePrefix(to), MAXBYTE)
|
||||||
|
var i int64 // 0
|
||||||
|
ErrStopIteration := errors.New("err: stop iteration")
|
||||||
|
err := s.db.Scan(min, func(key []byte) error {
|
||||||
|
if bytes.Compare(key, max) <= 0 {
|
||||||
|
quit := false
|
||||||
|
score, member, err := s.splitScoreKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fn(i, score, member, &quit); quit {
|
||||||
|
return ErrStopIteration
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != ErrStopIteration {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// +key,z = ""
|
||||||
|
func (s *SortedSet) rawKey() []byte {
|
||||||
|
return rawKey(s.key, ElemType(SORTEDSET))
|
||||||
|
}
|
||||||
|
|
||||||
|
// z[key]
|
||||||
|
func (s *SortedSet) keyPrefix() []byte {
|
||||||
|
return bytes.Join([][]byte{[]byte{byte(SORTEDSET)}, SOK, s.key, EOK}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// z[key]m
|
||||||
|
func (s *SortedSet) memberKey(member []byte) []byte {
|
||||||
|
return bytes.Join([][]byte{s.keyPrefix(), []byte{'m'}, member}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// z[key]s score
|
||||||
|
func (s *SortedSet) scorePrefix(score []byte) []byte {
|
||||||
|
return bytes.Join([][]byte{s.keyPrefix(), []byte{'s'}, score, []byte{' '}}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// z[key]s score member
|
||||||
|
func (s *SortedSet) scoreKey(score, member []byte) []byte {
|
||||||
|
return bytes.Join([][]byte{s.keyPrefix(), []byte{'s'}, score, []byte{' '}, member}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// split (z[key]s score member) into (score, member)
|
||||||
|
func (s *SortedSet) splitScoreKey(skey []byte) ([]byte, []byte, error) {
|
||||||
|
buf := bytes.TrimPrefix(skey, s.keyPrefix())
|
||||||
|
pairs := bytes.Split(buf[1:], []byte{' '}) // skip score mark 's'
|
||||||
|
if len(pairs) != 2 {
|
||||||
|
return nil, nil, errors.New("invalid score/member key: " + string(skey))
|
||||||
|
}
|
||||||
|
return pairs[0], pairs[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// split (z[key]m member) into (member)
|
||||||
|
func (s *SortedSet) splitMemberKey(mkey []byte) ([]byte, error) {
|
||||||
|
buf := bytes.TrimPrefix(mkey, s.keyPrefix())
|
||||||
|
return buf[1:], nil // skip member mark 'm'
|
||||||
|
}
|
27
tests/test_hash.go
Normal file
27
tests/test_hash.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/prologic/bitcask"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db, err := bitcask.Open("test.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
l := db.Hash([]byte("foo"))
|
||||||
|
err = l.Set([]byte("1"), []byte("one"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = l.Set([]byte("2"), []byte("two"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = l.Set([]byte("3"), []byte("three"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
33
tests/test_list.go
Normal file
33
tests/test_list.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/prologic/bitcask"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db, err := bitcask.Open("test.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
l := db.List([]byte("foo"))
|
||||||
|
err = l.Append([]byte("one"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = l.Append([]byte("two"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = l.Append([]byte("three"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
len, err := l.Len()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("len: %d\n", len)
|
||||||
|
}
|
31
tests/test_sortedset.go
Normal file
31
tests/test_sortedset.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/prologic/bitcask"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db, err := bitcask.Open("test.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
z := db.SortedSet([]byte("foo"))
|
||||||
|
added, err := z.Add(
|
||||||
|
bitcask.Int64ToScore(1), []byte("a"),
|
||||||
|
bitcask.Int64ToScore(2), []byte("b"),
|
||||||
|
bitcask.Int64ToScore(3), []byte("c"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("added %d\n", added)
|
||||||
|
|
||||||
|
score, err := z.Score([]byte("b"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("score: %d\n", bitcask.ScoreToInt64(score))
|
||||||
|
}
|
BIN
tests/tests
Executable file
BIN
tests/tests
Executable file
Binary file not shown.
80
util.go
Normal file
80
util.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package bitcask
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrInvalidKeyFormat = errors.New("invalid key format includes +[],")
|
||||||
|
|
||||||
|
// Raw key:
|
||||||
|
// +key,type = value
|
||||||
|
// +name,s = "latermoon"
|
||||||
|
|
||||||
|
var (
|
||||||
|
SEP = []byte{','}
|
||||||
|
KEY = []byte{'+'} // Key Prefix
|
||||||
|
SOK = []byte{'['} // Start of Key
|
||||||
|
EOK = []byte{']'} // End of Key
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MINBYTE byte = 0
|
||||||
|
MAXBYTE byte = math.MaxUint8
|
||||||
|
)
|
||||||
|
|
||||||
|
type ElemType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
STRING ElemType = 's'
|
||||||
|
HASH ElemType = 'h'
|
||||||
|
LIST ElemType = 'l'
|
||||||
|
SORTEDSET ElemType = 'z'
|
||||||
|
NONE ElemType = '0'
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e ElemType) String() string {
|
||||||
|
switch byte(e) {
|
||||||
|
case 's':
|
||||||
|
return "string"
|
||||||
|
case 'h':
|
||||||
|
return "hash"
|
||||||
|
case 'l':
|
||||||
|
return "list"
|
||||||
|
case 'z':
|
||||||
|
return "sortedset"
|
||||||
|
default:
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawKey(key []byte, t ElemType) []byte {
|
||||||
|
return bytes.Join([][]byte{KEY, key, SEP, []byte{byte(t)}}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyKey(key []byte) error {
|
||||||
|
err := ErrInvalidKeyFormat
|
||||||
|
if bytes.Contains(key, SEP) {
|
||||||
|
return err
|
||||||
|
} else if bytes.Contains(key, KEY) {
|
||||||
|
return err
|
||||||
|
} else if bytes.Contains(key, SOK) {
|
||||||
|
return err
|
||||||
|
} else if bytes.Contains(key, EOK) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// itob returns an 8-byte big endian representation of v.
|
||||||
|
func itob(i int64) []byte {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(b, uint64(i))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func btoi(b []byte) int64 {
|
||||||
|
return int64(binary.LittleEndian.Uint64(b))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user