Compare commits

...

2 Commits

Author SHA1 Message Date
Pablu
c41b4cc5da Add ctrl+e to hide and unhide editor 2025-12-01 17:05:10 +01:00
Pablu
694bbb7934 Working select statements for know tables, with and without semi at the end 2025-12-01 16:36:04 +01:00
3 changed files with 294 additions and 24 deletions

View File

@@ -11,18 +11,27 @@ import (
func main() { func main() {
app := tview.NewApplication() app := tview.NewApplication()
menu := tview.NewList() menuView := tview.NewList()
table := tview.NewTable().SetBorders(true) tableView := tview.NewTable().SetBorders(true)
table.SetBorder(true).SetTitle("TABLE") sqlEditor := tview.NewTextArea()
menu.SetTitle("TABLES").SetBorder(true) sqlEditor.SetTitle("SQL Editor").SetBorder(true)
menu.ShowSecondaryText(false).SetDoneFunc(func() {
table.Clear() tableView.SetBorder(true).SetTitle("TABLE")
menuView.SetTitle("TABLES").SetBorder(true)
menuView.ShowSecondaryText(false).SetDoneFunc(func() {
tableView.Clear()
}) })
flex := tview.NewFlex(). verticalFlex := tview.NewFlex().
AddItem(menu, 0, 1, true). AddItem(menuView, 0, 1, true).
AddItem(table, 0, 3, true) AddItem(tableView, 0, 3, false)
horizontalFlex := tview.NewFlex().
SetDirection(tview.FlexRow).
AddItem(verticalFlex, 0, 4, true).
AddItem(sqlEditor, 0, 1, false)
m, err := engine.NewManager("db.sqlite") m, err := engine.NewManager("db.sqlite")
if err != nil { if err != nil {
@@ -33,19 +42,20 @@ func main() {
tables := m.GetTables() tables := m.GetTables()
for _, t := range tables { for _, t := range tables {
menu.AddItem(t.Name, "", 0, nil) menuView.AddItem(t.Name, "", 0, nil)
} }
menu.SetChangedFunc(func(index int, mainText, secondaryText string, shortcut rune) { menuView.SetChangedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
table.Clear() tableView.Clear()
t, ok := m.GetTable(mainText) t, ok := m.GetTable(mainText)
if !ok { if !ok {
panic("AHHHHHHH") panic("AHHHHHHH")
} }
tableView.SetFixed(1, 0)
for i, c := range t.Columns { for i, c := range t.Columns {
color := tcell.ColorDarkGreen color := tcell.ColorDarkGreen
table.SetCell(0, i, tview.NewTableCell(c.Name).SetTextColor(color).SetAlign(tview.AlignCenter)) tableView.SetCell(0, i, tview.NewTableCell(c.Name).SetTextColor(color).SetAlign(tview.AlignCenter))
} }
err = m.LoadTable(&t) err = m.LoadTable(&t)
@@ -55,25 +65,98 @@ func main() {
for ri, r := range t.Rows { for ri, r := range t.Rows {
for rc, c := range r.Values { for rc, c := range r.Values {
table.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.AlignCenter))
} }
} }
}) })
menu.SetCurrentItem(1)
table.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) { // 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
switch key { menuView.SetCurrentItem(1)
menuView.SetCurrentItem(0)
menuView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyEscape: case tcell.KeyEscape:
app.Stop() app.Stop()
case tcell.KeyEnter: case tcell.KeyEnter:
table.SetSelectable(true, true) app.SetFocus(tableView)
return nil
case tcell.KeyRune:
switch event.Rune() {
case 'j':
return tcell.NewEventKey(tcell.KeyDown, 'j', event.Modifiers())
case 'k':
return tcell.NewEventKey(tcell.KeyUp, 'k', event.Modifiers())
} }
}).SetSelectedFunc(func(row, column int) { }
table.GetCell(row, column).SetTextColor(tcell.ColorRed)
table.SetSelectable(false, false) return event
}) })
if err := app.SetRoot(flex, true).EnableMouse(true).Run(); err != nil { sqlEditor.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyEnter {
t, err := m.RunSql(sqlEditor.GetText())
if err != nil {
sqlEditor.SetText(err.Error(), true)
}
tableView.Clear()
tableView.SetFixed(1, 0)
for i, c := range t.Columns {
color := tcell.ColorDarkGreen
tableView.SetCell(0, i, tview.NewTableCell(c.Name).SetTextColor(color).SetAlign(tview.AlignCenter))
}
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))
}
}
return nil
}
return event
})
tableView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyEscape:
app.SetFocus(menuView)
return nil
}
return event
})
menuHidden := false
editorHidden := false
horizontalFlex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyCtrlH:
if !menuHidden {
verticalFlex.ResizeItem(menuView, 0, 0)
app.SetFocus(tableView)
} else {
verticalFlex.ResizeItem(menuView, 0, 1)
app.SetFocus(menuView)
}
menuHidden = !menuHidden
case tcell.KeyCtrlE:
if !editorHidden {
horizontalFlex.ResizeItem(sqlEditor, 0, 0)
app.SetFocus(verticalFlex)
} else {
horizontalFlex.ResizeItem(sqlEditor, 0, 1)
app.SetFocus(sqlEditor)
}
editorHidden = !editorHidden
}
return event
})
if err := app.SetRoot(horizontalFlex, true).EnableMouse(true).Run(); err != nil {
panic(err) panic(err)
} }

