Compare commits
2 Commits
6182129022
...
c41b4cc5da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c41b4cc5da | ||
|
|
694bbb7934 |
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
108
manager.go
108
manager.go
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user