bolt: support editing
This commit is contained in:
parent
8af27f8834
commit
f78eec1872
@ -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 {
|
||||||
|
52
db_bolt.go
52
db_bolt.go
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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
20
main.go
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user