Files
yellowjacket/backend/database/database.go
T

111 lines
2.7 KiB
Go

// Package database provides SQLite database access.
package database
import (
"context"
"database/sql"
"embed"
"fmt"
"io/fs"
"log/slog"
"path"
_ "modernc.org/sqlite" // Register sqlite driver.
"yellowjacket/backend/database/sql/sqlcgen"
"yellowjacket/backend/system"
)
//go:generate go tool sqlc generate
//go:embed sql/schemas/*.sql
var schemas embed.FS
// DB wraps the SQLite database connection and queries.
type DB struct {
db *sql.DB
Ctx context.Context
Queries *sqlcgen.Queries
logger *slog.Logger
}
// NewDB opens the database and applies schema migrations.
func NewDB(logger *slog.Logger) (*DB, error) {
dbCtx := context.Background()
userDataDir, err := system.GetUserDataDirPath()
if err != nil {
return nil, fmt.Errorf("could not get user data directory: %w", err)
}
sqliteDBFilePath := path.Join(userDataDir, "yj.db")
logger.Debug("opening sqlite database", "filepath", sqliteDBFilePath)
db, err := sql.Open("sqlite", sqliteDBFilePath+"?_busy_timeout=5000&_journal_mode=WAL")
if err != nil {
return nil, fmt.Errorf("could not connect to sqlite database: %w", err)
}
db.SetMaxOpenConns(1) // SQLite only supports one writer at a time
// Execute SQL files from the embedded schemas directory
logger.Debug("reading sql schema files from embedded directory")
dirEntries, err := schemas.ReadDir("sql/schemas")
if err != nil {
return nil, fmt.Errorf("could not read schemas directory: %w", err)
}
logger.Debug("executing all sql schema files")
for _, dirEntry := range dirEntries {
if !dirEntry.IsDir() {
filePath := path.Join("sql/schemas", dirEntry.Name())
sqlContent, err := fs.ReadFile(schemas, filePath)
if err != nil {
return nil, fmt.Errorf("could not read file %s: %w", filePath, err)
}
logger.Debug(
"executing sql schema file",
"filepath",
filePath,
"sql",
string(sqlContent),
)
_, err = db.ExecContext(dbCtx, string(sqlContent)) // Execute the SQL
if err != nil {
return nil, fmt.Errorf("error executing sql from file %s: %w", filePath, err)
}
}
}
// Get generated queries
queries := sqlcgen.New(db)
return &DB{
db: db,
Ctx: dbCtx,
Queries: queries,
logger: logger,
}, err
}
// BeginTx starts a new database transaction.
func (d *DB) BeginTx() (*sql.Tx, error) {
return d.db.BeginTx(d.Ctx, nil)
}
// ExecContext executes a query without returning any rows.
func (d *DB) ExecContext(query string, args ...any) (sql.Result, error) {
return d.db.ExecContext(d.Ctx, query, args...)
}
// QueryContext executes a query that returns rows.
func (d *DB) QueryContext(query string, args ...any) (*sql.Rows, error) {
return d.db.QueryContext(d.Ctx, query, args...)
}