package db import ( "database/sql" "fmt" "os" _ "modernc.org/sqlite" ) // Store wraps a SQLite database connection for mc-proxy persistence. type Store struct { db *sql.DB } // Open opens (or creates) the SQLite database at path with WAL mode, // foreign keys, and a busy timeout. The file is created with 0600 permissions. func Open(path string) (*Store, error) { // Ensure the file has restrictive permissions if it doesn't exist. if _, err := os.Stat(path); os.IsNotExist(err) { f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0600) if err != nil { return nil, fmt.Errorf("creating database file: %w", err) } f.Close() } db, err := sql.Open("sqlite", path) if err != nil { return nil, fmt.Errorf("opening database: %w", err) } // Apply connection pragmas. pragmas := []string{ "PRAGMA journal_mode = WAL", "PRAGMA foreign_keys = ON", "PRAGMA busy_timeout = 5000", } for _, p := range pragmas { if _, err := db.Exec(p); err != nil { db.Close() return nil, fmt.Errorf("setting pragma %q: %w", p, err) } } return &Store{db: db}, nil } // Close closes the database connection. func (s *Store) Close() error { return s.db.Close() } // IsEmpty returns true if the listeners table has no rows. // Used to determine if the database needs seeding from config. func (s *Store) IsEmpty() (bool, error) { var count int err := s.db.QueryRow("SELECT COUNT(*) FROM listeners").Scan(&count) if err != nil { return false, err } return count == 0, nil }