Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c27c936
Restricts Migrator to Database types only
luislavena Oct 11, 2025
3cc1e9e
Refactor Migrator's SQL operations as Dialect
luislavena Oct 11, 2025
cfe6a2a
Reduces data exchange between SQLite/Crystal
luislavena Oct 11, 2025
5439f09
Cleanup make task
luislavena Oct 12, 2025
77e3644
Adds PostgreSQL for local development/testing
luislavena Oct 12, 2025
efe0839
Makes SQLite3 dependency development-only
luislavena Oct 12, 2025
f1eeac8
Initial PostgreSQL dialect implementation
luislavena Oct 12, 2025
8cf086d
Add dialect configuration for specs
luislavena Oct 12, 2025
ad3aaa6
Add for_each_dialect macro for tests
luislavena Oct 12, 2025
4779a19
Update helper methods to require db parameter
luislavena Oct 12, 2025
e5f5bbd
Update .new and .from_path tests to manage db connections
luislavena Oct 12, 2025
1d0094c
Refactor #prepared? and #prepare! tests
luislavena Oct 12, 2025
66e42da
Fix migrator spec compilation errors
luislavena Oct 12, 2025
326c8ee
Refactor apply-related tests for dialect support
luislavena Oct 12, 2025
81b47e1
Refactor rollback tests for dialect support
luislavena Oct 12, 2025
2ace165
Fix for_each_dialect macro to run PostgreSQL tests
luislavena Oct 12, 2025
777e80e
Generalize in-spec SQL to avoid dialect specific issues
luislavena Oct 12, 2025
f2a777e
Adds PostgreSQL for CI tests
luislavena Oct 12, 2025
b691604
Includes PostgreSQL in the CLI
luislavena Oct 12, 2025
7b24ddf
Try mounting service as localhost
luislavena Oct 12, 2025
adf31a2
Adds CHANGELOG entry for new feature
luislavena Oct 12, 2025
a89bd13
Add MySQL driver dependency
luislavena Oct 12, 2025
4998f63
Add MySQL container to Docker Compose
luislavena Oct 12, 2025
17118f0
Implement MySQL dialect
luislavena Oct 12, 2025
bcd3172
Add MySQL support to migrator specs
luislavena Oct 12, 2025
a64bda5
Add MySQL dialect detection test
luislavena Oct 12, 2025
0fb16ea
Add MySQL service to CI pipeline
luislavena Oct 12, 2025
6798dcc
Add MySQL driver to CLI
luislavena Oct 12, 2025
dabef47
Document MySQL support in README
luislavena Oct 12, 2025
64f3050
Adds CHANGELOG entry for MySQL support
luislavena Oct 12, 2025
2f69fd6
Adds MySQL container CHANGELOG entry
luislavena Oct 12, 2025
0495b73
Downgrade MySQL used in tests to support old authentication
luislavena Oct 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/unreleased/added-20251012-201335.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: added
body: Support PostgreSQL as library or CLI
time: 2025-10-12T20:13:35.619382+02:00
custom:
Issue: ""
5 changes: 5 additions & 0 deletions .changes/unreleased/added-20251013-003617.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: added
body: Support MySQL databases as library or via CLI
time: 2025-10-13T00:36:17.19928+02:00
custom:
Issue: ""
5 changes: 5 additions & 0 deletions .changes/unreleased/deprecated-20251011-193908.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: deprecated
body: Limit Drift::Migrator to DB::Database types
time: 2025-10-11T19:39:08.945847+02:00
custom:
Issue: ""
5 changes: 5 additions & 0 deletions .changes/unreleased/improved-20251011-195702.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: improved
body: Abstract Migrator SQL operations as Dialect implementation
time: 2025-10-11T19:57:02.013986+02:00
custom:
Issue: ""
5 changes: 5 additions & 0 deletions .changes/unreleased/improved-20251012-125124.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: improved
body: Optimize rollback plan calculations and memory usage
time: 2025-10-12T12:51:24.699334+02:00
custom:
Issue: ""
5 changes: 5 additions & 0 deletions .changes/unreleased/internal-20251012-133617.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: internal
body: Adds PostgreSQL container for development/testing
time: 2025-10-12T13:36:17.161299+02:00
custom:
Issue: ""
5 changes: 5 additions & 0 deletions .changes/unreleased/internal-20251012-151331.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: internal
body: Move SQLite3 dependency to only development mode
time: 2025-10-12T15:13:31.081418+02:00
custom:
Issue: ""
5 changes: 5 additions & 0 deletions .changes/unreleased/internal-20251013-003809.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: internal
body: Add MySQL container for local and CI testing
time: 2025-10-13T00:38:09.800223+02:00
custom:
Issue: ""
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,39 @@ jobs:

test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_DATABASE: drift_test
MYSQL_USER: drift
MYSQL_PASSWORD: drift
MYSQL_ROOT_PASSWORD: drift_root
options: >-
--health-cmd="mysqladmin ping -h localhost"
--health-interval=10s
--health-timeout=5s
--health-retries=5
ports:
- 3306:3306
postgres:
image: postgres:18-alpine
env:
POSTGRES_DB: drift_test
POSTGRES_USER: drift
POSTGRES_PASSWORD: drift
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: crystal-lang/install-crystal@v1
- run: shards install
- run: crystal spec
env:
MYSQL_DB_URL: mysql://drift:drift@localhost:3306/drift_test
POSTGRES_DB_URL: postgres://drift:drift@localhost:5432/drift_test
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export FIXGID
# Make `help` the default task
.DEFAULT_GOAL := help

.PHONY: build console logs restart setup start stop help
.PHONY: console dev help restart setup stop

