# lift
`lift` is a Rust workspace for the Lift migration language.
This repository contains the language parser, a migration CLI, a small language
server, and a tree-sitter grammar. It is for tools that need to read, validate,
edit, scaffold, or run Lift migration files.
## What Lift looks like
Lift is a small migration DSL with tables, enums, inline SQL, and optional
rollback blocks.
```lift
create users {
id
email text unique
tenant_id uuid
remember_token
timestamps
soft_deletes
unique (tenant_id, email)
}
alter users {
add column timezone text default "UTC"
}
backfill {
update users
set timezone = 'UTC'
where timezone is null;
}
down {
drop users
}
```
The language also supports enum migrations:
```lift
create enum post_status { draft, published, archived }
alter enum post_status {
add value deleted
}
```
## Relationships
Lift handles relationships at the schema level with foreign keys.
Use `ref` on a column to point at another table:
```lift
create posts {
id
author_id ref users on delete cascade
title text
}
```
`author_id ref users` means `author_id` references `users(id)`.
You can combine `ref` with a few common modifiers:
- `nullable` for optional relationships
- `on delete cascade`, `on delete restrict`, `on delete set null`, or `on delete no action`
- `on update cascade`, `on update restrict`, `on update set null`, or `on update no action`
- `no index` if you do not want Lift to add the default index for the relation column
Example:
```lift
create comments {
id
post_id bigint ref posts on delete cascade
reviewer_id uuid ref users nullable on delete set null
body text
}
```
If you need a specific index name or a composite index, declare it yourself:
```lift
create comments {
id
post_id bigint ref posts no index
reviewer_id uuid ref users nullable no index
body text
index comments_post_id_idx (post_id)
index comments_post_reviewer_idx (post_id, reviewer_id)
}
```
## Migration files
The parser reads migrations from a directory. It recognizes:
- Lift files named `<id>_<name>.lift`
- SQL files named `<id>_<name>.sql`
- optional SQL rollback files named `<id>_<name>.down.sql`
Examples:
- `202604210001_create_users.lift`
- `202604220001_enable_pgcrypto.sql`
- `202604220001_enable_pgcrypto.down.sql`
Lift migrations may contain one optional `down { ... }` block. SQL migrations
use a separate `.down.sql` file when a rollback is needed.
When loading a directory, the parser sorts migrations by numeric id and returns
them as a single ordered list.
## Workspace
This workspace has four crates.
### `lift`
`crates/lift` parses Lift source and SQL migration files into Rust data
structures such as `MigrationFile`, `Statement`, `TableItem`, and `Column`. It
also ships the `lift` CLI for migration scaffolding and execution.
Use it when you need to:
- read a migration directory
- validate file names and migration contents
- inspect schema statements programmatically
- accept either Lift or SQL migrations
- scaffold and run migrations from the terminal
Install the CLI with an explicit backend feature:
```bash
cargo install lift --features sqlite
cargo install lift --no-default-features --features postgres
cargo install lift --no-default-features --features mariadb
```
Example commands:
```bash
lift migrate init
lift migrate make create_users
lift migrate make enable_pgcrypto --sql
lift migrate export users
lift migrate run --database-url sqlite:///tmp/app.db
lift migrate validate --path db/migrations
lift migrate rollback --steps 1
lift migrate refresh
lift migrate reset
```
Small example:
```rust
use lift::{Statement, default_migration_dir, load_migrations};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let migrations = load_migrations(&default_migration_dir())?;
for migration in migrations {
println!("{} {}", migration.id, migration.name);
for statement in &migration.up {
match statement {
Statement::CreateTable { name, .. } => println!("- create {name}"),
Statement::AlterTable { name, .. } => println!("- alter {name}"),
Statement::CreateEnum { name, .. } => println!("- create enum {name}"),
Statement::Sql { .. } => println!("- sql"),
Statement::Backfill { .. } => println!("- backfill"),
_ => {}
}
}
}
Ok(())
}
```
### `lift-migration`
`crates/lift-migration` is the vendored migration runtime used by the `lift`
CLI. It applies parsed Lift and SQL migrations to supported databases and keeps
the execution logic local to this workspace.
The crate also exposes `parse_migration_file` and `parse_source` when you want
to parse one migration at a time.
### `lift-lsp`
`crates/lift-lsp` is a language server for `.lift` files. It runs over stdio.
```bash
cargo run -p lift-lsp
```
Today it focuses on a small set of editor features:
- diagnostics for invalid Lift syntax
- diagnostics for invalid migration file names
- keyword completion for Lift syntax
### `tree-sitter-lift`
`crates/tree-sitter-lift` provides a tree-sitter grammar for Lift, along with
queries for highlighting, indentation, and injections.
Rust example:
```rust
let mut parser = tree_sitter::Parser::new();
parser.set_language(&tree_sitter_lift::language())?;
```
## Development
Build the workspace:
```bash
cargo build --workspace
```
Run tests:
```bash
cargo test --workspace
```
If you change `crates/tree-sitter-lift/grammar.js`, regenerate the parser:
```bash
tree-sitter generate crates/tree-sitter-lift
```
## License
Licensed under either of:
- MIT, see `LICENSE-MIT`
- Apache-2.0, see `LICENSE-APACHE`