Skip to content

Commit

Permalink
Add basic support for indexing (eatonphil#15)
Browse files Browse the repository at this point in the history
* Indices being stored, add \dt and \d helpers

* Finish up basic support for indices

* Expand supported applicable indexing

* Indexes, not indices

* Switch to 3rd-party rbtree

* Add support for null, primary key

* Fix for indextest

* Add more tests

* Fix libraryexample
  • Loading branch information
eatonphil authored Apr 27, 2020
1 parent 9ba0433 commit 9608511
Show file tree
Hide file tree
Showing 15 changed files with 1,213 additions and 102 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
main
main
coverage.out
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ fmt:
gofmt -w -s .

test:
go test -race .
go test -race -cover -coverprofile=coverage.out .

cover:
go tool cover -func=coverage.out

vet:
go vet .
107 changes: 103 additions & 4 deletions ast.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package gosql

import "fmt"
import (
"fmt"
"strings"
)

type expressionKind uint

Expand Down Expand Up @@ -28,7 +31,15 @@ type expression struct {
func (e expression) generateCode() string {
switch e.kind {
case literalKind:
return fmt.Sprintf(e.literal.value)
switch e.literal.kind {
case identifierKind:
return fmt.Sprintf("\"%s\"", e.literal.value)
case stringKind:
return fmt.Sprintf("'%s'", e.literal.value)
default:
return fmt.Sprintf(e.literal.value)
}

case binaryKind:
return e.binary.generateCode()
}
Expand All @@ -54,43 +65,131 @@ type SelectStatement struct {
where *expression
}

func (ss SelectStatement) GenerateCode() string {
item := []string{}
for _, i := range *ss.item {
s := "\t*"
if !i.asterisk {
s = "\t" + i.exp.generateCode()

if i.as != nil {
s = fmt.Sprintf("\t%s AS \"%s\"", s, i.as.value)
}
}
item = append(item, s)
}

from := ""
if ss.from != nil {
from = fmt.Sprintf("\nFROM\n\t\"%s\"", ss.from.table.value)
}

where := ""
if ss.where != nil {
where = fmt.Sprintf("\nWHERE\n\t%s", ss.where.generateCode())
}

return fmt.Sprintf("SELECT\n%s%s%s;", strings.Join(item, ",\n"), from, where)
}

type columnDefinition struct {
name token
datatype token
name token
datatype token
primaryKey bool
}

type CreateTableStatement struct {
name token
cols *[]*columnDefinition
}

func (cts CreateTableStatement) GenerateCode() string {
cols := []string{}
for _, col := range *cts.cols {
modifiers := ""
if col.primaryKey {
modifiers += " " + "PRIMARY KEY"
}
spec := fmt.Sprintf("\t\"%s\" %s%s", col.name.value, strings.ToUpper(col.datatype.value), modifiers)
cols = append(cols, spec)
}
return fmt.Sprintf("CREATE TABLE \"%s\" (\n%s\n);", cts.name.value, strings.Join(cols, ",\n"))
}

type CreateIndexStatement struct {
name token
unique bool
primaryKey bool
table token
exp expression
}

func (cis CreateIndexStatement) GenerateCode() string {
unique := ""
if cis.unique {
unique = " UNIQUE"
}
return fmt.Sprintf("CREATE%s INDEX \"%s\" ON \"%s\" (%s);", unique, cis.name.value, cis.table.value, cis.exp.generateCode())
}

type DropTableStatement struct {
name token
}

func (dts DropTableStatement) GenerateCode() string {
return fmt.Sprintf("DROP TABLE \"%s\";", dts.name.value)
}

type InsertStatement struct {
table token
cols *[]*identifier
values *[]*expression
}

func (is InsertStatement) GenerateCode() string {
values := []string{}
for _, exp := range *is.values {
values = append(values, exp.generateCode())
}
return fmt.Sprintf("INSERT INTO \"%s\" VALUES (%s);", is.table.value, strings.Join(values, ", "))
}

type AstKind uint

const (
SelectKind AstKind = iota
CreateTableKind
CreateIndexKind
DropTableKind
InsertKind
)

type Statement struct {
SelectStatement *SelectStatement
CreateTableStatement *CreateTableStatement
CreateIndexStatement *CreateIndexStatement
DropTableStatement *DropTableStatement
InsertStatement *InsertStatement
Kind AstKind
}

func (s Statement) GenerateCode() string {
switch s.Kind {
case SelectKind:
return s.SelectStatement.GenerateCode()
case CreateTableKind:
return s.CreateTableStatement.GenerateCode()
case CreateIndexKind:
return s.CreateIndexStatement.GenerateCode()
case DropTableKind:
return s.DropTableStatement.GenerateCode()
case InsertKind:
return s.InsertStatement.GenerateCode()
}

return "?unknown?"
}

type Ast struct {
Statements []*Statement
}
104 changes: 104 additions & 0 deletions ast_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package gosql

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestStatement_GenerateCode(t *testing.T) {
tests := []struct {
result string
stmt Statement
}{
{
`DROP TABLE "foo";`,
Statement{
DropTableStatement: &DropTableStatement{
name: token{value: "foo"},
},
Kind: DropTableKind,
},
},
{
`CREATE TABLE "users" (
"id" INT PRIMARY KEY,
"name" TEXT
);`,
Statement{
CreateTableStatement: &CreateTableStatement{
name: token{value: "users"},
cols: &[]*columnDefinition{
{
name: token{value: "id"},
datatype: token{value: "int"},
primaryKey: true,
},
{
name: token{value: "name"},
datatype: token{value: "text"},
},
},
},
Kind: CreateTableKind,
},
},
{
`CREATE UNIQUE INDEX "age_idx" ON "users" ("age");`,
Statement{
CreateIndexStatement: &CreateIndexStatement{
name: token{value: "age_idx"},
unique: true,
table: token{value: "users"},
exp: expression{literal: &token{value: "age", kind: identifierKind}, kind: literalKind},
},
Kind: CreateIndexKind,
},
},
{
`INSERT INTO "foo" VALUES (1, 'flubberty', true);`,
Statement{
InsertStatement: &InsertStatement{
table: token{value: "foo"},
values: &[]*expression{
{literal: &token{value: "1", kind: numericKind}, kind: literalKind},
{literal: &token{value: "flubberty", kind: stringKind}, kind: literalKind},
{literal: &token{value: "true", kind: boolKind}, kind: literalKind},
},
},
Kind: InsertKind,
},
},
{
`SELECT
"id",
"name"
FROM
"users"
WHERE
("id" = 2);`,
Statement{
SelectStatement: &SelectStatement{
item: &[]*selectItem{
{exp: &expression{literal: &token{value: "id", kind: identifierKind}, kind: literalKind}},
{exp: &expression{literal: &token{value: "name", kind: identifierKind}, kind: literalKind}},
},
from: &fromItem{&token{value: "users"}},
where: &expression{
binary: &binaryExpression{
a: expression{literal: &token{value: "id", kind: identifierKind}, kind: literalKind},
b: expression{literal: &token{value: "2", kind: numericKind}, kind: literalKind},
op: token{value: "=", kind: symbolKind},
},
kind: binaryKind,
},
},
Kind: SelectKind,
},
},
}

for _, test := range tests {
assert.Equal(t, test.result, test.stmt.GenerateCode())
}
}
26 changes: 21 additions & 5 deletions backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ func (c ColumnType) String() string {
}

type Cell interface {
AsText() string
AsInt() int32
AsBool() bool
AsText() *string
AsInt() *int32
AsBool() *bool
}

type Results struct {
Expand All @@ -33,12 +33,28 @@ type Results struct {
}

type ResultColumn struct {
Type ColumnType
Name string
Type ColumnType
Name string
NotNull bool
}

type Index struct {
Name string
Exp string
Type string
Unique bool
PrimaryKey bool
}

type TableMetadata struct {
Name string
Columns []ResultColumn
Indexes []Index
}

type Backend interface {
CreateTable(*CreateTableStatement) error
Insert(*InsertStatement) error
Select(*SelectStatement) (*Results, error)
GetTables() []TableMetadata
}
Loading

0 comments on commit 9608511

Please sign in to comment.