Sign in

    moonbitstack/moonorm/schema does not have a README file

    Column

    pub(all) struct Column {
    name : String
    col_type : ColumnType
    primary_key : Bool
    autoincrement : Bool
    nullable : Bool
    unique : Bool
    default : String?
    checks : Array[String]
    references : (String, String)?
    on_delete : String?
    on_update : String?
    }

    One declared column of a Model: its name, storage col_type, whether it is the (or part of the) primary key, whether it accepts NULL, and an optional foreign-key references target (table, column). This is the explicit stand-in for SQLAlchemy's mapped_column(...) — every fact that framework reads off the annotated attribute is stated here as data.

    Column::ddl

    fn Column::ddl(self : Column, inline_pk? : Bool) -> String

    Render this column as a single CREATE TABLE column definition, e.g. "user_id INTEGER NOT NULL REFERENCES users(id)". A PRIMARY KEY column emits that keyword; a non-nullable column emits NOT NULL; a references target emits a REFERENCES table(column) clause. Column and table names are trusted identifiers declared in code, never bound values.

    Column::ddl_for

    fn Column::ddl_for(self : Column, dialect :
    Dialect
    , inline_pk? : Bool) -> String

    A column definition rendered for a specific Dialect. An autoincrement primary key takes each engine's idiom: SQLite INTEGER PRIMARY KEY AUTOINCREMENT, PostgreSQL SERIAL PRIMARY KEY, MySQL INT AUTO_INCREMENT PRIMARY KEY. Otherwise it is sql_for plus the same constraint clauses as ddl.

    ColumnType

    pub(all) enum ColumnType {
    IntType
    TextType
    RealType
    BlobType
    BoolType
    VarcharType(Int)
    NumericType
    DateTimeType
    DateType
    TimeType
    UuidType
    JsonType
    } derive(Eq)

    A column's SQL storage type. These map onto SQLite's type affinities when a Model renders CREATE TABLE DDL: IntType/BoolType -> INTEGER, TextType -> TEXT, RealType -> REAL, BlobType -> BLOB. BoolType is a distinct declared type so DDL reads intently, but binds and reads as an integer 0/1.

    ColumnType::equal

    fn ColumnType::equal(ColumnType, ColumnType) -> Bool

    ColumnType::not_equal

    fn ColumnType::not_equal(x : ColumnType, y : ColumnType) -> Bool

    ColumnType::sql_for

    fn ColumnType::sql_for(self : ColumnType, dialect :
    Dialect
    ) -> String

    The declared type keyword for a ColumnType in a given SQL Dialect. SQLite is the affinity-based default; PostgreSQL and MySQL render their native spellings (BOOLEAN/BYTEA/DOUBLE PRECISION/UUID/JSONB on PG; INT/TINYINT(1)/DOUBLE/JSON on MySQL). This is the per-dialect counterpart of ColumnType::sql.

    Field

    pub struct Field[T] {
    column : Column
    encode : (T) ->
    Value

    }

    One field of a declarative model: a declared [Column] paired with the projection that reads the field's value out of a record T. A Field is the column metadata plus the "how to persist this one attribute" half of the mapping, factored to one place so a Model can be assembled from a list of fields rather than a hand-written to_columns.

    This is the shape moonctl's model generator emits: from a #orm-annotated struct it produces one field(...) per attribute (the encode closure is a trivial projection like h => Int(h.id)), and a single from_row decoder. The generated [Field] array then drives the column list, the CREATE TABLE DDL, the SELECT projection, and the INSERT binding — everything except decoding a row back into T, which needs the record constructor MoonBit cannot synthesise without reflection (see [Model::from_fields]).

    Field::name

    fn[T] Field::name(self : Field[T]) -> String

    The name of the column this field maps to.

    ManyToMany

    pub struct ManyToMany[S, T] {
    target : Model[T]
    junction : String
    source_fk : String
    target_fk : String
    target_key : String
    source_key : (S) ->
    Value

    }

    A many-to-many relationship from a source S to a target T through a junction (association) table — SQLAlchemy's relationship(secondary=...). The junction's source_fk column matches a key extracted from the source, and its target_fk column matches the target's target_key. Resolving the relationship JOINs the target through the junction. Load it with Session::load_many; like the 1:N / N:1 relations, MoonBit's lack of attribute interception makes the load an explicit call rather than a lazy attribute access.

    ManyToMany::query

    fn[S, T] ManyToMany::query(self : ManyToMany[S, T], source : S) ->
    Select

    The Select resolving this relationship for a source: the target rows joined to the junction on junction.target_fk = target.target_key, filtered to junction.source_fk = ? with the source key bound (never spliced). The target columns are projected qualified (target.col) so a same-named junction column never makes the projection ambiguous. Session::load_many runs it and maps.

    Model

    pub struct Model[T] {
    table : String
    columns : Array[Column]
    from_row : (
    Row
    ) -> T raise
    DbError

    to_columns : (T) -> Array[(String,
    Value
    )]
    ident : Map[Int, Map[String, T]]
    }

    A declarative model: the mapping between a database table and a MoonBit record type T. It bundles the table name, the ordered columns, and the two mapping closures that MoonBit cannot synthesise for want of reflection: from_row decodes a fetched Row into a T, and to_columns projects a T back into the (name, Value) pairs an INSERT binds. Build one with Model::new.

    This is the faithful explicit equivalent of a SQLAlchemy declarative class — the columns are the mapped_columns, and the two closures are the automatic attribute<->column mapping made visible. Being a plain value it stays pure and compiles on every backend.

    Model::column_names

    fn[T] Model::column_names(self : Model[T]) -> Array[String]

    The declared column names, in order.

    Model::create_table_sql

    fn[T] Model::create_table_sql(self : Model[T], if_not_exists? : Bool) -> String

    Render CREATE TABLE DDL for this model from its declared columns. With if_not_exists=true the statement is idempotent (CREATE TABLE IF NOT EXISTS). This is the explicit counterpart of SQLAlchemy's metadata.create_all().

    Model::create_table_sql_for

    fn[T] Model::create_table_sql_for(self : Model[T], dialect :
    Dialect
    , if_not_exists? : Bool) -> String

    CREATE TABLE DDL for this model rendered for a specific Dialect — the multi-dialect counterpart of create_table_sql. Uses Column::ddl_for, keeps a composite primary key as a table-level constraint, and appends ENGINE=InnoDB on MySQL.

    Model::from_fields

    fn[T] Model::from_fields(table : String, fields : Array[Field[T]], from_row : (
    Row
    ) -> T raise
    DbError
    ) -> Model[T]

    Build a Model[T] from a list of [Field]s and a single row decoder — the declarative path that removes the hand-written to_columns. The columns and the binding projection are both derived from fields: columns is each field's declared column, and to_columns maps a record to (name, field.encode(record)) pairs. You still supply from_row, because reconstructing a T from a fetched row needs the record constructor, and MoonBit — lacking reflection — cannot synthesise it; that one closure is the irreducible core of the mapping.

    This is the constructor moonctl-generated model metadata targets: the generator emits the [Field] array and the from_row decoder, and from_fields turns them into a live Model. Written by hand it reads just as directly:

    Model::from_fields( "hero", [ field("id", IntType, primary_key=true, h => Int(h.id)), field("name", TextType, nullable=false, h => Text(h.name)), ], r => { id: r.int(0), name: r.text(1) }, )

    Model::insert_of

    fn[T] Model::insert_of(self : Model[T], record : T) ->
    Insert

    Build an Insert that persists record, binding the pairs from to_columns. Values travel as ? placeholders exactly like the rest of the builder.

    Model::map_row

    Decode a single fetched Row into a record via the model's from_row.

    Model::map_rows

    Decode a whole result set into records, preserving row order.

    Model::new

    fn[T] Model::new(table : String, columns : Array[Column], from_row : (
    Row
    ) -> T raise
    DbError
    , to_columns : (T) -> Array[(String,
    Value
    )]) -> Model[T]

    Define a Model[T]. from_row should read the record's fields off the @moondb.Row (via Row::text / Row::int / Row::by_name …) — those accessors raise @moondb.DbError on a type or index mismatch, so from_row raises too and a decode bug surfaces at the call site. to_columns should list the column/value pairs to persist (typically every column except an autoincrement primary key). Write the closures in arrow form so the raise effect is inferred, e.g. (r) => \{ id: r.int(0), name: r.text(1) \}.

    Model::parent_tables

    fn[T] Model::parent_tables(self : Model[T]) -> Array[String]

    The distinct tables this model's foreign keys point at, itself excluded. A flush orders inserts so every table named here is written before this one, and deletes the other way round.

    Model::pk_columns

    fn[T] Model::pk_columns(self : Model[T]) -> Array[String]

    The declared primary-key column names, in declaration order. Empty when the model declares no key at all — the unit of work needs one to address a row, and Session::get / Session::delete_record say so rather than guessing.

    Model::pk_of

    fn[T] Model::pk_of(self : Model[T], record : T) -> Array[
    Value
    ]

    A record's primary-key values in pk_columns order, read off the same to_columns projection an INSERT binds — the only route to them, since MoonBit cannot look up an attribute by name.

    A projection that omits a key column — the usual shape for an autoincrement id the server assigns — stops the scan there, so the result is shorter than pk_columns. That length mismatch is how a session tells a record it can address from one whose key does not exist yet.

    Model::select

    A Select over this model's table with every declared column projected explicitly (so the projection order is fixed and known to from_row). Add where_ / order_by / limit to it as usual, then run it with Session::fetch_as to get mapped records back.

    Model::table_descriptor

    fn[T] Model::table_descriptor(self : Model[T]) ->
    Table

    The Table descriptor for this model (name + column names), for interop with the plain builder API.

    Relation

    pub struct Relation[S, T] {
    target : Model[T]
    key_column : String
    source_key : (S) ->
    Value

    to_many : Bool
    }

    A foreign-key relationship from a source record S to a target model T, resolved by matching target.key_column against a Value extracted from the source. to_many records the cardinality (a 1:N parent->children link versus a N:1 child->parent link) so callers know whether to expect many rows or one.

    Because MoonBit has no attribute interception, touching hero.team cannot silently emit a SELECT the way SQLAlchemy's lazy load does. The relationship is therefore a first-class value and the load is explicit — Session::load / Session::load_one — exactly the eager, explicit shape Diesel's belonging_to / preload and GORM's Preload take.

    Relation::batch_query

    fn[S, T] Relation::batch_query(self : Relation[S, T], sources : Array[S]) ->
    Select

    One SELECT covering every source's related rows — key_column IN (…) over the sources' keys — so loading a relation for a list costs one query instead of one per row. source_value is what buckets the results back.

    Relation::query

    fn[S, T] Relation::query(self : Relation[S, T], source : S) ->
    Select

    The Select that resolves this relationship for a given source record: the target model's projection filtered to key_column = ?, with the source-derived value bound (never spliced), so eager loading is injection-safe like every other query. Session::load runs this and maps the rows.

    Relation::source_value

    fn[S, T] Relation::source_value(self : Relation[S, T], source : S) ->
    Value

    The single Select that resolves this relationship for many sources at once: the target model's projection filtered to key_column IN (k1, k2, …), where the keys are the distinct values extracted from sources. This is the query behind an N+1-avoiding batch load — one round trip fetches the related rows of every source, instead of one query(source) per source. Every key binds as its own placeholder. With no sources the result filters on the empty set (IN (NULL)), matching nothing; Session::load_batch short-circuits that case without a query. The source-side key this relationship extracts from a source record — the value the target's key_column is matched against. Exposed so a batch load can bucket fetched rows back to their sources.

    belongs_to

    fn[S, T] belongs_to(target : Model[T], target_key : String, child_key : (S) ->
    Value
    ) -> Relation[S, T]

    A N:1 relationship: child -> its single parent in target, matched by the parent's target_key column (usually its primary key) equalling the foreign key extracted from the child by child_key. Load it with Session::load_one.

    column

    fn column(name : String, col_type : ColumnType, primary_key? : Bool, autoincrement? : Bool, nullable? : Bool, unique? : Bool, default? : String, checks? : Array[String], references? : (String, String), on_delete? : String, on_update? : String) -> Column

    Declare a Column. primary_key and references default off; a primary-key column is NOT NULL implicitly (SQLite treats INTEGER PRIMARY KEY as the rowid), and nullable defaults to true for every other column, matching SQLAlchemy's nullable=True default.

    field

    fn[T] field(name : String, col_type : ColumnType, encode : (T) ->
    Value
    , primary_key? : Bool, autoincrement? : Bool, nullable? : Bool, unique? : Bool, default? : String, checks? : Array[String], references? : (String, String), on_delete? : String, on_update? : String) -> Field[T]

    Declare a model field: its column metadata and the projection encode that pulls this field's [Value] out of a record. The column knobs (primary_key, nullable, references) mirror [column]; encode is the one-liner that binds the attribute, e.g. field("id", IntType, primary_key=true, h =>Int(h.id)).

    has_many

    fn[S, T] has_many(target : Model[T], foreign_key : String, parent_key : (S) ->
    Value
    ) -> Relation[S, T]

    A 1:N relationship: parent -> its children in target, matched by the child's foreign_key column equalling the parent key extracted by parent_key. Load it with Session::load, which returns every matching child record.

    many_to_many

    fn[S, T] many_to_many(target : Model[T], junction~ : String, source_fk~ : String, target_fk~ : String, target_key~ : String, source_key~ : (S) ->
    Value
    ) -> ManyToMany[S, T]

    Define a many-to-many relationship through junction. source_fk / target_fk are the junction columns pointing at the source key and the target_key.