db: return error from RenderForNav, contextAction.Callback

This commit is contained in:
mappu 2024-07-13 18:03:42 +12:00
parent ee3110162b
commit e1a9f187cb
9 changed files with 52 additions and 40 deletions

View File

@ -33,7 +33,7 @@ func (ld *badgerLoadedDatabase) Keepalive(ndata *navData) {
ld.arena = append(ld.arena, ndata) ld.arena = append(ld.arena, ndata)
} }
func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error {
// Load properties // Load properties
@ -74,13 +74,13 @@ func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
return nil return nil
}) })
if err != nil { if err != nil {
vcl.ShowMessage(fmt.Sprintf("Failed to load data: %s", err.Error())) return err
return
} }
// Valid // Valid
vcl_stringgrid_columnwidths(f.contentBox) vcl_stringgrid_columnwidths(f.contentBox)
f.contentBox.SetEnabled(true) f.contentBox.SetEnabled(true)
return nil
} }
func (n *badgerLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error { func (n *badgerLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {

View File

@ -39,7 +39,7 @@ func (ld *boltLoadedDatabase) Keepalive(ndata *navData) {
ld.arena = append(ld.arena, ndata) ld.arena = append(ld.arena, ndata)
} }
func (ld *boltLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { func (ld *boltLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error {
// Load properties // Load properties
@ -72,13 +72,13 @@ func (ld *boltLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
return nil return nil
}) })
if err != nil { if err != nil {
vcl.ShowMessage(fmt.Sprintf("Failed to load data for bucket %q: %s", bucketDisplayName, err.Error())) return err
return
} }
// Valid // Valid
vcl_stringgrid_columnwidths(f.contentBox) vcl_stringgrid_columnwidths(f.contentBox)
f.contentBox.SetEnabled(true) f.contentBox.SetEnabled(true)
return nil
} }
func (n *boltLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error { func (n *boltLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
@ -143,13 +143,14 @@ func (ld *boltLoadedDatabase) NavContext(ndata *navData) (ret []contextAction, e
if len(ndata.bucketPath) > 0 { if len(ndata.bucketPath) > 0 {
ret = append(ret, contextAction{"Delete bucket", ld.DeleteBucket}) ret = append(ret, contextAction{"Delete bucket", ld.DeleteBucket})
} }
return return
} }
func (ld *boltLoadedDatabase) AddChildBucket(ndata *navData) { func (ld *boltLoadedDatabase) AddChildBucket(ndata *navData) error {
bucketName := "" bucketName := ""
if !vcl.InputQuery(APPNAME, "Enter a name for the new bucket:", &bucketName) { if !vcl.InputQuery(APPNAME, "Enter a name for the new bucket:", &bucketName) {
return // cancel return nil // cancel
} }
err := ld.db.Update(func(tx *bbolt.Tx) error { err := ld.db.Update(func(tx *bbolt.Tx) error {
@ -164,11 +165,12 @@ func (ld *boltLoadedDatabase) AddChildBucket(ndata *navData) {
return err return err
}) })
if err != nil { if err != nil {
vcl.ShowMessageFmt("Error adding bucket: %v", err) return fmt.Errorf("Error adding bucket: %w", err)
} }
return nil
} }
func (ld *boltLoadedDatabase) DeleteBucket(ndata *navData) { func (ld *boltLoadedDatabase) DeleteBucket(ndata *navData) error {
err := ld.db.Update(func(tx *bbolt.Tx) error { err := ld.db.Update(func(tx *bbolt.Tx) error {
// Find parent of this bucket. // Find parent of this bucket.
if len(ndata.bucketPath) >= 2 { if len(ndata.bucketPath) >= 2 {
@ -181,8 +183,9 @@ func (ld *boltLoadedDatabase) DeleteBucket(ndata *navData) {
} }
}) })
if err != nil { if err != nil {
vcl.ShowMessageFmt("Error deleting bucket %q: %v", strings.Join(ndata.bucketPath, `/`), err) return fmt.Errorf("Error deleting bucket %q: %w", strings.Join(ndata.bucketPath, `/`), err)
} }
return nil
} }
func (ld *boltLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) error { func (ld *boltLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) error {

View File

@ -35,7 +35,7 @@ func (ld *debconfLoadedDatabase) Keepalive(ndata *navData) {
ld.arena = append(ld.arena, ndata) ld.arena = append(ld.arena, ndata)
} }
func (ld *debconfLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { func (ld *debconfLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error {
// Load properties // Load properties
@ -67,6 +67,7 @@ func (ld *debconfLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
// Valid // Valid
vcl_stringgrid_columnwidths(f.contentBox) vcl_stringgrid_columnwidths(f.contentBox)
f.contentBox.SetEnabled(true) f.contentBox.SetEnabled(true)
return nil
} }
func (n *debconfLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error { func (n *debconfLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {

View File

@ -18,8 +18,9 @@ func (n *noLoadedDatabase) RootElement() *vcl.TTreeNode {
return nil return nil
} }
func (n *noLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { func (n *noLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error {
f.propertiesBox.SetText("Open a database to get started...") f.propertiesBox.SetText("Open a database to get started...")
return nil
} }
func (n *noLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error { func (n *noLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {

View File

@ -35,7 +35,7 @@ func (ld *pebbleLoadedDatabase) Keepalive(ndata *navData) {
ld.arena = append(ld.arena, ndata) ld.arena = append(ld.arena, ndata)
} }
func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error {
ctx := context.Background() ctx := context.Background()
@ -59,8 +59,7 @@ func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
k := itr.Key() k := itr.Key()
v, err := itr.ValueAndErr() v, err := itr.ValueAndErr()
if err != nil { if err != nil {
vcl.ShowMessage(fmt.Sprintf("Failed to load data for key %q: %s", formatAny(k), err.Error())) return fmt.Errorf("Failed to load data for key %q: %w", formatAny(k), err)
return
} }
rpos := f.contentBox.RowCount() rpos := f.contentBox.RowCount()
@ -72,6 +71,7 @@ func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
// Valid // Valid
vcl_stringgrid_columnwidths(f.contentBox) vcl_stringgrid_columnwidths(f.contentBox)
f.contentBox.SetEnabled(true) f.contentBox.SetEnabled(true)
return nil
} }
func (n *pebbleLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error { func (n *pebbleLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {

View File

@ -40,34 +40,32 @@ func (ld *redisLoadedDatabase) Keepalive(ndata *navData) {
ld.arena = append(ld.arena, ndata) ld.arena = append(ld.arena, ndata)
} }
func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error {
ctx := context.Background() ctx := context.Background()
if len(ndata.bucketPath) == 0 { if len(ndata.bucketPath) == 0 {
// Top-level: Show info() on main Properties tab // Top-level: Show info() on main Properties tab
infostr, err := ld.db.Info(ctx).Result() infostr, err := ld.db.Info(ctx).Result()
if err != nil { if err != nil {
vcl.ShowMessage(fmt.Sprintf("Retreiving database info: %v", err)) return fmt.Errorf("Retreiving database info: %w", err)
return
} }
f.propertiesBox.SetText(infostr) f.propertiesBox.SetText(infostr)
// Leave data tab disabled (default behaviour) // Leave data tab disabled (default behaviour)
return nil
} else if len(ndata.bucketPath) == 1 { } else if len(ndata.bucketPath) == 1 {
// One selected database // One selected database
// Figure out its content // Figure out its content
err := ld.db.Do(ctx, "SELECT", ndata.bucketPath[0]).Err() err := ld.db.Do(ctx, "SELECT", ndata.bucketPath[0]).Err()
if err != nil { if err != nil {
vcl.ShowMessage(fmt.Sprintf("Switching to database %q: %v", ndata.bucketPath[0], err)) return fmt.Errorf("Switching to database %q: %w", ndata.bucketPath[0], err)
return
} }
allKeys, err := ld.db.Keys(ctx, "*").Result() allKeys, err := ld.db.Keys(ctx, "*").Result()
if err != nil { if err != nil {
vcl.ShowMessage(fmt.Sprintf("Listing keys in database %q: %v", ndata.bucketPath[0], err)) return fmt.Errorf("Listing keys in database %q: %w", ndata.bucketPath[0], err)
return
} }
f.propertiesBox.SetText(fmt.Sprintf("Database %s\nTotal keys: %d\n", ndata.bucketPath[0], len(allKeys))) f.propertiesBox.SetText(fmt.Sprintf("Database %s\nTotal keys: %d\n", ndata.bucketPath[0], len(allKeys)))
@ -84,8 +82,7 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
for _, key := range allKeys { for _, key := range allKeys {
typeName, err := ld.db.Type(ctx, key).Result() typeName, err := ld.db.Type(ctx, key).Result()
if err != nil { if err != nil {
vcl.ShowMessage(fmt.Sprintf("Loading %q/%q: %v", ndata.bucketPath[0], key, err)) return fmt.Errorf("Loading %q/%q: %w", ndata.bucketPath[0], key, err)
return
} }
rpos := f.contentBox.RowCount() rpos := f.contentBox.RowCount()
@ -97,16 +94,14 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
case "string": case "string":
val, err := ld.db.Get(ctx, key).Result() val, err := ld.db.Get(ctx, key).Result()
if err != nil { if err != nil {
vcl.ShowMessage(fmt.Sprintf("Loading %q/%q: %v", ndata.bucketPath[0], key, err)) return fmt.Errorf("Loading %q/%q: %w", ndata.bucketPath[0], key, err)
return
} }
f.contentBox.SetCells(2, rpos, val) f.contentBox.SetCells(2, rpos, val)
case "hash": case "hash":
val, err := ld.db.HGetAll(ctx, key).Result() val, err := ld.db.HGetAll(ctx, key).Result()
if err != nil { if err != nil {
vcl.ShowMessage(fmt.Sprintf("Loading %q/%q: %v", ndata.bucketPath[0], key, err)) return fmt.Errorf("Loading %q/%q: %w", ndata.bucketPath[0], key, err)
return
} }
// It's a map[string]string // It's a map[string]string
f.contentBox.SetCells(2, rpos, formatAny(val)) f.contentBox.SetCells(2, rpos, formatAny(val))
@ -129,10 +124,10 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
// Valid // Valid
vcl_stringgrid_columnwidths(f.contentBox) vcl_stringgrid_columnwidths(f.contentBox)
f.contentBox.SetEnabled(true) f.contentBox.SetEnabled(true)
return nil
} else { } else {
vcl.ShowMessage(fmt.Sprintf("Unexpected nav position %q", ndata.bucketPath)) return fmt.Errorf("Unexpected nav position %q", ndata.bucketPath)
return
} }
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"unsafe" "unsafe"
@ -36,15 +37,17 @@ func (ld *sqliteLoadedDatabase) Keepalive(ndata *navData) {
ld.arena = append(ld.arena, ndata) ld.arena = append(ld.arena, ndata)
} }
func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) error {
if len(ndata.bucketPath) == 0 { if len(ndata.bucketPath) == 0 {
// Top-level // Top-level
f.propertiesBox.SetText("Please select...") f.propertiesBox.SetText("Please select...")
return nil
} else if len(ndata.bucketPath) == 1 { } else if len(ndata.bucketPath) == 1 {
// Category (tables, ...) // Category (tables, ...)
f.propertiesBox.SetText("Please select...") f.propertiesBox.SetText("Please select...")
return nil
} else if len(ndata.bucketPath) == 2 && ndata.bucketPath[0] == sqliteTablesCaption { } else if len(ndata.bucketPath) == 2 && ndata.bucketPath[0] == sqliteTablesCaption {
// Render for specific table // Render for specific table
@ -67,8 +70,7 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
// work even when there are 0 results // work even when there are 0 results
columnNames, err := ld.sqliteGetColumnNamesForTable(tableName) columnNames, err := ld.sqliteGetColumnNamesForTable(tableName)
if err != nil { if err != nil {
vcl.ShowMessageFmt("Failed to load columns for table %q: %s", tableName, err.Error()) return fmt.Errorf("Failed to load columns for table %q: %w", tableName)
return
} }
populateColumns(columnNames, f.contentBox) populateColumns(columnNames, f.contentBox)
@ -79,8 +81,7 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
// Select * with small limit // 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) datar, err := ld.db.Query(`SELECT * FROM "` + tableName + `" LIMIT 1000`) // WARNING can't prepare this parameter, but it comes from the DB (trusted)
if err != nil { if err != nil {
vcl.ShowMessageFmt("Failed to load data for table %q: %s", tableName, err.Error()) return fmt.Errorf("Failed to load data for table %q: %w", tableName, err)
return
} }
defer datar.Close() defer datar.Close()
populateRows(datar, f.contentBox) populateRows(datar, f.contentBox)
@ -88,9 +89,11 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
// We successfully populated the data grid // We successfully populated the data grid
vcl_stringgrid_columnwidths(f.contentBox) vcl_stringgrid_columnwidths(f.contentBox)
f.contentBox.SetEnabled(true) f.contentBox.SetEnabled(true)
return nil
} else { } else {
// ??? unknown // ??? unknown
return errors.New("?")
} }

View File

@ -11,7 +11,7 @@ var ErrNotSupported error = errors.New("Unsupported action for this database typ
type contextAction struct { type contextAction struct {
Name string Name string
Callback func(ndata *navData) Callback func(ndata *navData) error
} }
// loadedDatabase is a DB-agnostic interface for each loaded database. // loadedDatabase is a DB-agnostic interface for each loaded database.
@ -19,7 +19,7 @@ type loadedDatabase interface {
DisplayName() string DisplayName() string
DriverName() string DriverName() string
RootElement() *vcl.TTreeNode RootElement() *vcl.TTreeNode
RenderForNav(f *TMainForm, ndata *navData) RenderForNav(f *TMainForm, ndata *navData) error
ApplyChanges(f *TMainForm, ndata *navData) error ApplyChanges(f *TMainForm, ndata *navData) error
ExecQuery(query string, resultArea *vcl.TStringGrid) error ExecQuery(query string, resultArea *vcl.TStringGrid) error
NavChildren(ndata *navData) ([]string, error) NavChildren(ndata *navData) ([]string, error)

13
main.go
View File

@ -507,7 +507,10 @@ func (f *TMainForm) OnNavContextPopup(sender vcl.IObject, mousePos types.TPoint,
mnuAction.SetCaption(action.Name) mnuAction.SetCaption(action.Name)
cb := action.Callback // Copy to avoid reuse of loop variable cb := action.Callback // Copy to avoid reuse of loop variable
mnuAction.SetOnClick(func(sender vcl.IObject) { mnuAction.SetOnClick(func(sender vcl.IObject) {
cb(ndata) err = cb(ndata)
if err != nil {
vcl.ShowMessage(err.Error())
}
f.OnNavContextRefresh(curItem, ndata) f.OnNavContextRefresh(curItem, ndata)
}) })
mnu.Items().Add(mnuAction) mnu.Items().Add(mnuAction)
@ -779,7 +782,13 @@ func (f *TMainForm) OnNavChange(sender vcl.IObject, node *vcl.TTreeNode) {
f.propertiesBox.Clear() f.propertiesBox.Clear()
vcl_stringgrid_clear(f.contentBox) vcl_stringgrid_clear(f.contentBox)
ld.RenderForNav(f, ndata) // Handover to the database type's own renderer function err := ld.RenderForNav(f, ndata) // Handover to the database type's own renderer function
if err != nil {
vcl.ShowMessage(err.Error())
// Ensure elements are disabled
f.contentBox.SetEnabled(false)
}
// We're in charge of common status bar text updates // We're in charge of common status bar text updates
f.StatusBar.SetSimpleText(ld.DisplayName() + " | " + ld.DriverName()) f.StatusBar.SetSimpleText(ld.DisplayName() + " | " + ld.DriverName())