package vm import ( "encoding/binary" "fmt" "os" "slices" "strconv" "strings" . "godb/internals/buffer" ) type MetaCommandResults int const ( MetaCommandSuccess MetaCommandResults = iota MetaCommandUnrecognised ) type PrepareResults int const ( PrepareSuccess PrepareResults = iota PrepareUnrecognised PrepareTooManyArgs PrepareValueTooLong PrepareSyntaxError ) type statementType int const ( StatementSelect statementType = iota StatementInsert StatementUnrecognized ) 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 ) 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 { statement := &Statement{} return statement } 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) 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 } func (statement *Statement) PrepareStatements(inputBuffer *InputBuffer) PrepareResults { statement.stype = StatementUnrecognized if strings.HasPrefix(inputBuffer.Buffer, "insert") { return statement.perpareInsert(inputBuffer) } if inputBuffer.Buffer == "select" { statement.stype = StatementSelect return PrepareSuccess } return PrepareUnrecognised } func (statement *Statement) ExecuteStatement(table *Table) ExecuteResult { var result ExecuteResult = ExecuteFail switch statement.stype { case StatementInsert: result = statement.execInsert(table) case StatementSelect: result = statement.execSelect(table) } return result }