Ormer
Home
Quick Start
GitHub
  • 简体中文
  • English
Home
Quick Start
GitHub
  • 简体中文
  • English
  • Introduction to Ormer
  • Quick Start
  • Model Definition
  • Database Connection
  • Data Operations
  • Query Builder
  • Advanced Queries
  • Transaction Management
  • Connection Pool
  • Hooks System
  • Migrations

Model Definition

Basic Definition

use ormer::Model;

#[derive(Debug, Model)]
#[table = "users"]
struct User {
    #[primary(auto)]
    id: i32,
    name: String,
    email: Option<String>,
}

Attributes

  • #[table = "table_name"] - Specifies the table name
  • #[table(schema = "schema", name = "table_name")] - Specifies schema and table independently
  • #[column(name = "column_name")] - Specifies the SQL column name
  • #[primary] - Primary key (supports composite primary keys)
  • #[primary(auto)] - Auto-increment primary key (only for single primary key or the first field of composite primary key)
  • #[unique] - Unique constraint (supports group and name)
  • #[index] - Index (supports group, name, order, and where)
  • #[default(...)] - Database default; use #[default(expr = "...")] for SQL expressions
  • #[check(expr = "...")] - CHECK constraint, with optional name
  • #[foreign(Type)] - Foreign key relationship, with optional name, on_delete, and on_update
  • #[data_type(i64)] - Database type override (e.g., Rust i32 field mapped to BIGINT in database)
  • #[hypertable(Duration::from_secs(86400))] - TimescaleDB hypertable chunk interval
  • #[compress] - PostgreSQL column-level compression (generates COMPRESSION pglz)

PostgreSQL and MSSQL preserve the schema prefix in #[table = "schema.table"]; SQLite and MySQL use the final table-name component.

Field Attributes

Unique Constraint

Single Column Unique

#[derive(Debug, Model)]
#[table = "users"]
struct User {
    #[primary(auto)]
    id: i32,
    
    #[unique]
    email: String,
}

Composite Unique

#[derive(Debug, Model)]
#[table = "user_roles"]
struct UserRole {
    #[primary(auto)]
    id: i32,
    
    #[unique(group = 1)]
    user_id: i32,
    
    #[unique(group = 1)]
    role_id: i32,
}

Indexes

#[derive(Debug, Model)]
#[table = "users"]
struct User {
    #[primary(auto)]
    id: i32,
    
    #[index]
    age: i32,
    
    #[index]
    created_at: String,
}

Nullable Fields

#[derive(Debug, Model)]
#[table = "users"]
struct User {
    #[primary(auto)]
    id: i32,
    name: String,
    
    email: Option<String>,
    phone: Option<String>,
}

Column Names, Defaults, and Constraints

#[derive(Debug, Model)]
#[table(schema = "auth", name = "users")]
struct User {
    #[primary(auto)]
    id: i32,
    #[column(name = "display_name")]
    #[default("")]
    #[check(expr = "length(display_name) > 0")]
    name: String,
    #[default(expr = "CURRENT_TIMESTAMP")]
    created_at: chrono::NaiveDateTime,
}

insert(&model) still writes all model fields explicitly; a database default applies only when the column is omitted from the INSERT, which insert_partial or insert_model can do.

Supported Types

Rust TypeSQL Type (SQLite)SQL Type (PostgreSQL)SQL Type (MySQL)SQL Type (MSSQL)
i32INTEGERINTEGERINTINT
i64INTEGERBIGINTBIGINTBIGINT
f64REALDOUBLEDOUBLEFLOAT
StringTEXTTEXTTEXTNVARCHAR(255)
boolINTEGER (0/1)BOOLEANBOOLEANBIT
Vec<u8>BLOBBYTEABLOBVARBINARY(MAX)
chrono::DateTime<chrono::Utc>TEXTTIMESTAMPTZDATETIMEDATETIME2
chrono::NaiveDateTimeTEXTTIMESTAMPTZDATETIMEDATETIME2
chrono::NaiveDateTEXTDATEDATEDATE
chrono::NaiveTimeTEXTTIMETIMETIME

All basic types can be wrapped with Option<T> for nullable fields.

Enum Types

use ormer::{Model, ModelEnum};

#[derive(Debug, Clone, ModelEnum, PartialEq)]
enum UserStatus {
    Active,
    Inactive,
    Banned,
}

#[derive(Debug, Model)]
#[table = "users"]
struct User {
    #[primary(auto)]
    id: i32,
    status: UserStatus,
    name: String,
}

Supports Option<EnumType> for nullable enum fields.

