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

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 TypeSQL Type (SQLite)SQL Type (PostgreSQL)SQL Type (MySQL)
i32INTEGERINTEGERINT
i64INTEGERBIGINTBIGINT
f64REALDOUBLEDOUBLE
StringTEXTTEXTTEXT
boolINTEGER (0/1)BOOLEANBOOLEAN

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 output
  • Clone - Clone support
  • PartialEq - 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
Prev
Quick Start
Next
Database Connection