Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 645ab29cdd | |||
| 4202b9b970 | |||
| 42bbe3957a | |||
| 5e2aae9032 | |||
| f54577f93f | |||
| f4d2d2ec39 | |||
| 5cd3f6c765 | |||
| 5d268d22af | |||
| 5e0422e10f | |||
| ef30a0d210 | |||
| 38847b3a7e | |||
| f432b52652 | |||
| f64522dfa1 | |||
| 79bce40581 | |||
| 3731ee1781 | |||
| 2481a1da20 | |||
| 1035086ed4 | |||
| 038eb44f48 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
testdata/
|
||||
liblcl*
|
||||
yvbolt
|
||||
yvbolt.exe
|
||||
|
||||
19
README.md
19
README.md
@@ -7,14 +7,17 @@ This is an experimental application and you should generally prefer to use [qbol
|
||||
## Features
|
||||
|
||||
- Native desktop application, running on Linux, Windows, and macOS
|
||||
- Connect to multiple databases
|
||||
- Browse table/bucket content
|
||||
- Run custom SQL queries
|
||||
- Safe handling for non-UTF8 key and data fields
|
||||
- Supported databases:
|
||||
- Bolt
|
||||
- Full compatibility via the upstream [etcd-io/bbolt](https://github.com/etcd-io/bbolt) library
|
||||
- Browse database content
|
||||
- Recursive bucket support
|
||||
- Safe handling for non-UTF8 key and data fields
|
||||
- SQLite
|
||||
- Browse table content
|
||||
- Uses CGo if available or modernc.org if not
|
||||
- Badger v4
|
||||
|
||||
## License
|
||||
|
||||
@@ -31,6 +34,16 @@ This project redistributes images from the famfamfam/silk icon set under the [CC
|
||||
|
||||
## Changelog
|
||||
|
||||
2024-06-25 v0.3.0
|
||||
|
||||
- Add support for running custom queries
|
||||
- Add BadgerDB v4 as supported database
|
||||
- Add support for CGo-free SQLite driver under cross-compilation
|
||||
- Add status bar showing currently selected DB
|
||||
- Update Bolt to v1.4.0-alpha.1
|
||||
- Fix missing icons in nav when selecting items
|
||||
- Fix extra quotemarks when browsing string content of database
|
||||
|
||||
2024-06-08 v0.2.0
|
||||
|
||||
- Add SQLite support (now requires CGo)
|
||||
|
||||
23
TODO
Normal file
23
TODO
Normal file
@@ -0,0 +1,23 @@
|
||||
- Insert
|
||||
- Update cell
|
||||
- Delete row(s)
|
||||
- Binary data viewer
|
||||
- Detect jpg/png and show as image
|
||||
- Warning if data table is filtered to 1000 rows, or add pagination
|
||||
- More DB types
|
||||
- MySQL
|
||||
- Postgres
|
||||
- Redis
|
||||
- MSSQL (recursive navigation for instances)
|
||||
- SSH tunnels
|
||||
- Special actions
|
||||
- Refresh tables
|
||||
- Per-db actions e.g. compact, export backup
|
||||
- Per-table actions e.g. drop table
|
||||
- Sqlite cli driver for ssh tunnel
|
||||
- https://ricardoanderegg.com/posts/sqlite-remote-explorer-gui/
|
||||
- https://github.com/litements/litexplore
|
||||
- Badger encryption key dialog
|
||||
- Badger in-memory
|
||||
- Makefile to cross-compile release binaries in docker
|
||||
- Win32 icon resource
|
||||
BIN
assets/chart_bar.png
Normal file
BIN
assets/chart_bar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 541 B |
BIN
assets/database_lightning.png
Normal file
BIN
assets/database_lightning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 775 B |
BIN
assets/lightning.png
Normal file
BIN
assets/lightning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 634 B |
143
badger.go
Normal file
143
badger.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/ying32/govcl/vcl"
|
||||
"github.com/ying32/govcl/vcl/types"
|
||||
)
|
||||
|
||||
type badgerLoadedDatabase struct {
|
||||
displayName string
|
||||
path string
|
||||
db *badger.DB
|
||||
nav *vcl.TTreeNode
|
||||
|
||||
arena []*navData // keepalive
|
||||
}
|
||||
|
||||
func (ld *badgerLoadedDatabase) DisplayName() string {
|
||||
return ld.displayName
|
||||
}
|
||||
|
||||
func (ld *badgerLoadedDatabase) DriverName() string {
|
||||
return "Badger v4"
|
||||
}
|
||||
|
||||
func (ld *badgerLoadedDatabase) RootElement() *vcl.TTreeNode {
|
||||
return ld.nav
|
||||
}
|
||||
|
||||
func (ld *badgerLoadedDatabase) Keepalive(ndata *navData) {
|
||||
ld.arena = append(ld.arena, ndata)
|
||||
}
|
||||
|
||||
func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
|
||||
// Load properties
|
||||
|
||||
content := fmt.Sprintf("Table statistics: %#v", ld.db.Tables())
|
||||
f.propertiesBox.SetText(content)
|
||||
|
||||
// Load data
|
||||
|
||||
f.contentBox.SetEnabled(false)
|
||||
f.contentBox.Clear()
|
||||
|
||||
// Badger always uses Key + Value as the columns
|
||||
|
||||
f.contentBox.Columns().Clear()
|
||||
colKey := f.contentBox.Columns().Add()
|
||||
colKey.SetCaption("Key")
|
||||
colKey.SetWidth(MY_WIDTH)
|
||||
colKey.SetAlignment(types.TaLeftJustify)
|
||||
colVal := f.contentBox.Columns().Add()
|
||||
colVal.SetCaption("Value")
|
||||
|
||||
err := ld.db.View(func(txn *badger.Txn) error {
|
||||
|
||||
// Valid
|
||||
f.contentBox.Clear()
|
||||
|
||||
// Create iterator
|
||||
opts := badger.DefaultIteratorOptions
|
||||
opts.PrefetchSize = 64
|
||||
it := txn.NewIterator(opts)
|
||||
defer it.Close()
|
||||
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
k := item.Key()
|
||||
err := item.Value(func(v []byte) error {
|
||||
dataEntry := f.contentBox.Items().Add()
|
||||
dataEntry.SetCaption(formatUtf8(k))
|
||||
dataEntry.SubItems().Add(formatUtf8(v))
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
vcl.ShowMessage(fmt.Sprintf("Failed to load data: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Valid
|
||||
f.contentBox.SetEnabled(true)
|
||||
}
|
||||
|
||||
func (ld *badgerLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
|
||||
// In the Badger implementation, there is only one child: "Data"
|
||||
if len(ndata.bucketPath) == 0 {
|
||||
return []string{"Data"}, nil
|
||||
|
||||
} else {
|
||||
// No children deeper than that
|
||||
return []string{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ld *badgerLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
|
||||
vcl.ShowMessage("Badger doesn't support querying")
|
||||
}
|
||||
|
||||
var _ loadedDatabase = &badgerLoadedDatabase{} // interface assertion
|
||||
|
||||
//
|
||||
|
||||
func (f *TMainForm) badgerAddDatabaseFromDirectory(path string) {
|
||||
// TODO load in background thread to stop blocking the UI
|
||||
db, err := badger.Open(badger.DefaultOptions(path))
|
||||
if err != nil {
|
||||
vcl.ShowMessage(fmt.Sprintf("Failed to load database '%s': %s", path, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
ld := &badgerLoadedDatabase{
|
||||
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
|
||||
ld.nav.SetImageIndex(imgDatabase)
|
||||
ld.nav.SetSelectedIndex(imgDatabase)
|
||||
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)
|
||||
}
|
||||
10
bolt.go
10
bolt.go
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/ying32/govcl/vcl"
|
||||
"github.com/ying32/govcl/vcl/types"
|
||||
"go.etcd.io/bbolt"
|
||||
"go.etcd.io/bbolt/version"
|
||||
)
|
||||
|
||||
type boltLoadedDatabase struct {
|
||||
@@ -26,6 +27,10 @@ func (ld *boltLoadedDatabase) DisplayName() string {
|
||||
return ld.displayName
|
||||
}
|
||||
|
||||
func (ld *boltLoadedDatabase) DriverName() string {
|
||||
return "Bolt " + version.Version
|
||||
}
|
||||
|
||||
func (ld *boltLoadedDatabase) RootElement() *vcl.TTreeNode {
|
||||
return ld.nav
|
||||
}
|
||||
@@ -89,6 +94,10 @@ func (ld *boltLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
|
||||
return boltChildBucketNames(ld.db, ndata.bucketPath)
|
||||
}
|
||||
|
||||
func (ld *boltLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
|
||||
vcl.ShowMessage("Bolt doesn't support querying")
|
||||
}
|
||||
|
||||
var _ loadedDatabase = &boltLoadedDatabase{} // interface assertion
|
||||
|
||||
//
|
||||
@@ -110,6 +119,7 @@ func (f *TMainForm) boltAddDatabaseFromFile(path string) {
|
||||
ld.nav = f.Buckets.Items().Add(nil, ld.displayName)
|
||||
ld.nav.SetHasChildren(true) // dynamically populate in OnNavExpanding
|
||||
ld.nav.SetImageIndex(imgDatabase)
|
||||
ld.nav.SetSelectedIndex(imgDatabase)
|
||||
navData := &navData{
|
||||
ld: ld,
|
||||
childrenLoaded: false, // will be loaded dynamically
|
||||
|
||||
11
format.go
11
format.go
@@ -14,9 +14,14 @@ func formatUtf8(in []byte) string {
|
||||
}
|
||||
|
||||
func formatAny(in interface{}) string {
|
||||
if _, ok := in.([]byte); ok {
|
||||
switch in := in.(type) {
|
||||
case []byte:
|
||||
return "<<binary>>"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%#v", in)
|
||||
case string:
|
||||
return in
|
||||
|
||||
default:
|
||||
return fmt.Sprintf("%#v", in)
|
||||
}
|
||||
}
|
||||
|
||||
43
go.mod
43
go.mod
@@ -3,8 +3,43 @@ module yvbolt
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/ying32/govcl v2.2.3+incompatible // indirect
|
||||
go.etcd.io/bbolt v1.3.10 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
github.com/dgraph-io/badger/v4 v4.2.0
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/ying32/govcl v2.2.3+incompatible
|
||||
go.etcd.io/bbolt v1.4.0-alpha.1
|
||||
modernc.org/sqlite v1.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/flatbuffers v1.12.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.12.3 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
go.opencensus.io v0.22.5 // indirect
|
||||
golang.org/x/mod v0.3.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
)
|
||||
|
||||
159
go.sum
159
go.sum
@@ -1,8 +1,159 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs=
|
||||
github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak=
|
||||
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
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=
|
||||
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
||||
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.4.0-alpha.1 h1:3yrqQzbRRPFPdOMWS/QQIVxVnzSkAZQYeWlZFv1kbj4=
|
||||
go.etcd.io/bbolt v1.4.0-alpha.1/go.mod h1:S/Z/Nm3iuOnyO1W4XuFfPci51Gj6F1Hv0z8hisyYYOw=
|
||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI=
|
||||
modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
||||
|
||||
22
images.go
22
images.go
@@ -10,14 +10,17 @@ import (
|
||||
var assetsFs embed.FS
|
||||
|
||||
const (
|
||||
imgDatabase = 0
|
||||
imgDatabaseAdd = 1
|
||||
imgDatabaseDelete = 2
|
||||
imgDatabaseSave = 3
|
||||
imgTable = 4
|
||||
imgTableAdd = 5
|
||||
imgTableDelete = 6
|
||||
imgTableSave = 7
|
||||
imgChartBar int32 = iota
|
||||
imgDatabase
|
||||
imgDatabaseAdd
|
||||
imgDatabaseDelete
|
||||
imgDatabaseLightning
|
||||
imgDatabaseSave
|
||||
imgLightning
|
||||
imgTable
|
||||
imgTableAdd
|
||||
imgTableDelete
|
||||
imgTableSave
|
||||
)
|
||||
|
||||
func loadImages(owner vcl.IComponent) *vcl.TImageList {
|
||||
@@ -37,10 +40,13 @@ func loadImages(owner vcl.IComponent) *vcl.TImageList {
|
||||
}
|
||||
|
||||
ilist := vcl.NewImageList(owner)
|
||||
ilist.Add(mustLoad("assets/chart_bar.png"), nil)
|
||||
ilist.Add(mustLoad("assets/database.png"), nil)
|
||||
ilist.Add(mustLoad("assets/database_add.png"), nil)
|
||||
ilist.Add(mustLoad("assets/database_delete.png"), nil)
|
||||
ilist.Add(mustLoad("assets/database_lightning.png"), nil)
|
||||
ilist.Add(mustLoad("assets/database_save.png"), nil)
|
||||
ilist.Add(mustLoad("assets/lightning.png"), nil)
|
||||
ilist.Add(mustLoad("assets/table.png"), nil)
|
||||
ilist.Add(mustLoad("assets/table_add.png"), nil)
|
||||
ilist.Add(mustLoad("assets/table_delete.png"), nil)
|
||||
|
||||
@@ -7,8 +7,10 @@ import (
|
||||
// loadedDatabase is a DB-agnostic interface for each loaded database.
|
||||
type loadedDatabase interface {
|
||||
DisplayName() string
|
||||
DriverName() string
|
||||
RootElement() *vcl.TTreeNode
|
||||
RenderForNav(f *TMainForm, ndata *navData)
|
||||
ExecQuery(query string, resultArea *vcl.TListView)
|
||||
NavChildren(ndata *navData) ([]string, error)
|
||||
Keepalive(ndata *navData)
|
||||
}
|
||||
|
||||
118
main.go
118
main.go
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
|
||||
const (
|
||||
MY_SPACING = 6
|
||||
MY_HEIGHT = 90
|
||||
MY_WIDTH = 180
|
||||
)
|
||||
|
||||
@@ -21,10 +21,13 @@ type TMainForm struct {
|
||||
|
||||
ImageList *vcl.TImageList
|
||||
Menu *vcl.TMainMenu
|
||||
StatusBar *vcl.TStatusBar
|
||||
Buckets *vcl.TTreeView
|
||||
Tabs *vcl.TPageControl
|
||||
propertiesBox *vcl.TMemo
|
||||
contentBox *vcl.TListView
|
||||
queryInput *vcl.TMemo
|
||||
queryResult *vcl.TListView
|
||||
|
||||
dbs []loadedDatabase
|
||||
}
|
||||
@@ -41,6 +44,7 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
||||
f.ImageList = loadImages(f)
|
||||
|
||||
f.SetCaption("yvbolt")
|
||||
f.ImageList.GetIcon(imgDatabaseLightning, f.Icon())
|
||||
|
||||
mnuFile := vcl.NewMenuItem(f)
|
||||
mnuFile.SetCaption("File")
|
||||
@@ -58,6 +62,12 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
||||
mnuFileSqliteOpen.SetOnClick(f.OnMnuFileSqliteOpenClick)
|
||||
mnuFile.Add(mnuFileSqliteOpen)
|
||||
|
||||
mnuFileBadgerOpen := vcl.NewMenuItem(mnuFile)
|
||||
mnuFileBadgerOpen.SetCaption("Open Badger v4 database...")
|
||||
mnuFileBadgerOpen.SetImageIndex(imgDatabaseAdd)
|
||||
mnuFileBadgerOpen.SetOnClick(f.OnMnuFileBadgerOpenClick)
|
||||
mnuFile.Add(mnuFileBadgerOpen)
|
||||
|
||||
mnuFileSqliteMemory := vcl.NewMenuItem(mnuFile)
|
||||
mnuFileSqliteMemory.SetCaption("New SQLite in-memory database")
|
||||
mnuFileSqliteMemory.SetImageIndex(imgDatabaseAdd)
|
||||
@@ -73,9 +83,28 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
||||
mnuFileExit.SetOnClick(f.OnMnuFileExitClick)
|
||||
mnuFile.Add(mnuFileExit)
|
||||
|
||||
mnuQuery := vcl.NewMenuItem(f)
|
||||
mnuQuery.SetCaption("Query")
|
||||
|
||||
mnuQueryExecute := vcl.NewMenuItem(mnuQuery)
|
||||
mnuQueryExecute.SetCaption("Execute")
|
||||
mnuQueryExecute.SetShortCutFromString("F5")
|
||||
mnuQueryExecute.SetOnClick(f.OnQueryExecute)
|
||||
mnuQueryExecute.SetImageIndex(imgLightning)
|
||||
mnuQuery.Add(mnuQueryExecute)
|
||||
|
||||
f.Menu = vcl.NewMainMenu(f)
|
||||
f.Menu.SetImages(f.ImageList)
|
||||
f.Menu.Items().Add(mnuFile)
|
||||
f.Menu.Items().Add(mnuQuery)
|
||||
|
||||
//
|
||||
|
||||
f.StatusBar = vcl.NewStatusBar(f)
|
||||
f.StatusBar.SetParent(f)
|
||||
f.StatusBar.SetSimpleText("")
|
||||
|
||||
//
|
||||
|
||||
f.Buckets = vcl.NewTreeView(f)
|
||||
f.Buckets.SetParent(f)
|
||||
@@ -93,11 +122,13 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
||||
|
||||
f.Tabs = vcl.NewPageControl(f)
|
||||
f.Tabs.SetParent(f)
|
||||
f.Tabs.SetAlign(types.AlClient)
|
||||
f.Tabs.SetAlign(types.AlClient) // fill remaining space
|
||||
f.Tabs.SetImages(f.ImageList)
|
||||
|
||||
propertiesTab := vcl.NewTabSheet(f.Tabs)
|
||||
propertiesTab.SetParent(f.Tabs)
|
||||
propertiesTab.SetCaption("Properties")
|
||||
propertiesTab.SetImageIndex(imgChartBar)
|
||||
|
||||
f.propertiesBox = vcl.NewMemo(propertiesTab)
|
||||
f.propertiesBox.SetParent(propertiesTab)
|
||||
@@ -111,6 +142,7 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
||||
dataTab := vcl.NewTabSheet(f.Tabs)
|
||||
dataTab.SetParent(f.Tabs)
|
||||
dataTab.SetCaption("Data")
|
||||
dataTab.SetImageIndex(imgTable)
|
||||
|
||||
f.contentBox = vcl.NewListView(dataTab)
|
||||
f.contentBox.SetParent(dataTab)
|
||||
@@ -120,6 +152,53 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
|
||||
f.contentBox.SetAutoWidthLastColumn(true)
|
||||
f.contentBox.SetReadOnly(true)
|
||||
f.contentBox.Columns().Clear()
|
||||
|
||||
queryTab := vcl.NewTabSheet(f.Tabs)
|
||||
queryTab.SetParent(f.Tabs)
|
||||
queryTab.SetCaption("Query")
|
||||
queryTab.SetImageIndex(imgLightning)
|
||||
|
||||
queryButtonBar := vcl.NewToolBar(queryTab)
|
||||
queryButtonBar.SetParent(queryTab)
|
||||
queryButtonBar.SetAlign(types.AlTop)
|
||||
queryButtonBar.BorderSpacing().SetLeft(MY_SPACING)
|
||||
queryButtonBar.BorderSpacing().SetTop(MY_SPACING)
|
||||
queryButtonBar.BorderSpacing().SetBottom(0)
|
||||
queryButtonBar.BorderSpacing().SetRight(MY_SPACING)
|
||||
queryButtonBar.SetImages(f.ImageList)
|
||||
queryButtonBar.SetShowCaptions(true)
|
||||
|
||||
queryExecBtn := vcl.NewToolButton(queryButtonBar)
|
||||
queryExecBtn.SetParent(queryButtonBar)
|
||||
queryExecBtn.SetCaption("Execute")
|
||||
// queryExecBtn.SetImageIndex(imgLightning)
|
||||
queryExecBtn.SetOnClick(f.OnQueryExecute)
|
||||
|
||||
f.queryInput = vcl.NewMemo(queryTab)
|
||||
f.queryInput.SetParent(queryTab)
|
||||
f.queryInput.SetHeight(MY_HEIGHT)
|
||||
f.queryInput.SetAlign(types.AlTop)
|
||||
f.queryInput.SetTop(1)
|
||||
f.queryInput.Font().SetName("monospace")
|
||||
f.queryInput.BorderSpacing().SetLeft(MY_SPACING)
|
||||
f.queryInput.BorderSpacing().SetTop(0)
|
||||
f.queryInput.BorderSpacing().SetRight(MY_SPACING)
|
||||
|
||||
vsplit := vcl.NewSplitter(queryTab)
|
||||
vsplit.SetParent(queryTab)
|
||||
vsplit.SetAlign(types.AlTop)
|
||||
vsplit.SetTop(2)
|
||||
|
||||
f.queryResult = vcl.NewListView(queryTab)
|
||||
f.queryResult.SetParent(queryTab)
|
||||
f.queryResult.SetAlign(types.AlClient) // fill remaining space
|
||||
f.queryResult.SetViewStyle(types.VsReport) // "Report style" i.e. has columns
|
||||
f.queryResult.SetAutoWidthLastColumn(true)
|
||||
f.queryResult.SetReadOnly(true)
|
||||
f.queryResult.Columns().Clear()
|
||||
f.queryResult.BorderSpacing().SetLeft(MY_SPACING)
|
||||
f.queryResult.BorderSpacing().SetRight(MY_SPACING)
|
||||
f.queryResult.BorderSpacing().SetBottom(MY_SPACING)
|
||||
}
|
||||
|
||||
func (f *TMainForm) OnMnuFileOpenClick(sender vcl.IObject) {
|
||||
@@ -142,12 +221,39 @@ func (f *TMainForm) OnMnuFileSqliteOpenClick(sender vcl.IObject) {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TMainForm) OnMnuFileBadgerOpenClick(sender vcl.IObject) {
|
||||
dlg := vcl.NewSelectDirectoryDialog(f)
|
||||
dlg.SetTitle("Select a database directory...")
|
||||
ret := dlg.Execute() // Fake blocking
|
||||
if ret {
|
||||
f.badgerAddDatabaseFromDirectory(dlg.FileName())
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TMainForm) OnMnuFileSqliteMemoryClick(sender vcl.IObject) {
|
||||
f.sqliteAddDatabaseFromFile(`:memory:`)
|
||||
}
|
||||
|
||||
func (f *TMainForm) OnMnuFileExitClick(sender vcl.IObject) {
|
||||
os.Exit(0)
|
||||
f.Close()
|
||||
}
|
||||
|
||||
func (f *TMainForm) OnQueryExecute(sender vcl.IObject) {
|
||||
// If query tab is not selected, switch to it, but do not exec
|
||||
if f.Tabs.ActivePageIndex() != 2 {
|
||||
f.Tabs.SetActivePageIndex(2)
|
||||
return
|
||||
}
|
||||
|
||||
// Execute
|
||||
node := f.Buckets.Selected()
|
||||
if node == nil {
|
||||
vcl.ShowMessage("No database selected")
|
||||
return
|
||||
}
|
||||
|
||||
ndata := (*navData)(node.Data())
|
||||
ndata.ld.ExecQuery(f.queryInput.Text(), f.queryResult)
|
||||
}
|
||||
|
||||
func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) {
|
||||
@@ -158,7 +264,11 @@ func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) {
|
||||
}
|
||||
|
||||
ndata := (*navData)(node.Data())
|
||||
|
||||
ndata.ld.RenderForNav(f, ndata) // Handover to the database type's own renderer function
|
||||
|
||||
// We're in charge of common status bar text updates
|
||||
f.StatusBar.SetSimpleText(ndata.ld.DisplayName() + " | " + ndata.ld.DriverName())
|
||||
}
|
||||
|
||||
func (f *TMainForm) OnNavExpanding(sender vcl.IObject, node *vcl.TTreeNode, allowExpansion *bool) {
|
||||
@@ -196,6 +306,8 @@ func (f *TMainForm) OnNavExpanding(sender vcl.IObject, node *vcl.TTreeNode, allo
|
||||
|
||||
node := f.Buckets.Items().AddChild(node, formatUtf8([]byte(bucketName)))
|
||||
node.SetHasChildren(true) // dynamically populate in OnNavExpanding
|
||||
node.SetImageIndex(imgTable)
|
||||
node.SetSelectedIndex(imgTable)
|
||||
navData := &navData{
|
||||
ld: ndata.ld,
|
||||
childrenLoaded: false, // will be loaded dynamically
|
||||
|
||||
143
sqlite.go
143
sqlite.go
@@ -6,7 +6,6 @@ import (
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/ying32/govcl/vcl"
|
||||
"github.com/ying32/govcl/vcl/types"
|
||||
)
|
||||
@@ -72,36 +71,16 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
// Use SELECT form instead of common PRAGMA table_info so we can just get names
|
||||
// We could possibly get this from the main data select, but this will
|
||||
// work even when there are 0 results
|
||||
colr, err := ld.db.Query(`SELECT name FROM pragma_table_info(?)`, tableName)
|
||||
columnNames, err := ld.sqliteGetColumnNamesForTable(tableName)
|
||||
if err != nil {
|
||||
vcl.ShowMessageFmt("Failed to load columns for table %q: %s", tableName, err.Error())
|
||||
return
|
||||
}
|
||||
defer colr.Close()
|
||||
|
||||
f.contentBox.Columns().Clear()
|
||||
numColumns := 0
|
||||
for colr.Next() {
|
||||
populateColumns(columnNames, f.contentBox)
|
||||
|
||||
var columnName string
|
||||
err = colr.Scan(&columnName)
|
||||
if err != nil {
|
||||
vcl.ShowMessageFmt("Failed to read column names for table %q: %s", tableName, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
col := f.contentBox.Columns().Add()
|
||||
col.SetCaption(columnName)
|
||||
col.SetWidth(MY_WIDTH)
|
||||
col.SetAlignment(types.TaLeftJustify)
|
||||
|
||||
numColumns++
|
||||
}
|
||||
if colr.Err() != nil {
|
||||
vcl.ShowMessageFmt("Failed to load columns for table %q: %s", tableName, err.Error())
|
||||
return
|
||||
}
|
||||
colr.Close() // will be double-closed
|
||||
// Select count(*) so we know to display a warning if there are too many entries
|
||||
// TODO
|
||||
|
||||
// Select * with small limit
|
||||
datar, err := ld.db.Query(`SELECT * FROM ` + tableName + ` LIMIT 1000`) // WARNING can't prepare this parameter, but it comes from the DB (trusted)
|
||||
@@ -110,27 +89,7 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
return
|
||||
}
|
||||
defer datar.Close()
|
||||
|
||||
for datar.Next() {
|
||||
fields := make([]interface{}, numColumns)
|
||||
pfields := make([]interface{}, numColumns)
|
||||
for i := 0; i < numColumns; i += 1 {
|
||||
pfields[i] = &fields[i]
|
||||
}
|
||||
|
||||
err = datar.Scan(pfields...)
|
||||
if err != nil {
|
||||
vcl.ShowMessageFmt("Failed to load data for table %q: %s", tableName, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
dataEntry := f.contentBox.Items().Add()
|
||||
dataEntry.SetCaption(formatAny(fields[0]))
|
||||
for i := 1; i < len(fields); i += 1 {
|
||||
dataEntry.SubItems().Add(formatAny(fields[i]))
|
||||
}
|
||||
|
||||
}
|
||||
populateRows(datar, f.contentBox)
|
||||
|
||||
// We successfully populated the data grid
|
||||
f.contentBox.SetEnabled(true)
|
||||
@@ -142,6 +101,97 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
|
||||
|
||||
}
|
||||
|
||||
func (ld *sqliteLoadedDatabase) sqliteGetColumnNamesForTable(tableName string) ([]string, error) {
|
||||
colr, err := ld.db.Query(`SELECT name FROM pragma_table_info(?)`, tableName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Query: %w", err)
|
||||
}
|
||||
defer colr.Close()
|
||||
|
||||
var ret []string
|
||||
|
||||
for colr.Next() {
|
||||
|
||||
var columnName string
|
||||
err = colr.Scan(&columnName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Scan: %w", colr.Err())
|
||||
}
|
||||
|
||||
ret = append(ret, columnName)
|
||||
}
|
||||
if colr.Err() != nil {
|
||||
return nil, colr.Err()
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func populateColumns(names []string, dest *vcl.TListView) {
|
||||
dest.Columns().Clear()
|
||||
for _, columnName := range names {
|
||||
col := dest.Columns().Add()
|
||||
col.SetCaption(columnName)
|
||||
col.SetWidth(MY_WIDTH)
|
||||
col.SetAlignment(types.TaLeftJustify)
|
||||
}
|
||||
}
|
||||
|
||||
func populateRows(rr *sql.Rows, dest *vcl.TListView) {
|
||||
|
||||
numColumns := int(dest.Columns().Count())
|
||||
|
||||
for rr.Next() {
|
||||
fields := make([]interface{}, numColumns)
|
||||
pfields := make([]interface{}, numColumns)
|
||||
for i := 0; i < numColumns; i += 1 {
|
||||
pfields[i] = &fields[i]
|
||||
}
|
||||
|
||||
err := rr.Scan(pfields...)
|
||||
if err != nil {
|
||||
vcl.ShowMessageFmt("Failed to load data: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
dataEntry := dest.Items().Add()
|
||||
dataEntry.SetCaption(formatAny(fields[0]))
|
||||
for i := 1; i < len(fields); i += 1 {
|
||||
dataEntry.SubItems().Add(formatAny(fields[i]))
|
||||
}
|
||||
|
||||
}
|
||||
if rr.Err() != nil {
|
||||
vcl.ShowMessageFmt("Failed to load data: %s", rr.Err().Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (ld *sqliteLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
|
||||
rr, err := ld.db.Query(query)
|
||||
if err != nil {
|
||||
vcl.ShowMessage(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer rr.Close()
|
||||
|
||||
resultArea.SetEnabled(false)
|
||||
resultArea.Clear()
|
||||
|
||||
columns, err := rr.Columns()
|
||||
if err != nil {
|
||||
vcl.ShowMessage(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
populateColumns(columns, resultArea)
|
||||
|
||||
populateRows(rr, resultArea)
|
||||
|
||||
resultArea.SetEnabled(true)
|
||||
}
|
||||
|
||||
func (ld *sqliteLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
|
||||
|
||||
if len(ndata.bucketPath) == 0 {
|
||||
@@ -201,6 +251,7 @@ func (f *TMainForm) sqliteAddDatabaseFromFile(path string) {
|
||||
|
||||
ld.nav = f.Buckets.Items().Add(nil, ld.displayName)
|
||||
ld.nav.SetImageIndex(imgDatabase)
|
||||
ld.nav.SetSelectedIndex(imgDatabase)
|
||||
ld.nav.SetHasChildren(true) // dynamically populate in OnNavExpanding
|
||||
navData := &navData{
|
||||
ld: ld,
|
||||
|
||||
12
sqlite_cgo.go
Normal file
12
sqlite_cgo.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//+build cgo
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
sqlite3 "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func (ld *sqliteLoadedDatabase) DriverName() string {
|
||||
ver1, _, _ := sqlite3.Version()
|
||||
return "SQLite " + ver1
|
||||
}
|
||||
11
sqlite_nocgo.go
Normal file
11
sqlite_nocgo.go
Normal file
@@ -0,0 +1,11 @@
|
||||
//+build !cgo
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func (ld *sqliteLoadedDatabase) DriverName() string {
|
||||
return "SQLite (modernc.org)"
|
||||
}
|
||||
Reference in New Issue
Block a user