Files
godb/internals/vm/vm.go
T

215 lines
4.6 KiB
Go
Raw Normal View History

2026-07-20 00:32:34 +05:30
package vm
import (
"encoding/binary"
2026-07-20 00:32:34 +05:30
"fmt"
"os"
"slices"
"strconv"
2026-07-20 00:32:34 +05:30
"strings"
. "godb/internals/buffer"
)
type MetaCommandResults int
const (
MetaCommandSuccess MetaCommandResults = iota
MetaCommandUnrecognised
)
type PrepareResults int
const (
PrepareSuccess PrepareResults = iota
PrepareUnrecognised
PrepareTooManyArgs
PrepareValueTooLong
2026-07-20 00:32:34 +05:30
PrepareSyntaxError
)
type statementType int
const (
StatementSelect statementType = iota
StatementInsert
StatementUnrecognized
2026-07-20 00:32:34 +05:30
)
const (
idSize = 8
usernameSize = 32
emailSize = 255
idOffset = 0
usernameOffset = idOffset + idSize
emailOffset = usernameOffset + usernameSize
pageSize = 4096
maxPages = 100
rowSize = idSize + usernameSize + emailSize
rowsPerPage = pageSize / rowSize
maxRows = rowsPerPage * maxPages
)
type ExecuteResult int
const (
ExecuteSuccess ExecuteResult = iota
ExecuteTableFull
ExecuteFail
)
2026-07-20 00:32:34 +05:30
type row struct {
id uint
username string
email string
}
type Statement struct {
stype statementType
row row
}
2026-07-21 23:22:15 +05:30
type Table struct {
numOfRows uint
pages [maxPages]any
}
2026-07-20 00:32:34 +05:30
func NewStatement() *Statement {
statement := &Statement{}
return statement
2026-07-20 00:32:34 +05:30
}
2026-07-21 23:22:15 +05:30
func NewTable() *Table {
newTable := Table{numOfRows: 0}
for i := range maxPages {
newTable.pages[i] = nil
}
return &newTable
}
func rowToUse(table *Table, rowNo uint) []byte {
pageNo := rowNo / rowsPerPage
var page []byte
if table.pages[pageNo] == nil {
page = make([]byte, pageSize)
table.pages[pageNo] = page
} else {
i := table.pages[pageNo]
if t, ok := i.([]byte); !ok {
page = []byte(t)
} else {
page = t
}
}
rowOffset := rowNo % rowsPerPage
byteOffset := rowOffset * rowSize
return page[byteOffset : byteOffset+rowSize]
}
func (row *row) seralizeRow(page []byte) {
binary.NativeEndian.PutUint64(page[idOffset:], uint64(row.id))
usernameBuff := page[usernameOffset : usernameOffset+usernameSize]
clear(usernameBuff)
copy(usernameBuff, row.username)
emailBuff := page[emailOffset : emailOffset+emailSize]
clear(emailBuff)
copy(emailBuff, row.email)
}
func (row *row) deSeralizeRow(page []byte) {
row.id = uint(binary.NativeEndian.Uint64(page[idOffset:idSize]))
row.username = string(page[usernameOffset : usernameOffset+usernameSize])
row.email = string(page[emailOffset : emailOffset+emailSize])
}
func (statement *Statement) execInsert(table *Table) ExecuteResult {
if table.numOfRows >= maxRows {
return ExecuteTableFull
}
statement.row.seralizeRow(rowToUse(table, table.numOfRows))
table.numOfRows += 1
return ExecuteSuccess
}
func (statement *Statement) execSelect(table *Table) ExecuteResult {
var row row
for i := range table.numOfRows {
row.deSeralizeRow(rowToUse(table, i))
fmt.Printf("%v\n", row)
}
return ExecuteSuccess
}
2026-07-20 00:32:34 +05:30
func DoMetaCommands(inputBuffer *InputBuffer) MetaCommandResults {
metacommand := MetaCommandUnrecognised
if inputBuffer.Buffer == ".exit" {
fmt.Println("bye!")
os.Exit(0)
} else {
metacommand = MetaCommandUnrecognised
}
return metacommand
}
func (statement *Statement) perpareInsert(inputBuffer *InputBuffer) PrepareResults {
inputs := strings.Split(inputBuffer.Buffer, " ")[1:]
if len(inputs) < 3 {
return PrepareSyntaxError
}
if len(inputs) > 3 {
return PrepareTooManyArgs
}
if slices.Contains(inputs, "null") || slices.Contains(inputs, "NULL") || slices.Contains(inputs, "Null") || slices.Contains(inputs, " ") {
return PrepareSyntaxError
}
tid, err := strconv.ParseUint(inputs[0], 10, 0)
if err != nil {
// fmt.Printf("supplied ID: %v isn't acceptable, please correct and retry\n", inputs[0])
return PrepareSyntaxError
}
id := uint(tid)
if len(inputs[1]) > usernameSize {
// fmt.Printf("%v is too long for username\n", inputs[1])
return PrepareValueTooLong
}
if len(inputs[2]) > emailSize {
// fmt.Printf("%v is too long for email\n", inputs[2])
return PrepareValueTooLong
}
statement.row.id = id
statement.row.username = inputs[1]
statement.row.email = inputs[2]
statement.stype = StatementInsert
return PrepareSuccess
}
2026-07-20 00:32:34 +05:30
func (statement *Statement) PrepareStatements(inputBuffer *InputBuffer) PrepareResults {
statement.stype = StatementUnrecognized
2026-07-20 00:32:34 +05:30
if strings.HasPrefix(inputBuffer.Buffer, "insert") {
return statement.perpareInsert(inputBuffer)
2026-07-20 00:32:34 +05:30
}
if inputBuffer.Buffer == "select" {
2026-07-20 00:32:34 +05:30
statement.stype = StatementSelect
return PrepareSuccess
}
return PrepareUnrecognised
}
func (statement *Statement) ExecuteStatement(table *Table) ExecuteResult {
var result ExecuteResult = ExecuteFail
2026-07-20 00:32:34 +05:30
switch statement.stype {
case StatementInsert:
result = statement.execInsert(table)
2026-07-20 00:32:34 +05:30
case StatementSelect:
result = statement.execSelect(table)
2026-07-20 00:32:34 +05:30
}
return result
2026-07-20 00:32:34 +05:30
}