Adding the challenge 0105

This commit is contained in:
2026-07-24 22:03:42 +02:00
parent 4cb1b6d76a
commit 9c71ad1c8f
6 changed files with 287 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
package db0105
import (
"bytes"
)
type KV struct {
log Log
mem map[string][]byte
}
func (kv *KV) Open() error {
if err := kv.log.Open(); err != nil {
return err
}
kv.mem = map[string][]byte{}
for {
ent := Entry{}
eof, err := kv.log.Read(&ent)
if err != nil {
return err
} else if eof {
break
}
if ent.deleted {
delete(kv.mem, string(ent.key))
} else {
kv.mem[string(ent.key)] = ent.val
}
}
return nil
}
func (kv *KV) Close() error { return kv.log.Close() }
func (kv *KV) Get(key []byte) (val []byte, ok bool, err error) {
val, ok = kv.mem[string(key)]
return
}
func (kv *KV) Set(key []byte, val []byte) (updated bool, err error) {
prev, exist := kv.mem[string(key)]
updated = !exist || !bytes.Equal(prev, val)
if updated {
if err = kv.log.Write(&Entry{key: key, val: val}); err != nil {
return false, err
}
kv.mem[string(key)] = val
}
return
}
func (kv *KV) Del(key []byte) (deleted bool, err error) {
_, deleted = kv.mem[string(key)]
if deleted {
if err = kv.log.Write(&Entry{key: key, deleted: true}); err != nil {
return false, err
}
delete(kv.mem, string(key))
}
return
}
// QzBQWVJJOUhU https://trialofcode.org/
+20
View File
@@ -0,0 +1,20 @@
package db0105
import (
"errors"
"io"
)
type Entry struct {
key []byte
val []byte
deleted bool
}
func (ent *Entry) Encode() []byte
var ErrBadSum = errors.New("bad checksum")
func (ent *Entry) Decode(r io.Reader) error
// QzBQWVJJOUhU https://trialofcode.org/
+126
View File
@@ -0,0 +1,126 @@
package db0105
import (
"bytes"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestKVBasic(t *testing.T) {
kv := KV{}
kv.log.FileName = ".test_db"
defer os.Remove(kv.log.FileName)
os.Remove(kv.log.FileName)
err := kv.Open()
assert.Nil(t, err)
defer kv.Close()
updated, err := kv.Set([]byte("k1"), []byte("v1"))
assert.True(t, updated && err == nil)
val, ok, err := kv.Get([]byte("k1"))
assert.True(t, string(val) == "v1" && ok && err == nil)
_, ok, err = kv.Get([]byte("xxx"))
assert.True(t, !ok && err == nil)
updated, err = kv.Del([]byte("xxx"))
assert.True(t, !updated && err == nil)
updated, err = kv.Del([]byte("k1"))
assert.True(t, updated && err == nil)
_, ok, err = kv.Get([]byte("xxx"))
assert.True(t, !ok && err == nil)
updated, err = kv.Set([]byte("k2"), []byte("v2"))
assert.True(t, updated && err == nil)
// reopen
kv.Close()
err = kv.Open()
assert.Nil(t, err)
_, ok, err = kv.Get([]byte("k1"))
assert.True(t, !ok && err == nil)
val, ok, err = kv.Get([]byte("k2"))
assert.True(t, string(val) == "v2" && ok && err == nil)
}
func TestKVRecovery(t *testing.T) {
kv := KV{}
kv.log.FileName = ".test_db"
defer os.Remove(kv.log.FileName)
prepare := func() {
os.Remove(kv.log.FileName)
err := kv.Open()
assert.Nil(t, err)
defer kv.Close()
updated, err := kv.Set([]byte("k1"), []byte("v1"))
assert.True(t, updated && err == nil)
updated, err = kv.Set([]byte("k2"), []byte("v2"))
assert.True(t, updated && err == nil)
}
prepare()
// simulate truncated log
fp, _ := os.OpenFile(kv.log.FileName, os.O_RDWR, 0o644)
st, _ := fp.Stat()
fp.Truncate(st.Size() - 1)
fp.Close()
// reopen
err := kv.Open()
assert.Nil(t, err)
// test
val, ok, err := kv.Get([]byte("k1"))
assert.True(t, string(val) == "v1" && ok && err == nil)
_, ok, err = kv.Get([]byte("k2")) // bad
assert.True(t, !ok && err == nil)
kv.Close()
prepare()
// simulate bad checksum
fp, _ = os.OpenFile(kv.log.FileName, os.O_RDWR, 0o644)
st, _ = fp.Stat()
fp.WriteAt([]byte{0}, st.Size()-1)
fp.Close()
// reopen
err = kv.Open()
assert.Nil(t, err)
// test
val, ok, err = kv.Get([]byte("k1"))
assert.True(t, string(val) == "v1" && ok && err == nil)
_, ok, err = kv.Get([]byte("k2")) // bad
assert.True(t, !ok && err == nil)
kv.Close()
}
func TestEntryEncode(t *testing.T) {
ent := Entry{key: []byte("k1"), val: []byte("xxx")}
data := []byte{0xe9, 0xec, 0x4d, 0x9e, 2, 0, 0, 0, 3, 0, 0, 0, 0, 'k', '1', 'x', 'x', 'x'}
assert.Equal(t, data, ent.Encode())
decoded := Entry{}
err := decoded.Decode(bytes.NewBuffer(data))
assert.Nil(t, err)
assert.Equal(t, ent, decoded)
ent = Entry{key: []byte("k1"), deleted: true}
data = []byte{0x4c, 0xd0, 0xfe, 0xe5, 2, 0, 0, 0, 0, 0, 0, 0, 1, 'k', '1'}
assert.Equal(t, data, ent.Encode())
decoded = Entry{}
err = decoded.Decode(bytes.NewBuffer(data))
assert.Nil(t, err)
assert.Equal(t, ent, decoded)
}
// QzBQWVJJOUhU https://trialofcode.org/
+30
View File
@@ -0,0 +1,30 @@
package db0105
import (
"os"
)
type Log struct {
FileName string
fp *os.File
}
func (log *Log) Open() (err error) {
log.fp, err = createFileSync(log.FileName)
return err
}
func (log *Log) Close() error {
return log.fp.Close()
}
func (log *Log) Write(ent *Entry) error {
if _, err := log.fp.Write(ent.Encode()); err != nil {
return err
}
return log.fp.Sync() // fsync
}
func (log *Log) Read(ent *Entry) (eof bool, err error)
// QzBQWVJJOUhU https://trialofcode.org/
+11
View File
@@ -0,0 +1,11 @@
//go:build !unix
package db0105
import "os"
func createFileSync(file string) (*os.File, error) {
return os.OpenFile(file, os.O_RDWR|os.O_CREATE, 0o644)
}
// QzBQWVJJOUhU https://trialofcode.org/
+34
View File
@@ -0,0 +1,34 @@
//go:build unix
package db0105
import (
"os"
"path"
"syscall"
)
// open or create a file and fsync the directory
func createFileSync(file string) (*os.File, error) {
fp, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE, 0o644)
if err != nil {
return nil, err
}
if err = syncDir(path.Base(file)); err != nil {
_ = fp.Close()
return nil, err
}
return fp, err
}
func syncDir(file string) error {
flags := os.O_RDONLY | syscall.O_DIRECTORY
dirfd, err := syscall.Open(path.Dir(file), flags, 0o644)
if err != nil {
return err
}
defer syscall.Close(dirfd)
return syscall.Fsync(dirfd)
}
// QzBQWVJJOUhU https://trialofcode.org/