2024-06-15 00:37:44 +00:00
|
|
|
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
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-27 23:34:00 +00:00
|
|
|
func (ld *badgerLoadedDatabase) NavContext(ndata *navData) ([]contextAction, error) {
|
|
|
|
return nil, nil // No special actions are supported
|
|
|
|
}
|
|
|
|
|
2024-06-15 00:37:44 +00:00
|
|
|
func (ld *badgerLoadedDatabase) ExecQuery(query string, resultArea *vcl.TListView) {
|
|
|
|
vcl.ShowMessage("Badger doesn't support querying")
|
|
|
|
}
|
|
|
|
|
2024-06-23 03:28:15 +00:00
|
|
|
func (ld *badgerLoadedDatabase) Close() {
|
|
|
|
_ = ld.db.Close()
|
|
|
|
ld.arena = nil
|
|
|
|
}
|
|
|
|
|
2024-06-15 00:37:44 +00:00
|
|
|
var _ loadedDatabase = &badgerLoadedDatabase{} // interface assertion
|
|
|
|
|
|
|
|
//
|
|
|
|
|
2024-06-23 04:29:11 +00:00
|
|
|
func (f *TMainForm) badgerAddDatabaseFromMemory() {
|
|
|
|
f.badgerAddDatabaseFrom(badger.DefaultOptions("").WithInMemory(true))
|
|
|
|
}
|
|
|
|
|
2024-06-15 00:37:44 +00:00
|
|
|
func (f *TMainForm) badgerAddDatabaseFromDirectory(path string) {
|
2024-06-23 04:29:11 +00:00
|
|
|
f.badgerAddDatabaseFrom(badger.DefaultOptions(path))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *TMainForm) badgerAddDatabaseFrom(opts badger.Options) {
|
2024-06-15 00:37:44 +00:00
|
|
|
// TODO load in background thread to stop blocking the UI
|
2024-06-23 04:29:11 +00:00
|
|
|
db, err := badger.Open(opts)
|
2024-06-15 00:37:44 +00:00
|
|
|
if err != nil {
|
2024-06-23 04:29:11 +00:00
|
|
|
vcl.ShowMessage(fmt.Sprintf("Failed to load database: %s", err.Error()))
|
2024-06-15 00:37:44 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ld := &badgerLoadedDatabase{
|
2024-06-23 04:29:11 +00:00
|
|
|
db: db,
|
|
|
|
}
|
|
|
|
|
|
|
|
if opts.Dir == "" {
|
|
|
|
ld.displayName = ":memory:" // SQLite-style naming
|
|
|
|
} else {
|
|
|
|
ld.displayName = filepath.Base(opts.Dir)
|
2024-06-15 00:37:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2024-06-23 02:54:49 +00:00
|
|
|
f.Buckets.SetSelected(ld.nav) // Select new element
|
2024-06-15 00:37:44 +00:00
|
|
|
|
|
|
|
ld.Keepalive(navData)
|
|
|
|
}
|