Files
sqv-engine/sql/parseCreateStatement.go

220 lines
4.8 KiB
Go

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
}