Skip to content

WLmutou/grorm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

grorm

GRoutines + ORM — A goroutine-native async ORM for Rust with multi-database support.

License

Features

  • Multi-database: PostgreSQL, MySQL, SQLite
  • Goroutine-native: Built on gorust, no tokio required
  • Chainable API: where_eq().limit().offset().order().find()
  • Transactions: Transaction::begin() with auto-rollback on drop
  • Auto table creation: create_table() generates DDL from model definitions
  • Index & constraints: #[index], #[unique], #[unique_index = "name"]
  • JOIN support: left_join(), inner_join(), right_join()
  • IN queries: where_in("name", vec![...])
  • Connection pooling: gorust channel-based connection pool
  • Derive macros: #[derive(DeriveModel)] auto-generates Model trait

Quick Start

Add to your Cargo.toml:

[dependencies]
grorm = "0.1.0"
gorust = "1.5"

SQLite Example

use grorm::{ConnectionConfig, ConnectionPool, SqliteDriverFactory, QueryBuilder, Value, Error};
use grorm::DeriveModel;
use gorust::runtime;

#[derive(Debug, DeriveModel)]
#[table = "users"]
struct User {
    id: i64,
    #[index]
    name: String,
    #[unique]
    email: String,
    age: i32,
}

#[runtime]
fn main() -> Result<(), Error> {
    let config = ConnectionConfig::sqlite("test.db");
    let pool = ConnectionPool::new(SqliteDriverFactory, config, 4);
    let mut conn = pool.get()?;

    let mut qb = QueryBuilder::<User>::new(conn.driver_mut());

    qb.create_table()?;

    let user = User { id: 0, name: "Alice".into(), email: "alice@x.com".into(), age: 30 };
    qb.insert(&user)?;

    let users = qb.where_eq("name", Value::from("Alice")).find()?;
    println!("{:?}", users);

    Ok(())
}

PostgreSQL Example

use grorm::{ConnectionConfig, ConnectionPool, PostgresDriverFactory, QueryBuilder, Value, Error};
use grorm::DeriveModel;
use gorust::runtime;

#[derive(Debug, DeriveModel)]
#[table = "users"]
struct User {
    id: i64,
    name: String,
    email: String,
    age: i32,
}

#[runtime]
fn main() -> Result<(), Error> {
    let config = ConnectionConfig::postgres("localhost", 5432, "mydb", "user", "pass");
    let pool = ConnectionPool::new(PostgresDriverFactory, config, 4);
    let mut conn = pool.get()?;

    let mut qb = QueryBuilder::<User>::new(conn.driver_mut());
    qb.create_table()?;

    qb.insert(&User { id: 0, name: "Alice".into(), email: "alice@x.com".into(), age: 30 })?;

    let users = qb.find_all()?;
    println!("{:?}", users);

    Ok(())
}

MySQL Example

use grorm::{ConnectionConfig, ConnectionPool, MysqlDriverFactory, QueryBuilder, Value, Error};
use grorm::DeriveModel;
use gorust::runtime;

#[derive(Debug, DeriveModel)]
#[table = "users"]
struct User {
    id: i64,
    name: String,
    email: String,
    age: i32,
}

#[runtime]
fn main() -> Result<(), Error> {
    let config = ConnectionConfig::mysql("localhost", 3306, "mydb", "user", "pass");
    let pool = ConnectionPool::new(MysqlDriverFactory, config, 4);
    let mut conn = pool.get()?;

    let mut qb = QueryBuilder::<User>::new(conn.driver_mut());
    qb.create_table()?;

    qb.insert(&User { id: 0, name: "Alice".into(), email: "alice@x.com".into(), age: 30 })?;

    let users = qb.find_all()?;
    println!("{:?}", users);

    Ok(())
}

Model Definition

Use #[derive(DeriveModel)] to define your model:

use grorm::DeriveModel;

#[derive(Debug, DeriveModel)]
#[table = "users"]                    // Override table name (default: snake_case + "s")
#[primary_key = "uuid"]               // Override primary key (default: "id")
struct User {
    id: i64,                          // Auto-increment primary key (when id = 0)
    #[index]                          // Create a regular index
    name: String,
    #[unique]                         // Create a unique constraint
    email: String,
    #[unique_index = "uq_name_age"]   // Composite unique index (same group name)
    first_name: String,
    #[unique_index = "uq_name_age"]
    last_name: String,
    age: i32,
}

Available Attributes

Attribute Scope Description
#[table = "name"] struct Override table name
#[primary_key = "col"] struct Override primary key column
#[index] field Create a regular index
#[unique] field Create a unique constraint
#[unique_index = "name"] field Group into composite unique index

CRUD Operations

Create Table

let mut qb = QueryBuilder::<User>::new(conn.driver_mut());
qb.create_table()?;

Insert

let user = User { id: 0, name: "Alice".into(), email: "alice@x.com".into(), age: 30 };
let id = qb.insert(&user)?;  // Returns Option<i64> (auto-generated id)

