sqlite: basic integration for the cli driver

This commit is contained in:
mappu 2024-06-29 12:13:34 +12:00
parent 471737f421
commit 650c9e7183
4 changed files with 64 additions and 32 deletions

View File

@ -6,6 +6,8 @@ import (
"path/filepath"
"unsafe"
_ "yvbolt/sqliteclidriver"
"github.com/ying32/govcl/vcl"
"github.com/ying32/govcl/vcl/types"
)
@ -102,7 +104,7 @@ func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) {
}
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 {
return nil, fmt.Errorf("Query: %w", err)
}
@ -243,10 +245,15 @@ var _ loadedDatabase = &sqliteLoadedDatabase{} // interface assertion
//
func (f *TMainForm) sqliteAddDatabaseFromFile(path string) {
func (f *TMainForm) sqliteAddDatabaseFromFile(path string, cliDriver bool) {
driver := "sqlite3"
if cliDriver {
driver = "sqliteclidriver"
}
// TODO load in background thread to stop blocking the UI
db, err := sql.Open("sqlite3", path)
db, err := sql.Open(driver, path)
if err != nil {
vcl.ShowMessage(fmt.Sprintf("Failed to load database '%s': %s", path, err.Error()))
return

19
main.go
View File

@ -21,8 +21,11 @@ const (
type TMainForm struct {
*vcl.TForm
ImageList *vcl.TImageList
Menu *vcl.TMainMenu
ImageList *vcl.TImageList
Menu *vcl.TMainMenu
SQLiteUseCliDriver *vcl.TMenuItem
StatusBar *vcl.TStatusBar
Buckets *vcl.TTreeView
Tabs *vcl.TPageControl
@ -103,6 +106,10 @@ func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
vcl_menuitem(mnuFileSqlite, "Open database...", imgDatabaseAdd, f.OnMnuFileSqliteOpenClick)
vcl_menuitem(mnuFileSqlite, "New in-memory database", imgDatabaseAdd, f.OnMnuFileSqliteMemoryClick)
vcl_menuseparator(mnuFileSqlite)
f.SQLiteUseCliDriver = vcl_menuitem(mnuFileSqlite, "Connect using CLI driver (experimental)", -1, nil)
f.SQLiteUseCliDriver.SetAutoCheck(true)
//
vcl_menuseparator(mnuFile)
@ -282,12 +289,14 @@ func (f *TMainForm) OnMnuFileBoltOpenReadonlyClick(sender vcl.IObject) {
}
func (f *TMainForm) OnMnuFileSqliteOpenClick(sender vcl.IObject) {
cliDriver := f.SQLiteUseCliDriver.Checked()
dlg := vcl.NewOpenDialog(f)
dlg.SetTitle("Select a database file...")
dlg.SetFilter("SQLite database|*.db;*.db3;*.sqlite;*.sqlite3|All files|*.*")
ret := dlg.Execute() // Fake blocking
if ret {
f.sqliteAddDatabaseFromFile(dlg.FileName())
f.sqliteAddDatabaseFromFile(dlg.FileName(), cliDriver)
}
}
@ -318,7 +327,9 @@ func (f *TMainForm) OnMnuFilePebbleMemoryClick(sender vcl.IObject) {
}
func (f *TMainForm) OnMnuFileSqliteMemoryClick(sender vcl.IObject) {
f.sqliteAddDatabaseFromFile(`:memory:`)
cliDriver := f.SQLiteUseCliDriver.Checked()
f.sqliteAddDatabaseFromFile(`:memory:`, cliDriver)
}
func (f *TMainForm) OnMnuFileRedisConnectClick(sender vcl.IObject) {

View File

@ -1,7 +1,16 @@
// sqliteclidriver is a database/sql driver for SQLite implemented on top of
// the sqlite3 command-line tool. This allows it to be used over remote SSH
// connections.
//
// Functionality is limited.
//
// Known caveats:
// - Lexer only understands ? if it's separated by spaces
// - Bad error handling
// - Few supported types
// - Has to escape parameters for CLI instead of preparing them, so not safe for untrusted usage
// - No context handling
// - No way to configure sqlite3 command line path
package sqliteclidriver
import (

View File

@ -14,39 +14,44 @@ func TestSqliteCliDriver(t *testing.T) {
_, err = db.Exec(`CREATE TABLE my_test_table ( id INTEGER PRIMARY KEY, extra TEXT NOT NULL );`)
require.NoError(t, err)
_, err = db.Exec(`INSERT INTO my_test_table (id, extra) VALUES (1337, "abcdef"), (9001, "whoop");`)
_, err = db.Exec(`INSERT INTO my_test_table (id, extra) VALUES ( ? , ? ), ( ? , ? );`, 1337, "abcdef", 9001, "whoop")
require.NoError(t, err)
res, err := db.Query(`SELECT * FROM my_test_table ORDER BY id ASC;`)
require.NoError(t, err)
// Repeat this part to ensure we can make followup queries on the same connection
for i := 0; i < 3; i++ {
cols, err := res.Columns()
require.NoError(t, err)
require.EqualValues(t, cols, []string{"id", "extra"})
res, err := db.Query(`SELECT * FROM my_test_table ORDER BY id ASC;`)
require.NoError(t, err)
var rowCount int = 0
cols, err := res.Columns()
require.NoError(t, err)
require.EqualValues(t, cols, []string{"id", "extra"})
for res.Next() {
rowCount++
var rowCount int = 0
var idVal int
var extraVal string
err = res.Scan(&idVal, &extraVal)
if err != nil {
t.Fatal(err)
for res.Next() {
rowCount++
var idVal int
var extraVal string
err = res.Scan(&idVal, &extraVal)
if err != nil {
t.Fatal(err)
}
switch rowCount {
case 1:
require.EqualValues(t, 1337, idVal)
require.EqualValues(t, "abcdef", extraVal)
case 2:
require.EqualValues(t, 9001, idVal)
require.EqualValues(t, "whoop", extraVal)
}
}
switch rowCount {
case 1:
require.EqualValues(t, 1337, idVal)
require.EqualValues(t, "abcdef", extraVal)
require.Equal(t, rowCount, 2)
case 2:
require.EqualValues(t, 9001, idVal)
require.EqualValues(t, "whoop", extraVal)
}
}
require.Equal(t, rowCount, 2)
}