Cleanup parser statements, add delete and insert statement, add TASKS.md for tracking tasks
This commit is contained in:
5
TASKS.md
Normal file
5
TASKS.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[] Make lexer detect Text by quotes are same as ident?
|
||||||
|
[] Make lexer understand numbers
|
||||||
|
[] Add boolean and NULL types
|
||||||
|
[] Handle extra fields like WHERE, ORDER etc, in parser, for Delete and select
|
||||||
|
[] Think about a better way than to return an error with Affected Rows when inserting or deleting
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
engine "git.pablu.de/pablu/sqv-engine"
|
engine "git.pablu.de/pablu/sqv-engine"
|
||||||
@@ -24,7 +25,13 @@ func populateTable(tableView *tview.Table, table engine.Table) {
|
|||||||
tableView.ScrollToBeginning()
|
tableView.ScrollToBeginning()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
dbFile = flag.String("path", "db.sqlite", "Use to set db path")
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
app := tview.NewApplication()
|
app := tview.NewApplication()
|
||||||
|
|
||||||
menuView := tview.NewList()
|
menuView := tview.NewList()
|
||||||
@@ -49,7 +56,7 @@ func main() {
|
|||||||
AddItem(verticalFlex, 0, 4, true).
|
AddItem(verticalFlex, 0, 4, true).
|
||||||
AddItem(sqlEditor, 0, 1, false)
|
AddItem(sqlEditor, 0, 1, false)
|
||||||
|
|
||||||
m, err := engine.NewManager("db.sqlite")
|
m, err := engine.NewManager(*dbFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Ran into an error on opening Manager, err: %v\n", err)
|
log.Fatalf("Ran into an error on opening Manager, err: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|||||||
53
manager.go
53
manager.go
@@ -126,21 +126,60 @@ func (m *Manager) RunSql(sqlText string) (Table, error) {
|
|||||||
return Table{}, err
|
return Table{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
selectStmt, ok := stmt.(*engine.SelectStatement)
|
switch v := stmt.(type) {
|
||||||
if !ok {
|
case *engine.SelectStatement:
|
||||||
return Table{}, fmt.Errorf("Input statement is not of correct Syntax, select statement")
|
return m.tableFromSelectStatement(sqlText, v)
|
||||||
|
|
||||||
|
case *engine.InsertStatement:
|
||||||
|
if !slices.ContainsFunc(m.tables, func(t Table) bool {
|
||||||
|
return v.Table == t.Name
|
||||||
|
}) {
|
||||||
|
return Table{}, fmt.Errorf("Table not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
table, ok := m.GetTable(selectStmt.From)
|
res, err := m.conn.Exec(sqlText)
|
||||||
|
if err != nil {
|
||||||
|
return Table{}, err
|
||||||
|
}
|
||||||
|
affected, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return Table{}, err
|
||||||
|
}
|
||||||
|
return Table{}, fmt.Errorf("Rows affected: %v", affected)
|
||||||
|
|
||||||
|
case *engine.DeleteStatement:
|
||||||
|
if !slices.ContainsFunc(m.tables, func(t Table) bool {
|
||||||
|
return v.Table == t.Name
|
||||||
|
}) {
|
||||||
|
return Table{}, fmt.Errorf("Table not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := m.conn.Exec(sqlText)
|
||||||
|
if err != nil {
|
||||||
|
return Table{}, err
|
||||||
|
}
|
||||||
|
affected, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return Table{}, err
|
||||||
|
}
|
||||||
|
return Table{}, fmt.Errorf("Rows affected: %v", affected)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Table{}, fmt.Errorf("Input statement is not of correct Syntax, select statement")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) tableFromSelectStatement(sqlText string, stmt *engine.SelectStatement) (Table, error) {
|
||||||
|
table, ok := m.GetTable(stmt.From)
|
||||||
if !ok {
|
if !ok {
|
||||||
return Table{}, fmt.Errorf("Selected Table does not exist, have you perhaps misstyped the table Name?")
|
return Table{}, fmt.Errorf("Selected Table does not exist, have you perhaps misstyped the table Name?")
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := make([]Column, 0)
|
fields := make([]Column, 0)
|
||||||
if slices.Contains(selectStmt.Fields, "*") {
|
if slices.Contains(stmt.Fields, "*") {
|
||||||
fields = table.Columns
|
fields = table.Columns
|
||||||
} else {
|
} else {
|
||||||
for _, columnName := range selectStmt.Fields {
|
for _, columnName := range stmt.Fields {
|
||||||
index := slices.IndexFunc(table.Columns, func(c Column) bool {
|
index := slices.IndexFunc(table.Columns, func(c Column) bool {
|
||||||
if c.Name == columnName {
|
if c.Name == columnName {
|
||||||
return true
|
return true
|
||||||
@@ -153,7 +192,7 @@ func (m *Manager) RunSql(sqlText string) (Table, error) {
|
|||||||
}
|
}
|
||||||
table.Columns = fields
|
table.Columns = fields
|
||||||
|
|
||||||
err = m.loadTableRaw(&table, fields, sqlText)
|
err := m.loadTableRaw(&table, fields, sqlText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Table{}, err
|
return Table{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
12
sql/ast.go
12
sql/ast.go
@@ -33,5 +33,17 @@ type SelectStatement struct {
|
|||||||
Fields []string
|
Fields []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InsertStatement struct {
|
||||||
|
Table string
|
||||||
|
Values map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteStatement struct {
|
||||||
|
Table string
|
||||||
|
Extra []string
|
||||||
|
}
|
||||||
|
|
||||||
func (_ *CreateTableStatement) isEnumValue() {}
|
func (_ *CreateTableStatement) isEnumValue() {}
|
||||||
func (_ *SelectStatement) isEnumValue() {}
|
func (_ *SelectStatement) isEnumValue() {}
|
||||||
|
func (_ *InsertStatement) isEnumValue() {}
|
||||||
|
func (_ *DeleteStatement) isEnumValue() {}
|
||||||
|
|||||||
12
sql/lexer.go
12
sql/lexer.go
@@ -29,6 +29,11 @@ const (
|
|||||||
CREATE
|
CREATE
|
||||||
TABLE
|
TABLE
|
||||||
|
|
||||||
|
INSERT
|
||||||
|
INTO
|
||||||
|
VALUES
|
||||||
|
RETURNING
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
FROM
|
FROM
|
||||||
WHERE
|
WHERE
|
||||||
@@ -37,6 +42,8 @@ const (
|
|||||||
ORDER
|
ORDER
|
||||||
TOP
|
TOP
|
||||||
|
|
||||||
|
DELETE
|
||||||
|
|
||||||
PRIMARY
|
PRIMARY
|
||||||
FOREIGN
|
FOREIGN
|
||||||
REFERENCES
|
REFERENCES
|
||||||
@@ -80,6 +87,11 @@ var keywords map[string]Token = map[string]Token{
|
|||||||
"AUTOINCREMENT": AUTOINCREMENT,
|
"AUTOINCREMENT": AUTOINCREMENT,
|
||||||
"CONSTRAINT": CONSTRAINT,
|
"CONSTRAINT": CONSTRAINT,
|
||||||
"NUMERIC": NUMERIC,
|
"NUMERIC": NUMERIC,
|
||||||
|
"INSERT": INSERT,
|
||||||
|
"INTO": INTO,
|
||||||
|
"VALUES": VALUES,
|
||||||
|
"RETURNING": RETURNING,
|
||||||
|
"DELETE": DELETE,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Position struct {
|
type Position struct {
|
||||||
|
|||||||
219
sql/parseCreateStatement.go
Normal file
219
sql/parseCreateStatement.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Parser) parseCreateTable() (*CreateTableStatement, error) {
|
||||||
|
if !p.expectNext(TABLE) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
tok, ok := p.expectOne(QUOTE, SINGLE_QUOTE, BACKQUOTE, IDENT, IF)
|
||||||
|
if !ok {
|
||||||
|
return nil, p.unexpectedToken(IDENT, IF)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tok {
|
||||||
|
case IF:
|
||||||
|
if !p.expectSequence(NOT, EXISTS) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
fallthrough
|
||||||
|
case QUOTE, SINGLE_QUOTE, BACKQUOTE:
|
||||||
|
if !p.expectNext(IDENT) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, _, lit := p.rescan()
|
||||||
|
|
||||||
|
stmt := CreateTableStatement{
|
||||||
|
TableName: lit,
|
||||||
|
Columns: make([]Column, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
if !p.expectNext(LPAREN) {
|
||||||
|
return nil, p.unexpectedToken(LPAREN)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
_, tok, _ := p.scan()
|
||||||
|
|
||||||
|
switch tok {
|
||||||
|
case RPAREN:
|
||||||
|
if !p.expectNext(SEMI) {
|
||||||
|
return nil, p.unexpectedToken(SEMI)
|
||||||
|
}
|
||||||
|
return &stmt, nil
|
||||||
|
|
||||||
|
case IDENT:
|
||||||
|
column, err := p.parseColumn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stmt.Columns = append(stmt.Columns, column)
|
||||||
|
|
||||||
|
// TODO: HANDLE AND SAVE CONSTRAINTS
|
||||||
|
case CONSTRAINT:
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
if !p.expectNext(IDENT) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
// _, _, constraintName := p.rescan()
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
|
||||||
|
case FOREIGN:
|
||||||
|
if !p.expectSequence(KEY, LPAREN) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
|
||||||
|
if !p.expectNext(IDENT) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
_, _, columnName := p.rescan()
|
||||||
|
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
|
||||||
|
if !p.expectSequence(RPAREN, REFERENCES) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := p.references()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
column := slices.IndexFunc(stmt.Columns, func(c Column) bool {
|
||||||
|
return c.Name == columnName
|
||||||
|
})
|
||||||
|
|
||||||
|
stmt.Columns[column].Extra = append(stmt.Columns[column].Extra, ref)
|
||||||
|
|
||||||
|
case PRIMARY:
|
||||||
|
if !p.expectSequence(KEY, LPAREN) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
|
||||||
|
if !p.expectNext(IDENT) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
primaryKeyNames := make([]string, 0)
|
||||||
|
_, _, columnName := p.rescan()
|
||||||
|
primaryKeyNames = append(primaryKeyNames, columnName)
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
|
||||||
|
for {
|
||||||
|
tok, ok := p.expectOne(RPAREN, COMMA)
|
||||||
|
if !ok {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
if tok == RPAREN {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
if !p.expectNext(IDENT) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, columnName := p.rescan()
|
||||||
|
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
primaryKeyNames = append(primaryKeyNames, columnName)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkName := range primaryKeyNames {
|
||||||
|
column := slices.IndexFunc(stmt.Columns, func(c Column) bool {
|
||||||
|
return c.Name == pkName
|
||||||
|
})
|
||||||
|
stmt.Columns[column].Extra = append(stmt.Columns[column].Extra, "PRIMARY_KEY")
|
||||||
|
}
|
||||||
|
|
||||||
|
case COMMA:
|
||||||
|
continue
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, p.unexpectedToken(IDENT, RPAREN, FOREIGN, COMMA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseColumn() (Column, error) {
|
||||||
|
_, _, lit := p.rescan()
|
||||||
|
column := Column{Name: lit, Extra: make([]string, 0)}
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
|
||||||
|
if _, ok := p.expectOne(TEXT, INTEGER, REAL, BLOB, NUMERIC); !ok {
|
||||||
|
return Column{}, p.unexpectedToken(TEXT, INTEGER, REAL, BLOB, NUMERIC)
|
||||||
|
}
|
||||||
|
_, _, column.Type = p.rescan()
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, tok, lit := p.scan()
|
||||||
|
switch tok {
|
||||||
|
case COMMA:
|
||||||
|
return column, nil
|
||||||
|
case RPAREN:
|
||||||
|
p.unscan()
|
||||||
|
return column, nil
|
||||||
|
|
||||||
|
case PRIMARY:
|
||||||
|
fallthrough
|
||||||
|
case NOT:
|
||||||
|
if _, ok := p.expectOne(NULL, KEY); !ok {
|
||||||
|
return Column{}, p.unexpectedToken(NULL, KEY)
|
||||||
|
}
|
||||||
|
_, _, rlit := p.rescan()
|
||||||
|
column.Extra = append(column.Extra, fmt.Sprintf("%v_%v", lit, rlit))
|
||||||
|
|
||||||
|
case REFERENCES:
|
||||||
|
ref, err := p.references()
|
||||||
|
if err != nil {
|
||||||
|
return Column{}, err
|
||||||
|
}
|
||||||
|
column.Extra = append(column.Extra, ref)
|
||||||
|
|
||||||
|
case AUTOINCREMENT:
|
||||||
|
column.Extra = append(column.Extra, "AUTOINCREMENT")
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Column{}, p.unexpectedToken(COMMA, RPAREN, PRIMARY, NOT, REFERENCES)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) references() (string, error) {
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
if !p.expectNext(IDENT) {
|
||||||
|
return "", p.unexpectedToken(IDENT)
|
||||||
|
}
|
||||||
|
_, _, referenceTableName := p.rescan()
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
|
||||||
|
if !p.expectNext(LPAREN) {
|
||||||
|
return "", p.unexpectedToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
|
||||||
|
if !p.expectNext(IDENT) {
|
||||||
|
return "", p.unexpectedToken()
|
||||||
|
}
|
||||||
|
_, _, referenceColumnName := p.rescan()
|
||||||
|
|
||||||
|
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
||||||
|
|
||||||
|
if !p.expectNext(RPAREN) {
|
||||||
|
return "", p.unexpectedToken(RPAREN)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("ref %v.%v", referenceTableName, referenceColumnName), nil
|
||||||
|
}
|
||||||
15
sql/parseDeleteStatement.go
Normal file
15
sql/parseDeleteStatement.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
func (p *Parser) parseDelete() (*DeleteStatement, error) {
|
||||||
|
if !p.expectSequence(FROM, IDENT) {
|
||||||
|
return nil, p.unexpectedToken(INTO)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := DeleteStatement{}
|
||||||
|
|
||||||
|
_, _, res.Table = p.rescan()
|
||||||
|
|
||||||
|
p.consumeUntilOne(50, EOF, SEMI)
|
||||||
|
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
69
sql/parseInsertStatement.go
Normal file
69
sql/parseInsertStatement.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func (p *Parser) parseInsert() (*InsertStatement, error) {
|
||||||
|
if !p.expectSequence(INTO, IDENT) {
|
||||||
|
return nil, p.unexpectedToken(INTO)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := InsertStatement{}
|
||||||
|
|
||||||
|
_, _, res.Table = p.rescan()
|
||||||
|
|
||||||
|
if !p.expectNext(LPAREN) {
|
||||||
|
return nil, p.unexpectedToken(LPAREN)
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldNames := make([]string, 0)
|
||||||
|
|
||||||
|
for loop := true; loop; {
|
||||||
|
_, tok, val := p.scan()
|
||||||
|
switch tok {
|
||||||
|
case IDENT:
|
||||||
|
fieldNames = append(fieldNames, val)
|
||||||
|
case RPAREN:
|
||||||
|
loop = false
|
||||||
|
case COMMA:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return nil, p.unexpectedToken(IDENT, RPAREN, COMMA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.expectSequence(VALUES, LPAREN) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
values := make([]any, 0, len(fieldNames))
|
||||||
|
for loop := true; loop; {
|
||||||
|
_, tok, val := p.scan()
|
||||||
|
switch tok {
|
||||||
|
case IDENT:
|
||||||
|
// TODO, convert to actual datatype?
|
||||||
|
values = append(values, val)
|
||||||
|
case COMMA, QUOTE, SINGLE_QUOTE, BACKQUOTE:
|
||||||
|
continue
|
||||||
|
case RPAREN:
|
||||||
|
loop = false
|
||||||
|
default:
|
||||||
|
return nil, p.unexpectedToken(IDENT, RPAREN, COMMA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(values) != len(fieldNames) {
|
||||||
|
return nil, fmt.Errorf("Expected same amount of Values as Fields, but got %v fields, and %v values", fieldNames, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle things like RETURNING *, also handle multiple Values
|
||||||
|
if !p.consumeUntilOne(50, SEMI, EOF) {
|
||||||
|
return nil, fmt.Errorf("Expected semicolon but never found after 50 tries")
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Values = make(map[string]any)
|
||||||
|
for i, name := range fieldNames {
|
||||||
|
res.Values[name] = values[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
47
sql/parseSelectStatement.go
Normal file
47
sql/parseSelectStatement.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func (p *Parser) parseSelect() (*SelectStatement, error) {
|
||||||
|
tok, ok := p.expectOne(ASTERIKS, IDENT)
|
||||||
|
if !ok {
|
||||||
|
return nil, p.unexpectedToken(ASTERIKS, IDENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := make([]string, 1)
|
||||||
|
fields[0] = "*"
|
||||||
|
if tok == IDENT {
|
||||||
|
_, _, n := p.rescan()
|
||||||
|
fields[0] = n
|
||||||
|
for {
|
||||||
|
tok, ok := p.expectOne(COMMA, FROM)
|
||||||
|
if !ok {
|
||||||
|
return nil, p.unexpectedToken(COMMA, FROM)
|
||||||
|
}
|
||||||
|
if tok == FROM {
|
||||||
|
p.unscan()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.expectNext(IDENT) {
|
||||||
|
return nil, p.unexpectedToken(IDENT)
|
||||||
|
}
|
||||||
|
_, _, n := p.rescan()
|
||||||
|
fields = append(fields, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.expectSequence(FROM, IDENT) {
|
||||||
|
return nil, p.unexpectedToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, tableName := p.rescan()
|
||||||
|
if !p.consumeUntilOne(50, SEMI, EOF) {
|
||||||
|
return nil, fmt.Errorf("Expected semicolon but never found after 50 tries")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SelectStatement{
|
||||||
|
From: tableName,
|
||||||
|
Fields: fields,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
265
sql/parser.go
265
sql/parser.go
@@ -23,9 +23,9 @@ func NewParser(r io.Reader) *Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) Parse() (Statement, error) {
|
func (p *Parser) Parse() (Statement, error) {
|
||||||
tok, ok := p.expectOne(CREATE, EOF, SELECT)
|
tok, ok := p.expectOne(CREATE, EOF, SELECT, INSERT, DELETE)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, p.unexpectedToken(CREATE, EOF)
|
return nil, p.unexpectedToken(CREATE, EOF, SELECT, INSERT, DELETE)
|
||||||
} else if tok == EOF {
|
} else if tok == EOF {
|
||||||
return nil, io.EOF
|
return nil, io.EOF
|
||||||
}
|
}
|
||||||
@@ -37,240 +37,15 @@ func (p *Parser) Parse() (Statement, error) {
|
|||||||
return p.parseCreateTable()
|
return p.parseCreateTable()
|
||||||
case SELECT:
|
case SELECT:
|
||||||
return p.parseSelect()
|
return p.parseSelect()
|
||||||
|
case INSERT:
|
||||||
|
return p.parseInsert()
|
||||||
|
case DELETE:
|
||||||
|
return p.parseDelete()
|
||||||
default:
|
default:
|
||||||
panic("SHOULD NEVER BE REACHED")
|
panic("SHOULD NEVER BE REACHED")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) parseSelect() (*SelectStatement, error) {
|
|
||||||
tok, ok := p.expectOne(ASTERIKS, IDENT)
|
|
||||||
if !ok {
|
|
||||||
return nil, p.unexpectedToken(ASTERIKS, IDENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := make([]string, 1)
|
|
||||||
fields[0] = "*"
|
|
||||||
if tok == IDENT {
|
|
||||||
_, _, n := p.rescan()
|
|
||||||
fields[0] = n
|
|
||||||
for {
|
|
||||||
tok, ok := p.expectOne(COMMA, FROM)
|
|
||||||
if !ok {
|
|
||||||
return nil, p.unexpectedToken(COMMA, FROM)
|
|
||||||
}
|
|
||||||
if tok == FROM {
|
|
||||||
p.unscan()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !p.expectNext(IDENT) {
|
|
||||||
return nil, p.unexpectedToken(IDENT)
|
|
||||||
}
|
|
||||||
_, _, n := p.rescan()
|
|
||||||
fields = append(fields, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !p.expectSequence(FROM, IDENT) {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, tableName := p.rescan()
|
|
||||||
if !p.consumeUntilOne(50, SEMI, EOF) {
|
|
||||||
return nil, fmt.Errorf("Expected semicolon but never found after 50 tries")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &SelectStatement{
|
|
||||||
From: tableName,
|
|
||||||
Fields: fields,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) parseCreateTable() (*CreateTableStatement, error) {
|
|
||||||
if !p.expectNext(TABLE) {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
tok, ok := p.expectOne(QUOTE, SINGLE_QUOTE, BACKQUOTE, IDENT, IF)
|
|
||||||
if !ok {
|
|
||||||
return nil, p.unexpectedToken(IDENT, IF)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tok {
|
|
||||||
case IF:
|
|
||||||
if !p.expectSequence(NOT, EXISTS) {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
fallthrough
|
|
||||||
case QUOTE, SINGLE_QUOTE, BACKQUOTE:
|
|
||||||
if !p.expectNext(IDENT) {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, _, lit := p.rescan()
|
|
||||||
|
|
||||||
stmt := CreateTableStatement{
|
|
||||||
TableName: lit,
|
|
||||||
Columns: make([]Column, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
if !p.expectNext(LPAREN) {
|
|
||||||
return nil, p.unexpectedToken(LPAREN)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
_, tok, _ := p.scan()
|
|
||||||
|
|
||||||
switch tok {
|
|
||||||
case RPAREN:
|
|
||||||
if !p.expectNext(SEMI) {
|
|
||||||
return nil, p.unexpectedToken(SEMI)
|
|
||||||
}
|
|
||||||
return &stmt, nil
|
|
||||||
|
|
||||||
case IDENT:
|
|
||||||
column, err := p.parseColumn()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stmt.Columns = append(stmt.Columns, column)
|
|
||||||
|
|
||||||
// TODO: HANDLE AND SAVE CONSTRAINTS
|
|
||||||
case CONSTRAINT:
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
if !p.expectNext(IDENT) {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
// _, _, constraintName := p.rescan()
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
|
|
||||||
case FOREIGN:
|
|
||||||
if !p.expectSequence(KEY, LPAREN) {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
|
|
||||||
if !p.expectNext(IDENT) {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
_, _, columnName := p.rescan()
|
|
||||||
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
|
|
||||||
if !p.expectSequence(RPAREN, REFERENCES) {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
ref, err := p.references()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
column := slices.IndexFunc(stmt.Columns, func(c Column) bool {
|
|
||||||
return c.Name == columnName
|
|
||||||
})
|
|
||||||
|
|
||||||
stmt.Columns[column].Extra = append(stmt.Columns[column].Extra, ref)
|
|
||||||
|
|
||||||
case PRIMARY:
|
|
||||||
if !p.expectSequence(KEY, LPAREN) {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
|
|
||||||
if !p.expectNext(IDENT) {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
primaryKeyNames := make([]string, 0)
|
|
||||||
_, _, columnName := p.rescan()
|
|
||||||
primaryKeyNames = append(primaryKeyNames, columnName)
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
|
|
||||||
for {
|
|
||||||
tok, ok := p.expectOne(RPAREN, COMMA)
|
|
||||||
if !ok {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
if tok == RPAREN {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
if !p.expectNext(IDENT) {
|
|
||||||
return nil, p.unexpectedToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, columnName := p.rescan()
|
|
||||||
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
primaryKeyNames = append(primaryKeyNames, columnName)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pkName := range primaryKeyNames {
|
|
||||||
column := slices.IndexFunc(stmt.Columns, func(c Column) bool {
|
|
||||||
return c.Name == pkName
|
|
||||||
})
|
|
||||||
stmt.Columns[column].Extra = append(stmt.Columns[column].Extra, "PRIMARY_KEY")
|
|
||||||
}
|
|
||||||
|
|
||||||
case COMMA:
|
|
||||||
continue
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, p.unexpectedToken(IDENT, RPAREN, FOREIGN, COMMA)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) parseColumn() (Column, error) {
|
|
||||||
_, _, lit := p.rescan()
|
|
||||||
column := Column{Name: lit, Extra: make([]string, 0)}
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
|
|
||||||
if _, ok := p.expectOne(TEXT, INTEGER, REAL, BLOB, NUMERIC); !ok {
|
|
||||||
return Column{}, p.unexpectedToken(TEXT, INTEGER, REAL, BLOB, NUMERIC)
|
|
||||||
}
|
|
||||||
_, _, column.Type = p.rescan()
|
|
||||||
|
|
||||||
for {
|
|
||||||
_, tok, lit := p.scan()
|
|
||||||
switch tok {
|
|
||||||
case COMMA:
|
|
||||||
return column, nil
|
|
||||||
case RPAREN:
|
|
||||||
p.unscan()
|
|
||||||
return column, nil
|
|
||||||
|
|
||||||
case PRIMARY:
|
|
||||||
fallthrough
|
|
||||||
case NOT:
|
|
||||||
if _, ok := p.expectOne(NULL, KEY); !ok {
|
|
||||||
return Column{}, p.unexpectedToken(NULL, KEY)
|
|
||||||
}
|
|
||||||
_, _, rlit := p.rescan()
|
|
||||||
column.Extra = append(column.Extra, fmt.Sprintf("%v_%v", lit, rlit))
|
|
||||||
|
|
||||||
case REFERENCES:
|
|
||||||
ref, err := p.references()
|
|
||||||
if err != nil {
|
|
||||||
return Column{}, err
|
|
||||||
}
|
|
||||||
column.Extra = append(column.Extra, ref)
|
|
||||||
|
|
||||||
case AUTOINCREMENT:
|
|
||||||
column.Extra = append(column.Extra, "AUTOINCREMENT")
|
|
||||||
|
|
||||||
default:
|
|
||||||
return Column{}, p.unexpectedToken(COMMA, RPAREN, PRIMARY, NOT, REFERENCES)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) unexpectedToken(expected ...Token) error {
|
func (p *Parser) unexpectedToken(expected ...Token) error {
|
||||||
l := len(expected)
|
l := len(expected)
|
||||||
pos, tok, lit := p.rescan()
|
pos, tok, lit := p.rescan()
|
||||||
@@ -295,34 +70,6 @@ func (p *Parser) unexpectedToken(expected ...Token) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) references() (string, error) {
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
if !p.expectNext(IDENT) {
|
|
||||||
return "", p.unexpectedToken(IDENT)
|
|
||||||
}
|
|
||||||
_, _, referenceTableName := p.rescan()
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
|
|
||||||
if !p.expectNext(LPAREN) {
|
|
||||||
return "", p.unexpectedToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
|
|
||||||
if !p.expectNext(IDENT) {
|
|
||||||
return "", p.unexpectedToken()
|
|
||||||
}
|
|
||||||
_, _, referenceColumnName := p.rescan()
|
|
||||||
|
|
||||||
p.consumeIfOne(QUOTE, SINGLE_QUOTE, BACKQUOTE)
|
|
||||||
|
|
||||||
if !p.expectNext(RPAREN) {
|
|
||||||
return "", p.unexpectedToken(RPAREN)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("ref %v.%v", referenceTableName, referenceColumnName), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) expectSequence(token ...Token) bool {
|
func (p *Parser) expectSequence(token ...Token) bool {
|
||||||
for _, tok := range token {
|
for _, tok := range token {
|
||||||
if !p.expectNext(tok) {
|
if !p.expectNext(tok) {
|
||||||
|
|||||||
Reference in New Issue
Block a user