Getting Started

Configuration

Basic Connection

#![allow(unused)]
fn main() {
// Simple connection
Database::init("postgres://localhost/mydb").await?;

// With TideConfig (recommended)
TideConfig::init()
    .database("postgres://localhost/mydb")
    .connect()
    .await?;
}

Pool Configuration

#![allow(unused)]
fn main() {
TideConfig::init()
    .database("postgres://localhost/mydb")
    .max_connections(20)        // Maximum pool size
    .min_connections(5)         // Minimum idle connections
    .connect_timeout(Duration::from_secs(10))
    .idle_timeout(Duration::from_secs(300))
    .max_lifetime(Duration::from_secs(3600))
    .connect()
    .await?;
}

The driver has one timeout for handing out a connection, opening a new one when the pool has room, so connect_timeout and acquire_timeout set the same limit, and the longer of the two applies.

Database Types

#![allow(unused)]
fn main() {
TideConfig::init()
    .database_type(DatabaseType::Postgres)  // or MySQL, SQLite
    .database("postgres://localhost/mydb")
    .connect()
    .await?;
}

Resetting Global State

TideORM keeps its active database handle, global configuration, and tokenization settings in process-global state so models can run without threading a connection through every call. That state is now intentionally resettable, which is useful for tests, benchmarks, and applications that need to reconfigure TideORM during the same process lifetime.

#![allow(unused)]
fn main() {
use tideorm::prelude::*;

Database::reset_global();
TideConfig::reset();
TokenConfig::reset();

TideConfig::init()
    .database("sqlite::memory:")
    .database_type(DatabaseType::SQLite)
    .connect()
    .await?;
}

TideConfig::apply() and TideConfig::connect() overwrite previously applied TideORM configuration. If a new configuration omits the database type, the stored type is cleared instead of leaving the old backend classification behind.


Data Type Mappings

Rust to SQL Type Reference

Rust TypePostgreSQLMySQLSQLiteNotes
i8—SMALLINTINTEGERThe PostgreSQL driver has no binding for i8; use i16 there
i16SMALLINTSMALLINTINTEGER
i32INTEGERINTINTEGER
i64BIGINTBIGINTINTEGERRecommended for primary keys
u8—TINYINT UNSIGNEDINTEGERNot supported on PostgreSQL
u16—SMALLINT UNSIGNEDINTEGERNot supported on PostgreSQL
u32INTEGERINT UNSIGNEDINTEGERPostgreSQL stores only up to i32::MAX
u64—BIGINT UNSIGNED—MySQL only
f32REALFLOATREAL
f64DOUBLE PRECISIONDOUBLEREAL
boolBOOLEANTINYINT(1)INTEGER
StringTEXTLONGTEXTTEXTSchema sync creates a MySQL key or indexed column as VARCHAR(255), because MySQL cannot index TEXT without a prefix length
Option<T>(nullable)(nullable)(nullable)Wraps any type to make it nullable
uuid::UuidUUIDBINARY(16)TEXTMySQL binds a UUID as 16 raw bytes, so the column must be BINARY(16)
rust_decimal::DecimalDECIMALDECIMAL(65,30)REALSQLite reads decimals back through f64, so REAL is forced; store a string or minor units if you need exactness
serde_json::ValueJSONBJSONTEXT
Vec<u8>BYTEALONGBLOBBLOBBinary data
chrono::NaiveDateDATEDATETEXTDate only
chrono::NaiveTimeTIMETIME(6)TEXTTime only
chrono::NaiveDateTimeTIMESTAMPDATETIME(6)TEXTNo timezone
chrono::DateTime<Utc>TIMESTAMPTZDATETIME(6)TEXTWith timezone

On PostgreSQL a field can also be an array: Vec<i32>, Vec<i64>, Vec<f64>, Vec<bool>, Vec<String> or Vec<serde_json::Value>. A field of any other type is a compile error on that field: store an enum as a String or an integer, and mark a field that is not a column #[tideorm(skip)].

