From c4218cee519c6b601e06e397fad672347011ec9c Mon Sep 17 00:00:00 2001 From: Pablu Date: Tue, 2 Dec 2025 09:50:20 +0100 Subject: [PATCH] Add more syntax to lexer and parser, and make improvements to tview --- cmd/sqv-tview/main.go | 23 ++++++++-- go.mod | 3 ++ go.sum | 15 +++++++ manager.go | 101 ++++++++++++++++++++++++++++++++---------- sql/lexer.go | 57 +++++++++++++++--------- sql/parser.go | 89 +++++++++++++++++++++++++++++++++---- table.go | 1 + 7 files changed, 233 insertions(+), 56 deletions(-) diff --git a/cmd/sqv-tview/main.go b/cmd/sqv-tview/main.go index 79a5154..c62cacf 100644 --- a/cmd/sqv-tview/main.go +++ b/cmd/sqv-tview/main.go @@ -6,6 +6,7 @@ import ( engine "git.pablu.de/pablu/sqv-engine" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" + // "github.com/uaraven/logview" ) func main() { @@ -24,6 +25,8 @@ func main() { tableView.Clear() }) + // logView := logview.NewLogView().SetTitle("Logs") + verticalFlex := tview.NewFlex(). AddItem(menuView, 0, 1, true). AddItem(tableView, 0, 3, false) @@ -32,12 +35,16 @@ func main() { SetDirection(tview.FlexRow). AddItem(verticalFlex, 0, 4, true). AddItem(sqlEditor, 0, 1, false) + // AddItem(logView, 0, 0, false) m, err := engine.NewManager("db.sqlite") if err != nil { log.Fatalf("Ran into an error on opening Manager, err: %v\n", err) } - m.Start() + err = m.Start() + if err != nil { + log.Fatalf("Ran into an error on starting Manager, err: %v\n", err) + } tables := m.GetTables() @@ -60,14 +67,15 @@ func main() { err = m.LoadTable(&t) if err != nil { - panic(err) + log.Fatalf("Error while loading Table, err: %v\n", err) } for ri, r := range t.Rows { for rc, c := range r.Values { - tableView.SetCell(ri+1, rc, tview.NewTableCell(c).SetTextColor(tcell.ColorWhite).SetAlign(tview.AlignCenter)) + tableView.SetCell(ri+1, rc, tview.NewTableCell(c).SetTextColor(tcell.ColorWhite).SetAlign(tview.AlignLeft).SetMaxWidth(30)) } } + tableView.ScrollToBeginning() }) // Idk this shouldnt be needed imo but with only 0 it doesnt work, and with 1, well we are on Table 1 not zero, WHICH WE CANT ALWAYS SAY THERE IS @@ -131,6 +139,8 @@ func main() { menuHidden := false editorHidden := false + // logHidden := true + horizontalFlex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { switch event.Key() { case tcell.KeyCtrlH: @@ -151,6 +161,13 @@ func main() { app.SetFocus(sqlEditor) } editorHidden = !editorHidden + // case tcell.KeyCtrlD: + // if !logHidden { + // horizontalFlex.ResizeItem(logView, 0, 0) + // } else { + // horizontalFlex.ResizeItem(logView, 0, 1) + // } + // logHidden = !logHidden } return event diff --git a/go.mod b/go.mod index 05f7508..adaeeb0 100644 --- a/go.mod +++ b/go.mod @@ -10,12 +10,14 @@ require ( ) require ( + code.rocketnine.space/tslocum/cbind v0.1.5 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/x/ansi v0.10.1 // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/gdamore/encoding v1.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect @@ -26,6 +28,7 @@ require ( github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/uaraven/logview v0.1.1 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.25.0 // indirect diff --git a/go.sum b/go.sum index 6f4f81b..851365c 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +code.rocketnine.space/tslocum/cbind v0.1.5 h1:i6NkeLLNPNMS4NWNi3302Ay3zSU6MrqOT+yJskiodxE= +code.rocketnine.space/tslocum/cbind v0.1.5/go.mod h1:LtfqJTzM7qhg88nAvNhx+VnTjZ0SXBJtxBObbfBWo/M= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= @@ -22,19 +24,25 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payR github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= +github.com/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU= github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= @@ -47,10 +55,13 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/rivo/tview v0.42.0 h1:b/ftp+RxtDsHSaynXTbJb+/n/BxDEi+W3UfF5jILK6c= github.com/rivo/tview v0.42.0/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/uaraven/logview v0.1.1 h1:0y2CXMKUqmNnHdj3oRwSn+TrJAoESDBkgqn23aJ5YP4= +github.com/uaraven/logview v0.1.1/go.mod h1:87JVTmYbK2ku6znEfUERs/+C3ZYmW8RO9ylpdA7vC64= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -83,6 +94,7 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -98,6 +110,8 @@ golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= @@ -108,6 +122,7 @@ golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= diff --git a/manager.go b/manager.go index df40335..9de6486 100644 --- a/manager.go +++ b/manager.go @@ -51,9 +51,10 @@ func NewManager(path string) (*Manager, error) { sqls = append(sqls, sql) } - schema := strings.Join(sqls, ";") + schema := strings.Join(sqls, ";\n") schema += ";" - // fmt.Println(schema) + + fmt.Println(schema) return &Manager{ parser: engine.NewParser(strings.NewReader(schema)), @@ -219,8 +220,8 @@ func (m *Manager) GetTable(name string) (Table, bool) { return table, true } -func (m *Manager) LoadTable(table *Table) error { - rows, err := m.conn.Query(fmt.Sprintf("SELECT * FROM %v", table.Name)) +func (m *Manager) loadTableRaw(table *Table, s string, args ...any) error { + rows, err := m.conn.Query(s, args...) if err != nil { return err } @@ -229,17 +230,32 @@ func (m *Manager) LoadTable(table *Table) error { for rows.Next() { cols := make([]any, len(table.Columns)) for i, column := range table.Columns { - switch column.Type { - case BLOB: - cols[i] = new([]byte) - case TEXT: - cols[i] = new(string) - case INTEGER: - cols[i] = new(int) - case REAL: - cols[i] = new(float64) - default: - panic("THIS SHOULD NEVER HAPPEN, WE HIT AN UNKNOWN COLUMN.TYPE") + if column.Flags.Has(NOT_NULL) { + switch column.Type { + case BLOB: + cols[i] = new([]byte) + case TEXT: + cols[i] = new(string) + case INTEGER: + cols[i] = new(int) + case REAL: + cols[i] = new(float64) + default: + panic("THIS SHOULD NEVER HAPPEN, WE HIT AN UNKNOWN COLUMN.TYPE") + } + } else { + switch column.Type { + case BLOB: + cols[i] = new([]byte) + case TEXT: + cols[i] = new(sql.NullString) + case INTEGER: + cols[i] = new(sql.NullInt64) + case REAL: + cols[i] = new(sql.NullFloat64) + default: + panic("THIS SHOULD NEVER HAPPEN, WE HIT AN UNKNOWN COLUMN.TYPE") + } } } @@ -256,6 +272,16 @@ func (m *Manager) LoadTable(table *Table) error { return nil } +func (m *Manager) LoadTableMaxRows(table *Table, maxRows int) error { + sql := fmt.Sprintf("SELECT * FROM %v LIMIT ?", table.Name) + return m.loadTableRaw(table, sql, maxRows) +} + +func (m *Manager) LoadTable(table *Table) error { + sql := fmt.Sprintf("SELECT * FROM %v", table.Name) + return m.loadTableRaw(table, sql) +} + func anyToStr(a []any) []string { res := make([]string, len(a)) @@ -268,7 +294,30 @@ func anyToStr(a []any) []string { case *float64: res[i] = strconv.FormatFloat(*v, 'f', 2, 64) case *[]byte: - res[i] = hex.EncodeToString(*v) + buf := *v + if len(buf) > 512 { + buf = buf[0:512] + } + res[i] = hex.EncodeToString(buf) + case *sql.NullInt64: + if v.Valid { + res[i] = strconv.Itoa(int(v.Int64)) + } else { + res[i] = "NULL" + } + case *sql.NullFloat64: + if v.Valid { + res[i] = strconv.FormatFloat(v.Float64, 'f', 2, 64) + } else { + res[i] = "NULL" + } + case *sql.NullString: + if v.Valid { + res[i] = v.String + } else { + res[i] = "NULL" + } + default: panic("THIS SHOULD NEVER HAPPEN, WE GOT SERVED AN UNKNOWN TYPE") } @@ -301,14 +350,16 @@ func (m *Manager) convertCreateTableStatementToTable(cts *engine.CreateTableStat refTable, ok := m.GetTable(tableName) if !ok { fmt.Println(m.tables) - return Table{}, fmt.Errorf("Reference table '%v' not found", tableName) + fmt.Println(res) + fmt.Println("Reference was skipped") + // return Table{}, fmt.Errorf("Reference table '%v' not found", tableName) + } else { + colIndex := slices.IndexFunc(refTable.Columns, func(c Column) bool { + return c.Name == columnName + }) + + ref = &refTable.Columns[colIndex] } - - colIndex := slices.IndexFunc(refTable.Columns, func(c Column) bool { - return c.Name == columnName - }) - - ref = &refTable.Columns[colIndex] } var columnType ColumnType @@ -319,7 +370,7 @@ func (m *Manager) convertCreateTableStatementToTable(cts *engine.CreateTableStat columnType = BLOB case "TEXT": columnType = TEXT - case "INTEGER": + case "INTEGER", "NUMERIC": columnType = INTEGER default: panic("This shouldnt happen") @@ -348,6 +399,8 @@ func extrasToFlags(extras []string) ColumnFlag { res |= FOREIGN_KEY case "NOT_NULL": res |= NOT_NULL + case "AUTOINCREMENT": + res |= AUTO_INCREMENT default: log.Panicf("NOT IMPLEMENTED EXTRA: %v", extra) } diff --git a/sql/lexer.go b/sql/lexer.go index a5e8c8e..7c51200 100644 --- a/sql/lexer.go +++ b/sql/lexer.go @@ -21,6 +21,9 @@ const ( COMMA ASTERIKS ASSIGN + BACKQUOTE + QUOTE + SINGLE_QUOTE // Keywords CREATE @@ -41,36 +44,42 @@ const ( NOT IF EXISTS + AUTOINCREMENT + CONSTRAINT TEXT INTEGER NULL REAL BLOB + NUMERIC ) var keywords map[string]Token = map[string]Token{ - "CREATE": CREATE, - "TABLE": TABLE, - "PRIMARY": PRIMARY, - "FOREIGN": FOREIGN, - "REFERENCES": REFERENCES, - "KEY": KEY, - "NOT": NOT, - "TEXT": TEXT, - "INTEGER": INTEGER, - "NULL": NULL, - "IF": IF, - "EXISTS": EXISTS, - "SELECT": SELECT, - "FROM": FROM, - "WHERE": WHERE, - "AND": AND, - "OR": OR, - "ORDER": ORDER, - "TOP": TOP, - "REAL": REAL, - "BLOB": BLOB, + "CREATE": CREATE, + "TABLE": TABLE, + "PRIMARY": PRIMARY, + "FOREIGN": FOREIGN, + "REFERENCES": REFERENCES, + "KEY": KEY, + "NOT": NOT, + "TEXT": TEXT, + "INTEGER": INTEGER, + "NULL": NULL, + "IF": IF, + "EXISTS": EXISTS, + "SELECT": SELECT, + "FROM": FROM, + "WHERE": WHERE, + "AND": AND, + "OR": OR, + "ORDER": ORDER, + "TOP": TOP, + "REAL": REAL, + "BLOB": BLOB, + "AUTOINCREMENT": AUTOINCREMENT, + "CONSTRAINT": CONSTRAINT, + "NUMERIC": NUMERIC, } type Position struct { @@ -116,6 +125,12 @@ func (l *Lexer) Lex() (Position, Token, string) { return l.pos, ASTERIKS, "*" case '=': return l.pos, ASSIGN, "=" + case '`': + return l.pos, BACKQUOTE, "`" + case '"': + return l.pos, QUOTE, "\"" + case '\'': + return l.pos, SINGLE_QUOTE, "'" default: if unicode.IsSpace(r) { continue diff --git a/sql/parser.go b/sql/parser.go index 0f8b703..d2ac103 100644 --- a/sql/parser.go +++ b/sql/parser.go @@ -91,11 +91,22 @@ func (p *Parser) parseCreateTable() (*CreateTableStatement, error) { return nil, p.unexpectedToken() } - tok, ok := p.expectOne(IDENT, IF) + tok, ok := p.expectOne(QUOTE, SINGLE_QUOTE, BACKQUOTE, IDENT, IF) if !ok { return nil, p.unexpectedToken(IDENT, IF) - } else if tok == IF && !p.expectSequence(NOT, EXISTS, IDENT) { - return nil, p.unexpectedToken() + } + + 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() @@ -104,11 +115,13 @@ func (p *Parser) parseCreateTable() (*CreateTableStatement, error) { 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 { @@ -125,12 +138,29 @@ func (p *Parser) parseCreateTable() (*CreateTableStatement, error) { } 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, IDENT) { + 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() } @@ -147,12 +177,19 @@ func (p *Parser) parseCreateTable() (*CreateTableStatement, error) { stmt.Columns[column].Extra = append(stmt.Columns[column].Extra, ref) case PRIMARY: - if !p.expectSequence(KEY, LPAREN, IDENT) { + 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) @@ -163,11 +200,14 @@ func (p *Parser) parseCreateTable() (*CreateTableStatement, error) { 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) } @@ -190,9 +230,10 @@ func (p *Parser) parseCreateTable() (*CreateTableStatement, error) { 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); !ok { - return Column{}, p.unexpectedToken(TEXT, INTEGER, REAL, BLOB) + if _, ok := p.expectOne(TEXT, INTEGER, REAL, BLOB, NUMERIC); !ok { + return Column{}, p.unexpectedToken(TEXT, INTEGER, REAL, BLOB, NUMERIC) } _, _, column.Type = p.rescan() @@ -222,6 +263,9 @@ func (p *Parser) parseColumn() (Column, error) { column.Extra = append(column.Extra, ref) fmt.Println(ref) + case AUTOINCREMENT: + column.Extra = append(column.Extra, "AUTOINCREMENT") + default: return Column{}, p.unexpectedToken(COMMA, RPAREN, PRIMARY, NOT, REFERENCES) } @@ -253,17 +297,26 @@ 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.expectSequence(LPAREN, IDENT) { + 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) } @@ -316,6 +369,26 @@ func (p *Parser) consumeUntil(token Token, max int) bool { return false } +func (p *Parser) consumeIfOne(token ...Token) { + _, tok, _ := p.scan() + if slices.ContainsFunc(token, func(t Token) bool { + return tok == t + }) { + return + } + + p.unscan() +} + +func (p *Parser) consumeIf(token Token) { + _, tok, _ := p.scan() + if tok == token { + return + } + + p.unscan() +} + func (p *Parser) scan() (Position, Token, string) { if p.buf.avail { p.buf.avail = false diff --git a/table.go b/table.go index 543c4e9..b6c4eaf 100644 --- a/table.go +++ b/table.go @@ -12,6 +12,7 @@ const ( PRIMARY_KEY ColumnFlag = 1 << iota FOREIGN_KEY NOT_NULL + AUTO_INCREMENT NONE ColumnFlag = 0 )