glimr/db/gen/schema_parser

Schema Parser

Schema files are just Gleam code — table("users", [ id(), string("email"), ... ]) — which means we’re parsing Gleam source text, not a custom DSL. This module extracts table names, column definitions, modifiers like nullable() and default(), and index declarations from that source text so the code generator and migration differ have a structured representation to work with.

Types

Carries everything needed to generate both the Gleam type field and the SQL column definition. The renamed_from field is transient — it only exists during migration generation so the differ can emit RENAME instead of drop+add.

pub type Column {
  Column(
    name: String,
    column_type: ColumnType,
    nullable: Bool,
    default: option.Option(DefaultValue),
    renamed_from: option.Option(String),
  )
}

Constructors

The codegen module maps each variant to a Gleam type, a decoder function, and a JSON encoder. The SQL module maps them to driver-specific DDL types. Adding a new variant here means updating both modules — the compiler will tell you everywhere you missed.

pub type ColumnType {
  Id
  String
  Text
  Int
  SmallInt
  BigInt
  Float
  Boolean
  Timestamp
  UnixTimestamp
  Date
  Json
  Uuid
  Foreign(
    table: String,
    on_delete: option.Option(ForeignAction),
    on_update: option.Option(ForeignAction),
  )
  Array(ColumnType)
  Enum(name: String, variants: List(String))
  Decimal(precision: Int, scale: Int)
  Blob
  Time
}

Constructors

Each variant maps to a specific SQL DEFAULT clause that may differ between Postgres and SQLite. DefaultNow becomes CURRENT_TIMESTAMP everywhere, but DefaultAutoUuid needs gen_random_uuid() on Postgres and a randomblob hack on SQLite.

pub type DefaultValue {
  DefaultBool(Bool)
  DefaultString(String)
  DefaultInt(Int)
  DefaultFloat(Float)
  DefaultNow
  DefaultUnixNow
  DefaultAutoUuid
  DefaultNull
  DefaultEmptyArray
}

Constructors

  • DefaultBool(Bool)
  • DefaultString(String)
  • DefaultInt(Int)
  • DefaultFloat(Float)
  • DefaultNow
  • DefaultUnixNow
  • DefaultAutoUuid
  • DefaultNull
  • DefaultEmptyArray

What should the database do when a referenced row is deleted or updated? Without specifying this, most databases default to RESTRICT (block the operation), which is safe but rigid. Cascade deletes child rows automatically, SetNull clears the FK, and NoAction defers the check to transaction commit time.

pub type ForeignAction {
  Cascade
  Restrict
  SetNull
  SetDefault
  NoAction
}

Constructors

  • Cascade
  • Restrict
  • SetNull
  • SetDefault
  • NoAction

The parsed representation of an index(["col"]) or unique(["col"]) |> named("custom") call from a schema file. The migration system compares these against the previous snapshot to detect added or removed indexes.

pub type Index {
  Index(
    columns: List(String),
    unique: Bool,
    name: option.Option(String),
  )
}

Constructors

  • Index(
      columns: List(String),
      unique: Bool,
      name: option.Option(String),
    )

Column definitions in schema files are pipe chains like string("email") |> nullable() |> array(). Rather than threading all these flags through individual parse functions, we extract everything into one record up front. This keeps parse_column_item clean — it just reads the modifiers and applies them to the base column.

pub type Modifiers {
  Modifiers(
    base: String,
    nullable: Bool,
    default: option.Option(DefaultValue),
    renamed_from: option.Option(String),
    array_depth: Int,
    on_delete: option.Option(ForeignAction),
    on_update: option.Option(ForeignAction),
    enum_name_override: option.Option(String),
  )
}

Constructors

The single source of truth that the code generator and migration differ both consume. One parse pass produces this, and everything downstream — types, decoders, encoders, DDL — derives from it.

pub type Table {
  Table(
    name: String,
    columns: List(Column),
    indexes: List(Index),
  )
}

Constructors

  • Table(name: String, columns: List(Column), indexes: List(Index))

Values

pub fn columns(table: Table) -> List(Column)

Accessor for the column list. Table is opaque to downstream modules, so the code generator and migration differ use this instead of reaching into the record directly.

pub fn indexes(table: Table) -> List(Index)

Same reasoning as columns — keeps the Table internals encapsulated while giving the snapshot builder and migration differ access to the index definitions.

pub fn parse(content: String) -> Result(Table, String)

The only public entry point — give it a schema file’s text and get back a fully parsed Table or an error explaining what’s missing. Everything downstream (codegen, migrations, validation) starts from this result.

Search Document