lift 0.1.5

Lift migration parser and CLI tooling.
Documentation

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.

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:

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:

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:

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:

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:

cargo install lift --features sqlite
cargo install lift --no-default-features --features postgres
cargo install lift --no-default-features --features mariadb

Example commands:

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:

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.

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:

let mut parser = tree_sitter::Parser::new();
parser.set_language(&tree_sitter_lift::language())?;

Development

Build the workspace:

cargo build --workspace

Run tests:

cargo test --workspace

If you change crates/tree-sitter-lift/grammar.js, regenerate the parser:

tree-sitter generate crates/tree-sitter-lift

License

Licensed under either of:

  • MIT, see LICENSE-MIT
  • Apache-2.0, see LICENSE-APACHE