Merge pull request 'Merging into main' (#1) from dev into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
tmp
|
||||
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"godb/internals/repl"
|
||||
)
|
||||
|
||||
func main() {
|
||||
repl.Run()
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type InputBuffer struct {
|
||||
Buffer string
|
||||
Reader *bufio.Reader
|
||||
}
|
||||
|
||||
func NewInputBuffer() *InputBuffer {
|
||||
return &InputBuffer{
|
||||
Buffer: "",
|
||||
Reader: bufio.NewReader(os.Stdin),
|
||||
}
|
||||
}
|
||||
|
||||
func (inputBuffer *InputBuffer) ReadConsole() {
|
||||
line, err := inputBuffer.Reader.ReadString('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
fmt.Println("Error reading input", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
inputBuffer.Buffer = strings.TrimSpace(line)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadConsole(t *testing.T) {
|
||||
mockMessage := "Reading for Unit test of readconsole"
|
||||
|
||||
inputBuffer := &InputBuffer{
|
||||
Buffer: "",
|
||||
Reader: bufio.NewReader(strings.NewReader(mockMessage)),
|
||||
}
|
||||
|
||||
inputBuffer.ReadConsole()
|
||||
|
||||
if inputBuffer.Buffer != mockMessage {
|
||||
t.Errorf("ReadConsole() should be %q but is %q", mockMessage, inputBuffer.Buffer)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package repl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
. "godb/internals/buffer"
|
||||
. "godb/internals/vm"
|
||||
)
|
||||
|
||||
func PrintConsole() {
|
||||
fmt.Print("godb > ")
|
||||
}
|
||||
|
||||
func Run() {
|
||||
|
||||
inputBuffer := NewInputBuffer()
|
||||
statement := NewStatement()
|
||||
table := NewTable()
|
||||
for {
|
||||
PrintConsole()
|
||||
inputBuffer.ReadConsole()
|
||||
if len(inputBuffer.Buffer) > 0 && strings.HasPrefix(inputBuffer.Buffer, ".") {
|
||||
switch DoMetaCommands(inputBuffer) {
|
||||
case MetaCommandSuccess:
|
||||
continue
|
||||
case MetaCommandUnrecognised:
|
||||
fmt.Printf("unrecognised command %v\n", inputBuffer.Buffer)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch statement.PrepareStatements(inputBuffer) {
|
||||
case PrepareSuccess:
|
||||
//todo
|
||||
case PrepareSyntaxError:
|
||||
fmt.Println("Syntax error, could not parse statement")
|
||||
case PrepareUnrecognised:
|
||||
fmt.Printf("Unrecognised keyword at the start of '%s'.\n", inputBuffer.Buffer)
|
||||
}
|
||||
|
||||
switch statement.ExecuteStatement(table) {
|
||||
case ExecuteSuccess:
|
||||
fmt.Println("Executed.")
|
||||
case ExecuteTableFull:
|
||||
fmt.Println("Error: Table is full")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
. "godb/internals/buffer"
|
||||
)
|
||||
|
||||
type MetaCommandResults int
|
||||
|
||||
const (
|
||||
MetaCommandSuccess MetaCommandResults = iota
|
||||
MetaCommandUnrecognised
|
||||
)
|
||||
|
||||
type PrepareResults int
|
||||
|
||||
const (
|
||||
PrepareSuccess PrepareResults = iota
|
||||
PrepareUnrecognised
|
||||
PrepareSyntaxError
|
||||
)
|
||||
|
||||
type statementType int
|
||||
|
||||
const (
|
||||
StatementSelect statementType = iota
|
||||
StatementInsert
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
type row struct {
|
||||
id uint
|
||||
username string
|
||||
email string
|
||||
}
|
||||
|
||||
type Statement struct {
|
||||
stype statementType
|
||||
row row
|
||||
}
|
||||
|
||||
type Table struct {
|
||||
numOfRows uint
|
||||
pages [maxPages]any
|
||||
}
|
||||
|
||||
func NewStatement() *Statement {
|
||||
return &Statement{0, row{0, "", ""}}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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) PrepareStatements(inputBuffer *InputBuffer) PrepareResults {
|
||||
if strings.HasPrefix(inputBuffer.Buffer, "insert") {
|
||||
argsAssigned, err := fmt.Sscanf(inputBuffer.Buffer, "insert %d %s %s", &statement.row.id, &statement.row.username, &statement.row.email)
|
||||
// if err != nil {
|
||||
// fmt.Println("error parsing insert statement")
|
||||
// fmt.Println(err.Error())
|
||||
// }
|
||||
if err != nil || argsAssigned > 3 {
|
||||
return PrepareSyntaxError
|
||||
}
|
||||
statement.stype = StatementInsert
|
||||
return PrepareSuccess
|
||||
}
|
||||
if strings.HasPrefix(inputBuffer.Buffer, "select") {
|
||||
statement.stype = StatementSelect
|
||||
return PrepareSuccess
|
||||
}
|
||||
return PrepareUnrecognised
|
||||
}
|
||||
|
||||
func (statement *Statement) ExecuteStatement(table *Table) ExecuteResult {
|
||||
var result ExecuteResult
|
||||
switch statement.stype {
|
||||
case StatementInsert:
|
||||
result = statement.execInsert(table)
|
||||
case StatementSelect:
|
||||
result = statement.execSelect(table)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 TakshakRamteke
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
BINARY_NAME=godb
|
||||
|
||||
run:
|
||||
@go run cmd/godb/main.go
|
||||
|
||||
build:
|
||||
@go build -o tmp/${BINARY_NAME} cmd/godb/main.go
|
||||
|
||||
start:
|
||||
@./tmp/${BINARY_NAME}
|
||||
|
||||
test-unit:
|
||||
@go test ./internals/... -v
|
||||
|
||||
test-inte:
|
||||
@go test ./tests/... -v
|
||||
|
||||
test: build test-unit test-inte
|
||||
|
||||
clean:
|
||||
@go clean
|
||||
@rm -rf ${BINARY_NAME}
|
||||
|
||||
reset: clean build test clean
|
||||
|
||||
.PHONY: clean build test
|
||||
@@ -0,0 +1,23 @@
|
||||
### Godb
|
||||
|
||||
A SQLite like database made in golang
|
||||
|
||||
Inorder to run this locally, after cloning this repo run `go mod tidy`
|
||||
|
||||
Then use the appropriate `make` command listed below
|
||||
|
||||
Run
|
||||
|
||||
```make run```
|
||||
|
||||
Build
|
||||
|
||||
```make build```
|
||||
|
||||
Start
|
||||
|
||||
```make start```
|
||||
|
||||
Cleanup
|
||||
|
||||
```make clean```
|
||||
@@ -0,0 +1,85 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func runner(commands []string, t *testing.T) []string {
|
||||
executablePath := "../tmp/godb"
|
||||
t.Logf("starting godb from %v\n", executablePath)
|
||||
|
||||
executable := exec.Command(executablePath)
|
||||
|
||||
results := make([]string, 0)
|
||||
|
||||
stdin, err := executable.StdinPipe()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create stdin %q\n", err)
|
||||
}
|
||||
|
||||
stdout, err := executable.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create stdout %q\n", err)
|
||||
}
|
||||
|
||||
if err := executable.Start(); err != nil {
|
||||
t.Errorf("Failed to start executable %q\n", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer stdin.Close()
|
||||
for _, c := range commands {
|
||||
_, _ = io.WriteString(stdin, c+"\n")
|
||||
}
|
||||
}()
|
||||
|
||||
reader := bufio.NewReader(stdout)
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if line != "" {
|
||||
formattedline := fmt.Sprintf("%v", strings.Trim(strings.ReplaceAll(line, "\x00", ""), "\n"))
|
||||
results = append(results, formattedline)
|
||||
}
|
||||
if err != nil {
|
||||
t.Logf("stoped reading due to %q\n", err)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err := executable.Wait(); err != nil {
|
||||
t.Logf("Error occured while waiting for cleanup %q\n", err)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
commands := []string{
|
||||
"insert 1 user1 person1@example.com",
|
||||
"select",
|
||||
".exit",
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"godb > Executed.",
|
||||
"godb > {1 user1 person1@example.com}",
|
||||
"Executed.",
|
||||
"godb > bye!",
|
||||
}
|
||||
results := runner(commands, t)
|
||||
|
||||
for i := range results {
|
||||
results[i] = strings.ReplaceAll(results[i], "\n", "")
|
||||
}
|
||||
|
||||
if !slices.Equal(expected, results) {
|
||||
t.Errorf("Output Mismatch\nExpected: %v\nGot: %v\n", expected, results)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user