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
-
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))
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.