On MySQL and MariaDB every timestamp is a DATETIME(6): MySQL's TIMESTAMP only spans 1970–2038 and a column without fractional digits drops microseconds. The connection runs in UTC, so a DateTime<Utc> is stored as its UTC wall time. Text and binary columns are LONGTEXT/LONGBLOB because TEXT and BLOB stop at 64 KB, and tables are created DEFAULT CHARSET=utf8mb4 whatever the database default is, so any Rust String fits.

Date and Time Types

TideORM provides proper support for all common date/time scenarios:

Use chrono::DateTime<Utc> for timestamps that should include timezone information:

#![allow(unused)]
fn main() {
use chrono::{DateTime, Utc};

#[tideorm::model(table = "sessions")]
pub struct Session {
    #[tideorm(primary_key, auto_increment)]
    pub id: i64,
    pub user_id: i64,
    pub token: String,
    pub expires_at: DateTime<Utc>,        // Maps to TIMESTAMPTZ in PostgreSQL
    pub created_at: DateTime<Utc>,        // Maps to TIMESTAMPTZ in PostgreSQL
    pub updated_at: DateTime<Utc>,        // Maps to TIMESTAMPTZ in PostgreSQL
}
}

In migrations, use timestamptz():

#![allow(unused)]
fn main() {
schema.create_table("sessions", |t| {
    t.id();
    t.big_integer("user_id").not_null();
    t.string("token").not_null();
    t.timestamptz("expires_at").not_null();
    t.timestamps();  // Uses TIMESTAMPTZ by default
}).await?;
}

DateTime without Timezone

Use chrono::NaiveDateTime when you don't need timezone information:

#![allow(unused)]
fn main() {
use chrono::NaiveDateTime;

#[tideorm::model(table = "logs")]
pub struct Log {
    #[tideorm(primary_key, auto_increment)]
    pub id: i64,
    pub message: String,
    pub logged_at: NaiveDateTime,         // Maps to TIMESTAMP (no timezone)
}
}

In migrations, use timestamp():

#![allow(unused)]
fn main() {
schema.create_table("logs", |t| {
    t.id();
    t.text("message").not_null();
    t.timestamp("logged_at").default_now();
}).await?;
}

Date Only

Use chrono::NaiveDate for date-only fields:

#![allow(unused)]
fn main() {
use chrono::NaiveDate;

#[tideorm::model(table = "events")]
pub struct Event {
    #[tideorm(primary_key, auto_increment)]
    pub id: i64,
    pub name: String,
    pub event_date: NaiveDate,            // Maps to DATE
}
}

In migrations, use date():

#![allow(unused)]
fn main() {
schema.create_table("events", |t| {
    t.id();
    t.string("name").not_null();
    t.date("event_date").not_null();
}).await?;
}

Time Only

Use chrono::NaiveTime for time-only fields:

#![allow(unused)]
fn main() {
use chrono::NaiveTime;

#[tideorm::model(table = "schedules")]
pub struct Schedule {
    #[tideorm(primary_key, auto_increment)]
    pub id: i64,
    pub name: String,
    pub start_time: NaiveTime,            // Maps to TIME
    pub end_time: NaiveTime,
}
}

In migrations, use time():

#![allow(unused)]
fn main() {
schema.create_table("schedules", |t| {
    t.id();
    t.string("name").not_null();
    t.time("start_time").not_null();
    t.time("end_time").not_null();
}).await?;
}

Examples

See the TideORM Examples repository for complete working examples.


Testing

Use the smallest command that covers your change.

# Fast library validation
cargo test --lib

# Broad compatibility pass
cargo test --all-features

# Default backend suite
cargo test --features postgres

# SQLite integration suite
cargo test --test sqlite_integration_tests --features "sqlite runtime-tokio" --no-default-features

# PostgreSQL integration suite
cargo test --test postgres_integration_tests

# MySQL integration suite
cargo test --test mysql_integration_tests --features mysql

# MariaDB integration suite
cargo test --test mariadb_integration_tests --features mysql

# SQLite smoke test
cargo test --test sqlite_ci_smoke_test --features "sqlite runtime-tokio" --no-default-features

# Rebuild the book after documentation changes
mdbook build

Integration suites may require local database servers and environment variables. When a test needs a fresh TideORM setup, clear global state first with Database::reset_global(), TideConfig::reset(), and TokenConfig::reset().