package db import ( "database/sql" "fmt" "os" "path/filepath" "syscall" _ "modernc.org/sqlite" ) type DBConfig struct { JournalMode string Synchronous string BusyTimeout int } func OpenDB(path string, config *DBConfig) (*sql.DB, error) { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0755); err != nil { return nil, fmt.Errorf("failed to create database directory: %w", err) } // Check if database file exists and get file info for diagnostics var fileInfo os.FileInfo if info, err := os.Stat(path); err == nil { fileInfo = info } db, err := sql.Open("sqlite", path) if err != nil { return nil, fmt.Errorf("failed to open database: %w", err) } if err := db.Ping(); err != nil { db.Close() // Enhanced error diagnostics diag := fmt.Sprintf("database path: %s", path) if fileInfo != nil { diag += fmt.Sprintf(", file size: %d bytes, mode: %s", fileInfo.Size(), fileInfo.Mode()) } // Check directory permissions if dirInfo, err := os.Stat(dir); err == nil { diag += fmt.Sprintf(", dir mode: %s", dirInfo.Mode()) } // Check if it's a file system issue if pathErr, ok := err.(*os.PathError); ok { if pathErr.Err == syscall.ENOSPC { diag += ", filesystem full" } else if pathErr.Err == syscall.EACCES { diag += ", permission denied" } } return nil, fmt.Errorf("failed to ping database: %w (%s)", err, diag) } // Set default config if not provided if config == nil { config = &DBConfig{ JournalMode: "DELETE", Synchronous: "FULL", BusyTimeout: 10000, } } pragmas := []string{ fmt.Sprintf("PRAGMA journal_mode=%s", config.JournalMode), "PRAGMA foreign_keys=ON", fmt.Sprintf("PRAGMA synchronous=%s", config.Synchronous), fmt.Sprintf("PRAGMA busy_timeout=%d", config.BusyTimeout), "PRAGMA temp_store=MEMORY", "PRAGMA mmap_size=268435456", } for _, pragma := range pragmas { if _, err := db.Exec(pragma); err != nil { db.Close() return nil, fmt.Errorf("failed to set pragma: %w", err) } } return db, nil } func EnsureSchema(db *sql.DB) error { schema := ` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, token TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_users_token ON users(token); ` if _, err := db.Exec(schema); err != nil { return fmt.Errorf("failed to create schema: %w", err) } return nil }