Skip to content

Commit 854abb0

Browse files
committed
Initial version of log
0 parents  commit 854abb0

File tree

8 files changed

+275
-0
lines changed

8 files changed

+275
-0
lines changed

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2021 Dmytro Krasun
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
19+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
21+
OR OTHER DEALINGS IN THE SOFTWARE.

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# appendy
2+
3+
## License
4+
5+
appendy is released under [the MIT license](LICENSE).

encode.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package main
2+
3+
import (
4+
"encoding/binary"
5+
)
6+
7+
func encodePut(key []byte, value []byte) []byte {
8+
encodedKeyLen := encodeLen(len(key))
9+
encodedValueLen := encodeLen(len(value))
10+
len := len(encodedKeyLen) + len(key) + len(encodedValueLen) + len(value)
11+
data := make([]byte, 0, len)
12+
13+
encodedLen := encodeLen(len)
14+
15+
data = append(data, encodedLen...)
16+
data = append(data, encodedKeyLen...)
17+
data = append(data, key...)
18+
data = append(data, encodedValueLen...)
19+
data = append(data, value...)
20+
21+
return data
22+
}
23+
24+
func encodeLen(len int) []byte {
25+
var encoded [8]byte
26+
binary.LittleEndian.PutUint64(encoded[:], uint64(len))
27+
28+
return encoded[:]
29+
}
30+
31+
func decodeLen(encoded []byte) int {
32+
return int(binary.LittleEndian.Uint64(encoded))
33+
}
34+
35+
func decode(encoded []byte) ([]byte, []byte, bool) {
36+
keyLen := decodeLen(encoded[0:8])
37+
key := encoded[8 : 8+keyLen]
38+
keyPartLen := 8 + keyLen
39+
40+
if keyPartLen+1 == len(encoded) {
41+
return key, nil, true
42+
}
43+
44+
valueLenStart := 8 + keyLen
45+
value := encoded[valueLenStart+8:]
46+
47+
return key, value, false
48+
}
49+
50+
func encodeDelete(key []byte) []byte {
51+
encodedKeyLen := encodeLen(len(key))
52+
len := len(encodedKeyLen) + len(key) + 1
53+
data := make([]byte, 0, len)
54+
55+
encodedLen := encodeLen(len)
56+
57+
data = append(data, encodedLen...)
58+
data = append(data, encodedKeyLen...)
59+
data = append(data, key...)
60+
// mark deletion
61+
data = append(data, 0)
62+
63+
return data
64+
}

file.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
)
8+
9+
func putEntry(file *os.File, key []byte, value []byte) error {
10+
return appendToFile(file, encodePut(key, value))
11+
}
12+
13+
func deleteEntry(file *os.File, key []byte) error {
14+
return appendToFile(file, encodeDelete(key))
15+
}
16+
17+
func appendToFile(file *os.File, data []byte) error {
18+
if _, err := file.Seek(0, 2); err != nil {
19+
return fmt.Errorf("failed to seek to the end: %w", err)
20+
}
21+
22+
if _, err := file.Write(data); err != nil {
23+
return fmt.Errorf("failed to write to the file: %w", err)
24+
}
25+
26+
if err := file.Sync(); err != nil {
27+
return fmt.Errorf("failed to sync the file: %w", err)
28+
}
29+
30+
return nil
31+
}
32+
33+
func loadEntries(file *os.File) ([]entry, error) {
34+
entries := make([]entry, 0)
35+
deleteKeys := make([][]byte, 0)
36+
offset := 0
37+
for {
38+
var encodedEntryLen [8]byte
39+
n, err := file.ReadAt(encodedEntryLen[:], int64(offset))
40+
if err != nil && err != io.EOF {
41+
return nil, fmt.Errorf("failed to read: %w", err)
42+
}
43+
if n < 8 && err == io.EOF {
44+
for _, deleteKey := range deleteKeys {
45+
entries = deleteByKey(entries, deleteKey)
46+
}
47+
48+
return entries, nil
49+
}
50+
offset += n
51+
52+
entryLen := decodeLen(encodedEntryLen[:])
53+
encodedEntry := make([]byte, entryLen, entryLen)
54+
n, err = file.ReadAt(encodedEntry, int64(offset))
55+
56+
if n < entryLen {
57+
return nil, fmt.Errorf("the file is corrupted, failed to read entry: %w", err)
58+
}
59+
offset += n
60+
61+
key, value, deleted := decode(encodedEntry)
62+
if deleted {
63+
deleteKeys = append(deleteKeys, key)
64+
} else {
65+
entries = append(entries, entry{key, value})
66+
}
67+
}
68+
}

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/krasun/appendy
2+
3+
go 1.14

main.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
func main() {
8+
s, _ := Open("test.db")
9+
10+
s.Put([]byte("test"), []byte("val1"))
11+
s.Put([]byte("test1"), []byte("val1"))
12+
s.Delete([]byte("test"))
13+
14+
val, ok, _ := s.Get([]byte("test1"))
15+
fmt.Println(string(val))
16+
fmt.Println(ok)
17+
18+
val2, ok, _ := s.Get([]byte("test"))
19+
fmt.Println(val2)
20+
fmt.Println(ok)
21+
fmt.Println(len(val2))
22+
fmt.Println(nil == val2)
23+
}

storage.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os"
7+
)
8+
9+
type Storage struct {
10+
entries []entry
11+
file *os.File
12+
}
13+
14+
type entry struct {
15+
key []byte
16+
value []byte
17+
}
18+
19+
func Open(path string) (*Storage, error) {
20+
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
21+
if err != nil {
22+
return nil, fmt.Errorf("failed to open file %s: %w", path, err)
23+
}
24+
25+
entries, err := loadEntries(file)
26+
if err != nil {
27+
return nil, fmt.Errorf("failed to load entries from %s: %w", path, err)
28+
}
29+
30+
return &Storage{file: file, entries: entries}, nil
31+
}
32+
33+
func (s *Storage) Close() error {
34+
if err := s.file.Close(); err != nil {
35+
return fmt.Errorf("failed to close file %s: %w", s.file.Name(), err)
36+
}
37+
38+
return nil
39+
}
40+
41+
func (s *Storage) Put(key []byte, value []byte) error {
42+
if key == nil || value == nil {
43+
return fmt.Errorf("key/value can not be nil")
44+
}
45+
46+
if err := putEntry(s.file, key, value); err != nil {
47+
return fmt.Errorf("failed to append to file %s: %w", s.file.Name(), err)
48+
}
49+
50+
s.entries = append(s.entries, entry{key, value})
51+
52+
return nil
53+
}
54+
55+
func (s *Storage) Get(key []byte) ([]byte, bool, error) {
56+
for _, entry := range s.entries {
57+
if bytes.Equal(entry.key, key) {
58+
return entry.value, true, nil
59+
}
60+
}
61+
62+
return nil, false, nil
63+
}
64+
65+
func (s *Storage) Delete(key []byte) error {
66+
if err := deleteEntry(s.file, key); err != nil {
67+
return fmt.Errorf("failed to append to file %s: %w", s.file.Name(), err)
68+
}
69+
70+
s.entries = deleteByKey(s.entries, key)
71+
72+
return nil
73+
}

util.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package main
2+
3+
import "bytes"
4+
5+
func deleteByKey(entries []entry, key []byte) []entry {
6+
b := entries[:0]
7+
for _, entry := range entries {
8+
if !bytes.Equal(entry.key, key) {
9+
b = append(b, entry)
10+
}
11+
}
12+
13+
return b
14+
}
15+
16+
func delete(slice []entry, i int) []entry {
17+
return append(slice[:i], slice[i+1:]...)
18+
}

0 commit comments

Comments
 (0)