bolt: support editing

This commit is contained in:
mappu 2024-07-06 11:45:41 +12:00
parent 8af27f8834
commit f78eec1872
9 changed files with 103 additions and 1 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"unsafe" "unsafe"
@ -83,6 +84,10 @@ func (ld *badgerLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
f.contentBox.SetEnabled(true) f.contentBox.SetEnabled(true)
} }
func (n *badgerLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return errors.New("Editing is not supported")
}
func (ld *badgerLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { func (ld *badgerLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
// In the Badger implementation, there is only one child: "Data" // In the Badger implementation, there is only one child: "Data"
if len(ndata.bucketPath) == 0 { if len(ndata.bucketPath) == 0 {

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"sort" "sort"
@ -80,6 +81,57 @@ func (ld *boltLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
f.contentBox.SetEnabled(true) f.contentBox.SetEnabled(true)
} }
func (n *boltLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
if n.db.IsReadOnly() {
return errors.New("Database was opened read-only")
}
// We have rendered row IDs, need to convert back to a bolt primary key
// TODO stash the real key inside f.contentBox.Objects()
// FIXME breaks if you try and edit the primary key(!)
primaryKeyForRendered := func(rowid int32) []byte {
return []byte(f.contentBox.Cells(0, rowid))
}
return n.db.Update(func(tx *bbolt.Tx) error {
// Get current bucket handle
b := boltTargetBucket(tx, ndata.bucketPath)
// Edit
for rowid, _ /*editcells*/ := range f.updateRows {
k := primaryKeyForRendered(rowid)
v := f.contentBox.Cells(1, rowid) // There's only one value cell
err := b.Put(k, []byte(v))
if err != nil {
return fmt.Errorf("Updating cell %q: %w", formatUtf8(k), err)
}
}
// Delete by key (affects rowids after re-render)
for rowid, _ := range f.deleteRows {
k := primaryKeyForRendered(rowid)
err := b.Delete(k)
if err != nil {
return fmt.Errorf("Deleting cell %q: %w", formatUtf8(k), err)
}
}
// Insert all new entries
for rowid, _ := range f.insertRows {
k := primaryKeyForRendered(rowid)
v := f.contentBox.Cells(1, rowid) // There's only one value cell
err := b.Put(k, []byte(v))
if err != nil {
return fmt.Errorf("Inserting cell %q: %w", formatUtf8(k), err)
}
}
// Done
return nil
})
}
func (ld *boltLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { func (ld *boltLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
// In the bolt implementation, the nav is a recursive tree of child buckets // In the bolt implementation, the nav is a recursive tree of child buckets
return boltChildBucketNames(ld.db, ndata.bucketPath) return boltChildBucketNames(ld.db, ndata.bucketPath)

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -69,6 +70,10 @@ func (ld *debconfLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
f.contentBox.SetEnabled(true) f.contentBox.SetEnabled(true)
} }
func (n *debconfLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return errors.New("Editing is not supported")
}
func (ld *debconfLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { func (ld *debconfLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
// In the debconf implementation, there is only one child: "Data" // In the debconf implementation, there is only one child: "Data"
if len(ndata.bucketPath) == 0 { if len(ndata.bucketPath) == 0 {

View File

@ -1,6 +1,8 @@
package main package main
import ( import (
"errors"
"github.com/ying32/govcl/vcl" "github.com/ying32/govcl/vcl"
) )
@ -22,6 +24,10 @@ func (n *noLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
f.propertiesBox.SetText("Open a database to get started...") f.propertiesBox.SetText("Open a database to get started...")
} }
func (n *noLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return errors.New("Editing is not supported")
}
func (n *noLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) { func (n *noLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringGrid) {
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"unsafe" "unsafe"
@ -74,6 +75,10 @@ func (ld *pebbleLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
f.contentBox.SetEnabled(true) f.contentBox.SetEnabled(true)
} }
func (n *pebbleLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return errors.New("Editing is not supported")
}
func (ld *pebbleLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { func (ld *pebbleLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
// In the pebble implementation, there is only one child: "Data" // In the pebble implementation, there is only one child: "Data"
if len(ndata.bucketPath) == 0 { if len(ndata.bucketPath) == 0 {

View File

@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strconv" "strconv"
"unsafe" "unsafe"
@ -137,6 +138,10 @@ func (ld *redisLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
} }
} }
func (n *redisLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return errors.New("Editing is not supported")
}
func (ld *redisLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { func (ld *redisLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
// ctx := context.Background() // ctx := context.Background()

View File

@ -2,6 +2,7 @@ package main
import ( import (
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"unsafe" "unsafe"
@ -96,6 +97,10 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
} }
func (n *sqliteLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) error {
return errors.New("Editing is not supported")
}
func (ld *sqliteLoadedDatabase) sqliteGetColumnNamesForTable(tableName string) ([]string, error) { func (ld *sqliteLoadedDatabase) sqliteGetColumnNamesForTable(tableName string) ([]string, error) {
colr, err := ld.db.Query(`SELECT name FROM pragma_table_info( ? )`, tableName) colr, err := ld.db.Query(`SELECT name FROM pragma_table_info( ? )`, tableName)
if err != nil { if err != nil {

View File

@ -19,6 +19,7 @@ type loadedDatabase interface {
DriverName() string DriverName() string
RootElement() *vcl.TTreeNode RootElement() *vcl.TTreeNode
RenderForNav(f *TMainForm, ndata *navData) RenderForNav(f *TMainForm, ndata *navData)
ApplyChanges(f *TMainForm, ndata *navData) error
ExecQuery(query string, resultArea *vcl.TStringGrid) ExecQuery(query string, resultArea *vcl.TStringGrid)
NavChildren(ndata *navData) ([]string, error) NavChildren(ndata *navData) ([]string, error)
NavContext(ndata *navData) ([]contextAction, error) NavContext(ndata *navData) ([]contextAction, error)

20
main.go
View File

@ -647,7 +647,25 @@ func (f *TMainForm) OnDataCommitClick(sender vcl.IObject) {
return // Not an active data view return // Not an active data view
} }
// TODO node := f.Buckets.Selected()
if node == nil {
vcl.ShowMessage("No database selected")
return
}
scrollPos := f.contentBox.TopRow()
ndata := (*navData)(node.Data())
err := ndata.ld.ApplyChanges(f, ndata)
if err != nil {
vcl.ShowMessage(err.Error())
}
// Refresh content
f.OnNavChange(f.Buckets, node) // Refresh RHS pane/data content
// Preserve scroll position
f.contentBox.SetTopRow(scrollPos)
} }
func (f *TMainForm) OnNavContextClose(sender vcl.IObject) { func (f *TMainForm) OnNavContextClose(sender vcl.IObject) {