Eboshi is a lightweight Clojure library for managing SQL migrations stored as EDN files and executing them against a relational database. The project provides a migration-runner protocol and a MySQL implementation using next.jdbc.
- Migrations are represented as EDN files with a map shape:
{:name "1600000000000-create-users"
:up ["create table if not exists users (id int auto_increment primary key, name varchar(255));"]
:down ["drop table if exists users;"]}- Files are ordered by filename (timestamp prefix) and the
:namefield is used to track applied migrations. - The
MigrationRunnerprotocol declares two operations:find-last-migration-name— returns the last applied migration name (maybe String)execute!— apply or revert a migration and return the migration map
src/eboshi/services/migrations.clj— high-level migration helpers (create, up!, down!, sync!)src/eboshi/protocols/migration_runner.clj—s/defprotocolfor migration runners (Schema is used for runtime validation)src/eboshi/infra/mysql_migration_runner.clj— MySQL runner implementation usingnext.jdbctest/...— unit and integration tests. Integration tests use Testcontainers'MySQLContainerand require Docker.
Create a database spec for a local MySQL instance (ensure MySQL driver is in your deps):
;; URL string form
(def db-spec "jdbc:mysql://localhost:3306/eboshi?useSSL=false&serverTimezone=UTC&user=root&password=secret")
;; or map form
(def db-spec {:jdbcUrl "jdbc:mysql://localhost:3306/eboshi?useSSL=false&serverTimezone=UTC"
:user "root"
:password "secret"})Create and use the MySQL migration runner:
(require '[eboshi.infra.mysql-migration-runner :as mysql-runner]
'[eboshi.services.migrations :as migrations])
(def runner (mysql-runner/make-mysql-migration-runner db-spec))
(def config {:migrations-dir "/path/to/migrations"})
;; write a migration (helper provided by the library)
(migrations/create config "add-users")
;; apply next pending migration
(migrations/up! config runner)
;; apply all pending migrations
(migrations/sync! config runner)
;; revert last migration
(migrations/down! config runner)You can drive eboshi directly from a configuration EDN file by calling the functions in the eboshi.core namespace. This is useful when running from a REPL, embedding eboshi in scripts, or invoking it from other tooling.
Example config file (eboshi.edn):
{:runner :mysql
:spec "jdbc:mysql://localhost:3306/eboshi?useSSL=false&serverTimezone=UTC&user=root&password=secret"
:config {:migrations-dir "./migrations"}}Notes:
:runnershould match a supported runner keyword (for example:mysql).:speccan be a JDBC URL string or a map, depending on your runner implementation.:configshould include:migrations-dirpointing to your migrations folder.
Call from a REPL:
(require '[eboshi.core :as eboshi.core])
;; apply next pending migration using the config file
(eboshi.core/up!)
;; apply all pending migrations
(eboshi.core/sync!)
;; revert last migration
(eboshi.core/down!)
;; create a new migration file (writes into :migrations-dir defined in the config)
(eboshi.core/create! "add-users")Default config behavior:
- If you omit the
config-file-path, the core functions call the library's default config loader (seeeboshi.services.eboshi/load-config). Pass a path when you need to use a specific EDN file.
Examples in scripts:
- From a script or automation that can run Clojure code, require
eboshi.coreand call the desired function with the path to your EDN config file.
The project includes unit and integration tests. Integration tests use Testcontainers and require Docker to be available.
Using Leiningen:
lein testIf you run only unit tests or want to skip integration tests, use your usual tooling/aliases as configured in the project.
- To add another backend, implement the
eboshi.protocols.migration-runner/MigrationRunnerprotocol. - Keep migration files valid EDN. Avoid embedding functions or non-standard tagged literals.
- Add tests for new behavior and follow existing patterns in
test/.
See LICENSE in the project root.