From 9c71ad1c8f356d08660c2324a692942021295687 Mon Sep 17 00:00:00 2001 From: edipretoro Date: Fri, 24 Jul 2026 22:03:42 +0200 Subject: [PATCH] Adding the challenge 0105 --- 0105/kv.go | 66 +++++++++++++++++++++++++ 0105/kv_entry.go | 20 ++++++++ 0105/kv_test.go | 126 +++++++++++++++++++++++++++++++++++++++++++++++ 0105/log.go | 30 +++++++++++ 0105/os_other.go | 11 +++++ 0105/os_unix.go | 34 +++++++++++++ 6 files changed, 287 insertions(+) create mode 100644 0105/kv.go create mode 100644 0105/kv_entry.go create mode 100644 0105/kv_test.go create mode 100644 0105/log.go create mode 100644 0105/os_other.go create mode 100644 0105/os_unix.go diff --git a/0105/kv.go b/0105/kv.go new file mode 100644 index 0000000..9ef7320 --- /dev/null +++ b/0105/kv.go @@ -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/ diff --git a/0105/kv_entry.go b/0105/kv_entry.go new file mode 100644 index 0000000..27e5f3c --- /dev/null +++ b/0105/kv_entry.go @@ -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/ diff --git a/0105/kv_test.go b/0105/kv_test.go new file mode 100644 index 0000000..2e2e70f --- /dev/null +++ b/0105/kv_test.go @@ -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/ diff --git a/0105/log.go b/0105/log.go new file mode 100644 index 0000000..f67a430 --- /dev/null +++ b/0105/log.go @@ -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/ diff --git a/0105/os_other.go b/0105/os_other.go new file mode 100644 index 0000000..222b6f2 --- /dev/null +++ b/0105/os_other.go @@ -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/ diff --git a/0105/os_unix.go b/0105/os_unix.go new file mode 100644 index 0000000..734c664 --- /dev/null +++ b/0105/os_unix.go @@ -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/