From e2d39f715fc6db242f68f93a4a4f8ba5b66fa226 Mon Sep 17 00:00:00 2001 From: Pablu Date: Tue, 28 Oct 2025 19:13:43 +0100 Subject: [PATCH] Initial giu version, not very good looking, maybe prefer CharmBracelet Tea --- cmd/sqv-cli/main.go | 24 +++++++ cmd/sqv-giu/main.go | 93 ++++++++++++++++++++++++ go.mod | 21 ++++++ go.sum | 2 + manager.go | 138 ++++++++++++++++++++++++++++++++++++ sql/ast.go | 15 +++- sql/parser.go | 2 +- sql/util.go | 4 +- table.go | 31 ++++---- cmd/sqv/main.go => test.sql | 39 +--------- 10 files changed, 313 insertions(+), 56 deletions(-) create mode 100644 cmd/sqv-cli/main.go create mode 100644 cmd/sqv-giu/main.go create mode 100644 manager.go rename cmd/sqv/main.go => test.sql (62%) diff --git a/cmd/sqv-cli/main.go b/cmd/sqv-cli/main.go new file mode 100644 index 0000000..cc8d2b5 --- /dev/null +++ b/cmd/sqv-cli/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "log" + "os" + + engine "git.pablu.de/pablu/sqv-engine" +) + +func main() { + file, err := os.ReadFile("test.sql") + if err != nil { + log.Fatal(err) + } + + m := engine.NewManagerFromFile(string(file)) + err = m.Start() + if err != nil { + log.Fatal(err) + } + + fmt.Print(m) +} diff --git a/cmd/sqv-giu/main.go b/cmd/sqv-giu/main.go new file mode 100644 index 0000000..6a33444 --- /dev/null +++ b/cmd/sqv-giu/main.go @@ -0,0 +1,93 @@ +package main + +import ( + "log" + "os" + + engine "git.pablu.de/pablu/sqv-engine" + g "github.com/AllenDang/giu" +) + +type Test struct { + tables []engine.Table + enabled []bool +} + +func (t *Test) loop() { + tableMenuItems := make([]g.Widget, len(t.tables)) + + for i, table := range t.tables { + tableMenuItems[i] = g.MenuItem(table.Name).OnClick(func() { + t.enabled[i] = !t.enabled[i] + }) + } + + g.MainMenuBar().Layout( + g.Menu("Tables").Layout( + tableMenuItems..., + ), + ).Build() + + for i, table := range t.tables { + if t.enabled[i] { + g.Window(table.Name).Layout( + SqlTable(table), + ) + } + } +} + +func SqlTable(table engine.Table) *g.TableWidget { + tableColumns := make([]*g.TableColumnWidget, 0) + for _, column := range table.Columns { + tableColumns = append(tableColumns, g.TableColumn(column.Name)) + } + + tableRows := make([]*g.TableRowWidget, 0) + for _, row := range table.Rows { + tableRows = append(tableRows, SqlValueRow(row)) + } + + return g.Table().Columns( + tableColumns..., + ).Rows(tableRows...) +} + +func SqlValueRow(row engine.Row) *g.TableRowWidget { + v := make([]g.Widget, 0) + for _, values := range row.Values { + v = append(v, g.Label(values)) + } + + return g.TableRow(v...) +} + +func main() { + wnd := g.NewMasterWindow("SQV", 800, 600, 0) + + //nolint:gocritic // should be here for doc + // errMarkers = imgui.NewErrorMarkers() + file, err := os.ReadFile("test.sql") + if err != nil { + log.Fatal(err) + } + m := engine.NewManagerFromFile(string(file)) + if err := m.Start(); err != nil { + log.Fatal(err) + } + + t := Test{ + tables: m.GetTables(), + enabled: make([]bool, len(m.GetTables())), + } + + t.tables[2].Rows = []engine.Row{ + engine.Row{ + Values: []string{ + "HELLO", "WORLD", "!", + }, + }, + } + + wnd.Run(t.loop) +} diff --git a/go.mod b/go.mod index 20a24f2..16f4bf9 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,24 @@ module git.pablu.de/pablu/sqv-engine go 1.25.3 + +require ( + github.com/AllenDang/giu v0.14.1 + github.com/mattn/go-sqlite3 v1.14.32 +) + +require ( + github.com/AllenDang/cimgui-go v1.3.2-0.20250409185506-6b2ff1aa26b5 // indirect + github.com/AllenDang/go-findfont v0.0.0-20200702051237-9f180485aeb8 // indirect + github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 // indirect + github.com/gucio321/glm-go v0.0.0-20241029220517-e1b5a3e011c8 // indirect + github.com/mazznoer/csscolorparser v0.1.6 // indirect + github.com/napsy/go-css v1.0.0 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect + github.com/sahilm/fuzzy v0.1.1 // indirect + golang.design/x/hotkey v0.4.1 // indirect + golang.design/x/mainthread v0.3.0 // indirect + golang.org/x/image v0.27.0 // indirect + golang.org/x/sys v0.25.0 // indirect + gopkg.in/eapache/queue.v1 v1.1.0 // indirect +) diff --git a/go.sum b/go.sum index eaa7a5f..091f952 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHr github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= github.com/gucio321/glm-go v0.0.0-20241029220517-e1b5a3e011c8 h1:aczNwZRrReVWrZcqxvDjDmxP1NFISTAu+1Cp+3OCbUg= github.com/gucio321/glm-go v0.0.0-20241029220517-e1b5a3e011c8/go.mod h1:Z3+NtD1rjXUVZg97dojhs70i5oneOrZ1xcFKfF/c2Ts= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mazznoer/csscolorparser v0.1.6 h1:uK6p5zBA8HaQZJSInHgHVmkVBodUAy+6snSmKJG7pqA= github.com/mazznoer/csscolorparser v0.1.6/go.mod h1:OQRVvgCyHDCAquR1YWfSwwaDcM0LhnSffGnlbOew/3I= github.com/napsy/go-css v1.0.0 h1:I1EiqpOJqo8eshGhm6OQXefXOfNgnp1SLOVfqcTeY2U= diff --git a/manager.go b/manager.go new file mode 100644 index 0000000..4f1ba24 --- /dev/null +++ b/manager.go @@ -0,0 +1,138 @@ +package engine + +import ( + "errors" + "fmt" + "io" + "log" + "slices" + "strings" + + "git.pablu.de/pablu/sqv-engine/sql" +) + +type Manager struct { + parser *sql.Parser + + tables []Table +} + +func NewManagerFromFile(sqlTxt string) *Manager { + return &Manager{ + parser: sql.NewParser(strings.NewReader(sqlTxt)), + } +} + +func (m *Manager) Start() error { + for { + stmt, err := m.parser.Parse() + if err != nil && errors.Is(err, io.EOF) { + fmt.Println("Finished parsing") + break + } else if err != nil { + log.Fatal(err) + } + + switch v := stmt.(type) { + case *sql.CreateTableStatement: + t, err := m.convertCreateTableStatementToTable(v) + if err != nil { + return err + } + m.tables = append(m.tables, t) + + default: + panic("NOT IMPLEMENTED") + } + } + + return nil +} + +func (m *Manager) Refresh(sqlTxt string) error { + m.parser = sql.NewParser(strings.NewReader(sqlTxt)) + m.tables = make([]Table, 0) + + return m.Start() +} + +func (m *Manager) GetTables() []Table { + return m.tables +} + +func (m *Manager) GetTable(name string) (Table, bool) { + index := slices.IndexFunc(m.tables, func(t Table) bool { + return t.Name == name + }) + + if index >= 0 { + return m.tables[index], true + } + + return Table{}, false +} + +func (m *Manager) convertCreateTableStatementToTable(cts *sql.CreateTableStatement) (Table, error) { + res := Table{ + Name: cts.TableName, + Columns: make([]Column, len(cts.Columns)), + Rows: make([]Row, 0), + } + + for i, column := range cts.Columns { + flags := extrasToFlags(column.Extra) + var ref *Column = nil + if flags.Has(FOREIGN_KEY) { + index := slices.IndexFunc(column.Extra, func(c string) bool { + return strings.HasPrefix(c, "ref") + }) + refExtra := column.Extra[index] + refStr := strings.Split(refExtra, " ")[1] + + s := strings.Split(refStr, ".") + tableName := s[0] + columnName := s[1] + + refTable, ok := m.GetTable(tableName) + if !ok { + fmt.Println(m.tables) + return Table{}, fmt.Errorf("Reference table '%v' not found", tableName) + } + + colIndex := slices.IndexFunc(refTable.Columns, func(c Column) bool { + return c.Name == columnName + }) + + ref = &refTable.Columns[colIndex] + } + + res.Columns[i] = Column{ + Type: column.Type, + Name: column.Name, + Reference: ref, + Flags: flags, + } + } + + return res, nil +} + +func extrasToFlags(extras []string) ColumnFlag { + res := NONE + for _, extra := range extras { + + // This is not good + switch strings.Split(extra, " ")[0] { + case "PRIMARY_KEY": + res |= PRIMARY_KEY + case "ref": + res |= FOREIGN_KEY + case "NOT_NULL": + res |= NOT_NULL + default: + log.Panicf("NOT IMPLEMENTED EXTRA: %v", extra) + } + } + + return res +} diff --git a/sql/ast.go b/sql/ast.go index 75f394a..7504a6d 100644 --- a/sql/ast.go +++ b/sql/ast.go @@ -2,13 +2,17 @@ package sql import "fmt" +type Statement interface { + isEnumValue() +} + type CreateTableStatement struct { TableName string Columns []Column } func (c *CreateTableStatement) Print() { -fmt.Printf("Name: %v\nColumns:\n", c.TableName) + fmt.Printf("Name: %v\nColumns:\n", c.TableName) for _, column := range c.Columns { fmt.Printf("- Name: %v\n Type: %v\n Extras:\n", column.Name, column.Type) for _, extra := range column.Extra { @@ -22,3 +26,12 @@ type Column struct { Type string Extra []string } + +// Unused, just for example sake for now +type SelectStatement struct { + From string + Fields []string +} + +func (_ *CreateTableStatement) isEnumValue() {} +func (_ *SelectStatement) isEnumValue() {} diff --git a/sql/parser.go b/sql/parser.go index 5c581c3..17494d9 100644 --- a/sql/parser.go +++ b/sql/parser.go @@ -22,7 +22,7 @@ func NewParser(r io.Reader) *Parser { return &Parser{s: NewLexer(r)} } -func (p *Parser) Parse() (*CreateTableStatement, error) { +func (p *Parser) Parse() (Statement, error) { tok, ok := p.expectOne(CREATE, EOF) if !ok { return nil, p.unexpectedToken(CREATE, EOF) diff --git a/sql/util.go b/sql/util.go index 82c5b0f..4b97ff7 100644 --- a/sql/util.go +++ b/sql/util.go @@ -7,7 +7,5 @@ import ( func arrayToString(a []Token, delim string) string { - return strings.Trim(strings.Replace(fmt.Sprint(a), " ", delim, -1), "[]") - //return strings.Trim(strings.Join(strings.Split(fmt.Sprint(a), " "), delim), "[]") - //return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(a)), delim), "[]") + return strings.Trim(strings.ReplaceAll(fmt.Sprint(a), " ", delim), "[]") } diff --git a/table.go b/table.go index 08528d4..dbeb6ad 100644 --- a/table.go +++ b/table.go @@ -1,34 +1,39 @@ package engine type Table struct { - Name string - TableValues []TableValue - Rows []Row + Name string + Columns []Column + Rows []Row } -type ValueFlags uint32 +type ColumnFlag uint32 const ( - PRIMARY_KEY ValueFlags = 1 << iota + PRIMARY_KEY ColumnFlag = 1 << iota FOREIGN_KEY NOT_NULL + + NONE ColumnFlag = 0 ) -func (v ValueFlags) Has(flag ValueFlags) bool { +func (v ColumnFlag) Has(flag ColumnFlag) bool { return v&flag == flag } -type TableValue struct { +type Column struct { Type string Name string - Reference *TableValue - Flags ValueFlags + Reference *Column + Flags ColumnFlag } -type Value interface { - Representation() string -} + +// For testing purposes its string right now +// type Value interface { +// Representation() string +// } type Row struct { - Values map[TableValue]Value + // This should be map but to Column so its always the right Column but for testing this is a slice now + Values []string } diff --git a/cmd/sqv/main.go b/test.sql similarity index 62% rename from cmd/sqv/main.go rename to test.sql index 050e496..c2f8ed1 100644 --- a/cmd/sqv/main.go +++ b/test.sql @@ -1,27 +1,7 @@ -package main - -import ( - "errors" - "fmt" - "io" - "log" - "strings" - - "git.pablu.de/pablu/sqv-engine/sql" -) - -func main() { - s := `CREATE TABLE TEST( +CREATE TABLE TEST( ID text PRIMARY KEY ); -CREATE TABLE sessions ( - session_id text PRIMARY KEY, - access_token text NOT NULL, - user_email text NOT NULL, - FOREIGN KEY(user_email) REFERENCES users(email) -); - CREATE TABLE IF NOT EXISTS users ( email text PRIMARY KEY, username text @@ -60,20 +40,3 @@ CREATE TABLE IF NOT EXISTS auth_states ( state_id text PRIMARY KEY, code_verifier text NOT NULL ); - ` - - parser := sql.NewParser(strings.NewReader(s)) - - for { - stmt, err := parser.Parse() - if err != nil && errors.Is(err, io.EOF) { - fmt.Println("Finished parsing") - break - } else if err != nil { - log.Fatal(err) - } - - stmt.Print() - fmt.Println() - } -}