Adding the challenge 0301

This commit is contained in:
2026-07-29 22:54:42 +02:00
parent df8c61b7b9
commit 89975de979
15 changed files with 807 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
package db0301
func check(cond bool) {
if !cond {
panic("assertion failure")
}
}
// QzBQWVJJOUhU https://trialofcode.org/
+57
View File
@@ -0,0 +1,57 @@
package db0301
import (
"encoding/binary"
"errors"
"slices"
)
type CellType uint8
const (
TypeI64 CellType = 1
TypeStr CellType = 2
)
type Cell struct {
Type CellType
I64 int64
Str []byte
}
func (cell *Cell) Encode(toAppend []byte) []byte {
switch cell.Type {
case TypeI64:
return binary.LittleEndian.AppendUint64(toAppend, uint64(cell.I64))
case TypeStr:
toAppend = binary.LittleEndian.AppendUint32(toAppend, uint32(len(cell.Str)))
return append(toAppend, cell.Str...)
default:
panic("unreachable")
}
}
func (cell *Cell) Decode(data []byte) (rest []byte, err error) {
switch cell.Type {
case TypeI64:
if len(data) < 8 {
return data, errors.New("expect more data")
}
cell.I64 = int64(binary.LittleEndian.Uint64(data[0:8]))
return data[8:], nil
case TypeStr:
if len(data) < 4 {
return data, errors.New("expect more data")
}
size := int(binary.LittleEndian.Uint32(data[0:4]))
if len(data) < 4+size {
return data, errors.New("expect more data")
}
cell.Str = slices.Clone(data[4 : 4+size])
return data[4+size:], nil
default:
panic("unreachable")
}
}
// QzBQWVJJOUhU https://trialofcode.org/
+27
View File
@@ -0,0 +1,27 @@
package db0301
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTableCell(t *testing.T) {
cell := Cell{Type: TypeI64, I64: -2}
data := []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
assert.Equal(t, data, cell.Encode(nil))
decoded := Cell{Type: TypeI64}
rest, err := decoded.Decode(data)
assert.True(t, len(rest) == 0 && err == nil)
assert.Equal(t, cell, decoded)
cell = Cell{Type: TypeStr, Str: []byte("asdf")}
data = []byte{4, 0, 0, 0, 'a', 's', 'd', 'f'}
assert.Equal(t, data, cell.Encode(nil))
decoded = Cell{Type: TypeStr}
rest, err = decoded.Decode(data)
assert.True(t, len(rest) == 0 && err == nil)
assert.Equal(t, cell, decoded)
}
// QzBQWVJJOUhU https://trialofcode.org/
+87
View File
@@ -0,0 +1,87 @@
package db0301
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
}
type UpdateMode int
const (
ModeUpsert UpdateMode = 0 // insert or update
ModeInsert UpdateMode = 1 // insert new
ModeUpdate UpdateMode = 2 // update existing
)
func (kv *KV) SetEx(key []byte, val []byte, mode UpdateMode) (updated bool, err error) {
prev, exist := kv.mem[string(key)]
switch mode {
case ModeUpsert:
updated = !exist || !bytes.Equal(prev, val)
case ModeInsert:
updated = !exist
case ModeUpdate:
updated = exist && !bytes.Equal(prev, val)
default:
panic("unreachable")
}
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) Set(key []byte, val []byte) (updated bool, err error) {
return kv.SetEx(key, val, ModeUpsert)
}
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/
+63
View File
@@ -0,0 +1,63 @@
package db0301
import (
"encoding/binary"
"errors"
"hash/crc32"
"io"
)
type Entry struct {
key []byte
val []byte
deleted bool
}
func (ent *Entry) Encode() []byte {
data := make([]byte, 4+4+4+1+len(ent.key)+len(ent.val))
binary.LittleEndian.PutUint32(data[4:8], uint32(len(ent.key)))
copy(data[4+4+4+1:], ent.key)
if ent.deleted {
data[4+4+4] = 1
} else {
binary.LittleEndian.PutUint32(data[8:12], uint32(len(ent.val)))
copy(data[4+4+4+1+len(ent.key):], ent.val)
}
binary.LittleEndian.PutUint32(data[0:4], crc32.ChecksumIEEE(data[4:]))
return data
}
var ErrBadSum = errors.New("bad checksum")
func (ent *Entry) Decode(r io.Reader) error {
var header [4 + 4 + 4 + 1]byte
if _, err := io.ReadFull(r, header[:]); err != nil {
return err
}
klen := int(binary.LittleEndian.Uint32(header[4:8]))
vlen := int(binary.LittleEndian.Uint32(header[8:12]))
deleted := header[4+4+4]
data := make([]byte, klen+vlen)
if _, err := io.ReadFull(r, data); err != nil {
return err
}
h := crc32.NewIEEE()
h.Write(header[4:])
h.Write(data)
if h.Sum32() != binary.LittleEndian.Uint32(header[0:4]) {
return ErrBadSum
}
ent.key = data[:klen]
if deleted != 0 {
ent.deleted = true
} else {
ent.deleted = false
ent.val = data[klen:]
}
return nil
}
// QzBQWVJJOUhU https://trialofcode.org/
+158
View File
@@ -0,0 +1,158 @@
package db0301
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 TestKVUpdateMode(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.SetEx([]byte("k1"), []byte("v1"), ModeUpdate)
assert.True(t, !updated && err == nil)
updated, err = kv.SetEx([]byte("k1"), []byte("v1"), ModeUpdate)
assert.True(t, !updated && err == nil)
updated, err = kv.SetEx([]byte("k1"), []byte("v1"), ModeInsert)
assert.True(t, updated && err == nil)
updated, err = kv.SetEx([]byte("k1"), []byte("xx"), ModeInsert)
assert.True(t, !updated && err == nil)
updated, err = kv.SetEx([]byte("k1"), []byte("yy"), ModeUpdate)
assert.True(t, updated && err == nil)
updated, err = kv.SetEx([]byte("k1"), []byte("zz"), ModeUpsert)
assert.True(t, updated && err == nil)
updated, err = kv.SetEx([]byte("k2"), []byte("tt"), ModeUpsert)
assert.True(t, updated && 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/
+40
View File
@@ -0,0 +1,40 @@
package db0301
import (
"io"
"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) {
err = ent.Decode(log.fp)
if err == io.EOF || err == io.ErrUnexpectedEOF || err == ErrBadSum {
return true, nil
} else if err != nil {
return false, err
} else {
return false, nil
}
}
// QzBQWVJJOUhU https://trialofcode.org/
+11
View File
@@ -0,0 +1,11 @@
//go:build !unix
package db0301
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 db0301
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/
+95
View File
@@ -0,0 +1,95 @@
package db0301
import (
"errors"
"slices"
)
type Schema struct {
Table string
Cols []Column
PKey []int // indexes of primary key columns
}
type Column struct {
Name string
Type CellType
}
type Row []Cell
func (schema *Schema) NewRow() Row {
return make(Row, len(schema.Cols))
}
func (row Row) EncodeKey(schema *Schema) (key []byte) {
key = append(key, []byte(schema.Table)...)
key = append(key, 0x00)
check(len(row) == len(schema.Cols))
for idx, value := range row {
if slices.Contains(schema.PKey, idx) {
check(value.Type == schema.Cols[idx].Type)
key = row[idx].Encode(key)
}
}
return key
}
func (row Row) EncodeVal(schema *Schema) (val []byte) {
check(len(row) == len(schema.Cols))
for idx, value := range row {
if !slices.Contains(schema.PKey, idx) {
check(value.Type == schema.Cols[idx].Type)
val = row[idx].Encode(val)
}
}
return val
}
func (row Row) DecodeKey(schema *Schema, key []byte) (err error) {
check(len(row) == len(schema.Cols))
if len(key) < len(schema.Table)+1 {
return errors.New("bad key")
}
if string(key[:len(schema.Table)+1]) != schema.Table+"\x00" {
return errors.New("bad key")
}
key = key[len(schema.Table)+1:]
for idx, col := range schema.Cols {
if !slices.Contains(schema.PKey, idx) {
continue
}
row[idx] = Cell{Type: col.Type}
if key, err = row[idx].Decode(key); err != nil {
return err
}
}
if len(key) != 0 {
return errors.New("trailing garbage")
}
return nil
}
func (row Row) DecodeVal(schema *Schema, val []byte) (err error) {
check(len(row) == len(schema.Cols))
for idx, col := range schema.Cols {
if slices.Contains(schema.PKey, idx) {
continue
}
row[idx] = Cell{Type: col.Type}
if val, err = row[idx].Decode(val); err != nil {
return err
}
}
if len(val) != 0 {
return errors.New("trailing garbage")
}
return nil
}
// QzBQWVJJOUhU https://trialofcode.org/
+38
View File
@@ -0,0 +1,38 @@
package db0301
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRowEncode(t *testing.T) {
schema := &Schema{
Table: "link",
Cols: []Column{
{Name: "time", Type: TypeI64},
{Name: "src", Type: TypeStr},
{Name: "dst", Type: TypeStr},
},
PKey: []int{1, 2}, // (src, dst)
}
row := Row{
Cell{Type: TypeI64, I64: 123},
Cell{Type: TypeStr, Str: []byte("a")},
Cell{Type: TypeStr, Str: []byte("b")},
}
key := []byte{'l', 'i', 'n', 'k', 0, 1, 0, 0, 0, 'a', 1, 0, 0, 0, 'b'}
val := []byte{123, 0, 0, 0, 0, 0, 0, 0}
assert.Equal(t, key, row.EncodeKey(schema))
assert.Equal(t, val, row.EncodeVal(schema))
decoded := schema.NewRow()
err := decoded.DecodeKey(schema, key)
assert.Nil(t, err)
err = decoded.DecodeVal(schema, val)
assert.Nil(t, err)
assert.Equal(t, row, decoded)
}
// QzBQWVJJOUhU https://trialofcode.org/
+50
View File
@@ -0,0 +1,50 @@
package db0301
type Parser struct {
buf string
pos int
}
func NewParser(s string) Parser {
return Parser{buf: s, pos: 0}
}
func isSpace(ch byte) bool {
switch ch {
case '\t', '\n', '\v', '\f', '\r', ' ':
return true
}
return false
}
func isAlpha(ch byte) bool {
return 'a' <= (ch|32) && (ch|32) <= 'z'
}
func isDigit(ch byte) bool {
return '0' <= ch && ch <= '9'
}
func isNameStart(ch byte) bool {
return isAlpha(ch) || ch == '_'
}
func isNameContinue(ch byte) bool {
return isAlpha(ch) || isDigit(ch) || ch == '_'
}
func isSeparator(ch byte) bool {
return ch < 128 && !isNameContinue(ch)
}
func (p *Parser) skipSpaces() {
for p.pos < len(p.buf) && isSpace(p.buf[p.pos]) {
p.pos += 1
}
}
func (p *Parser) tryKeyword(kw string) bool
func (p *Parser) tryName() (string, bool)
func (p *Parser) isEnd() bool {
p.skipSpaces()
return p.pos >= len(p.buf)
}
// QzBQWVJJOUhU https://trialofcode.org/
+28
View File
@@ -0,0 +1,28 @@
package db0301
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseName(t *testing.T) {
p := NewParser(" a b0 _0_ 123 ")
name, ok := p.tryName()
assert.True(t, ok && name == "a")
name, ok = p.tryName()
assert.True(t, ok && name == "b0")
name, ok = p.tryName()
assert.True(t, ok && name == "_0_")
_, ok = p.tryName()
assert.False(t, ok)
}
func TestParseKeyword(t *testing.T) {
p := NewParser(" select HELLO ")
assert.False(t, p.tryKeyword("sel"))
assert.True(t, p.tryKeyword("SELECT"))
assert.True(t, p.tryKeyword("hello") && p.isEnd())
}
// QzBQWVJJOUhU https://trialofcode.org/
+45
View File
@@ -0,0 +1,45 @@
package db0301
type DB struct {
KV KV
}
func (db *DB) Open() error { return db.KV.Open() }
func (db *DB) Close() error { return db.KV.Close() }
func (db *DB) Select(schema *Schema, row Row) (ok bool, err error) {
key := row.EncodeKey(schema)
val, ok, err := db.KV.Get(key)
if err != nil || !ok {
return ok, err
}
if err = row.DecodeVal(schema, val); err != nil {
return false, err
}
return true, nil
}
func (db *DB) Insert(schema *Schema, row Row) (updated bool, err error) {
key := row.EncodeKey(schema)
val := row.EncodeVal(schema)
return db.KV.SetEx(key, val, ModeInsert)
}
func (db *DB) Upsert(schema *Schema, row Row) (updated bool, err error) {
key := row.EncodeKey(schema)
val := row.EncodeVal(schema)
return db.KV.SetEx(key, val, ModeUpsert)
}
func (db *DB) Update(schema *Schema, row Row) (updated bool, err error) {
key := row.EncodeKey(schema)
val := row.EncodeVal(schema)
return db.KV.SetEx(key, val, ModeUpdate)
}
func (db *DB) Delete(schema *Schema, row Row) (deleted bool, err error) {
key := row.EncodeKey(schema)
return db.KV.Del(key)
}
// QzBQWVJJOUhU https://trialofcode.org/
+65
View File
@@ -0,0 +1,65 @@
package db0301
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTableByPKey(t *testing.T) {
db := DB{}
db.KV.log.FileName = ".test_db"
defer os.Remove(db.KV.log.FileName)
os.Remove(db.KV.log.FileName)
err := db.Open()
assert.Nil(t, err)
defer db.Close()
schema := &Schema{
Table: "link",
Cols: []Column{
{Name: "time", Type: TypeI64},
{Name: "src", Type: TypeStr},
{Name: "dst", Type: TypeStr},
},
PKey: []int{1, 2}, // (src, dst)
}
row := Row{
Cell{Type: TypeI64, I64: 123},
Cell{Type: TypeStr, Str: []byte("a")},
Cell{Type: TypeStr, Str: []byte("b")},
}
ok, err := db.Select(schema, row)
assert.True(t, !ok && err == nil)
updated, err := db.Insert(schema, row)
assert.True(t, updated && err == nil)
out := Row{
Cell{},
Cell{Type: TypeStr, Str: []byte("a")},
Cell{Type: TypeStr, Str: []byte("b")},
}
ok, err = db.Select(schema, out)
assert.True(t, ok && err == nil)
assert.Equal(t, row, out)
row[0].I64 = 456
updated, err = db.Update(schema, row)
assert.True(t, updated && err == nil)
ok, err = db.Select(schema, out)
assert.True(t, ok && err == nil)
assert.Equal(t, row, out)
deleted, err := db.Delete(schema, row)
assert.True(t, deleted && err == nil)
ok, err = db.Select(schema, row)
assert.True(t, !ok && err == nil)
}
// QzBQWVJJOUhU https://trialofcode.org/