Model Definition
Models are the core of Ormer, defining the mapping between database table structures and Rust types.
Basic Model Definition
Use the #[derive(Model)] macro to define a Rust struct as a database model:
use ormer::Model;
#[derive(Debug, Model)]
#[table = "users"]
struct User {
#[primary]
id: i32,
name: String,
age: i32,
email: String,
}
Attribute Description
#[table = "table_name"]- Specifies the database table name#[primary]- Marks the primary key field#[primary(auto)]- Marks an auto-increment primary key#[unique]- Unique constraint#[index]- Creates an index
Field Attributes in Detail
1. Primary Key Fields
Regular Primary Key
#[derive(Debug, Model)]
#[table = "users"]
struct User {
#[primary]
id: i32, // Requires manual value assignment
name: String,
}
Auto-Increment Primary Key
#[derive(Debug, Model)]
#[table = "users"]
struct User {
#[primary(auto)]
id: i32, // Automatically generated by database
name: String,
}
When inserting data with auto-increment primary key:
// Method 1: Use Option<i32>
#[derive(Debug, Model)]
#[table = "users"]
struct User {
#[primary(auto)]
id: Option<i32>,
name: String,
}
db.insert(&User {
id: None, // Automatically generated by database
name: "Alice".to_string(),
}).await?;
// Method 2: Use i32, manually specified
db.insert(&User {
id: 1, // Manually specified, but database will ignore
name: "Alice".to_string(),
}).await?;
2. Unique Constraints
Single Column Unique
#[derive(Debug, Model)]
#[table = "users"]
struct User {
#[primary(auto)]
id: i32,
#[unique]
email: String, // Email must be unique
}
Composite Unique Constraint
Use the group parameter to create composite unique indexes:
#[derive(Debug, Model)]
#[table = "user_roles"]
struct UserRole {
#[primary(auto)]
id: i32,
#[unique(group = 1)]
user_id: i32,
#[unique(group = 1)]
role_id: i32,
// (user_id, role_id) combination must be unique
}
3. Indexes
Create indexes for frequently queried fields to improve performance:
#[derive(Debug, Model)]
#[table = "users"]
struct User {
#[primary(auto)]
id: i32,
#[index]
age: i32, // Add index when frequently querying by age
#[index]
created_at: String,
}
4. Nullable Fields
Use Option<T> to represent nullable fields:
#[derive(Debug, Model)]
#[table = "users"]
struct User {
#[primary(auto)]
id: i32,
name: String,
// Optional fields
email: Option<String>,
phone: Option<String>,
age: Option<i32>,
}
When inserting data:
db.insert(&User {
id: 1,
name: "Alice".to_string(),
email: Some("alice@example.com".to_string()),
phone: None, // No phone number
age: Some(25),
}).await?;
Supported Field Types
Basic Types
| Rust Type | SQL Type (SQLite) | SQL Type (PostgreSQL) | SQL Type (MySQL) |
|---|---|---|---|
i32 | INTEGER | INTEGER | INT |
i64 | INTEGER | BIGINT | BIGINT |
f64 | REAL | DOUBLE | DOUBLE |
String | TEXT | TEXT | TEXT |
bool | INTEGER (0/1) | BOOLEAN | BOOLEAN |
Option Types
All basic types can be wrapped in Option:
Option<i32>
Option<i64>
Option<f64>
Option<String>
Option<bool>
Complete Example
use ormer::Model;
#[derive(Debug, Model, Clone)]
#[table = "products"]
struct Product {
#[primary(auto)]
id: i32,
#[unique]
sku: String, // Product SKU, unique
name: String,
price: f64,
#[index]
category_id: i32, // Category ID, indexed
#[index]
stock: i32, // Inventory
description: Option<String>, // Optional description
is_active: bool, // Whether active
}
Foreign Key Relationships
Although Ormer doesn't enforce foreign key constraints, you can define foreign key relationships in your models:
#[derive(Debug, Model)]
#[table = "posts"]
struct Post {
#[primary(auto)]
id: i32,
#[foreign_key = "users(id)"]
user_id: i32, // Foreign key referencing users table id
title: String,
content: String,
}
Model Trait Methods
After defining a model, the #[derive(Model)] macro automatically implements the following methods:
query() / select()
Create query builders:
let query = User::query();
let select = User::select();
from_row()
Build model from database row:
let user = User::from_row(&row)?;
field_values()
Get field value list (for INSERT/UPDATE):
let values = user.field_values();
primary_key_column() / primary_key_value()
Get primary key information:
let pk_column = User::primary_key_column(); // "id"
let pk_value = user.primary_key_value(); // Actual value
Creating Tables
Use the create_table method to create tables:
db.create_table::<User>().await?;
This generates SQL similar to:
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
age INTEGER,
email TEXT UNIQUE
)
Note: The create_table method only creates the table and does not validate the table schema. If the table already exists, it may report an error.
Validating Table Schema
Use the validate_table method to validate whether the table schema matches the model definition:
// Validate table schema
db.validate_table::<User>().await?;
This method checks:
- Whether the table exists
- Whether the column count matches
- Whether the column names match
- Whether the column types are compatible
- Whether the NOT NULL constraints match
- Whether the primary key constraints match
If validation fails, a SchemaMismatch error is returned.
Dropping Tables
db.drop_table::<User>().await?;
Generates SQL:
DROP TABLE IF EXISTS users
Best Practices
1. Derive Common Traits for Models
#[derive(Debug, Model, Clone, PartialEq)]
struct User {
// ...
}
Debug- Debug outputClone- Clone supportPartialEq- Comparison support (useful for testing)
2. Use Meaningful Table Names
// ✅ Recommended: Plural form
#[table = "users"]
#[table = "blog_posts"]
#[table = "user_roles"]
// ❌ Avoid: Singular or inconsistent
#[table = "user"]
#[table = "User"]
3. Use Indexes Reasonably
// ✅ Add indexes for frequently queried fields
#[index]
status: String,
#[index]
created_at: String,
// ❌ Don't add indexes to all fields
4. Use Option for Optional Data
// ✅ Clearly expresses that field can be null
email: Option<String>,
// ❌ Using empty string to represent no value
email: String, // May need "" to represent empty