glimr/db/gen/migrate/sql

Migration SQL Generation

The bridge between “what changed in the schema” and “what SQL to run.” This module diffs old and new snapshots to discover table/column/index changes, then emits driver-specific DDL — Postgres and SQLite have enough syntax differences (SERIAL vs INTEGER PRIMARY KEY AUTOINCREMENT, BOOLEAN vs INTEGER, JSONB vs TEXT) that a single SQL template can’t cover both.

Types

Each variant maps 1:1 to a SQL DDL statement. Having explicit variants instead of a generic “run this SQL” lets us topologically sort CreateTable by FK deps, order drops before creates for indexes, and produce human-readable descriptions for the CLI — none of which would be possible with raw SQL strings.

pub type Change {
  CreateTable(table: schema_parser.Table)
  DropTable(name: String)
  AddColumn(table: String, column: schema_parser.Column)
  DropColumn(table: String, column: String)
  AlterColumn(
    table: String,
    column: schema_parser.Column,
    old: snapshot.ColumnSnapshot,
  )
  RenameColumn(table: String, old_name: String, new_name: String)
  CreateIndex(table: String, index: schema_parser.Index)
  DropIndex(table: String, index_name: String)
  CreateEnumType(name: String, variants: List(String))
}

Constructors

Every SQL generator function branches on this to pick the right dialect. Postgres has real BOOLEAN, JSONB, UUID, and SERIAL types; SQLite folds most of those into TEXT or INTEGER. Keeping the driver as a simple enum means we can pattern-match cleanly instead of threading config through every function.

pub type Driver {
  Postgres
  Sqlite
}

Constructors

  • Postgres
  • Sqlite

The output of comparing two snapshots. This flat list of changes becomes the input to generate_sql, which turns each one into a DDL statement. Keeping it as a plain list rather than a nested tree makes it easy to filter, reorder (topological sort for FK deps), and map over when generating both the SQL and the CLI summary output.

pub type SchemaDiff {
  SchemaDiff(changes: List(Change))
}

Constructors

  • SchemaDiff(changes: List(Change))

Values

pub fn compute_diff(
  old: snapshot.Snapshot,
  new: snapshot.Snapshot,
  tables: List(schema_parser.Table),
  is_filtered: Bool,
) -> SchemaDiff

The heart of the migration system. Compares the saved snapshot against the current schema to figure out what SQL to generate. The is_filtered flag matters when you’re generating migrations for a single model — in that case we skip drop detection, because a table missing from the filter doesn’t mean it was deleted, it just wasn’t included this run.

pub fn describe_change(change: Change) -> String

Produces the summary lines you see in the terminal when a migration is generated (“Create table: users”, “Add column: posts.slug”). Keeping this separate from the SQL generation means the CLI can show what will happen before the SQL is actually written to disk.

pub fn generate_sql(diff: SchemaDiff, driver: Driver) -> String

Turns the diff into runnable SQL. Before emitting anything, it topologically sorts CreateTable changes so a posts table that references users gets created after users — otherwise the FK constraint would reference a table that doesn’t exist yet. Indexes come after their tables for the same reason.

Search Document