chore: sync from fn-registry agent
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// Package datafactory opens and migrates data_factory.db. It is consumed by
|
||||
// projects/fn_monitoring/apps/sqlite_api to expose REST + WS endpoints over
|
||||
// the data_factory schema (nodes, connections, runs, databases).
|
||||
//
|
||||
// The C++ app in apps/data_factory (main.cpp) does NOT depend on this
|
||||
// package. The Go subpackage lives in apps/data_factory/datafactory/ so it
|
||||
// does not collide with main.cpp at the Go-toolchain level.
|
||||
//
|
||||
// Migrations are read from disk at runtime (apps/data_factory/migrations/
|
||||
// relative to FN_REGISTRY_ROOT, or via an explicit migrationsDir argument).
|
||||
// This keeps the SQL as the single source of truth — the C++ side reads
|
||||
// the same files via its own bridge.
|
||||
package datafactory
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// Open opens data_factory.db at dbPath (creating the file and the parent
|
||||
// directory if needed) and applies every *.sql under migrationsDir in
|
||||
// lexical order. Idempotent — re-running on an already-migrated DB is a
|
||||
// no-op (duplicate-column / already-exists errors are tolerated).
|
||||
// Returns a RW *sql.DB; callers are responsible for Close().
|
||||
func Open(dbPath, migrationsDir string) (*sql.DB, error) {
|
||||
if err := os.MkdirAll(filepath.Dir(dbPath), 0o755); err != nil {
|
||||
return nil, fmt.Errorf("datafactory: mkdir %s: %w", filepath.Dir(dbPath), err)
|
||||
}
|
||||
dsn := dbPath + "?_journal_mode=WAL&_busy_timeout=5000&_foreign_keys=on"
|
||||
db, err := sql.Open("sqlite3", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("datafactory: open %s: %w", dbPath, err)
|
||||
}
|
||||
if err := db.Ping(); err != nil {
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("datafactory: ping %s: %w", dbPath, err)
|
||||
}
|
||||
if err := applyMigrations(db, migrationsDir); err != nil {
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("datafactory: migrate: %w", err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func applyMigrations(conn *sql.DB, dir string) error {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read migrations dir %s: %w", dir, err)
|
||||
}
|
||||
names := make([]string, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() && strings.HasSuffix(e.Name(), ".sql") {
|
||||
names = append(names, e.Name())
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, n := range names {
|
||||
b, err := os.ReadFile(filepath.Join(dir, n))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: read: %w", n, err)
|
||||
}
|
||||
if _, err := conn.Exec(string(b)); err != nil {
|
||||
if strings.Contains(err.Error(), "duplicate column") ||
|
||||
strings.Contains(err.Error(), "already exists") {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("%s: %w", n, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user