Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cmd/pavosql/cmd/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import (
)

var (
port uint16
port uint16
filePath string
)

func Command() *cobra.Command {
Expand All @@ -18,6 +19,7 @@ func Command() *cobra.Command {
},
}

serveCmd.Flags().StringVarP(&filePath, "file", "f", "/var/lib/pavosql/pavosql.db", "")
serveCmd.Flags().Uint16VarP(&port, "port", "p", 6677, "")

return serveCmd
Expand Down
3 changes: 3 additions & 0 deletions internal/db/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package db

type DB struct{}
56 changes: 56 additions & 0 deletions internal/pager/pager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package pager

import (
"sync"

"github.com/gkits/pavosql/pkg/atomic"
)

type Pager struct {
rw atomic.ReadWriterAt
freeList int64
mu sync.RWMutex
end int64
}

type (
readFn = func(int64) ([]byte, error)
commitFn = func(map[int64][]byte) error

set[T comparable] = map[T]struct{}
)

func (p *Pager) NewReader() (*Reader, error) {
p.mu.RLock()
defer p.mu.RUnlock()

reader := newReader(p.read)

return reader, nil
}

func (p *Pager) NewWriter() (*Writer, error) {
p.mu.Lock()
defer p.mu.Unlock()

writer := newWriter(newReader(p.read), make(set[int64]), p.end, p.commit)

return writer, nil
}

func (p *Pager) read(off int64) ([]byte, error) {
page := make([]byte, 99)
if _, err := p.rw.ReadAt(page, int64(off)); err != nil {
return nil, err
}
return page, nil
}

func (p *Pager) commit(changes map[int64][]byte) error {
for off, d := range changes {
if _, err := p.rw.WriteAt(d, off); err != nil {
return err
}
}
return p.rw.Commit()
}
23 changes: 23 additions & 0 deletions internal/pager/reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package pager

type Reader struct {
pages map[int64][]byte
read readFn
}

func newReader(callbackRead readFn) *Reader {
return &Reader{make(map[int64][]byte), callbackRead}
}

func (r *Reader) Read(off int64) ([]byte, error) {
if page, ok := r.pages[off]; ok {
return page, nil
}

page, err := r.read(off)
if err != nil {
return nil, err
}
r.pages[off] = page
return page, nil
}
60 changes: 60 additions & 0 deletions internal/pager/writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package pager

type Writer struct {
freelist set[int64]
freed set[int64]
new map[int64][]byte
nextPage int64
pageSize int64
commit commitFn
*Reader
}

func newWriter(r *Reader, freelist set[int64], nextPage int64, commitCallback commitFn) *Writer {
return &Writer{
Reader: r,

freelist: freelist,
freed: make(set[int64]),
new: make(map[int64][]byte),

commit: commitCallback,
nextPage: nextPage,
}
}

func (w *Writer) Alloc(d []byte) int64 {
off := w.nextPage
switch {
case len(w.freed) > 0:
for off := range w.freed {
delete(w.freed, off)
break
}
case len(w.freelist) > 0:
for off := range w.freelist {
delete(w.freelist, off)
break
}
default:
w.nextPage += w.pageSize
}
w.new[off] = d
return off
}

func (w *Writer) Free(off int64) {
if _, ok := w.freelist[off]; ok {
return
}
if _, ok := w.freed[off]; ok {
return
}
w.freed[off] = struct{}{}
}

func (w *Writer) Commit() error {
return w.commit(w.new)
}

func (w *Writer) Abort() {}
1 change: 1 addition & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package server
4 changes: 2 additions & 2 deletions internal/tree/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ func (n *node) Val(i uint16) []byte {
return n[off+4+kLen : off+4+kLen+vLen]
}

