Skip to content
Open
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
50 changes: 44 additions & 6 deletions app/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,53 @@ import (
"github.com/xwb1989/sqlparser"
)

func Generate[T any](_ sqlparser.Statement) []instructions.Instruction[T] {
// resultConstructor allows the generator to construct T values for use in
// instructions like String. This follows the same pattern as BTreeEngine.
type resultConstructor[T any] interface {
Text(string) T
Number(int64) T
Null() T
}

func Generate[T any](stmt sqlparser.Statement, rc resultConstructor[T]) []instructions.Instruction[T] {
selectStmt := stmt.(*sqlparser.Select)
tableExpr := selectStmt.From[0].(*sqlparser.AliasedTableExpr)
tableName := sqlparser.GetTableName(tableExpr.Expr).String()

return []instructions.Instruction[T]{
instructions.OpenRead[T]{RootPage: 5, CursorID: 0},
// Schema lookup (addresses 0-10)
// Open cursor 0 on the schema table (always page 1)
instructions.OpenRead[T]{RootPage: 1, CursorID: 0},
instructions.Rewind[T]{Cursor: 0},
instructions.Column[T]{Cursor: 0, Column: 0, Register: 1},
instructions.Column[T]{Cursor: 0, Column: 1, Register: 2},
instructions.Column[T]{Cursor: 0, Column: 2, Register: 3},
instructions.ResultRow[T]{FromRegister: 1, ToRegister: 3},

// Loop: check if this row is a "table" type entry
instructions.Column[T]{Cursor: 0, Column: 0, Register: 1}, // Read 'type' into reg1
instructions.String[T]{Register: 2, Value: rc.Text("table")},
instructions.Ne[T]{Register1: 1, Register2: 2, JumpAddress: 9}, // If not "table", go to Next

// Check if the name matches our target table
instructions.Column[T]{Cursor: 0, Column: 1, Register: 3}, // Read 'name' into reg3
instructions.String[T]{Register: 4, Value: rc.Text(tableName)},
instructions.Ne[T]{Register1: 3, Register2: 4, JumpAddress: 9}, // If not target, go to Next

// Found the table! Jump past the loop
instructions.Goto[T]{JumpAddress: 11},

// Next: advance cursor and loop back, or fall through to halt if exhausted
instructions.Next[T]{Cursor: 0, FromAddress: 2},
instructions.Halt[T]{}, // Table not found (error case)

// Extract root page and open data cursor (addresses 11-13)
instructions.Column[T]{Cursor: 0, Column: 3, Register: 5}, // Read rootpage into reg5
instructions.OpenReadFromRegister[T]{CursorID: 1, PageRegister: 5},
instructions.Rewind[T]{Cursor: 1},

// Data reading (addresses 14+) - using cursor 1
instructions.Column[T]{Cursor: 1, Column: 0, Register: 6},
instructions.Column[T]{Cursor: 1, Column: 1, Register: 7},
instructions.Column[T]{Cursor: 1, Column: 2, Register: 8},
instructions.ResultRow[T]{FromRegister: 6, ToRegister: 8},
instructions.Next[T]{Cursor: 1, FromAddress: 14},
instructions.Halt[T]{},
}
}
17 changes: 17 additions & 0 deletions app/machine/instructions/goto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package instructions

import (
"github/com/lucasbn/sqlite-clone/app/machine/common"
"github/com/lucasbn/sqlite-clone/app/machine/state"
)

type Goto[T any] struct {
JumpAddress uint64
}

var _ Instruction[any] = Goto[any]{}

func (g Goto[T]) Execute(s *state.MachineState[T], b common.BTreeEngine[T]) [][]T {
s.CurrentAddress = int(g.JumpAddress)
return [][]T{}
}
58 changes: 58 additions & 0 deletions app/machine/instructions/ne.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package instructions

import (
"github/com/lucasbn/sqlite-clone/app/machine/common"
"github/com/lucasbn/sqlite-clone/app/machine/state"
"github/com/lucasbn/sqlite-clone/app/types"
)

type Ne[T any] struct {
Register1 int
Register2 int
JumpAddress uint64
}

var _ Instruction[any] = Ne[any]{}

func (ne Ne[T]) Execute(s *state.MachineState[T], b common.BTreeEngine[T]) [][]T {
val1 := s.Registers.Get(ne.Register1)
val2 := s.Registers.Get(ne.Register2)

equal := compareValues(val1, val2)

if !equal {
s.CurrentAddress = int(ne.JumpAddress)
} else {
s.CurrentAddress++
}

return [][]T{}
}

// compareValues compares two values for equality. It handles the specific
// Entry types used by this SQLite clone.
func compareValues[T any](a, b T) bool {
// Type assert to Entry interface and compare based on type
aEntry, aOk := any(a).(types.Entry)
bEntry, bOk := any(b).(types.Entry)

if !aOk || !bOk {
return false
}

switch aVal := aEntry.(type) {
case types.TextEntry:
if bVal, ok := bEntry.(types.TextEntry); ok {
return aVal.Value == bVal.Value
}
case types.NumberEntry:
if bVal, ok := bEntry.(types.NumberEntry); ok {
return aVal.Value == bVal.Value
}
case types.NullEntry:
_, ok := bEntry.(types.NullEntry)
return ok
}

return false
}
Comment on lines +32 to +58
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compareValues function has hardcoded logic for the types.Entry interface and its concrete types (TextEntry, NumberEntry, NullEntry). This creates a tight coupling between a generic instruction and a specific type system, defeating the purpose of the generic T parameter.

Similar to OpenReadFromRegister, this should either be made non-generic (working only with Entry types) or refactored to use a more flexible comparison mechanism. Consider adding a comparison method to a constraint interface or making the instruction non-generic.

Copilot uses AI. Check for mistakes.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe T should be a "comparable" or something like that rather than any

37 changes: 37 additions & 0 deletions app/machine/instructions/open_read_from_register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package instructions

import (
"github/com/lucasbn/sqlite-clone/app/machine/common"
"github/com/lucasbn/sqlite-clone/app/machine/state"
"github/com/lucasbn/sqlite-clone/app/types"
)

type OpenReadFromRegister[T any] struct {
CursorID uint64
PageRegister int
}

var _ Instruction[any] = OpenReadFromRegister[any]{}

func (o OpenReadFromRegister[T]) Execute(s *state.MachineState[T], b common.BTreeEngine[T]) [][]T {
s.CurrentAddress++

// Get the root page number from the register
regValue := s.Registers.Get(o.PageRegister)

// Type assert to extract the int64 value from NumberEntry
entry, ok := any(regValue).(types.Entry)
if !ok {
panic("OpenReadFromRegister: register value is not an Entry")
}

numEntry, ok := entry.(types.NumberEntry)
if !ok {
panic("OpenReadFromRegister: register value is not a NumberEntry")
}

rootPage := uint64(numEntry.Value)
b.NewCursor(o.CursorID, rootPage)

return [][]T{}
}
20 changes: 20 additions & 0 deletions app/machine/instructions/string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package instructions

import (
"github/com/lucasbn/sqlite-clone/app/machine/common"
"github/com/lucasbn/sqlite-clone/app/machine/state"
)

type String[T any] struct {
Register int
Value T
}

var _ Instruction[int] = String[int]{}

func (str String[T]) Execute(s *state.MachineState[T], b common.BTreeEngine[T]) [][]T {
s.CurrentAddress++

s.Registers = s.Registers.Set(str.Register, str.Value)
return [][]T{}
}
2 changes: 1 addition & 1 deletion app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"github.com/davecgh/go-spew/spew"
)

type DatabaseHeader struct {

Check failure on line 16 in app/main.go

View workflow job for this annotation

GitHub Actions / style-check

exported type DatabaseHeader should have comment or be unexported
HeaderString [16]byte
PageSize uint16
FileWriteVersion uint8
Expand Down Expand Up @@ -64,7 +64,7 @@
stmt := parser.MustParse(command)

// 2. Generate the bytecode
instructions := generator.Generate[types.Entry](stmt)
instructions := generator.Generate(stmt, &types.EntryConstructor{})

// 3. Configure the virtual machine
m := machine.NewMachine(
Expand Down
Loading