package main import ( "database/sql" "fmt" "path/filepath" "unsafe" _ "github.com/mattn/go-sqlite3" "github.com/ying32/govcl/vcl" ) const ( sqliteTablesCaption = "Tables" ) type sqliteLoadedDatabase struct { displayName string path string db *sql.DB nav *vcl.TTreeNode arena []*navData // keepalive } func (ld *sqliteLoadedDatabase) DisplayName() string { return ld.displayName } func (ld *sqliteLoadedDatabase) RootElement() *vcl.TTreeNode { return ld.nav } func (ld *sqliteLoadedDatabase) Keepalive(ndata *navData) { ld.arena = append(ld.arena, ndata) } func (ld *sqliteLoadedDatabase) RenderForNav(f *TMainForm, ndata *navData) { if len(ndata.bucketPath) == 0 { // Top-level f.propertiesBox.SetText("Please select...") f.contentBox.SetEnabled(false) f.contentBox.Clear() } else if len(ndata.bucketPath) == 1 { // Category (tables, ...) f.propertiesBox.SetText("Please select...") f.contentBox.SetEnabled(false) f.contentBox.Clear() } else if len(ndata.bucketPath) == 2 && ndata.bucketPath[0] == sqliteTablesCaption { // Render for specific table tableName := ndata.bucketPath[1] // Get some basic properties r := ld.db.QueryRow(`SELECT sql FROM sqlite_schema WHERE name = ?;`, tableName) var schemaStmt string err := r.Scan(&schemaStmt) if err != nil { schemaStmt = fmt.Sprintf("* Failed to describe table %q: %s", tableName, err.Error()) } // Display table properties f.propertiesBox.SetText(fmt.Sprintf("Selected table %q\n\nSchema:\n\n%s", tableName, schemaStmt)) // Load column details ld.db.Query(`pragma table_info(?)`, tableName) // Select * with small limit f.contentBox.SetEnabled(false) f.contentBox.Clear() } else { // ??? unknown } } func (ld *sqliteLoadedDatabase) NavChildren(ndata *navData) ([]string, error) { if len(ndata.bucketPath) == 0 { // The top-level children are always: return []string{sqliteTablesCaption}, nil } if len(ndata.bucketPath) == 1 && ndata.bucketPath[0] == sqliteTablesCaption { rr, err := ld.db.Query(`SELECT name FROM sqlite_master WHERE type='table' ORDER BY name ASC;`) if err != nil { return nil, err } defer rr.Close() var gather []string for rr.Next() { var tableName string err = rr.Scan(&tableName) if err != nil { return nil, err } gather = append(gather, tableName) } if rr.Err() != nil { return nil, rr.Err() } return gather, nil } if len(ndata.bucketPath) == 2 { return nil, nil // Never any deeper children } return nil, fmt.Errorf("unknown nav path %#v", ndata.bucketPath) } var _ loadedDatabase = &sqliteLoadedDatabase{} // interface assertion // func (f *TMainForm) sqliteAddDatabaseFromFile(path string) { // TODO load in background thread to stop blocking the UI db, err := sql.Open("sqlite3", path) if err != nil { vcl.ShowMessage(fmt.Sprintf("Failed to load database '%s': %s", path, err.Error())) return } ld := &sqliteLoadedDatabase{ path: path, displayName: filepath.Base(path), db: db, } ld.nav = f.Buckets.Items().Add(nil, ld.displayName) ld.nav.SetImageIndex(imgDatabase) ld.nav.SetHasChildren(true) // dynamically populate in OnNavExpanding navData := &navData{ ld: ld, childrenLoaded: false, // will be loaded dynamically bucketPath: []string{}, // empty = root } ld.nav.SetData(unsafe.Pointer(navData)) f.dbs = append(f.dbs, ld) ld.Keepalive(navData) }