func (n *node) Pointer(i uint16) uint64 {
func (n *node) Pointer(i uint16) int64 {
if !n.indexInBounds(i) {
panic(ErrIndexOutOfBounds)
}
off := n.offset(i)
kLen := binary.LittleEndian.Uint16(n[off:])
return binary.LittleEndian.Uint64(n[off+2+kLen : off+2+kLen+8])
return int64(binary.LittleEndian.Uint64(n[off+2+kLen : off+2+kLen+8]))
}

// Binary searches the target key inside n and returns its position and weither it exists.
Expand Down
74 changes: 70 additions & 4 deletions internal/tree/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@ package tree
import (
"errors"
"fmt"
"slices"
)

type pager interface {
ReadPage(off uint64) ([PageSize]byte, error)
ReadPage(int64) ([PageSize]byte, error)
Alloc([PageSize]byte) (int64, error)
Free(int64) error
Commit() error
Rollback() error
Abort() error
}

type Tree struct {
root uint64
pager pager
root int64
pager pager
readOnly bool
}

func New() *Tree {
Expand Down Expand Up @@ -50,9 +54,71 @@ func (t *Tree) Get(k []byte) ([]byte, error) {
}

func (t *Tree) Set(k []byte, v []byte) error {
if t.readOnly {
return errors.New("tree: cannot write onto read only tree")
}
page, err := t.pager.ReadPage(t.root)
if err != nil {
return fmt.Errorf("tree: failed to read root page: %w", err)
}
cur := node(page)

visited := []node{cur}
for {
i, exists := cur.Search(k)

switch cur.Type() {
case PointerPage:
ptr := cur.Pointer(i)
page, err = t.pager.ReadPage(ptr)
if err != nil {
return fmt.Errorf("tree: failed to read page: %w", err)
}
cur = node(page)
visited = append(visited, cur)
continue

case LeafPage:
if !exists {
return errors.New("key does not exists on leaf node")
}

if cur.CanSet(k, v) {
newNode := cur.Set(i, k, v)
ptr, err := t.pager.Alloc(newNode)
if err != nil {
return fmt.Errorf("tree: failed to allocate page: %w", err)
}
// TODO: pass ptr to parent node
_ = ptr
break
}

// TODO: handle splitting
left, right := cur.Split()
_, _ = left, right

default:
return errors.New("invalid page type")
}
break
}

for _, n := range slices.Backward(visited) {
ptr, err := t.pager.Alloc(n)
if err != nil {
return err
}
// TODO: pass ptr to parent nodes
_ = ptr
}

return nil
}

func (t *Tree) Delete(k []byte) error {
if t.readOnly {
return errors.New("tree: cannot write onto read only tree")
}
return nil
}
91 changes: 91 additions & 0 deletions internal/tree/tree_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package tree_test

import (
"testing"

"github.com/gkits/pavosql/internal/tree"
)

func TestTree_Get(t *testing.T) {
tests := []struct {
name string // description of this test case
// Named input parameters for target function.
k []byte
want []byte
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tr := tree.New()
got, gotErr := tr.Get(tt.k)
if gotErr != nil {
if !tt.wantErr {
t.Errorf("Get() failed: %v", gotErr)
}
return
}
if tt.wantErr {
t.Fatal("Get() succeeded unexpectedly")
}
// TODO: update the condition below to compare got with tt.want.
if true {
t.Errorf("Get() = %v, want %v", got, tt.want)
}
})
}
}

func TestTree_Set(t *testing.T) {
tests := []struct {
name string // description of this test case
// Named input parameters for target function.
k []byte
v []byte
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tr := tree.New()
gotErr := tr.Set(tt.k, tt.v)
if gotErr != nil {
if !tt.wantErr {
t.Errorf("Set() failed: %v", gotErr)
}
return
}
if tt.wantErr {
t.Fatal("Set() succeeded unexpectedly")
}
})
}
}

func TestTree_Delete(t *testing.T) {
tests := []struct {
name string // description of this test case
// Named input parameters for target function.
k []byte
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tr := tree.New()
gotErr := tr.Delete(tt.k)
if gotErr != nil {
if !tt.wantErr {
t.Errorf("Delete() failed: %v", gotErr)
}
return
}
if tt.wantErr {
t.Fatal("Delete() succeeded unexpectedly")
}
})
}
}
Loading
Loading