Initial giu version, not very good looking, maybe prefer CharmBracelet Tea

This commit is contained in:
Pablu
2025-10-28 19:13:43 +01:00
parent 396b531013
commit e2d39f715f
10 changed files with 313 additions and 56 deletions

24
cmd/sqv-cli/main.go Normal file
View File

@@ -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)
}

93
cmd/sqv-giu/main.go Normal file
View File

@@ -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)
}

21
go.mod
View File

@@ -1,3 +1,24 @@
module git.pablu.de/pablu/sqv-engine module git.pablu.de/pablu/sqv-engine
go 1.25.3 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
)

2
go.sum
View File

@@ -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/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 h1:aczNwZRrReVWrZcqxvDjDmxP1NFISTAu+1Cp+3OCbUg=
github.com/gucio321/glm-go v0.0.0-20241029220517-e1b5a3e011c8/go.mod h1:Z3+NtD1rjXUVZg97dojhs70i5oneOrZ1xcFKfF/c2Ts= 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 h1:uK6p5zBA8HaQZJSInHgHVmkVBodUAy+6snSmKJG7pqA=
github.com/mazznoer/csscolorparser v0.1.6/go.mod h1:OQRVvgCyHDCAquR1YWfSwwaDcM0LhnSffGnlbOew/3I= github.com/mazznoer/csscolorparser v0.1.6/go.mod h1:OQRVvgCyHDCAquR1YWfSwwaDcM0LhnSffGnlbOew/3I=
github.com/napsy/go-css v1.0.0 h1:I1EiqpOJqo8eshGhm6OQXefXOfNgnp1SLOVfqcTeY2U= github.com/napsy/go-css v1.0.0 h1:I1EiqpOJqo8eshGhm6OQXefXOfNgnp1SLOVfqcTeY2U=

138
manager.go Normal file
View File

@@ -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
}

View File

@@ -2,13 +2,17 @@ package sql
import "fmt" import "fmt"
type Statement interface {
isEnumValue()
}
type CreateTableStatement struct { type CreateTableStatement struct {
TableName string TableName string
Columns []Column Columns []Column
} }
func (c *CreateTableStatement) Print() { 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 { for _, column := range c.Columns {
fmt.Printf("- Name: %v\n Type: %v\n Extras:\n", column.Name, column.Type) fmt.Printf("- Name: %v\n Type: %v\n Extras:\n", column.Name, column.Type)
for _, extra := range column.Extra { for _, extra := range column.Extra {
@@ -22,3 +26,12 @@ type Column struct {
Type string Type string
Extra []string Extra []string
} }
// Unused, just for example sake for now
type SelectStatement struct {
From string
Fields []string
}
func (_ *CreateTableStatement) isEnumValue() {}
func (_ *SelectStatement) isEnumValue() {}

View File

@@ -22,7 +22,7 @@ func NewParser(r io.Reader) *Parser {
return &Parser{s: NewLexer(r)} return &Parser{s: NewLexer(r)}
} }
func (p *Parser) Parse() (*CreateTableStatement, error) { func (p *Parser) Parse() (Statement, error) {
tok, ok := p.expectOne(CREATE, EOF) tok, ok := p.expectOne(CREATE, EOF)
if !ok { if !ok {
return nil, p.unexpectedToken(CREATE, EOF) return nil, p.unexpectedToken(CREATE, EOF)

View File

@@ -7,7 +7,5 @@ import (
func arrayToString(a []Token, delim string) string { func arrayToString(a []Token, delim string) string {
return strings.Trim(strings.Replace(fmt.Sprint(a), " ", delim, -1), "[]") return strings.Trim(strings.ReplaceAll(fmt.Sprint(a), " ", delim), "[]")
//return strings.Trim(strings.Join(strings.Split(fmt.Sprint(a), " "), delim), "[]")
//return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(a)), delim), "[]")
} }

View File

@@ -1,34 +1,39 @@
package engine package engine
type Table struct { type Table struct {
Name string Name string
TableValues []TableValue Columns []Column
Rows []Row Rows []Row
} }
type ValueFlags uint32 type ColumnFlag uint32
const ( const (
PRIMARY_KEY ValueFlags = 1 << iota PRIMARY_KEY ColumnFlag = 1 << iota
FOREIGN_KEY FOREIGN_KEY
NOT_NULL NOT_NULL
NONE ColumnFlag = 0
) )
func (v ValueFlags) Has(flag ValueFlags) bool { func (v ColumnFlag) Has(flag ColumnFlag) bool {
return v&flag == flag return v&flag == flag
} }
type TableValue struct { type Column struct {
Type string Type string
Name string Name string
Reference *TableValue Reference *Column
Flags ValueFlags Flags ColumnFlag
} }
type Value interface {
Representation() string // For testing purposes its string right now
} // type Value interface {
// Representation() string
// }
type Row struct { 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
} }

View File

@@ -1,27 +1,7 @@
package main CREATE TABLE TEST(
import (
"errors"
"fmt"
"io"
"log"
"strings"
"git.pablu.de/pablu/sqv-engine/sql"
)
func main() {
s := `CREATE TABLE TEST(
ID text PRIMARY KEY 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 ( CREATE TABLE IF NOT EXISTS users (
email text PRIMARY KEY, email text PRIMARY KEY,
username text username text
@@ -60,20 +40,3 @@ CREATE TABLE IF NOT EXISTS auth_states (
state_id text PRIMARY KEY, state_id text PRIMARY KEY,
code_verifier text NOT NULL 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()
}
}