Query

// Find all
let all = qb.find_all()?;

// Find by id
let user = qb.find_by_id(1)?;

// Find one with conditions
let user = qb.where_eq("age", Value::from(30)).find_one()?;

// Find with conditions
let users = qb.where_eq("age", Value::from(30)).find()?;

// Find with column name
let users = qb.find_where("name", Value::from("Alice"))?;

// Find with model (non-zero fields become conditions)
let filter = User { id: 0, name: "".into(), email: "".into(), age: 30 };
let users = qb.where_model(&filter).find()?;

// IN query
let users = qb.where_in("name", vec![Value::from("Alice"), Value::from("Bob")]).find()?;

// Pagination
let users = qb.order("age", true).limit(10).offset(0).find()?;

// Count
let total = qb.count()?;

Update

// Update single column
let rows = qb.where_eq("name", Value::from("Alice"))
    .update_one("age", Value::from(31))?;

// Update from model (non-zero/non-empty fields)
let update = User { id: 0, name: "".into(), email: "".into(), age: 31 };
let rows = qb.where_eq("name", Value::from("Alice"))
    .update_model(&update)?;

Delete

// Delete with conditions
let rows = qb.where_eq("name", Value::from("Alice")).delete()?;

// Delete all
let rows = qb.delete_all()?;

Transactions

let mut tx = Transaction::<User>::begin(conn.driver_mut())?;

tx.insert(&User { id: 0, name: "Alice".into(), email: "alice@x.com".into(), age: 30 })?;
tx.where_eq("name", Value::from("Bob")).update_one("age", Value::from(26))?;

tx.commit()?;
// If tx goes out of scope without commit, it auto-rolls back

All QueryBuilder methods are available on Transaction:

  • insert, find, find_all, find_one, find_by_id, find_where
  • where_eq, where_in, where_model
  • update_one, update_model
  • delete, delete_all
  • count, limit, offset, order

JOIN Support

let mut qb = QueryBuilder::<User>::new(conn.driver_mut());

qb.left_join("orders", "users.id = orders.user_id")
  .inner_join("profiles", "users.id = profiles.user_id")
  .right_join("scores", "users.id = scores.user_id");

let results = qb.find()?;

Connection Pool

// Create pool
let config = ConnectionConfig::sqlite("test.db");
let pool = ConnectionPool::new(SqliteDriverFactory, config, 4);

// Get connection (blocks if pool exhausted)
let mut conn = pool.get()?;

// Use driver directly
let mut qb = QueryBuilder::<User>::new(conn.driver_mut());

Error Handling

All public APIs return Result<T, grorm::Error>. The Error enum covers:

Variant Description
Connection Auth, network errors
Query SQL syntax, constraint violations
Execute Write operation errors
Protocol Wire format, parsing errors
Model Serialization/deserialization errors
Pool Pool exhausted, closed
Config Invalid DSN, configuration
Io Wrapped I/O errors
NotFound Entity not found
Transaction Begin, commit, rollback errors

Project Structure

grorm/
├── Cargo.toml
├── README.md
├── src/
│   ├── lib.rs              # Library root, re-exports
│   ├── error.rs            # Unified error types
│   ├── driver/             # Database driver abstraction
│   │   ├── mod.rs          # ConnectionConfig, DatabaseDriver trait
│   │   ├── postgres.rs     # PostgreSQL driver
│   │   ├── mysql.rs        # MySQL driver
│   │   └── sqlite.rs       # SQLite driver
│   ├── protocol/           # Database wire protocols
│   │   ├── mod.rs
│   │   ├── pg.rs           # PostgreSQL protocol
│   │   ├── myproto.rs      # MySQL protocol
│   │   └── sqlite_proto.rs # SQLite protocol (mock)
│   ├── query/              # Low-level SQL builders
│   │   ├── mod.rs
│   │   ├── select.rs
│   │   ├── insert.rs
│   │   ├── update.rs
│   │   └── delete.rs
│   ├── types/              # Type mapping (Rust ↔ SQL)
│   │   ├── mod.rs
│   │   ├── value.rs        # Value enum
│   │   ├── from_sql.rs     # FromSql trait
│   │   └── to_sql.rs       # ToSql trait
│   ├── orm/                # ORM core
│   │   ├── mod.rs
│   │   ├── model.rs        # Model trait, ColumnInfo
│   │   ├── query.rs        # QueryBuilder (chainable API)
│   │   └── transaction.rs  # Transaction support
│   └── pool/               # Connection pool (gorust channels)
│       └── mod.rs
├── grorm-macros/           # Procedural macros
│   ├── Cargo.toml
│   └── src/
│       └── lib.rs          # #[derive(DeriveModel)]
└── examples/
    ├── sqlite_demo.rs
    ├── postgres_demo.rs
    └── mysql_demo.rs

License

MIT

About

Goroutine-native ORM for Rust

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors