mirror of
https://github.com/nxshock/zkv.git
synced 2024-11-27 11:21:02 +05:00
New version
* Simplify internal structures * Update minimal Go version * Add Delete() function
This commit is contained in:
parent
5c459416b1
commit
4ec53665af
5
errors.go
Normal file
5
errors.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package zkv
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrNotExists = errors.New("not exists")
|
10
go.mod
10
go.mod
@ -1,14 +1,14 @@
|
|||||||
module github.com/nxshock/zkv
|
module github.com/nxshock/zkv
|
||||||
|
|
||||||
go 1.17
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/klauspost/compress v1.14.2
|
github.com/klauspost/compress v1.15.12
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.8.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
18
go.sum
18
go.sum
@ -1,13 +1,19 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/klauspost/compress v1.14.2 h1:S0OHlFk/Gbon/yauFJ4FfJJF5V0fc5HbBTJazi28pRw=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
|
||||||
|
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -15,10 +15,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Record struct {
|
type Record struct {
|
||||||
Type RecordType `json:"t"`
|
Type RecordType
|
||||||
KeyHash []byte `json:"h"`
|
KeyHash []byte
|
||||||
KeyBytes []byte `json:"k,omitempty"` // optional
|
ValueBytes []byte
|
||||||
ValueBytes []byte `json:"v"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRecord(recordType RecordType, key, value interface{}) (*Record, error) {
|
func newRecord(recordType RecordType, key, value interface{}) (*Record, error) {
|
||||||
@ -35,7 +34,6 @@ func newRecord(recordType RecordType, key, value interface{}) (*Record, error) {
|
|||||||
record := &Record{
|
record := &Record{
|
||||||
Type: recordType,
|
Type: recordType,
|
||||||
KeyHash: hashBytes(keyBytes),
|
KeyHash: hashBytes(keyBytes),
|
||||||
KeyBytes: keyBytes,
|
|
||||||
ValueBytes: valueBytes}
|
ValueBytes: valueBytes}
|
||||||
|
|
||||||
return record, nil
|
return record, nil
|
||||||
|
3
utils.go
3
utils.go
@ -5,7 +5,6 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func encode(value interface{}) ([]byte, error) {
|
func encode(value interface{}) ([]byte, error) {
|
||||||
@ -39,7 +38,7 @@ func skip(r io.Reader, count int64) (err error) {
|
|||||||
case io.Seeker:
|
case io.Seeker:
|
||||||
_, err = r.Seek(count, io.SeekCurrent)
|
_, err = r.Seek(count, io.SeekCurrent)
|
||||||
default:
|
default:
|
||||||
_, err = io.CopyN(ioutil.Discard, r, count)
|
_, err = io.CopyN(io.Discard, r, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
46
zkv.go
46
zkv.go
@ -3,7 +3,6 @@ package zkv
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@ -12,10 +11,6 @@ import (
|
|||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
SaveKeys bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
dataOffset map[string]int64
|
dataOffset map[string]int64
|
||||||
file *os.File
|
file *os.File
|
||||||
@ -23,8 +18,6 @@ type Database struct {
|
|||||||
filePath string
|
filePath string
|
||||||
offset int64
|
offset int64
|
||||||
|
|
||||||
options Options
|
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,10 +42,6 @@ func (db *Database) Set(key, value interface{}) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !db.options.SaveKeys {
|
|
||||||
record.KeyBytes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := record.Marshal()
|
b, err := record.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -81,7 +70,7 @@ func (db *Database) Get(key, value interface{}) error {
|
|||||||
|
|
||||||
offset, exists := db.dataOffset[string(hashToFind)]
|
offset, exists := db.dataOffset[string(hashToFind)]
|
||||||
if !exists {
|
if !exists {
|
||||||
return errors.New("not exists") // TODO: заменить на константную ошибку
|
return ErrNotExists
|
||||||
}
|
}
|
||||||
|
|
||||||
readF, err := os.Open(db.filePath)
|
readF, err := os.Open(db.filePath)
|
||||||
@ -106,7 +95,7 @@ func (db *Database) Get(key, value interface{}) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Compare(record.KeyHash, hashToFind) != 0 {
|
if !bytes.Equal(record.KeyHash, hashToFind) {
|
||||||
return fmt.Errorf("wrong hash on this offset: expected %s, got %s", base64.StdEncoding.EncodeToString(hashToFind), base64.StdEncoding.EncodeToString(record.KeyHash)) // TODO: заменить на константную ошибку
|
return fmt.Errorf("wrong hash on this offset: expected %s, got %s", base64.StdEncoding.EncodeToString(hashToFind), base64.StdEncoding.EncodeToString(record.KeyHash)) // TODO: заменить на константную ошибку
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,3 +157,34 @@ func Open(filePath string) (*Database, error) {
|
|||||||
|
|
||||||
return database, nil
|
return database, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *Database) Delete(key interface{}) error {
|
||||||
|
db.mu.Lock()
|
||||||
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
keyHash, err := hashInterface(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
record := &Record{
|
||||||
|
Type: RecordTypeDelete,
|
||||||
|
KeyHash: keyHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := record.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(db.dataOffset, string(record.KeyHash))
|
||||||
|
|
||||||
|
_, err = db.compressor.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
db.offset += int64(len(b))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
41
zkv_test.go
41
zkv_test.go
@ -77,3 +77,44 @@ func TestSmallWrites(t *testing.T) {
|
|||||||
err = db.Close()
|
err = db.Close()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeleteBasic(t *testing.T) {
|
||||||
|
const filePath = "TestDeleteBasic.zkv"
|
||||||
|
const recordCount = 100
|
||||||
|
defer os.Remove(filePath)
|
||||||
|
|
||||||
|
db, err := Open(filePath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for i := 1; i <= recordCount; i++ {
|
||||||
|
err = db.Set(i, i)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Len(t, db.dataOffset, recordCount)
|
||||||
|
|
||||||
|
err = db.Delete(50)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, db.dataOffset, recordCount-1)
|
||||||
|
var value int
|
||||||
|
err = db.Get(50, &value)
|
||||||
|
assert.Equal(t, 0, value)
|
||||||
|
assert.ErrorIs(t, err, ErrNotExists)
|
||||||
|
|
||||||
|
err = db.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// try to read
|
||||||
|
db, err = Open(filePath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, db.dataOffset, recordCount-1)
|
||||||
|
value = 0
|
||||||
|
err = db.Get(50, &value)
|
||||||
|
assert.Equal(t, 0, value)
|
||||||
|
assert.ErrorIs(t, err, ErrNotExists)
|
||||||
|
|
||||||
|
err = db.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user