View File

@@ -61,6 +61,114 @@ func NewManager(path string) (*Manager, error) {
}, nil }, nil
} }
func (m *Manager) RunSql(sqlText string) (Table, error) {
p := engine.NewParser(strings.NewReader(sqlText))
stmt, err := p.Parse()
if err != nil && !errors.Is(err, io.EOF) {
return Table{}, err
}
selectStmt, ok := stmt.(*engine.SelectStatement)
if !ok {
panic("HELP ITS NOT A SELECT STATMET")
}
table, ok := m.GetTable(selectStmt.From)
if !ok {
panic("HELP TABLE NOT FOUND")
}
rows, err := m.conn.Query(sqlText)
if err != nil {
return Table{}, err
}
table.Rows = make([]Row, 0)
if slices.Contains(selectStmt.Fields, "*") {
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")
}
}
err = rows.Scan(cols...)
if err != nil {
return Table{}, err
}
table.Rows = append(table.Rows, Row{
Values: anyToStr(cols),
})
}
return table, nil
} else {
columns := make([]Column, len(selectStmt.Fields))
firstTime := true
for rows.Next() {
cols := make([]any, len(selectStmt.Fields))
for i, s := range selectStmt.Fields {
for _, column := range table.Columns {
if column.Name != s {
continue
}
if firstTime {
columns[i] = column
}
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")
}
}
}
firstTime = false
err = rows.Scan(cols...)
if err != nil {
return Table{}, err
}
table.Rows = append(table.Rows, Row{
Values: anyToStr(cols),
})
}
nTable := Table{
Name: selectStmt.From,
Columns: columns,
Rows: table.Rows,
}
return nTable, nil
}
}
func (m *Manager) Start() error { func (m *Manager) Start() error {
for { for {
stmt, err := m.parser.Parse() stmt, err := m.parser.Parse()

View File

@@ -23,18 +23,75 @@ func NewParser(r io.Reader) *Parser {
} }
func (p *Parser) Parse() (Statement, error) { func (p *Parser) Parse() (Statement, error) {
tok, ok := p.expectOne(CREATE, EOF) tok, ok := p.expectOne(CREATE, EOF, SELECT)
if !ok { if !ok {
return nil, p.unexpectedToken(CREATE, EOF) return nil, p.unexpectedToken(CREATE, EOF)
} else if tok == EOF { } else if tok == EOF {
return nil, io.EOF return nil, io.EOF
} }
switch tok {
case EOF:
return nil, io.EOF
case CREATE:
return p.parseCreateTable()
case SELECT:
return p.parseSelect()
default:
panic("SHOULD NEVER BE REACHED")
}
}
func (p *Parser) parseSelect() (*SelectStatement, error) {
tok, ok := p.expectOne(ASTERIKS, IDENT)
if !ok {
return nil, p.unexpectedToken(ASTERIKS, IDENT)
}
fields := make([]string, 1)
fields[0] = "*"
if tok == IDENT {
_, _, n := p.rescan()
fields[0] = n
for {
tok, ok := p.expectOne(COMMA, FROM)
if !ok {
return nil, p.unexpectedToken(COMMA, FROM)
}
if tok == FROM {
p.unscan()
break
}
if !p.expectNext(IDENT) {
return nil, p.unexpectedToken(IDENT)
}
_, _, n := p.rescan()
fields = append(fields, n)
}
}
if !p.expectSequence(FROM, IDENT) {
return nil, p.unexpectedToken()
}
_, _, tableName := p.rescan()
if !p.consumeUntilOne(50, SEMI, EOF) {
return nil, fmt.Errorf("Expected semicolon but never found after 50 tries")
}
return &SelectStatement{
From: tableName,
Fields: fields,
}, nil
}
func (p *Parser) parseCreateTable() (*CreateTableStatement, error) {
if !p.expectNext(TABLE) { if !p.expectNext(TABLE) {
return nil, p.unexpectedToken() return nil, p.unexpectedToken()
} }
tok, ok = p.expectOne(IDENT, IF) tok, ok := p.expectOne(IDENT, IF)
if !ok { if !ok {
return nil, p.unexpectedToken(IDENT, IF) return nil, p.unexpectedToken(IDENT, IF)
} else if tok == IF && !p.expectSequence(NOT, EXISTS, IDENT) { } else if tok == IF && !p.expectSequence(NOT, EXISTS, IDENT) {
@@ -237,6 +294,28 @@ func (p *Parser) expectOne(token ...Token) (Token, bool) {
return tok, ok return tok, ok
} }
func (p *Parser) consumeUntilOne(max int, token ...Token) bool {
for range max {
_, tok, _ := p.scan()
if slices.ContainsFunc(token, func(t Token) bool {
return tok == t
}) {
return true
}
}
return false
}
func (p *Parser) consumeUntil(token Token, max int) bool {
for range max {
_, tok, _ := p.scan()
if tok == token {
return true
}
}
return false
}
func (p *Parser) scan() (Position, Token, string) { func (p *Parser) scan() (Position, Token, string) {
if p.buf.avail { if p.buf.avail {
p.buf.avail = false p.buf.avail = false