Files
gh-lmorchard-lmorchard-agen…/skills/go-cli-builder/assets/templates/database.go.template
2025-11-30 08:37:58 +08:00

128 lines
3.0 KiB
Plaintext

package database
import (
"database/sql"
_ "embed"
"fmt"
"os"
"path/filepath"
_ "github.com/mattn/go-sqlite3"
)
//go:embed schema.sql
var schemaSQL string
// DB wraps a SQLite database connection
type DB struct {
conn *sql.DB
}
// New creates and initializes a new database connection
func New(dbPath string) (*DB, error) {
// Ensure directory exists
dir := filepath.Dir(dbPath)
if dir != "." && dir != "/" {
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("failed to create database directory: %w", err)
}
}
// Open database connection
conn, err := sql.Open("sqlite3", fmt.Sprintf("%s?_foreign_keys=ON&_journal_mode=WAL", dbPath))
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
// Set connection pool limits (SQLite works best with limited concurrency)
conn.SetMaxOpenConns(1)
conn.SetMaxIdleConns(1)
db := &DB{conn: conn}
// Initialize schema and run migrations
if err := db.InitSchema(); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to initialize schema: %w", err)
}
if err := db.RunMigrations(); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to run migrations: %w", err)
}
return db, nil
}
// Close closes the database connection
func (db *DB) Close() error {
if db.conn != nil {
return db.conn.Close()
}
return nil
}
// InitSchema creates the initial database schema
func (db *DB) InitSchema() error {
_, err := db.conn.Exec(schemaSQL)
if err != nil {
return fmt.Errorf("failed to execute schema: %w", err)
}
return nil
}
// IsInitialized checks if the database has been initialized
func (db *DB) IsInitialized() (bool, error) {
// Check if schema_migrations table exists
var count int
err := db.conn.QueryRow(`
SELECT COUNT(*)
FROM sqlite_master
WHERE type='table' AND name='schema_migrations'
`).Scan(&count)
if err != nil {
return false, fmt.Errorf("failed to check initialization: %w", err)
}
return count > 0, nil
}
// GetMigrationVersion returns the current migration version
func (db *DB) GetMigrationVersion() (int, error) {
var version int
err := db.conn.QueryRow("SELECT COALESCE(MAX(version), 0) FROM schema_migrations").Scan(&version)
if err != nil {
return 0, fmt.Errorf("failed to get migration version: %w", err)
}
return version, nil
}
// ApplyMigration applies a specific migration
func (db *DB) ApplyMigration(version int, sql string) error {
tx, err := db.conn.Begin()
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() { _ = tx.Rollback() }()
// Execute migration SQL
if _, err := tx.Exec(sql); err != nil {
return fmt.Errorf("failed to execute migration %d: %w", version, err)
}
// Record migration
if _, err := tx.Exec(
"INSERT INTO schema_migrations (version) VALUES (?)",
version,
); err != nil {
return fmt.Errorf("failed to record migration %d: %w", version, err)
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("failed to commit migration %d: %w", version, err)
}
return nil
}