# gleager

[![Package Version](https://img.shields.io/hexpm/v/gleager)](https://hex.pm/packages/gleager)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleager/)

A database migration tool for Gleam. Works with any SQL driver that implements
the `Driver` type — sqlight, pturso, or your own.

```sh
gleam add gleager
```

## Quick start

Create a `migrations` directory with versioned SQL files:

```sql
-- migrations/001_create_users.sql
-- migrate:up
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT);

-- migrate:down
DROP TABLE users;
```

```sql
-- migrations/002_add_posts.sql
-- migrate:up
CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL, title TEXT, body TEXT);
CREATE INDEX idx_posts_user_id ON posts(user_id);

-- migrate:down
DROP INDEX idx_posts_user_id;
DROP TABLE posts;
```

Then apply them with sqlight:

```gleam
import gleager
import gleager/types.{Driver}
import gleam/dynamic/decode
import gleam/list
import sqlight

pub fn main() {
  use conn <- sqlight.with_connection("dev.db")

  let driver = Driver(
    migration_dir: "migrations",
    exec: fn(sql, args) {
      case args {
        [] -> sqlight.exec(sql, on: conn)
        _ -> {
          let values = list.map(args, sqlight.text)
          case sqlight.query(sql, on: conn, with: values, expecting: decode.int) {
            Ok(_) -> Ok(Nil)
            Error(e) -> Error(e)
          }
        }
      }
    },
    query: fn(sql, args, decoder) {
      let values = list.map(args, sqlight.text)
      sqlight.query(sql, on: conn, with: values, expecting: decoder)
    },
  )

  // Apply all pending migrations
  let assert Ok(Nil) = gleager.up(driver, steps: None)

  // Or apply only the next 2
  let assert Ok(Nil) = gleager.up(driver, steps: Some(2))

  // Roll back all applied migrations
  let assert Ok(Nil) = gleager.down(driver, steps: None)

  // Or roll back only the last one
  let assert Ok(Nil) = gleager.down(driver, steps: Some(1))
}
```

## Migration file format

Migration files live in a single directory. The version is extracted from the
filename prefix:

```
migrations/
  001_create_users.sql
  002_add_posts.sql
  003_add_settings.sql
```

Each file uses annotated sections:

```sql
-- migrate:up
<SQL to apply>

-- migrate:down
<SQL to roll back>
```

Migrations are applied in version order (`up`) and rolled back in reverse
(`down`). Each migration runs inside a transaction: either all statements
succeed and the migration is recorded in the `schema_migrations` tracking table,
or any failure rolls back the entire migration.

## Driver

gleager is database-agnostic. The `Driver(e)` type defines two callbacks:

- `exec(sql, args)` — run SQL statements, optionally with parameters
- `query(sql, args, decoder)` — run a parameterised query and decode the result
  rows

See `gleager/types` for the full type definition, or the sqlight example above
for a working adapter.

## Development

```sh
gleam test  # Run the test suite
```
