sqlite: basic editing support
This commit is contained in:
parent
0f2a3e021a
commit
eca27dcd4f
123
db_sqlite.go
123
db_sqlite.go
@ -1,10 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
_ "yvbolt/sqliteclidriver"
|
||||
@ -187,6 +189,127 @@ func (ld *sqliteLoadedDatabase) ExecQuery(query string, resultArea *vcl.TStringG
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *sqliteLoadedDatabase) ApplyChanges(f *TMainForm, ndata *navData) (retErr error) {
|
||||
|
||||
if len(ndata.bucketPath) != 2 {
|
||||
return errors.New("invalid selection")
|
||||
}
|
||||
tableName := ndata.bucketPath[1]
|
||||
|
||||
// We have rendered row IDs, need to convert back to an SQLite primary key
|
||||
// TODO stash the real key inside f.contentBox.Objects()
|
||||
// FIXME breaks if you try and edit the primary key(!)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
tx, err := n.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var commitOK bool = false
|
||||
defer func() {
|
||||
if !commitOK {
|
||||
err := tx.Rollback()
|
||||
if err != nil {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Data grid properties
|
||||
var columnNames []string
|
||||
for i := int32(0); i < f.contentBox.ColCount(); i++ {
|
||||
columnNames = append(columnNames, f.contentBox.Columns().Items(i).Title().Caption())
|
||||
}
|
||||
|
||||
// Query sqlite table metadata to determine which of these is the PRIMARY KEY
|
||||
var primaryColumnName string
|
||||
err = tx.QueryRowContext(ctx, `SELECT l.name FROM pragma_table_info(?) as l WHERE l.pk = 1;`, tableName).Scan(&primaryColumnName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Finding primary key for update: %w", err)
|
||||
}
|
||||
|
||||
// Convert it to an index
|
||||
var primaryColumnIdx int = -1
|
||||
for i := 0; i < len(columnNames); i++ {
|
||||
if columnNames[i] == primaryColumnName {
|
||||
primaryColumnIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if primaryColumnIdx == -1 {
|
||||
return fmt.Errorf("Primary key %q missing from available columns", primaryColumnName)
|
||||
}
|
||||
|
||||
// SQLite can only LIMIT 1 on update/delete if it was compiled with
|
||||
// SQLITE_ENABLE_UPDATE_DELETE_LIMIT, which isn't the case for the mattn
|
||||
// cgo library
|
||||
// Skip that, and just rely on primary key uniqueness
|
||||
|
||||
// Edit
|
||||
for rowid, editcells := range f.updateRows {
|
||||
stmt := `UPDATE "` + tableName + `" SET `
|
||||
params := []interface{}{} // FIXME reinstate types for the driver (although SQLite doesn't mind)
|
||||
|
||||
for ct, cell := range editcells {
|
||||
if ct > 0 {
|
||||
stmt += `, `
|
||||
}
|
||||
stmt += `"` + columnNames[cell] + `" = ?`
|
||||
params = append(params, f.contentBox.Cells(cell, rowid))
|
||||
}
|
||||
stmt += ` WHERE "` + primaryColumnName + `" = ?`
|
||||
pkVal := f.contentBox.Cells(int32(primaryColumnIdx), rowid)
|
||||
params = append(params, pkVal)
|
||||
|
||||
_, err = tx.ExecContext(ctx, stmt, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Updating row %q: %w", pkVal, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete by key (affects rowids after re-render)
|
||||
for rowid, _ := range f.deleteRows {
|
||||
pkVal := f.contentBox.Cells(int32(primaryColumnIdx), rowid)
|
||||
|
||||
stmt := `DELETE FROM "` + tableName + `" WHERE "` + primaryColumnName + `" = ?`
|
||||
|
||||
_, err = tx.ExecContext(ctx, stmt, pkVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Deleting row %q: %w", pkVal, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert all new entries
|
||||
for rowid, _ := range f.insertRows {
|
||||
stmt := `INSERT INTO "` + tableName + `" (` + strings.Join(columnNames, `, `) + `) VALUES (`
|
||||
params := []interface{}{} // FIXME reinstate types for the driver (although SQLite doesn't mind)
|
||||
|
||||
for colid := 0; colid < len(columnNames); colid++ {
|
||||
if colid > 0 {
|
||||
stmt += `, `
|
||||
}
|
||||
stmt += "?"
|
||||
params = append(params, f.contentBox.Cells(int32(colid), rowid))
|
||||
}
|
||||
stmt += `)`
|
||||
|
||||
_, err = tx.ExecContext(ctx, stmt, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Inserting row: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commitOK = true // no need for rollback
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ld *sqliteLoadedDatabase) NavChildren(ndata *navData) ([]string, error) {
|
||||
|
||||
if len(ndata.bucketPath) == 0 {
|
||||
|
Loading…
Reference in New Issue
Block a user