Changelog
View SourceAll notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.3.1] - 2026-06-10
Fixed
- edoc: module header used raw
=<and<table>which the edoc XML parser rejects, breakingrebar3 edoc/ hex doc generation.
[0.3.0] - 2026-06-10
Added
- Strict mode (
strict => true): records every applied migration in a<table>_historytable and failsup/1,2with{error, {out_of_order, Versions}}when a file version=<current was never applied — closes the silent-skip gap of timestamp versioning with multiple developers. First run backfills history;force/2rebuilds it;drop/1drops it;dry_runbypasses it. - New optional driver callback
applied_versions/2; implemented in the PG, MySQL and SQLite drivers. create/2: generate a{utc_timestamp}_{title}.up/.down.sqlpair (UTCYYYYMMDDHHMMSSversion, +1 bump on same-second collision, UTF-8 titles supported).- CLI escript
erlang_migrate_cli(rebar3 escriptize):new <title> [dir]— file generation only, keeping the zero-dependency design.
Fixed
- UTF-8:
erlang_migrate_sourcecrashed (badarg) on non-Latin1 migration titles (list_to_binary→unicode:characters_to_binary); core log lines used~sinstead of~ts, mojibaking UTF-8 titles.
[0.2.3] - 2026-06-06
Fixed
set_version(all drivers): wrap DELETE+UPSERT in a single transaction to prevent partial state on process crash (#1)run_one_up/run_one_down: retryset_version(false)on transient failure afterexec_sqlsucceeds — configurable viaset_version_retriesandset_version_retry_ms(#2)exec_sql(MySQL, SQLite): wrap inBEGIN/COMMIT/ROLLBACK— was unprotected, PG already had it (#5)force/2: validate that the requested version exists in source files whendiris configured; rejects phantom versions like999999(#6)- Table name regex: allow schema-qualified names (
public.schema_migrations) in all three drivers (#11)
Added
- GracefulStop: send
erlang_migrate_abortto the migration process to abort between migrations (MigPid ! erlang_migrate_abort) (#3) - dry_run mode: set
dry_run => truein Config to log what would be applied without touching the database (#14) - Structured logging: logger callback now supports
fun(Level, Meta, Msg)(3-arity) in addition to the existingfun(Level, Msg)—Metais a map carryingversion,title,dry_run, etc. (#16) - Log "applied up N" / "applied down N" on success for each migration (#16)
goto/2: usedropwhile/takewhilefor O(range) traversal instead of full-list comprehension (#15)
Tests
- 95 EUnit tests (was 57), 0 failures; 38 new tests covering all fixes above
[0.2.2] - 2026-05-28
Fixed
exec_sql(PG): replace broken SAVEPOINT approach withBEGIN/COMMIT/ROLLBACKset_version(all drivers): replace non-atomic DELETE+INSERT with upsert (PG:ON CONFLICT DO UPDATE, MySQL:REPLACE INTO, SQLite:INSERT OR REPLACE)apply_up/apply_down/version/force: replaceok = Driver:...badmatch patterns withcaseexpressions for proper{error, _}propagation- Table name injection: add
validate_table_name/1whitelist (^[a-zA-Z_][a-zA-Z0-9_]*$) in all three drivers driver/1: validate driver module is loaded viacode:which/1before use- Remove dead code:
clear_dirty/2export and function body removed fromerlang_migrate_pg - Fix
current_version/2edoc: correct return type to{ok, Version, Dirty} - README: fix PostgreSQL minimum version (
18+→10+), sync install example versions to0.2.2, fix epgsql version to4.8.0, correct Migration history table description
Refactored
- Extract
run_one_up/6andrun_one_down/7to reduceapply_up/apply_downnesting from 5 to 1 level
[0.2.1] - 2026-05-28
Fixed
- Fix edoc module header: use backtick+single-quote syntax (dir') instead of double backtick to resolve doc chunk generation error
[0.2.0] - 2026-05-28
Added
- Multi-database support via
driverconfig key (defaults toerlang_migrate_pg) erlang_migrate_mysql— MySQL 8+ driver usingmysql-otp,GET_LOCKadvisory lockerlang_migrate_sqlite— SQLite 3+ driver usingesqlite, OTPglobal:set_lockerlang_migrate_driverbehaviour — implement all 8 callbacks to add a new databasedown/1— roll back all applied migrationsgoto/2— jump to any version (auto up or down)version/1now returns{ok, Version, Dirty}(was{ok, Version})lock_timeoutconfig key — advisory lock wait timeout in ms (default 15 000)loggerconfig key — optionalfun(Level, Msg) -> okcallback- 33 EUnit tests for core logic + 24 tests for MySQL/SQLite drivers (57 total)
Changed
- Single-row tracking table — aligned with golang-migrate:
set_versionis nowDELETE + INSERTso the table always holds at most one row;force/2is correct with no stale dirty rows - Zero hard dependencies —
epgsql,mysql,esqliteare all opt-in; add only the driver you use to your ownrebar.config tableconfig key — customise the tracking table name without touchingerlang_migratesource
Fixed
force/2stale-dirty-row bug (multi-row model left dirty rows thatforcecould not clear; single-row model eliminates the issue)erlang:phash2range — was16#7FFFFFFFFFFFFFFF(too large); now1 bsl 30
[0.1.0] - 2026-05-28
Added
- Initial release
- Plain SQL migration files (
.up.sql/.down.sqlpairs) - Sequential integer versioning (
000001_title.up.sql) - Migration state stored in
schema_migrationstable in the target PostgreSQL database - PostgreSQL advisory lock — safe for concurrent multi-node Erlang clusters
- Dirty state machine — blocks further runs after partial failure
force/2to recover from dirty stateup/1,up/2— apply all or N pending migrationsdown/2— roll back N applied migrationsversion/1— query current versiondrop/1— drop theschema_migrationstable- Minimal dependencies: only
epgsql - 7 EUnit tests for file scanner (
erlang_migrate_source)