{"id":16871875,"url":"https://github.com/luislavena/drift","last_synced_at":"2025-09-05T06:31:32.552Z","repository":{"id":214071905,"uuid":"440214160","full_name":"luislavena/drift","owner":"luislavena","description":"SQL-driven schema migration tool and library for Crystal","archived":false,"fork":false,"pushed_at":"2025-07-22T21:23:26.000Z","size":111,"stargazers_count":27,"open_issues_count":4,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-07-22T23:25:26.204Z","etag":null,"topics":["cli","crystal","library","sql","sqlite"],"latest_commit_sha":null,"homepage":"","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/luislavena.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"luislavena"}},"created_at":"2021-12-20T15:15:33.000Z","updated_at":"2025-07-22T21:19:50.000Z","dependencies_parsed_at":"2023-12-25T17:39:15.981Z","dependency_job_id":"21939574-ff30-4685-8573-fab9fd18a4bf","html_url":"https://github.com/luislavena/drift","commit_stats":null,"previous_names":["luislavena/drift"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/luislavena/drift","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luislavena%2Fdrift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luislavena%2Fdrift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luislavena%2Fdrift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luislavena%2Fdrift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/luislavena","download_url":"https://codeload.github.com/luislavena/drift/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luislavena%2Fdrift/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273722703,"owners_count":25156300,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","crystal","library","sql","sqlite"],"created_at":"2024-10-13T15:10:02.427Z","updated_at":"2025-09-05T06:31:32.132Z","avatar_url":"https://github.com/luislavena.png","language":"Crystal","readme":"# Drift\n\u003e SQL-driven schema migration tool and library\n\nDrift provides a framework-agnostic schema migration approach based on SQL\nfiles. No magic DSL to learn, no weird ORM limitations to deal with, just\nplain and simple SQL files in a folder, tracked and applied or reverted\n(rollback) sequentially or in batches, and that's all.\n\n\u003e [!WARNING]\n\u003e Drift is still in **early development** and the UX/DX around the CLI and\n\u003e library/classes might change between versions.\n\n## Features\n\n* Self-contained migration files (both migrate and rollback statements within\n  same `.sql` file).\n* No new DSL or ORM to learn, just plain `.sql` files in a folder.\n* A CLI to facilitate generation and execution of migrations.\n* An optional library to be integrated within your project (Eg. to check and\n  run migrations on start).\n* Currently works with SQLite3, but can be adapted to be DB-agnostic,\n  compatible with any [Crystal DB](https://github.com/crystal-lang/crystal-db)\n  adapter.\n\n## Overview\n\nDrift aims to be a slim layer that orchestrates executing SQL statements\nagainst a database. This applies to both [the CLI](#as-tool-cli) and\n[the library](#as-library-crystal-shard) (Crystal shard).\n\nEach migration file must contain two special comments before any SQL\nstatements: `drift:migrate` to indicate that the following statements should\nbe executed when migrating and `drift:rollback` to indicate the statements\nshould be executed when rolling back the migration.\n\nEach type (migrate, rollback) within the file can contain multiple SQL\nstatements. Statements can span multiple lines, but all must be properly\nterminated using `;`.\n\n```sql\n-- drift:migrate\nCREATE TABLE users (\n  id INTEGER PRIMARY KEY,\n  name TEXT,\n  phone TEXT\n);\n\n-- drift:rollback\nDROP TABLE IF EXISTS users;\n```\n\nFor complex SQL statements that include semicolons within the statement itself\n(like triggers, functions, or procedures), you can use the special comments\n`-- drift:begin` and `-- drift:end` to wrap the entire statement:\n\n```sql\n-- drift:migrate\n-- drift:begin\nCREATE TRIGGER update_timestamp AFTER\nUPDATE ON users BEGIN\nUPDATE users\nSET\n    last_modified = CURRENT_TIMESTAMP\nWHERE\n    id = NEW.id;\n\nINSERT INTO\n    audit_log (user_id, action)\nVALUES\n    (NEW.id, 'updated');\n\nEND;\n-- drift:end\n\n-- drift:rollback\nDROP TRIGGER IF EXISTS update_timestamp;\n```\n\nEach migration must be identified by a unique ID. The recommended pattern for\nthis is to generate those IDs using dates and time. Eg. `20220601204500` as\nID translates to a migration created June 1st, 2022 at 8:45pm.\n\nThe CLI uses this convention when creating new migration files.\n\nThese migration files will be applied (migrate) one by one by executing each\nof the SQL statements present on each migration.\n\nOnce completed, information about each applied migration will be stored within\nthe database itself, so can be used later to determine which one could be\nrolled back, which ones were applied and when. All this information is\nstored in a dedicated table named `drift_migrations`.\n\nWhen rolling back, the applied migrations will be executed\nin reverse order using the information on the previously mentioned table.\n\n## Requirements\n\nDrift CLI is a standalone, self-contained executable capable of connecting to\nSQLite databases.\n\nDrift (as library) only depends on Crystal's\n[`db`](https://github.com/crystal-lang/crystal-db) common API. To use it with\nto specific adapters, you need to add the respective dependencies and require\nthem part of your application. See more about in the\n[library usage](#as-library-crystal-shard) section.\n\n## Usage\n\n### As tool (CLI)\n\nDrift CLI can be used standalone of any framework or tool to manage the\nstructure of your database.\n\nTo simplify its usage, it follows these conventions:\n\n* Migrations are stored and retrieved from `database/migrations` directory.\n  This directory will be automatically created when creating your\n  first migration.\n* You can override this default by using `--path` option.\n* To select which database to use, you can use `--db` option. The value for\n  this must be a valid [connection URI](https://crystal-lang.org/reference/1.4/database/#open-database).\n* If no option is provided, the CLI uses `DB_URL` environment variable\n  instead.\n\n#### Commands\n\nTo create a new migration:\n\n```console\n$ drift new CreateUsers\nCreated: database/migrations/20220601204500_create_users.sql\n```\n\nTo apply the migrations:\n\n```console\n$ drift migrate --db sqlite3:app.db\nMigrating: 20220601204500_create_users\nMigrated:  20220601204500_create_users (10.31ms)\n```\n\n1. Drift looks at all of the migration files in your `database/migrations`\n  directory.\n2. It queries the `drift_migrations` table to see which migrations have and\n  haven't been run.\n3. Any migration that does not appear in the `drift_migrations` table is\n  considered pending and is executed, as described in the\n  [overview](#overview) section.\n\nTo verify which migrations were applied:\n\n```console\n$ drift status --db sqlite3:app.db\n+--------------------------------------+------+-------+---------------------+----------+\n| Migration                            | Ran? | Batch | Applied At          | Duration |\n+--------------------------------------+------+-------+---------------------+----------+\n| 20220601204500_create_users          | Yes  | 1     | 2022-06-01 20:49:13 |  10.31ms |\n| 20220602123215_create_articles       |      |       |                     |          |\n+--------------------------------------+------+-------+---------------------+----------+\n```\n\nEach time migrations are applied, a new *batch* is defined. This allow\nrollback to revert all the migrations that were applied in a single batch.\n\n```console\n$ drift rollback\nRolling back: 20220601204500_create_users\nRolled back:  20220601204500_create_users (2.03ms)\n```\n\nWhile Drift doesn't offer a mechanism to drop your database and start from\nzero, it offers a way to rollback all the migrations to it's original\nstate:\n\n```console\n$ drift reset\nRolling back: 20220602123215_create_articles\nRolled back:  20220602123215_create_articles (4.33ms)\nRolling back: 20220601204500_create_users\nRolled back:  20220601204500_create_users (2.03ms)\n```\n\n### As library (Crystal shard)\n\nOutside of the CLI, you can streamline the usage of Drift as part of your\napplication:\n\n```crystal\nrequire \"sqlite3\"\nrequire \"drift\"\n\ndb = DB.connect \"sqlite3:app.db\"\n\nmigrator = Drift::Migrator.from_path(db, \"database/migrations\")\nmigrator.apply!\n\ndb.close\n```\n\nThe above is a simplified version of what happens when doing `drift migrate`\nin the CLI. For example, you could apply these migrations as part of your\napplication start process.\n\nInternally, the library interconnects the following elements:\n\nA **migration** (`Drift::Migration`): represents each individual SQL file. It\ncontains all the SQL statements found within the SQL file that can be used to\napply or rollback the changes. Each migration must have a unique ID that\nidentifies itself, helping differentiate it from others and useful to keep\ntrack and order of application.\n\nThe **context** (`Drift::Context`): represents a collection of migrations that\nwere loaded (parsed) from the filesystem, hardcoded or bundled within the\napplication. This is used as lookup table when mapping applied migration IDs\nto the respective files.\n\nThe **migrator** (`Drift::Migrator`): in charge of orchestrating the\ncollection of migrations (context) against the database connection. To keep\ntrack of the state changes (which migration were applied, when were applied),\nthe migrator uses a dedicated table named `drift_migrations` that is\nautomatically created if not found.\n\n#### Embedding migrations\n\nBy default, Drift will load and use migration files from the filesystem. This\napproach works great during development, but it increases complexity for\ndistribution of binaries.\n\nTo help with that, `Drift.embed_as` macro is available, which will collect\nall the migration files from the filesystem and bundles them within the\ngenerated executable, removing the need to distribute them along your\napplication.\n\n```crystal\nrequire \"sqlite3\"\nrequire \"drift\"\n\nDrift.embed_as(\"my_migrations\", \"database/migrations\")\n\ndb = DB.connect \"sqlite3:app.db\"\n\nmigrator = Drift::Migrator.new(db, my_migrations)\nmigrator.apply!\n\ndb.close\n```\n\nIn the above example, `Drift.embed_as` created `my_migrations` method\nbundling all the migrations found in `database/migrations` directory.\n\nWhen using classes or modules, you can also define instance or class methods\nby prepending `self.` to the method name to use by Drift.\n\n## Contribution policy\n\nInspired by [Litestream](https://github.com/benbjohnson/litestream) and\n[SQLite](https://sqlite.org/copyright.html#notopencontrib), this project is\nopen to code contributions for bug fixes only. Features carry a long-term\nburden so they will not be accepted at this time. Please\n[submit an issue](https://github.com/luislavena/drift/issues/new) if you have\na feature you would like to request or discuss.\n\n## License\n\nLicensed under the Apache License, Version 2.0. You may obtain a copy of\nthe license [here](./LICENSE).\n","funding_links":["https://github.com/sponsors/luislavena"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluislavena%2Fdrift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fluislavena%2Fdrift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluislavena%2Fdrift/lists"}