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 // Badger always uses Key + Value as the columns colKey := f.contentBox.Columns().Add() colKey.Title().SetCaption("Key") colKey.SetWidth(MY_WIDTH) colKey.SetAlignment(types.TaLeftJustify) colVal := f.contentBox.Columns().Add() colVal.Title().SetCaption("Value") err := ld.db.View(func(txn *badger.Txn) error { // 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 { rpos := f.contentBox.RowCount() f.contentBox.SetRowCount(rpos + 1) f.contentBox.SetCells(0, rpos, formatUtf8(k)) f.contentBox.SetCells(1, rpos, 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) NavContext(ndata *navData) ([]contextAction, error) { return nil, nil // No special actions are supported } func (ld *badgerLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) { vcl.ShowMessage("Badger doesn't support querying") } func (ld *badgerLoadedDatabase) Close() { _ = ld.db.Close() ld.arena = nil } var _ loadedDatabase = &badgerLoadedDatabase{} // interface assertion // func (f *TMainForm) badgerAddDatabaseFromMemory() { f.badgerAddDatabaseFrom(badger.DefaultOptions("").WithInMemory(true)) } func (f *TMainForm) badgerAddDatabaseFromDirectory(path string) { f.badgerAddDatabaseFrom(badger.DefaultOptions(path)) } func (f *TMainForm) badgerAddDatabaseFrom(opts badger.Options) { // TODO load in background thread to stop blocking the UI db, err := badger.Open(opts) if err != nil { vcl.ShowMessage(fmt.Sprintf("Failed to load database: %s", err.Error())) return } ld := &badgerLoadedDatabase{ db: db, } if opts.Dir == "" { ld.displayName = ":memory:" // SQLite-style naming } else { ld.displayName = filepath.Base(opts.Dir) } 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) f.Buckets.SetSelected(ld.nav) // Select new element ld.Keepalive(navData) }