Compare commits

...
22 Commits
Author SHA1 Message Date
TakshakRamteke 28acfd8d7e Merge pull request 'Merging into main' (#1) from dev into main
Reviewed-on: #1
2026-07-25 07:59:52 +00:00
TakshakRamteke 23fd1d5a1e feat: added licence 2026-07-25 13:28:00 +05:30
TakshakRamteke e666b26d3d feat: added first e2e test for insert and select 2026-07-25 01:04:58 +05:30
TakshakRamteke 318295f677 feat: added unit tests 2026-07-24 23:59:41 +05:30
TakshakRamteke e625b29d3c feat: added functionality for insert and select 2026-07-23 23:40:10 +05:30
TakshakRamteke c719848c25 feat: added functionality for insert and select 2026-07-22 01:46:52 +05:30
TakshakRamteke b2a61c66e4 feat: added a struct for table 2026-07-21 23:22:15 +05:30
TakshakRamteke 1ec9de4b0b chor: minor readme change 2026-07-20 00:38:35 +05:30
TakshakRamteke 208820217c feat: split into code for buffer and vm 2026-07-20 00:32:34 +05:30
TakshakRamteke 5550c92c23 feat: split code for repl 2026-07-19 23:33:02 +05:30
TakshakRamteke d97308b4f1 feat: added a git ignore and changed the build output path 2026-07-19 22:18:21 +05:30
TakshakRamteke a522c63f47 feat: upated the codebase to be a bit more idiomatic 2026-07-19 22:16:05 +05:30
TakshakRamteke df75480238 feat: added a makefile and switched to a more idiomatic structure 2026-07-19 20:37:26 +05:30
TakshakRamteke c3016be8e9 chor: minor code cleanup 2026-06-30 00:06:37 +05:30
TakshakRamtekeandGitHub 4968b2417f Merge pull request #3 from TakshakRamteke/dev
feat: added readme for the project
2025-02-05 23:10:02 +05:30
TakshakRamteke 82f77db592 feat: added readme for the project 2025-02-05 23:08:55 +05:30
TakshakRamtekeandGitHub aab9ad70c9 Merge pull request #2 from TakshakRamteke/dev
feat: basic command execution functionality
2025-02-04 09:41:53 +05:30
TakshakRamteke d2a5477217 FEAT : skeleton for a in memory table 2024-11-29 02:08:45 +05:30
TakshakRamteke b5a31e7775 FEAT : added a simple compiler and support for commands 2024-11-29 01:42:48 +05:30
TakshakRamteke ecc188de14 FIX : assigned value for buffer length 2024-11-24 23:34:30 +05:30
TakshakRamtekeandGitHub 4e273a2037 Merge pull request #1 from TakshakRamteke/dev
FEAT : added logic for a basic REPL
2024-11-24 23:32:23 +05:30
TakshakRamteke 8f6ad9817b FEAT : added logic for a basic REPL 2024-11-24 23:30:40 +05:30
12 changed files with 448 additions and 2 deletions
+1
View File
@@ -0,0 +1 @@
tmp
+9
View File
@@ -0,0 +1,9 @@
package main
import (
"godb/internals/repl"
)
func main() {
repl.Run()
}
+2 -2
View File
@@ -1,3 +1,3 @@
module godb/main
module godb
go 1.23.2
go 1.26.3
+30
View File
@@ -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)
}
+23
View File
@@ -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)
}
}
+50
View File
@@ -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")
}
}
}
+180
View File
@@ -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
}
+19
View File
@@ -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.
View File
+26
View File
@@ -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
+23
View File
@@ -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```
+85
View File
@@ -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)
}
}