ModelEnum values can also be used with IN, comparison, and ordering conditions:

let active: Vec<User> = db
    .select::<User>()
    .filter(|u| u.status.is_in([UserStatus::Active, UserStatus::Banned]))
    .collect()
    .await?;

For an existing numeric enum or wrapper type, use #[data_type(i32)] instead of deriving ModelEnum. A nullable field must use #[data_type(Option<i32>)]:

#[repr(i32)]
#[derive(Debug, Clone, Copy)]
enum Status {
    Active = 1,
    Disabled = 0,
}

#[derive(Debug, Model)]
#[table = "users"]
struct User {
    #[primary]
    id: i32,
    #[data_type(i32)]
    status: Status,
    #[data_type(Option<i32>)]
    old_status: Option<Status>,
}

PostgreSQL Arrays

PostgreSQL supports Vec<i32>, Vec<i64>, Vec<Option<i64>>, and Vec<String>. These fields map to native array types; Vec<String> uses TEXT[], not a JSON column:

#[derive(Debug, Model)]
#[table = "users"]
struct User {
    #[primary]
    id: i32,
    tags: Vec<String>,
    scores: Vec<i32>,
}

For a custom numeric enum inside Vec<T>, use #[data_type(Vec<i32>)]:

#[data_type(Vec<i32>)]
roles: Vec<Status>,

Array types and this syntax currently target the PostgreSQL backend.

Complete Example

use ormer::Model;

#[derive(Debug, Model, Clone)]
#[table = "products"]
struct Product {
    #[primary(auto)]
    id: i32,
    
    #[unique]
    sku: String,
    name: String,
    price: f64,
    
    #[index]
    category_id: i32,
    stock: i32,
    
    description: Option<String>,
    is_active: bool,
}

Foreign Key Relationships

#[derive(Debug, Model)]
#[table = "posts"]
struct Post {
    #[primary(auto)]
    id: i32,
    
    #[foreign(User)]
    user_id: i32,
    
    title: String,
    content: String,
}

Model Relations

Foreign-key fields describe the database constraint. To load related models, use #[has_many] and #[belongs_to]:

#[derive(Debug, Clone, Model)]
#[table = "users"]
struct User {
    #[primary(auto)]
    id: i32,
    name: String,
    #[has_many(Post.user_id)]
    posts: Vec<Post>,
}

#[derive(Debug, Clone, Model)]
#[table = "posts"]
struct Post {
    #[primary(auto)]
    id: i32,
    #[foreign(User.id)]
    user_id: i32,
    #[belongs_to(user_id)]
    user: Option<User>,
    title: String,
}

Relation fields are not database columns. A #[belongs_to] field must be Option<T>, and a #[has_many] field must be Vec<T>.

Composite Primary Keys

Add #[primary] to multiple fields to define a composite primary key:

#[derive(Debug, Model)]
#[table = "user_roles"]
struct UserRole {
    #[primary]
    user_id: i32,
    #[primary]
    role_id: i32,
    assigned_at: String,
}

Only the first primary key field can use auto:

#[primary(auto)]
id: i32,
#[primary]
product_id: i32,

Use primary_key_columns() to get the list of primary key column names.

Table Operations

Creating Tables

db.create_table::<User>().execute().await?;

Validating Tables

db.validate_table::<User>().await?;

Dropping Tables

db.drop_table::<User>().execute().await?;

Model Wrappers

// Base model
#[derive(Debug, Model, Clone)]
#[table = "users"]
struct User {
    #[primary(auto)]
    id: i32,
    name: String,
    age: i32,
    email: Option<String>,
}

// Wrapper - different table name
#[derive(Debug, Model)]
#[table = "archive_users"]
struct ArchiveUser(User);

#[derive(Debug, Model)]
#[table = "temp_users"]
struct TempUser(User);

Usage Example

db.create_table::<User>().execute().await?;
db.create_table::<ArchiveUser>().execute().await?;

db.insert(&User {
    id: 0,
    name: "Alice".to_string(),
    age: 25,
    email: Some("alice@example.com".to_string()),
}).await?;

let archive_user = ArchiveUser(User {
    id: 0,
    name: "Bob".to_string(),
    age: 30,
    email: Some("bob@example.com".to_string()),
});
db.insert(&archive_user).execute().await?;

let archived: Vec<ArchiveUser> = db
    .select::<ArchiveUser>()
    .collect::<Vec<_>>()
    .await?;

for au in &archived {
    println!("User: {}", au.inner().name);
}
Last Updated: 8/6/26, 11:03 PM
Contributors: fawdlstty
Prev
Quick Start
Next
Database Connection