2025-03-02 16:07:30 +01:00
<div align="center">
<a href="https://github.com/Phundrak/georm">
<img src="assets/logo.png" alt="Georm logo" width="150px" />
</a>
</div>
2025-02-01 01:25:34 +01:00
<h1 align="center">Georm</h1>
<div align="center">
<strong>
2025-06-05 00:49:11 +02:00
A simple, type-safe SQLx ORM for PostgreSQL
2025-02-01 01:25:34 +01:00
</strong>
</div>
<br/>
<div align="center">
<!-- Github Actions -->
<a href="https://github.com/phundrak/georm/actions/workflows/ci.yaml?query=branch%3Amain">
2025-06-05 00:49:11 +02:00
<img src="https://img.shields.io/github/actions/workflow/status/phundrak/georm/ci.yaml?branch=main&style=flat-square" alt="actions status" />
</a>
2025-02-01 01:25:34 +01:00
<!-- Version -->
<a href="https://crates.io/crates/georm">
2025-03-02 16:07:30 +01:00
<img src="https://img.shields.io/crates/v/georm.svg?style=flat-square" alt="Crates.io version" />
</a>
2025-02-01 01:25:34 +01:00
<!-- Docs -->
<a href="https://docs.rs/georm">
2025-03-02 16:07:30 +01:00
<img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square" alt="docs.rs docs" />
</a>
2025-06-05 00:49:11 +02:00
<!-- License -->
<a href="#license ">
<img src="https://img.shields.io/badge/license-MIT%20OR%20GPL--3.0-blue?style=flat-square" alt="License" />
</a>
2025-02-01 01:25:34 +01:00
</div>
2025-06-05 00:49:11 +02:00
## Overview
2025-02-01 01:25:34 +01:00
2025-06-05 00:49:11 +02:00
Georm is a lightweight, opinionated Object-Relational Mapping (ORM) library built on top of [SQLx ](https://crates.io/crates/sqlx ) for PostgreSQL. It provides a clean, type-safe interface for common database operations while leveraging SQLx's compile-time query verification.
2025-02-01 01:25:34 +01:00
2025-06-05 00:49:11 +02:00
### Key Features
2025-02-01 01:25:34 +01:00
2025-06-05 00:49:11 +02:00
- **Type Safety**: Compile-time verified SQL queries using SQLx macros
2025-08-09 12:19:06 +02:00
- **Flexible Executor**: Works with both `PgPool` and `Transaction` for atomic operations.
2025-06-05 00:49:11 +02:00
- **Zero Runtime Cost**: No reflection or runtime query building
- **Simple API**: Intuitive derive macros for common operations
- **Relationship Support**: One-to-one, one-to-many, and many-to-many relationships
2025-06-07 16:16:46 +02:00
- **Composite Primary Keys**: Support for multi-field primary keys
2025-06-05 00:49:11 +02:00
- **Defaultable Fields**: Easy entity creation with database defaults and auto-generated values
- **PostgreSQL Native**: Optimized for PostgreSQL features and data types
2025-02-01 01:25:34 +01:00
2025-06-05 00:49:11 +02:00
## Quick Start
2025-02-01 01:25:34 +01:00
2025-06-05 00:49:11 +02:00
### Installation
2025-02-01 01:25:34 +01:00
2025-06-05 00:49:11 +02:00
Add Georm and SQLx to your `Cargo.toml` :
2025-02-01 01:25:34 +01:00
2025-06-05 00:49:11 +02:00
```toml
[ dependencies ]
2025-06-07 12:57:28 +02:00
sqlx = { version = "0.8.6" , features = [ "runtime-tokio-rustls" , "postgres" , "macros" ] }
2025-06-05 00:49:11 +02:00
georm = "0.1"
2025-02-01 01:25:34 +01:00
```
2025-06-05 00:49:11 +02:00
### Basic Usage
1. **Define your database schema** :
2025-02-01 01:25:34 +01:00
```sql
2025-06-05 00:49:11 +02:00
CREATE TABLE authors (
2025-02-01 01:25:34 +01:00
id SERIAL PRIMARY KEY ,
2025-06-05 00:49:11 +02:00
name VARCHAR ( 100 ) NOT NULL ,
email VARCHAR ( 255 ) UNIQUE NOT NULL
2025-02-01 01:25:34 +01:00
);
2025-06-05 00:49:11 +02:00
CREATE TABLE posts (
2025-02-01 01:25:34 +01:00
id SERIAL PRIMARY KEY ,
2025-06-05 00:49:11 +02:00
title VARCHAR ( 200 ) NOT NULL ,
content TEXT NOT NULL ,
published BOOLEAN DEFAULT FALSE ,
author_id INT NOT NULL REFERENCES authors ( id ),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW ()
2025-02-01 01:25:34 +01:00
);
```
2025-06-05 00:49:11 +02:00
2. **Define your Rust entities** :
2025-02-01 01:25:34 +01:00
```rust
2025-06-05 00:49:11 +02:00
use georm ::Georm ;
2025-06-07 12:57:28 +02:00
#[derive(Georm)]
2025-06-05 00:49:11 +02:00
#[georm(table = "authors" )]
2025-02-01 01:25:34 +01:00
pub struct Author {
2025-06-05 00:49:11 +02:00
#[georm(id)]
2025-02-01 01:25:34 +01:00
pub id : i32 ,
pub name : String ,
2025-06-05 00:49:11 +02:00
pub email : String ,
}
2025-06-07 12:57:28 +02:00
#[derive(Georm)]
2025-06-05 00:49:11 +02:00
#[georm(table = "posts" )]
pub struct Post {
#[georm(id)]
pub id : i32 ,
pub title : String ,
pub content : String ,
pub published : bool ,
#[georm(relation = {
entity = Author,
table = "authors" ,
name = "author"
})]
pub author_id : i32 ,
pub created_at : chrono ::DateTime < chrono ::Utc > ,
2025-02-01 01:25:34 +01:00
}
```
2025-06-05 00:49:11 +02:00
3. **Use the generated methods** :
```rust
use sqlx ::PgPool ;
async fn example ( pool : & PgPool ) -> sqlx ::Result < () > {
2025-08-09 12:19:06 +02:00
// Start a transaction
let mut tx = pool . begin (). await ? ;
2025-06-05 00:49:11 +02:00
// Create an author
let author = Author {
id : 0 , // Will be auto-generated
name : "Jane Doe" . to_string (),
email : "jane@example.com" . to_string (),
};
2025-08-09 12:19:06 +02:00
let author = author . create ( & mut * tx ). await ? ;
2025-06-05 00:49:11 +02:00
// Create a post
let post = Post {
id : 0 ,
title : "Hello, Georm!" . to_string (),
content : "This is my first post using Georm." . to_string (),
published : false ,
author_id : author . id ,
created_at : chrono ::Utc ::now (),
};
2025-08-09 12:19:06 +02:00
let post = post . create ( & mut * tx ). await ? ;
// Commit the transaction
tx . commit (). await ? ;
2025-06-05 00:49:11 +02:00
2025-08-09 12:19:06 +02:00
// Find all posts (using the pool directly)
2025-06-05 00:49:11 +02:00
let all_posts = Post ::find_all ( pool ). await ? ;
// Get the post's author
let post_author = post . get_author ( pool ). await ? ;
println! ( "Post ' {} ' by {} " , post . title , post_author . name );
Ok (())
}
```
## Advanced Features
2025-06-07 16:16:46 +02:00
### Composite Primary Keys
Georm supports composite primary keys by marking multiple fields with `#[georm(id)]` :
```rust
#[derive(Georm)]
#[georm(table = "user_roles" )]
pub struct UserRole {
#[georm(id)]
pub user_id : i32 ,
#[georm(id)]
pub role_id : i32 ,
pub assigned_at : chrono ::DateTime < chrono ::Utc > ,
}
```
This automatically generates a composite ID struct:
```rust
// Generated automatically
pub struct UserRoleId {
pub user_id : i32 ,
pub role_id : i32 ,
}
// Usage
let id = UserRoleId { user_id : 1 , role_id : 2 };
let user_role = UserRole ::find ( pool , & id ). await ? ;
```
**Note** : Relationships are not yet supported for entities with composite primary keys.
2025-08-07 20:27:45 +02:00
### Defaultable and Generated Fields
2025-06-05 00:49:11 +02:00
2025-08-07 20:27:45 +02:00
Georm provides three attributes for handling fields with database-managed values:
#### `#[georm(defaultable)]` - Optional Override Fields
For fields with database defaults that can be manually overridden:
2025-06-05 00:49:11 +02:00
2025-02-01 01:25:34 +01:00
```rust
2025-06-07 12:57:28 +02:00
#[derive(Georm)]
2025-06-05 00:49:11 +02:00
#[georm(table = "posts" )]
pub struct Post {
#[georm(id, defaultable)]
pub id : i32 , // Auto-generated serial
pub title : String ,
#[georm(defaultable)]
pub published : bool , // Has database default (false)
#[georm(defaultable)]
pub created_at : chrono ::DateTime < chrono ::Utc > , // DEFAULT NOW()
pub author_id : i32 ,
}
```
2025-08-07 20:27:45 +02:00
#### `#[georm(generated)]` - Generated by Default
For PostgreSQL `GENERATED BY DEFAULT` columns that can be overridden but are typically auto-generated:
```rust
#[derive(Georm)]
#[georm(table = "products" )]
pub struct Product {
#[georm(id, generated_always)]
pub id : i32 , // GENERATED ALWAYS AS IDENTITY
#[georm(generated)]
pub sku_number : i32 , // GENERATED BY DEFAULT AS IDENTITY
pub name : String ,
pub price : sqlx ::types ::BigDecimal ,
}
```
#### `#[georm(generated_always)]` - Always Generated
For PostgreSQL `GENERATED ALWAYS` columns that are strictly managed by the database:
```rust
#[derive(Georm)]
#[georm(table = "products" )]
pub struct Product {
#[georm(id, generated_always)]
pub id : i32 , // GENERATED ALWAYS AS IDENTITY
pub name : String ,
pub price : sqlx ::types ::BigDecimal ,
pub discount_percent : i32 ,
#[georm(generated_always)]
pub final_price : Option < sqlx ::types ::BigDecimal > , // GENERATED ALWAYS AS (expression) STORED
}
```
#### Generated Structs and Behavior
Both `defaultable` and `generated` fields create a companion `<Entity>Default` struct:
2025-06-05 00:49:11 +02:00
```rust
use georm ::Defaultable ;
2025-08-07 20:27:45 +02:00
let product_default = ProductDefault {
name : "Laptop" . to_string (),
price : BigDecimal ::from ( 999 ),
discount_percent : 10 ,
sku_number : None , // Let database auto-generate
// Note: generated_always fields are excluded from this struct
2025-06-05 00:49:11 +02:00
};
2025-08-07 20:27:45 +02:00
let created_product = product_default . create ( pool ). await ? ;
2025-06-05 00:49:11 +02:00
```
2025-08-07 20:27:45 +02:00
#### Key Differences
| Attribute | INSERT Behavior | UPDATE Behavior | Use Case |
|--------------------|------------------------------------|-----------------|----------------------------------------------|
| `defaultable` | Optional (can override defaults) | Included | Fields with database defaults |
| `generated` | Optional (can override generation) | Included | `GENERATED BY DEFAULT` columns |
| `generated_always` | **Excluded** (always generated) | **Excluded** | `GENERATED ALWAYS` columns, computed columns |
#### Important Notes
- **`generated_always` fields are completely excluded** from INSERT and UPDATE statements to prevent database errors
- **`generated` and `generated_always` cannot be used together** on the same field
- **`generated` fields behave like `defaultable` fields** but are semantically distinct for future enhancements
- **Option types cannot be marked as `defaultable` ** to prevent `Option<Option<T>>` situations
2025-06-05 00:49:11 +02:00
### Relationships
Georm supports comprehensive relationship modeling with two approaches: field-level relationships for foreign keys and struct-level relationships for reverse lookups.
#### Field-Level Relationships (Foreign Keys)
Use the `relation` attribute on foreign key fields to generate lookup methods:
```rust
2025-06-07 12:57:28 +02:00
#[derive(Georm)]
2025-06-05 00:49:11 +02:00
#[georm(table = "posts" )]
pub struct Post {
#[georm(id)]
2025-02-01 01:25:34 +01:00
pub id : i32 ,
2025-06-05 00:49:11 +02:00
pub title : String ,
#[georm(relation = {
entity = Author, // Target entity type
table = "authors" , // Target table name
name = "author" , // Method name (generates get_author)
remote_id = "id" , // Target table's key column (default: "id" )
nullable = false // Whether relationship can be null (default: false)
})]
pub author_id : i32 ,
2025-02-01 01:25:34 +01:00
}
```
2025-06-05 00:49:11 +02:00
**Generated method** : `post.get_author(pool).await? -> Author`
For nullable relationships:
2025-02-01 01:25:34 +01:00
```rust
2025-06-07 12:57:28 +02:00
#[derive(Georm)]
2025-06-05 00:49:11 +02:00
#[georm(table = "posts" )]
pub struct Post {
#[georm(id)]
2025-02-01 01:25:34 +01:00
pub id : i32 ,
2025-06-05 00:49:11 +02:00
pub title : String ,
#[georm(relation = {
entity = Category,
table = "categories" ,
name = "category" ,
nullable = true // Allows NULL values
})]
pub category_id : Option < i32 > ,
2025-02-01 01:25:34 +01:00
}
```
2025-06-05 00:49:11 +02:00
**Generated method** : `post.get_category(pool).await? -> Option<Category>`
#### Struct-Level Relationships (Reverse Lookups)
Define relationships at the struct level to query related entities that reference this entity:
##### One-to-One Relationships
2025-02-01 01:25:34 +01:00
```rust
2025-06-07 12:57:28 +02:00
#[derive(Georm)]
2025-06-05 00:49:11 +02:00
#[georm(
table = "users" ,
one_to_one = [{
entity = Profile, // Related entity type
name = "profile" , // Method name (generates get_profile)
table = "profiles" , // Related table name
remote_id = "user_id" , // Foreign key in related table
}]
)]
pub struct User {
#[georm(id)]
pub id : i32 ,
pub username : String ,
}
```
**Generated method** : `user.get_profile(pool).await? -> Option<Profile>`
##### One-to-Many Relationships
```rust
2025-06-07 12:57:28 +02:00
#[derive(Georm)]
2025-06-05 00:49:11 +02:00
#[georm(
table = "authors" ,
one_to_many = [{
entity = Post, // Related entity type
name = "posts" , // Method name (generates get_posts)
table = "posts" , // Related table name
remote_id = "author_id" // Foreign key in related table
}, {
entity = Comment, // Multiple relationships allowed
name = "comments" ,
table = "comments" ,
remote_id = "author_id"
}]
)]
2025-02-01 01:25:34 +01:00
pub struct Author {
#[georm(id)]
pub id : i32 ,
pub name : String ,
}
```
2025-06-05 00:49:11 +02:00
**Generated methods** :
- `author.get_posts(pool).await? -> Vec<Post>`
- `author.get_comments(pool).await? -> Vec<Comment>`
2025-02-01 01:25:34 +01:00
2025-06-05 00:49:11 +02:00
##### Many-to-Many Relationships
For many-to-many relationships, specify the link table that connects the entities:
```sql
-- Example schema for books and genres
CREATE TABLE books (
id SERIAL PRIMARY KEY ,
title VARCHAR ( 200 ) NOT NULL
);
CREATE TABLE genres (
id SERIAL PRIMARY KEY ,
name VARCHAR ( 100 ) NOT NULL
);
CREATE TABLE book_genres (
book_id INT NOT NULL REFERENCES books ( id ),
genre_id INT NOT NULL REFERENCES genres ( id ),
PRIMARY KEY ( book_id , genre_id )
);
```
2025-02-01 01:25:34 +01:00
```rust
2025-06-07 12:57:28 +02:00
#[derive(Georm)]
2025-02-01 01:25:34 +01:00
#[georm(
table = "books" ,
many_to_many = [{
2025-06-05 00:49:11 +02:00
entity = Genre, // Related entity type
name = "genres" , // Method name (generates get_genres)
table = "genres" , // Related table name
remote_id = "id" , // Primary key in related table (default: "id" )
link = { // Link table configuration
table = "book_genres" , // Join table name
from = "book_id" , // Column referencing this entity
to = "genre_id" // Column referencing related entity
}
2025-02-01 01:25:34 +01:00
}]
)]
pub struct Book {
#[georm(id)]
2025-06-05 00:49:11 +02:00
pub id : i32 ,
pub title : String ,
}
2025-06-07 12:57:28 +02:00
#[derive(Georm)]
2025-06-05 00:49:11 +02:00
#[georm(
table = "genres" ,
many_to_many = [{
entity = Book,
name = "books" ,
table = "books" ,
link = {
table = "book_genres" ,
from = "genre_id" , // Note: reversed perspective
to = "book_id"
}
}]
)]
pub struct Genre {
#[georm(id)]
pub id : i32 ,
pub name : String ,
}
```
**Generated methods** :
- `book.get_genres(pool).await? -> Vec<Genre>`
- `genre.get_books(pool).await? -> Vec<Book>`
#### Relationship Attribute Reference
| Attribute | Description | Required | Default |
|--------------|------------------------------------------------------|----------|---------|
| `entity` | Target entity type | Yes | N/A |
| `name` | Method name (generates `get_{name}` ) | Yes | N/A |
| `table` | Target table name | Yes | N/A |
| `remote_id` | Target table's key column | No | `"id"` |
| `nullable` | Whether relationship can be null (field-level only) | No | `false` |
| `link.table` | Join table name (many-to-many only) | Yes* | N/A |
| `link.from` | Column referencing this entity (many-to-many only) | Yes* | N/A |
| `link.to` | Column referencing target entity (many-to-many only) | Yes* | N/A |
*Required for many-to-many relationships
#### Complex Relationship Example
Here's a comprehensive example showing multiple relationship types:
```rust
2025-06-07 12:57:28 +02:00
#[derive(Georm)]
2025-06-05 00:49:11 +02:00
#[georm(
table = "posts" ,
one_to_many = [{
entity = Comment,
name = "comments" ,
table = "comments" ,
remote_id = "post_id"
}],
many_to_many = [{
entity = Tag,
name = "tags" ,
table = "tags" ,
link = {
table = "post_tags" ,
from = "post_id" ,
to = "tag_id"
}
}]
)]
pub struct Post {
#[georm(id)]
pub id : i32 ,
pub title : String ,
pub content : String ,
// Field-level relationship (foreign key)
#[georm(relation = {
entity = Author,
table = "authors" ,
name = "author"
})]
pub author_id : i32 ,
// Nullable field-level relationship
#[georm(relation = {
entity = Category,
table = "categories" ,
name = "category" ,
nullable = true
})]
pub category_id : Option < i32 > ,
2025-02-01 01:25:34 +01:00
}
```
2025-06-05 00:49:11 +02:00
**Generated methods** :
- `post.get_author(pool).await? -> Author` (from field relation)
- `post.get_category(pool).await? -> Option<Category>` (nullable field relation)
- `post.get_comments(pool).await? -> Vec<Comment>` (one-to-many)
- `post.get_tags(pool).await? -> Vec<Tag>` (many-to-many)
## API Reference
### Core Operations
2025-08-09 12:19:06 +02:00
All entities implementing `Georm<Id>` get these methods. All database operations now accept any `sqlx` -compatible executor, which can be a connection pool (`&PgPool` ) or a transaction (`&mut Transaction<'_, Postgres>` ).
```rust
async fn example ( pool : & PgPool , post_id : i32 ) -> sqlx ::Result < () > {
// Operations on the pool
let all_posts = Post ::find_all ( pool ). await ? ;
// Operations within a transaction
let mut tx = pool . begin (). await ? ;
let post = Post ::find ( & mut * tx , & post_id ). await ? ;
if let Some ( post ) = post {
post . delete ( & mut * tx ). await ? ;
}
tx . commit (). await ? ;
Ok (())
}
```
The available methods are:
2025-06-05 00:49:11 +02:00
```rust
// Query operations
2025-08-09 12:19:06 +02:00
Post ::find_all ( executor ). await ? ;
Post ::find ( executor , & post_id ). await ? ;
2025-06-05 00:49:11 +02:00
// Mutation operations
2025-08-09 12:19:06 +02:00
post . create ( executor ). await ? ;
post . update ( executor ). await ? ;
2025-08-09 15:09:22 +02:00
post . upsert ( executor ). await ? ;
2025-08-09 12:19:06 +02:00
post . delete ( executor ). await ? ;
Post ::delete_by_id ( executor , & post_id ). await ? ;
2025-06-05 00:49:11 +02:00
// Utility
2025-08-09 12:19:06 +02:00
post . get_id ();
2025-06-05 00:49:11 +02:00
```
### Defaultable Operations
Entities with defaultable fields get a companion `<Entity>Default` struct:
```rust
// Create with defaults
post_default . create ( pool ). await ? ;
```
## Configuration
### Attributes Reference
#### Struct-level attributes
```rust
#[georm(
table = "table_name" , // Required: database table name
one_to_one = [{ /* ... */ }], // Optional: one-to-one relationships
one_to_many = [{ /* ... */ }], // Optional: one-to-many relationships
many_to_many = [{ /* ... */ }] // Optional: many-to-many relationships
)]
```
#### Field-level attributes
```rust
#[georm(id)] // Mark as primary key
#[georm(defaultable)] // Mark as defaultable field
2025-08-07 20:27:45 +02:00
#[georm(generated)] // Mark as generated by default field
#[georm(generated_always)] // Mark as always generated field
2025-06-05 00:49:11 +02:00
#[georm(relation = { /* ... */ })] // Define relationship
```
## Performance
Georm is designed for zero runtime overhead:
- **Compile-time queries**: All SQL is verified at compile time
- **No reflection**: Direct field access, no runtime introspection
- **Minimal allocations**: Efficient use of owned vs borrowed data
- **SQLx integration**: Leverages SQLx's optimized PostgreSQL driver
2025-06-05 23:56:15 +02:00
## Examples
### Comprehensive Example
For an example showcasing user management, comments, and follower relationships, see the example in `examples/postgres/users-comments-and-followers/` . This example demonstrates:
- User management and profile management
- Comment system with user associations
- Follower/following relationships (many-to-many)
- Interactive CLI interface with CRUD operations
- Database migrations and schema setup
To run the example:
```bash
# Set up your database
export DATABASE_URL = "postgres://username:password@localhost/georm_example"
# Run migrations
cargo sqlx migrate run
# Run the example
cd examples/postgres/users-comments-and-followers
cargo run help # For a list of all available actions
```
2025-06-05 00:49:11 +02:00
## Comparison
| Feature | Georm | SeaORM | Diesel |
|----------------------|-------|--------|--------|
2025-08-01 09:26:00 +02:00
| Compile-time safety | ✅ | ✅ | ✅ |
| Relationship support | ✅ | ✅ | ✅ |
| Async support | ✅ | ✅ | ⚠️[^1] |
2025-06-05 00:49:11 +02:00
| Learning curve | Low | Medium | High |
2025-08-01 09:26:00 +02:00
| Macro simplicity | ✅ | ❌ | ❌ |
| Advanced queries | ❌ | ✅ | ✅ |
[^1]: Requires `diesel-async`
2025-06-05 00:49:11 +02:00
## Roadmap
### High Priority
2025-06-12 15:22:35 +02:00
- **Simplified Relationship Syntax**: Remove redundant table/remote_id specifications by inferring them from target entity metadata
- **Multi-Database Support**: MySQL and SQLite support with feature flags
2025-06-05 00:49:11 +02:00
### Medium Priority
2025-06-07 16:16:46 +02:00
- **Composite Key Relationships**: Add relationship support (one-to-one, one-to-many, many-to-many) for entities with composite primary keys
2025-06-05 23:56:15 +02:00
- **Field-Based Queries**: Generate `find_by_{field_name}` methods that return `Vec<T>` for regular fields or `Option<T>` for unique fields
2025-06-05 00:49:11 +02:00
- **Relationship Optimization**: Eager loading and N+1 query prevention
2025-06-12 15:22:35 +02:00
- **Automatic Table Name Inference**: Infer table names from struct names (PascalCase → snake_case plural), eliminating the need for explicit `#[georm(table = "...")]` attributes
- **Alternative Attribute Syntax**: Introduce path-based attribute syntax as sugar (e.g., `#[table("users")]` , `#[id]` ) while maintaining backward compatibility
2025-06-05 00:49:11 +02:00
### Lower Priority
- **Migration Support**: Schema generation and evolution utilities
2025-06-12 15:22:35 +02:00
- **Soft Delete**: Optional soft delete with `deleted_at` timestamps
2025-06-05 00:49:11 +02:00
- **Enhanced Error Handling**: Custom error types with better context
## Contributing
We welcome contributions! Please see our [Contributing Guide ](CONTRIBUTING.md ) for details.
### Development Setup
#### Prerequisites
2025-06-07 12:57:28 +02:00
- **Rust 1.86+**: Georm uses modern Rust features and follows the MSRV specified in `rust-toolchain.toml`
2025-06-05 00:49:11 +02:00
- **PostgreSQL 12+**: Required for running tests and development
- **Git**: For version control
- **Jujutsu**: For version control (alternative to Git)
#### Required Tools
The following tools are used in the development workflow:
- **[just ](https://github.com/casey/just )**: Task runner for common development commands
- **[cargo-deny ](https://github.com/EmbarkStudios/cargo-deny )**: License and security auditing
- **[sqlx-cli ](https://github.com/launchbadge/sqlx/tree/main/sqlx-cli )**: Database migrations and management
- **[bacon ](https://github.com/Canop/bacon )**: Background code checker (optional but recommended)
Install these tools:
```bash
# Install just (task runner)
cargo install just
# Install cargo-deny (for auditing)
cargo install cargo-deny
# Install sqlx-cli (for database management)
cargo install sqlx-cli --no-default-features --features native-tls,postgres
# Install bacon (optional, for live feedback)
cargo install bacon
```
#### Quick Start
```bash
# Clone the repository
git clone https://github.com/Phundrak/georm.git
cd georm
# Set up your PostgreSQL database and set DATABASE_URL
export DATABASE_URL = "postgres://username:password@localhost/georm_test"
# Run migrations
just migrate
# Run all tests
just test
# Run linting
just lint
# Run security audit
just audit
# Run all checks (format, lint, audit, test)
just check-all
```
#### Available Commands (via just)
```bash
just # Default: run linting
just build # Build the project
just build-release # Build in release mode
just test # Run all tests
just lint # Run clippy linting
just audit # Run security and license audit
just migrate # Run database migrations
just format # Format all code
just format-check # Check code formatting
just check-all # Run all checks (format, lint, audit, test)
just clean # Clean build artifacts
```
#### Running Specific Tests
```bash
# Run tests for a specific module
cargo test --test simple_struct
cargo test --test defaultable_struct
cargo test --test m2m_relationship
# Run tests with output
cargo test -- --nocapture
# Run a specific test function
cargo test defaultable_struct_should_exist
```
#### Development with Bacon (Optional)
For continuous feedback during development:
```bash
# Run clippy continuously
bacon
# Run tests continuously
bacon test
# Build docs continuously
bacon doc
```
2025-06-05 18:37:53 +02:00
#### Devenv Development Environment (Optional)
2025-06-05 00:49:11 +02:00
2025-06-05 18:37:53 +02:00
If you use [Nix ](https://nixos.org/ ), you can use the provided devenv configuration for a reproducible development environment:
2025-06-05 00:49:11 +02:00
```bash
# Enter the development shell with all tools pre-installed
2025-06-05 18:37:53 +02:00
devenv shell
2025-06-05 00:49:11 +02:00
# Or use direnv for automatic environment activation
direnv allow
```
2025-06-05 18:37:53 +02:00
The devenv configuration provides:
2025-06-07 12:57:28 +02:00
- Exact Rust version (1.86) with required components
2025-06-05 00:49:11 +02:00
- All development tools (just, cargo-deny, sqlx-cli, bacon)
- LSP support (rust-analyzer)
- SQL tooling (sqls for SQL language server)
2025-06-05 18:37:53 +02:00
- PostgreSQL database for development
2025-06-05 00:49:11 +02:00
2025-06-05 18:37:53 +02:00
**Devenv configuration:**
2025-06-05 00:49:11 +02:00
- **Rust toolchain**: Specified version with rustfmt, clippy, and rust-analyzer
- **Development tools**: just, cargo-deny, sqlx-cli, bacon
- **SQL tools**: sqls (SQL language server)
2025-06-05 18:37:53 +02:00
- **Database**: PostgreSQL with automatic setup
- **Platform support**: Cross-platform (Linux, macOS, etc.)
2025-06-05 00:49:11 +02:00
#### Database Setup for Tests
Tests require a PostgreSQL database. Set up a test database:
```sql
-- Connect to PostgreSQL as superuser
CREATE DATABASE georm_test ;
CREATE USER georm_user WITH PASSWORD 'georm_password' ;
GRANT ALL PRIVILEGES ON DATABASE georm_test TO georm_user ;
```
Set the environment variable:
```bash
export DATABASE_URL = "postgres://georm_user:georm_password@localhost/georm_test"
```
#### IDE Setup
- Ensure `rust-analyzer` is configured
- Set up PostgreSQL connection for SQL syntax highlighting
#### Code Style
The project uses standard Rust formatting:
```bash
# Format code
just format
# Check formatting (CI)
just format-check
```
Clippy linting is enforced:
```bash
# Run linting
just lint
# Fix auto-fixable lints
cargo clippy --fix
```
## License
Licensed under either of
* MIT License ([LICENSE-MIT ](LICENSE-MIT.md ) or http://opensource.org/licenses/MIT)
* GNU General Public License v3.0 ([LICENSE-GPL ](LICENSE-GPL.md ) or https://www.gnu.org/licenses/gpl-3.0.html)
at your option.
## Acknowledgments
- Built on top of the excellent [SQLx ](https://github.com/launchbadge/sqlx ) library
- Inspired by [Hibernate ](https://hibernate.org/ )