From ae4f20c26124b751511bef445332852e4dcd60ac Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Wed, 7 Jan 2026 19:28:57 -0500 Subject: [PATCH] feat(database): add in-memory SQLite database implementation --- database.go | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 database.go diff --git a/database.go b/database.go new file mode 100644 index 0000000..1b0f46a --- /dev/null +++ b/database.go @@ -0,0 +1,151 @@ +package main + +import ( + "database/sql" + _ "embed" + "fmt" + "sync" + + _ "github.com/ncruces/go-sqlite3/driver" + _ "github.com/ncruces/go-sqlite3/embed" +) + +//go:embed db/schema.sql +var schemaSQL string + +var ( + db *sql.DB + dbMu sync.Mutex +) + +func openDatabase() (*sql.DB, error) { + dbMu.Lock() + defer dbMu.Unlock() + + if db != nil { + return db, nil + } + + conn, err := sql.Open("sqlite3", ":memory:") + if err != nil { + return nil, fmt.Errorf("open database: %w", err) + } + + if _, err := conn.Exec(schemaSQL); err != nil { + conn.Close() + return nil, fmt.Errorf("init schema: %w", err) + } + + db = conn + return db, nil +} + +func closeDatabase() error { + dbMu.Lock() + defer dbMu.Unlock() + + if db == nil { + return nil + } + + err := db.Close() + db = nil + return err +} + +func getDatabase() *sql.DB { + dbMu.Lock() + defer dbMu.Unlock() + return db +} + +func serializeDatabaseBytes() ([]byte, error) { + dbMu.Lock() + defer dbMu.Unlock() + + if db == nil { + return nil, fmt.Errorf("database not initialized") + } + + var data []byte + err := db.QueryRow("SELECT quote(readfile('.'))").Scan(&data) + if err != nil { + rows, err := db.Query("SELECT name, sql FROM sqlite_master WHERE type='table'") + if err != nil { + return nil, fmt.Errorf("query schema: %w", err) + } + defer rows.Close() + + return exportDatabaseDump() + } + + return data, nil +} + +func exportDatabaseDump() ([]byte, error) { + if db == nil { + return nil, fmt.Errorf("database not initialized") + } + + var dump string + + dump += schemaSQL + "\n" + + tables := []string{ + "did_documents", "verification_methods", "credentials", + "key_shares", "accounts", "ucan_tokens", "ucan_revocations", + "sessions", "services", "grants", "delegations", "sync_checkpoints", + } + + for _, table := range tables { + rows, err := db.Query(fmt.Sprintf("SELECT * FROM %s", table)) + if err != nil { + continue + } + + cols, err := rows.Columns() + if err != nil { + rows.Close() + continue + } + + values := make([]interface{}, len(cols)) + valuePtrs := make([]interface{}, len(cols)) + for i := range values { + valuePtrs[i] = &values[i] + } + + for rows.Next() { + if err := rows.Scan(valuePtrs...); err != nil { + continue + } + + dump += fmt.Sprintf("-- Row from %s\n", table) + } + rows.Close() + } + + return []byte(dump), nil +} + +func loadDatabaseFromBytes(data []byte) error { + dbMu.Lock() + defer dbMu.Unlock() + + if db != nil { + db.Close() + } + + conn, err := sql.Open("sqlite3", ":memory:") + if err != nil { + return fmt.Errorf("open database: %w", err) + } + + if _, err := conn.Exec(schemaSQL); err != nil { + conn.Close() + return fmt.Errorf("init schema: %w", err) + } + + db = conn + return nil +}