Skip to content

Support MySQL and PostgreSQL databases - #32

Open
luislavena wants to merge 12 commits into
mainfrom
feature-multi-dialect-support
Open

Support MySQL and PostgreSQL databases#32
luislavena wants to merge 12 commits into
mainfrom
feature-multi-dialect-support

Conversation

@luislavena

@luislavena luislavena commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Drift only worked with SQLite, so any project running on MySQL or PostgreSQL had to pick a different migration tool. This change extends both the library and the CLI to all three engines, keeping the same plain SQL files in a folder approach independently of the database you choose.

A new Dialect abstraction owns the SQL used to track applied migrations on each engine and is picked automatically from the live connection, so the Migrator API does not change. The library still depends only on crystal-db; applications require the driver they use. The CLI remains a standalone executable and selects the engine from the connection URI.

The shared lifecycle spec now runs against real MySQL and PostgreSQL servers, wired automatically in the development container and in CI.

Note: MySQL cannot roll back a failed migration batch, since DDL statements trigger an implicit commit. SQLite and PostgreSQL roll back the whole batch. The README covers this caveat.

Breaking changes for library consumers:

  • Database drivers moved to development dependencies, applications now require their own driver (most already do).
  • Drift::Migrator::MigrationEntry is now Drift::MigrationEntry.
    This type is internal, so it should not impact anyone (and if
    someone out there depends on it, we have a different problem to
    talk about).

The library only needs the crystal-db common API, and
applications already require their own driver. Only the CLI
and the specs use the real drivers, and both are built from
this repository.
The upcoming Dialect abstraction returns these entries, so the
type needs to live outside Migrator.
Moving the SQL that tracks applied migrations behind a dialect
allows supporting multiple database engines without touching the
Migrator API. Crystal's DB API cannot expose the URI of a live
connection, so the dialect is picked from the connection class
name.
The Migrator keeps orchestration only and the dialect owns
every query. Behavior on SQLite stays the same.
Only schema creation and catalog inspection need MySQL
specific SQL, the shared tracking queries work unchanged.
Coverage against a live server is gated behind
MYSQL_DATABASE_URL.
crystal-pg only accepts numbered placeholders, so the
parameterized queries are redefined as complete statements
instead of patching the shared SQL. Coverage against a live
server is gated behind POSTGRES_DATABASE_URL.
Writing the Migrator contract once and registering it per
engine holds every dialect to identical expectations. SQLite
runs it first, MySQL and PostgreSQL hook in next.
A fresh clone still runs the SQLite suite with zero setup,
while the development container wires the database URLs
automatically. Skipped engines show as pending instead of
silently shrinking the suite.

MySQL 8.4 defaults to caching_sha2_password, which the
crystal-mysql driver does not implement. Startup flags enable
the native password plugin and make the entrypoint create the
root user with it.
The binary can now migrate any of the three engines by
connection URI. Both drivers are pure Crystal, so static
builds stay self-contained.
Service containers keep the gated groups always running on
pull requests, so a dialect regression fails the build even
when it was never exercised locally. MySQL needs server flags
that service containers cannot pass, so it starts via docker
run instead.
Readers need to know which driver to require and what to
expect from each engine, including MySQL's implicit commits
during failed batches.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant