--- name: sqlite_apply_versioned_migrations kind: function lang: go domain: infra version: "1.0.0" purity: impure signature: "func ApplyVersionedMigrations(conn *sql.DB, fsys fs.FS, dir string) error" description: "Aplica migraciones SQLite pendientes desde un fs.FS con tracking explicito de versiones en schema_migrations. Cada migracion corre en su propia transaccion; si falla se hace rollback y se retorna el error sin avanzar la version." tags: [sqlite, migrations, schema, versioned, transactional, embed, infra, pendiente-usar] uses_functions: [] uses_types: [error_go_core] returns: [] returns_optional: false error_type: "error_go_core" imports: - "database/sql" - "io/fs" - "fmt" - "path" - "sort" - "strconv" - "strings" - "time" tested: true tests: - "aplica todas desde cero y registra schema_migrations" - "idempotente por version, no vuelve a aplicar" - "migracion intermedia falla, version anterior no avanza" - "archivos sin prefijo numerico se ignoran" - "dir vacio no error y no crea schema_migrations" test_file_path: "functions/infra/sqlite_apply_versioned_migrations_test.go" file_path: "functions/infra/sqlite_apply_versioned_migrations.go" params: - name: conn desc: "Conexion SQLite abierta. Debe apuntar a la base de datos donde se gestionaran las migraciones." - name: fsys desc: "Sistema de archivos (embed.FS, os.DirFS, fstest.MapFS, etc.) que contiene el directorio de migraciones." - name: dir desc: "Ruta del directorio dentro de fsys que contiene los archivos .sql (ej. 'migrations')." output: "nil si todas las migraciones pendientes se aplicaron correctamente; error descriptivo con el nombre del archivo que fallo en caso contrario." --- ## Ejemplo ```go //go:embed migrations/*.sql var migrationsFS embed.FS func openDB(path string) (*sql.DB, error) { db, err := sql.Open("sqlite3", path+"?_foreign_keys=on&_journal_mode=WAL") if err != nil { return nil, err } if err := infra.ApplyVersionedMigrations(db, migrationsFS, "migrations"); err != nil { db.Close() return nil, fmt.Errorf("migrations: %w", err) } return db, nil } ``` ## Diferencias vs sqlite_apply_migrations_go_infra (naive) | Aspecto | `sqlite_apply_versioned_migrations` (esta) | `sqlite_apply_migrations` (naive) | |---|---|---| | Tracking | Tabla `schema_migrations` — sabe exactamente que versiones estan aplicadas | Sin tabla de tracking — reaplica todo cada vez | | Idempotencia | Por numero de version (`version <= current` se salta) | Por error — ignora "duplicate column / already exists" | | Transacciones | Una transaccion por archivo — rollback limpio si falla | Sin transacciones — sentencias sueltas | | Parsing SQL | Confia en SQLite multi-statement (`tx.Exec` del contenido completo) | Split manual por `;` (fragil con strings) | | Uso ideal | Apps con `operations.db` propias, BDs con datos vivos, deploy multi-PC | Bootstrap rapido, scripts de seed, migraciones sin estado persistente | **Regla practica:** usa `sqlite_apply_versioned_migrations` cuando necesites saber que se aplico, cuando, y garantizar que un fallo no deja la BD a medio migrar. Usa `sqlite_apply_migrations` para scripts de seed o inicializacion que no importa repetir. ## Notas - La funcion esta adaptada directamente de `fn_operations/migrate.go` — el patron probado en produccion del registry. - `schema_migrations` guarda `version` (INTEGER PK), `name` (filename), `applied_at` (RFC3339 UTC). - El SQL de cada archivo se ejecuta con una sola llamada `tx.Exec(content)` sin split por `;`. Esto funciona correctamente con el driver `go-sqlite3` (CGO) que soporta multi-statement. No usar con drivers pure-Go que no soporten multi-statement. - Archivos sin prefijo numerico parseable o sin extension `.sql` se ignoran silenciosamente. - Compatible con `embed.FS`, `os.DirFS`, y `fstest.MapFS` (util en tests).