diff --git a/app/generator/generator.go b/app/generator/generator.go index 6b7810b..e0ac051 100644 --- a/app/generator/generator.go +++ b/app/generator/generator.go @@ -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]{}, } } diff --git a/app/machine/instructions/goto.go b/app/machine/instructions/goto.go new file mode 100644 index 0000000..77d839a --- /dev/null +++ b/app/machine/instructions/goto.go @@ -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{} +} diff --git a/app/machine/instructions/ne.go b/app/machine/instructions/ne.go new file mode 100644 index 0000000..5e75e3f --- /dev/null +++ b/app/machine/instructions/ne.go @@ -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 +} diff --git a/app/machine/instructions/open_read_from_register.go b/app/machine/instructions/open_read_from_register.go new file mode 100644 index 0000000..1609dbe --- /dev/null +++ b/app/machine/instructions/open_read_from_register.go @@ -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{} +} diff --git a/app/machine/instructions/string.go b/app/machine/instructions/string.go new file mode 100644 index 0000000..4236f7b --- /dev/null +++ b/app/machine/instructions/string.go @@ -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{} +} diff --git a/app/main.go b/app/main.go index d6ff533..b3025f9 100644 --- a/app/main.go +++ b/app/main.go @@ -64,7 +64,7 @@ func main() { 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(