console: ## start a console session
@docker compose exec app sh -i 2>/dev/null || docker compose run --rm app -- sh -i
Expand Down
66 changes: 62 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,15 @@ in reverse order using the information on the previously mentioned table.
## Requirements

Drift CLI is a standalone, self-contained executable capable of connecting to
SQLite databases.
the following databases (dialects):

* MySQL
* PostgreSQL
* SQLite3

Drift (as library) only depends on Crystal's
[`db`](https://github.com/crystal-lang/crystal-db) common API. To use it with
to specific adapters, you need to add the respective dependencies and require
specific adapters, you need to add the respective dependencies and require
them part of your application. See more about in the
[library usage](#as-library-crystal-shard) section.

Expand Down Expand Up @@ -189,7 +193,35 @@ application:
require "sqlite3"
require "drift"

db = DB.connect "sqlite3:app.db"
db = DB.open "sqlite3:app.db"

migrator = Drift::Migrator.from_path(db, "database/migrations")
migrator.apply!

db.close
```

For a MySQL database:

```crystal
require "mysql"
require "drift"

db = DB.open "mysql://user:password@localhost/dbname"

migrator = Drift::Migrator.from_path(db, "database/migrations")
migrator.apply!

db.close
```

Or for a PostgreSQL database:

```crystal
require "pg"
require "drift"

db = DB.open "postgres://user:password@localhost/dbname"

migrator = Drift::Migrator.from_path(db, "database/migrations")
migrator.apply!
Expand Down Expand Up @@ -237,7 +269,7 @@ require "drift"

Drift.embed_as("my_migrations", "database/migrations")

db = DB.connect "sqlite3:app.db"
db = DB.open "sqlite3:app.db"

migrator = Drift::Migrator.new(db, my_migrations)
migrator.apply!
Expand All @@ -251,6 +283,32 @@ bundling all the migrations found in `database/migrations` directory.
When using classes or modules, you can also define instance or class methods
by prepending `self.` to the method name to use by Drift.

## Development

### Running tests

By default, tests run against all supported databases (MySQL, PostgreSQL,
SQLite3).

To skip specific databases:

```console
$ SKIP_MYSQL=true crystal spec # Skip MySQL tests
$ SKIP_POSTGRESQL=true crystal spec # Skip PostgreSQL tests
```

Start database services with Docker Compose:

```console
$ docker compose up -d mysql postgres
```

Stop database services:

```console
$ docker compose down
```

## Contribution policy

Inspired by [Litestream](https://github.com/benbjohnson/litestream) and
Expand Down
42 changes: 42 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,57 @@ services:
image: ghcr.io/luislavena/hydrofoil-crystal:${CRYSTAL_VERSION:-1.16}
command: overmind start -f Procfile.dev
working_dir: /workspace/${COMPOSE_PROJECT_NAME}
depends_on:
- mysql
- postgres
environment:
# Workaround Overmind socket issues with Vite
# Ref: https://github.com/luislavena/hydrofoil-crystal/issues/66
- OVERMIND_SOCKET=/tmp/overmind.sock
# Disable Shards' postinstall
- SHARDS_OPTS=--skip-postinstall
# Test DBs
- MYSQL_DB_URL=mysql://drift:drift@mysql:3306/drift_test
- POSTGRES_DB_URL=postgres://drift:drift@postgres:5432/drift_test

# Set these env variables using `export FIXUID=$(id -u) FIXGID=$(id -g)`
user: ${FIXUID:-1000}:${FIXGID:-1000}

volumes:
- .:/workspace/${COMPOSE_PROJECT_NAME}:cached

mysql:
image: mysql:8.0-oracle
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_DATABASE: drift_test
MYSQL_USER: drift
MYSQL_PASSWORD: drift
MYSQL_ROOT_PASSWORD: drift_root
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 3
volumes:
- mysql:/var/lib/mysql

postgres:
image: postgres:18-alpine
environment:
POSTGRES_DB: drift_test
POSTGRES_USER: drift
POSTGRES_PASSWORD: drift
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 5s
timeout: 5s
retries: 3
volumes:
- postgres:/var/lib/postgresql

volumes:
mysql:
driver: local
postgres:
driver: local
8 changes: 8 additions & 0 deletions shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ dependencies:
db:
github: crystal-lang/crystal-db
version: ~> 0.14.0
development_dependencies:
mysql:
github: crystal-lang/crystal-mysql
version: ~> 0.17.0
pg:
github: will/crystal-pg
# FIXME: lock until new crystal-pg release
commit: c5b8ac1ac5713fc58f974d8a4327887a0b297594
sqlite3:
github: crystal-lang/crystal-sqlite3
version: ~> 0.22.0
50 changes: 50 additions & 0 deletions spec/drift/dialect_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright 2022 Luis Lavena
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require "../spec_helper"

require "mysql"
require "pg"
require "sqlite3"

describe Drift::Dialect do
describe ".from_db" do
it "detects SQLite3 dialect" do
db = DB.open("sqlite3:%3Amemory%3A")
dialect = Drift::Dialect.from_db(db)

dialect.should be_a(Drift::Dialect::SQLite3)

db.close
end

it "detects PostgreSQL dialect" do
db = DB.open(ENV["POSTGRES_DB_URL"]? || "postgres://drift:drift@localhost:5432/drift")
dialect = Drift::Dialect.from_db(db)

dialect.should be_a(Drift::Dialect::PostgreSQL)

db.close
end

it "detects MySQL dialect" do
db = DB.open(ENV["MYSQL_DB_URL"]? || "mysql://drift:drift@localhost:3306/drift_test")
dialect = Drift::Dialect.from_db(db)

dialect.should be_a(Drift::Dialect::MySQL)

db.close
end
end
end
Loading