Skip to content

Commit

Permalink
Todos (#6)
Browse files Browse the repository at this point in the history
* Page type is stored as a uint8 instead of a uint16.

* Execution time is shown in the REPL.

* Moves KV Get and Set to a cursor. This means P1 on bytecodes is a cursor id instead of a root page.

* INSERT validates values arguments matches values parameters length.

* Driver allows in memory mode with :memory: string.

* Invalid meta commands in the REPL don't cause a crash.
  • Loading branch information
chirst authored Jul 29, 2024
1 parent 7401149 commit 49016c9
Show file tree
Hide file tree
Showing 20 changed files with 443 additions and 368 deletions.
2 changes: 1 addition & 1 deletion compiler/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type InsertStmt struct {
*StmtBase
TableName string
ColNames []string
ColValues []string
ColValues [][]string
}

// type Expr interface {
Expand Down
11 changes: 6 additions & 5 deletions compiler/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,29 +189,30 @@ func (p *parser) parseInsert(sb *StmtBase) (*InsertStmt, error) {
if p.nextNonSpace().value != kwValues {
return nil, fmt.Errorf(tokenErr, p.tokens[p.end].value)
}
return p.parseValue(stmt)
return p.parseValue(stmt, 0)
}

func (p *parser) parseValue(stmt *InsertStmt) (*InsertStmt, error) {
func (p *parser) parseValue(stmt *InsertStmt, valueIdx int) (*InsertStmt, error) {
if p.nextNonSpace().value != "(" {
return nil, fmt.Errorf(tokenErr, p.tokens[p.end].value)
}
stmt.ColValues = append(stmt.ColValues, []string{})
for {
v := p.nextNonSpace()
if v.tokenType != tkNumeric && v.tokenType != tkLiteral {
return nil, fmt.Errorf(literalErr, v.value)
}
if v.tokenType == tkLiteral && v.value[0] == '\'' && v.value[len(v.value)-1] == '\'' {
stmt.ColValues = append(stmt.ColValues, v.value[1:len(v.value)-1])
stmt.ColValues[valueIdx] = append(stmt.ColValues[valueIdx], v.value[1:len(v.value)-1])
} else {
stmt.ColValues = append(stmt.ColValues, v.value)
stmt.ColValues[valueIdx] = append(stmt.ColValues[valueIdx], v.value)
}
sep := p.nextNonSpace()
if sep.value != "," {
if sep.value == ")" {
sep2 := p.nextNonSpace()
if sep2.value == "," {
p.parseValue(stmt)
p.parseValue(stmt, valueIdx+1)
}
break
}
Expand Down
32 changes: 19 additions & 13 deletions compiler/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func TestParseSelect(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
ret, err := NewParser(c.tokens).Parse()
if err != nil {
t.Errorf("want no err got err %s", err.Error())
t.Errorf("want no err got err %s", err)
}
if !reflect.DeepEqual(ret, c.expect) {
t.Errorf("got %#v want %#v", ret, c.expect)
Expand Down Expand Up @@ -170,7 +170,7 @@ func TestParseCreate(t *testing.T) {
for _, c := range cases {
ret, err := NewParser(c.tokens).Parse()
if err != nil {
t.Errorf("expected no err got err %s", err.Error())
t.Errorf("expected no err got err %s", err)
}
if !reflect.DeepEqual(ret, c.expected) {
t.Errorf("expected %#v got %#v", c.expected, ret)
Expand Down Expand Up @@ -247,24 +247,30 @@ func TestParseInsert(t *testing.T) {
"first_name",
"last_name",
},
ColValues: []string{
"1",
"gud",
"dude",
"2",
"joe",
"doe",
"3",
"jan",
"ice",
ColValues: [][]string{
{
"1",
"gud",
"dude",
},
{
"2",
"joe",
"doe",
},
{
"3",
"jan",
"ice",
},
},
},
},
}
for _, c := range cases {
ret, err := NewParser(c.tokens).Parse()
if err != nil {
t.Errorf("expected no err got err %s", err.Error())
t.Errorf("expected no err got err %s", err)
}
if !reflect.DeepEqual(ret, c.expected) {
t.Errorf("expected %#v got %#v", c.expected, ret)
Expand Down
3 changes: 3 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package db

import (
"errors"
"time"

"github.com/chirst/cdb/compiler"
"github.com/chirst/cdb/kv"
Expand Down Expand Up @@ -48,6 +49,7 @@ func New(useMemory bool, filename string) (*DB, error) {
}

func (db *DB) Execute(sql string) vm.ExecuteResult {
start := time.Now()
tokens := compiler.NewLexer(sql).Lex()
statement, err := compiler.NewParser(tokens).Parse()
if err != nil {
Expand Down Expand Up @@ -76,6 +78,7 @@ func (db *DB) Execute(sql string) vm.ExecuteResult {
break
}
}
executeResult.Duration = time.Since(start)
return executeResult
}

Expand Down
19 changes: 11 additions & 8 deletions driver/driver.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
// Package driver enables cdb to be used with the go database/sql package.
package driver

// TODO
// - Specify whether or not to use memory and what database file name to use.
// - Question what the prepare step should do.
// - Think about making database return typed response instead of all strings.
// - Implement and test half finished methods.
// - Consider context methods.
// TODO there are several context methods that are not implemented.

import (
"database/sql"
Expand All @@ -27,9 +22,12 @@ func new() *cdbDriver {

type cdbDriver struct{}

// Open implements driver.Driver.
// Open implements driver.Driver. Name is the name of the database file. If the
// name is :memory: the database will not use a file and will not persist
// changes.
func (c *cdbDriver) Open(name string) (driver.Conn, error) {
d, err := db.New(true, "cdb")
isMemory := name == ":memory:"
d, err := db.New(isMemory, name)
if err != nil {
return nil, err
}
Expand All @@ -55,6 +53,9 @@ func (c *cdbConn) Close() error {

// Prepare implements driver.Conn.
func (c *cdbConn) Prepare(query string) (driver.Stmt, error) {
// TODO Prepare is supposed to compile the query and allow different
// parameterized queries to be executed with the same compiled query. This
// isn't supported by cdb at the moment so it is a pass through.
st := &cdbStmt{
cdb: c.cdb,
query: query,
Expand Down Expand Up @@ -134,6 +135,8 @@ func (c *cdbRows) Next(dest []driver.Value) error {
return io.EOF
}
for i, v := range c.rows[c.rowIdx] {
// TODO the value is a string pointer, but might be better as a typed
// value. It is a string pointer so it can be null.
dest[i] = *v
}
c.rowIdx += 1
Expand Down
8 changes: 4 additions & 4 deletions driver/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ import (
func TestDriver(t *testing.T) {
db, err := sql.Open("cdb", ":memory:")
if err != nil {
t.Fatalf("open err %s", err.Error())
t.Fatalf("open err %s", err)
}
_, err = db.Exec("CREATE TABLE foo (id INTEGER PRIMARY KEY, name TEXT)")
if err != nil {
t.Fatalf("exec err %s", err.Error())
t.Fatalf("exec err %s", err)
}
_, err = db.Exec("INSERT INTO foo (name) VALUES ('one')")
if err != nil {
t.Fatalf("exec err %s", err.Error())
t.Fatalf("exec err %s", err)
}
rows, err := db.Query("SELECT * FROM foo")
if err != nil {
t.Fatalf("query err %s", err.Error())
t.Fatalf("query err %s", err)
}
type foo struct {
id int
Expand Down
12 changes: 6 additions & 6 deletions kv/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ func TestEncoding(t *testing.T) {
v := []any{"table", "foo", "foo", 1, "{columns:[{name:\"first_name\",type:\"TEXT\"}]}"}
vb, err := Encode(v)
if err != nil {
t.Fatalf("expected no err got err: %s", err.Error())
t.Fatalf("expected no err got err: %s", err)
}
dv, err := Decode(vb)
if err != nil {
t.Fatalf("expected no err got err: %s", err.Error())
t.Fatalf("expected no err got err: %s", err)
}
if !reflect.DeepEqual(v, dv) {
t.Fatalf("expected %v to be %v", v, dv)
Expand All @@ -27,11 +27,11 @@ func TestEncoding(t *testing.T) {
v := 1
vb, err := EncodeKey(v)
if err != nil {
t.Fatal(err.Error())
t.Fatal(err)
}
dv, err := DecodeKey(vb)
if err != nil {
t.Fatal(err.Error())
t.Fatal(err)
}
if dv != v {
t.Fatalf("expected %d got %d", v, dv)
Expand All @@ -42,11 +42,11 @@ func TestEncoding(t *testing.T) {
for i := 0; i < math.MaxInt16; i += 1 {
k1, err := EncodeKey(i)
if err != nil {
t.Fatal(err.Error())
t.Fatal(err)
}
k2, err := EncodeKey(i + 1)
if err != nil {
t.Fatal(err.Error())
t.Fatal(err)
}
c := bytes.Compare(k1, k2)
if c != -1 {
Expand Down
Loading

0 comments on commit 49016c9

Please sign in to comment.