sqlite: initial support
This commit is contained in:
parent
cb4b35b059
commit
232a1dd0e8
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module yvbolt
|
|||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||||
github.com/ying32/govcl v2.2.3+incompatible // indirect
|
github.com/ying32/govcl v2.2.3+incompatible // indirect
|
||||||
go.etcd.io/bbolt v1.3.10 // indirect
|
go.etcd.io/bbolt v1.3.10 // indirect
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
golang.org/x/sys v0.4.0 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/ying32/govcl v2.2.3+incompatible h1:Iyfcl26yNE1USm+3uG+btQyhkoFIV18+VITrUdHu8Lw=
|
github.com/ying32/govcl v2.2.3+incompatible h1:Iyfcl26yNE1USm+3uG+btQyhkoFIV18+VITrUdHu8Lw=
|
||||||
github.com/ying32/govcl v2.2.3+incompatible/go.mod h1:yZVtbJ9Md1nAVxtHKIriKZn4K6TQYqI1en3sN/m9FJ8=
|
github.com/ying32/govcl v2.2.3+incompatible/go.mod h1:yZVtbJ9Md1nAVxtHKIriKZn4K6TQYqI1en3sN/m9FJ8=
|
||||||
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
||||||
|
10
main.go
10
main.go
@ -10,7 +10,6 @@ import (
|
|||||||
_ "github.com/ying32/govcl/pkgs/winappres" // Extra _syso files for Windows
|
_ "github.com/ying32/govcl/pkgs/winappres" // Extra _syso files for Windows
|
||||||
"github.com/ying32/govcl/vcl"
|
"github.com/ying32/govcl/vcl"
|
||||||
"github.com/ying32/govcl/vcl/types"
|
"github.com/ying32/govcl/vcl/types"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TMainForm struct {
|
type TMainForm struct {
|
||||||
@ -47,6 +46,11 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
|||||||
mnuFileOpen.SetOnClick(f.OnMnuFileOpenClick)
|
mnuFileOpen.SetOnClick(f.OnMnuFileOpenClick)
|
||||||
mnuFile.Add(mnuFileOpen)
|
mnuFile.Add(mnuFileOpen)
|
||||||
|
|
||||||
|
mnuFileSqliteMemory := vcl.NewMenuItem(mnuFile)
|
||||||
|
mnuFileSqliteMemory.SetCaption("New SQLite in-memory database")
|
||||||
|
mnuFileSqliteMemory.SetOnClick(f.OnmnuFileSqliteMemoryClick)
|
||||||
|
mnuFile.Add(mnuFileSqliteMemory)
|
||||||
|
|
||||||
mnuSep := vcl.NewMenuItem(mnuFile)
|
mnuSep := vcl.NewMenuItem(mnuFile)
|
||||||
mnuSep.SetCaption("-") // Creates separator
|
mnuSep.SetCaption("-") // Creates separator
|
||||||
mnuFile.Add(mnuSep)
|
mnuFile.Add(mnuSep)
|
||||||
@ -123,6 +127,10 @@ func (f *TMainForm) OnMnuFileExitClick(sender vcl.IObject) {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *TMainForm) OnmnuFileSqliteMemoryClick(sender vcl.IObject) {
|
||||||
|
f.SQLite_AddFromFile(`:memory:`)
|
||||||
|
}
|
||||||
|
|
||||||
func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) {
|
func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) {
|
||||||
|
|
||||||
if node.Data() == nil {
|
if node.Data() == nil {
|
||||||
|
132
sqlite.go
Normal file
132
sqlite.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/ying32/govcl/vcl"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sqliteTablesCaption = "Tables"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sqliteLoadedDatabase struct {
|
||||||
|
displayName string
|
||||||
|
path string
|
||||||
|
db *sql.DB
|
||||||
|
nav *vcl.TTreeNode
|
||||||
|
|
||||||
|
arena []*navData // keepalive
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ld *sqliteLoadedDatabase) DisplayName() string {
|
||||||
|
return ld.displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ld *sqliteLoadedDatabase) RootElement() *vcl.TTreeNode {
|
||||||
|
return ld.nav
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ld *sqliteLoadedDatabase) Keepalive(ndata *navData) {
|
||||||
|
ld.arena = append(ld.arena, ndata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||||
|
|
||||||
|
if len(ndata.bucketPath) == 0 {
|
||||||
|
// Top-level
|
||||||
|
f.propertiesBox.SetText("Please select...")
|
||||||
|
f.contentBox.SetEnabled(false)
|
||||||
|
f.contentBox.Clear()
|
||||||
|
|
||||||
|
} else if len(ndata.bucketPath) == 1 {
|
||||||
|
// Category (tables, ...)
|
||||||
|
f.propertiesBox.SetText("Please select...")
|
||||||
|
f.contentBox.SetEnabled(false)
|
||||||
|
f.contentBox.Clear()
|
||||||
|
|
||||||
|
} else if ndata.bucketPath[0] == sqliteTablesCaption {
|
||||||
|
// Render for specific table
|
||||||
|
f.propertiesBox.SetText(fmt.Sprintf("Selected table %q", ndata.bucketPath[1]))
|
||||||
|
// Load schema
|
||||||
|
// Select * with small limit
|
||||||
|
f.contentBox.SetEnabled(false)
|
||||||
|
f.contentBox.Clear()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ??? unknown
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ld *sqliteLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
|
||||||
|
|
||||||
|
if len(ndata.bucketPath) == 0 {
|
||||||
|
// The top-level children are always:
|
||||||
|
return []string{sqliteTablesCaption}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ndata.bucketPath[0] == sqliteTablesCaption {
|
||||||
|
rr, err := ld.db.Query(`SELECT name FROM sqlite_master WHERE type='table' ORDER BY name ASC;`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rr.Close()
|
||||||
|
|
||||||
|
var gather []string
|
||||||
|
for rr.Next() {
|
||||||
|
var tableName string
|
||||||
|
err = rr.Scan(&tableName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gather = append(gather, tableName)
|
||||||
|
}
|
||||||
|
if rr.Err() != nil {
|
||||||
|
return nil, rr.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return gather, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unknown nav path %#v", ndata.bucketPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ loadedDatabase = &sqliteLoadedDatabase{} // interface assertion
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
func (f *TMainForm) SQLite_AddFromFile(path string) {
|
||||||
|
|
||||||
|
// TODO load in background thread to stop blocking the UI
|
||||||
|
db, err := sql.Open("sqlite3", path)
|
||||||
|
if err != nil {
|
||||||
|
vcl.ShowMessage(fmt.Sprintf("Failed to load database '%s': %s", path, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ld := &sqliteLoadedDatabase{
|
||||||
|
path: path,
|
||||||
|
displayName: filepath.Base(path),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
|
||||||
|
ld.nav = f.Buckets.Items().Add(nil, ld.displayName)
|
||||||
|
ld.nav.SetHasChildren(true) // dynamically populate in OnNavExpanding
|
||||||
|
navData := &navData{
|
||||||
|
ld: ld,
|
||||||
|
childrenLoaded: false, // will be loaded dynamically
|
||||||
|
bucketPath: []string{}, // empty = root
|
||||||
|
}
|
||||||
|
ld.nav.SetData(unsafe.Pointer(navData))
|
||||||
|
|
||||||
|
f.dbs = append(f.dbs, ld)
|
||||||
|
|
||||||
|
ld.Keepalive(navData)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user