Skip to main content

Crate omnia_orm

Crate omnia_orm 

Source
Expand description

§Omnia ORM

A lightweight object-relational mapper based on sea-query but completely backend agnostic. This crate is intended as a helper for guests using omnia-wasi-sql to assist in query building and mapping return values to business structures.

This crate uses types from omnia-wasi-sql and re-exports Row and DataType for convenience.

It is intended that this crate is used in guest components only.

§Quick Start

§Define an Entity

use chrono::{DateTime, Utc};
use omnia_orm::entity; // Assuming re-exported or used directly

entity! {
    table = "posts",
    #[derive(Debug, Clone)]
    pub struct Post {
        pub id: i32,
        pub title: String,
        pub content: String,
        pub published: bool,
        pub created_at: DateTime<Utc>,
    }
}

§CRUD Operations

use omnia_wasi_sql::orm::{SelectBuilder, InsertBuilder, UpdateBuilder, DeleteBuilder, Filter};

// Select with filter
let posts = SelectBuilder::<Post>::new()
    .where(Filter::eq("published", true))
    .where(Filter::gt("created_at", Utc::now() - Duration::days(7)))
    .order_by_desc(None, "created_at")
    .limit(10)
    .fetch(provider, "db").await?;

// Insert
InsertBuilder::<Post>::new()
    .set("title", "Hello World")
    .set("content", "My first post")
    .set("published", true)
    .build()?;

// Or insert from an entity
let post = Post {
    id: 1,
    title: "Hello".to_string(),
    content: "World".to_string(),
    published: true,
    created_at: Utc::now(),
};
InsertBuilder::<Post>::from_entity(&post).build()?;

// Update
UpdateBuilder::<Post>::new()
    .set("published", true)
    .where(Filter::eq("id", 42))
    .build()?;

// Delete
DeleteBuilder::<Post>::new()
    .where(Filter::eq("id", 42))
    .build()?;

§Joins

use omnia_wasi_sql::orm::Join;

// Entity with default joins and column aliasing
entity! {
    table = "posts",
    columns = [
        ("users", "name", "author_name"),       // users.name AS author_name
        ("users", "email", "author_email"),    // users.email AS author_email
    ],
    joins = [
        Join::left("users", Filter::col_eq("posts", "author_id", "users", "id")),
    ],
    #[derive(Debug, Clone)]
    pub struct PostWithAuthor {
        pub id: i32,              // Auto: posts.id
        pub title: String,        // Auto: posts.title
        pub author_name: String,  // Manual: users.name AS author_name
        pub author_email: String, // Manual: users.email AS author_email
    }
}

// Joins happen automatically
let posts = SelectBuilder::<PostWithAuthor>::new()
    .fetch(provider, "db").await?;

// Or add ad-hoc joins
let posts = SelectBuilder::<Post>::new()
    .join(Join::left("users", Filter::col_eq("posts", "author_id", "users", "id")))
    .fetch(provider, "db").await?;

§Filtering

// Basic comparisons
Filter::eq("status", "active")
Filter::gt("views", 1000)
Filter::like("title", "%rust%")
Filter::in("id", vec![1, 2, 3])

// Logical combinators
Filter::and(vec![
    Filter::eq("published", true),
    Filter::gt("views", 100),
])

Filter::or(vec![
    Filter::eq("featured", true),
    Filter::gt("views", 5000),
])

// Table-qualified (for joins)
Filter::table_eq("posts", "published", true)
Filter::col_eq("posts", "author_id", "users", "id")

§Upserts

Handle INSERT ... ON CONFLICT scenarios using native database syntax (PostgreSQL/SQLite). These are atomic operations.

// Insert or update on conflict - atomic operation
InsertBuilder::<User>::new()
    .set("email", "test@example.com")
    .set("name", "John Doe")
    .on_conflict("email")
    .do_update(&["name"])
    .build()?;
// Generates: INSERT INTO users (email, name) VALUES ($1, $2)
//            ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name

§License

MIT OR Apache-2.0