Skip to content
Merged

V2 #1

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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
*.log
*.json
./db

2 changes: 1 addition & 1 deletion db/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

type Database struct {
Tables map[string]*Table `json:"tables"`
Tables map[string]*Table
}

func NewDatabase() *Database {
Expand Down
10 changes: 5 additions & 5 deletions db/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ const (
)

type Column struct {
Name string `json:"name"`
Type FieldType `json:"type"`
Name string
Type FieldType
}

type Row map[string]interface{}

type Table struct {
Name string `json:"name"`
Columns []Column `json:"columns"`
Rows []Row `json:"rows"`
Name string
Columns []Column
Rows []Row
}
11 changes: 6 additions & 5 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import (

type Engine struct {
DB *db.Database
Pager *pager.Pager
Pager *pager.Page
}

func NewEngine() (*Engine, error) {

var database *db.Database
if _, err := os.Stat(storage.Path); err == nil {
if _, err := os.Stat(storage.DBDir); err == nil {
database, err = storage.LoadFromDisk()
if err != nil {
return nil, err
Expand All @@ -25,11 +25,11 @@ func NewEngine() (*Engine, error) {
} else {
return nil, err
}
pagerFile, err := os.OpenFile(storage.Path, os.O_RDWR|os.O_CREATE, 0666)
err := os.MkdirAll(storage.DBDir, 0775)
if err != nil {
return nil, err
}
pgr := pager.PagerInit(pagerFile)
pgr := pager.NewPage()
if pgr == nil {
return nil, err
}
Expand All @@ -46,5 +46,6 @@ func (engine *Engine) Close() error {
if err := storage.SaveToDisk(engine.DB); err != nil {
return err
}
return engine.Pager.Close()

return nil
}
229 changes: 191 additions & 38 deletions pager/pager.go
Original file line number Diff line number Diff line change
@@ -1,64 +1,217 @@
package pager

import "os"
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"pebbledb/db"
)

const PageSize = 4096 // 4KB page size
const (
PageSize = 4096
PageHeaderSize = 16
ItemIDSize = 6
MaxItemsPerPage = 128
SpecialSpaceSize = 0 // Reserved for future user for indexing metadata
DataRegionSize = PageSize - PageHeaderSize - MaxItemsPerPage*ItemIDSize - SpecialSpaceSize
)

type Page struct {
ID int
Data [PageSize]byte
type PageHeader struct {
LSN uint64 // Future WAL support
NumItems uint16
PdLower uint16
PdUpper uint16
}

type ItemID struct {
Offset uint16
Length uint16
DeletedFlag uint8
_ [1]byte // Padding to align to 6 bytes
}
type Pager struct {
file *os.File
pageSize int
nextPageID int

type Page struct {
Header PageHeader
Items [MaxItemsPerPage]ItemID
Data [DataRegionSize]byte
}

func PagerInit(file *os.File) *Pager {
return &Pager{
file: file,
pageSize: PageSize,
nextPageID: 0,
func NewPage() *Page {
return &Page{
Header: PageHeader{
PdLower: PageHeaderSize,
PdUpper: DataRegionSize,
},
Items: [MaxItemsPerPage]ItemID{},
Data: [DataRegionSize]byte{},
}
}

func (p *Pager) NewPage() *Page {
page := &Page{
ID: p.nextPageID,
Data: [PageSize]byte{},
func (p *Page) InsertTuple(record []byte) (int, error) {
if len(record) > DataRegionSize {
return -1, errors.New("record too large")
}
if len(record) == 0 {
return -1, errors.New("record cannot be empty")
}
if p.Header.NumItems >= MaxItemsPerPage {
return -2, errors.New("page is full")
}

freeSpace := int(p.Header.PdUpper) - int(p.Header.PdLower)
requiredSpace := len(record) + ItemIDSize
if requiredSpace > freeSpace {
return -1, errors.New("not enough space in page")
}

newUpper := p.Header.PdUpper - uint16(len(record))
copy(p.Data[newUpper:], record)

slot := int(p.Header.NumItems)
p.Items[slot] = ItemID{
Offset: newUpper,
Length: uint16(len(record)),
DeletedFlag: 1, // Mark as not deleted
}
p.nextPageID++
return page

p.Header.PdUpper = newUpper
p.Header.PdLower += ItemIDSize
p.Header.NumItems++

return slot, nil
}
func (p *Pager) WritePage(page *Page) error {

offset := int64(page.ID) * PageSize
_, err := p.file.WriteAt(page.Data[:], offset)
if err != nil {
return err
func (p *Page) ReadTuple(slot int) ([]byte, error) {

if slot < 0 || slot >= int(p.Header.NumItems) {
return nil, errors.New("invalid slot number")
}
items := p.Items[slot]
if items.DeletedFlag == 0 {
return nil, errors.New("tuple has been deleted")
}
data := make([]byte, items.Length)
copy(data, p.Data[items.Offset:items.Offset+items.Length])
return data, nil
}

func (p *Page) DeleteTuple(slot int) error {
if slot < 0 || slot >= int(p.Header.NumItems) {
return errors.New("invalid slot number")
}
items := p.Items[slot]
if items.DeletedFlag == 0 {
return errors.New("tuple has already been deleted")
}
p.Items[slot].DeletedFlag = 0
return nil
}
func SerializeRow(row db.Row, columns []db.Column) ([]byte, error) {
var buf bytes.Buffer
for _, col := range columns {
val := row[col.Name]
switch col.Type {
case db.TypeInt:
if v, ok := val.(int); ok {
if err := binary.Write(&buf, binary.LittleEndian, int32(v)); err != nil {
return nil, err
}

}
case db.TypeString:
if str, ok := val.(string); ok {
if err := binary.Write(&buf, binary.LittleEndian, uint16(len(str))); err != nil {
return nil, err
}
if _, err := buf.Write([]byte(str)); err != nil {
return nil, err
}
}

func (p *Pager) ReadPage(pageID int) (*Page, error) {
page := &Page{
ID: pageID,
Data: [PageSize]byte{},
}
}
return buf.Bytes(), nil
}

offset := int64(pageID) * PageSize
_, err := p.file.ReadAt(page.Data[:], offset)
if err != nil {
return nil, err
func DeserializeRow(data []byte, columns []db.Column) (db.Row, error) {
row := make(db.Row)
buf := bytes.NewBuffer(data)

for _, col := range columns {
switch col.Type {
case db.TypeInt:
var val int32
if err := binary.Read(buf, binary.LittleEndian, &val); err != nil {
return nil, err
}
row[col.Name] = int(val)
case db.TypeString:
var length uint16
if err := binary.Read(buf, binary.LittleEndian, &length); err != nil {
return nil, err
}
strData := make([]byte, length)
if _, err := io.ReadFull(buf, strData); err != nil {
return nil, err
}
row[col.Name] = string(strData)
}
}
fmt.Printf("%v\n", row)
return row, nil
}

return page, nil
func SerializePage(page *Page) []byte {
buf := make([]byte, PageSize)
writer := bytes.NewBuffer(buf[:0])

if err := binary.Write(writer, binary.LittleEndian, &page.Header); err != nil {
panic("failed to write page header: " + err.Error())
}

for i := 0; i < MaxItemsPerPage; i++ {
if err := binary.Write(writer, binary.LittleEndian, &page.Items[i]); err != nil {
panic("failed to write page item: " + err.Error())
}
}

if _, err := writer.Write(page.Data[:]); err != nil {
panic("failed to write page data: " + err.Error())
}
final := writer.Bytes()
if len(final) < PageSize {
padding := make([]byte, PageSize-len(final))
final = append(final, padding...)
}

return final
}

func (p *Pager) Close() error {
if p.file != nil {
return p.file.Close()
func DeserializePage(buf []byte) (*Page, error) {
if len(buf) < PageSize {
return nil, errors.New("buffer too small to be a valid page")
}
return nil

page := &Page{}
reader := bytes.NewReader(buf)

if err := binary.Read(reader, binary.LittleEndian, &page.Header); err != nil {
return nil, err
}

for i := 0; i < MaxItemsPerPage; i++ {
var items ItemID
if err := binary.Read(reader, binary.LittleEndian, &items); err != nil {

return nil, err
}
page.Items[i] = items
}

if _, err := reader.Read(page.Data[:]); err != nil {
return nil, err
}
return page, nil
}
2 changes: 1 addition & 1 deletion repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func ReplInit(database *db.Database) {
fmt.Println("Welcome to PebbleDB ! Type 'Exit' to quit.")

for {
fmt.Println("> ")
fmt.Print("> ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
if input == "" {
Expand Down
Loading