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 }