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>
|
|
|
|
|
A simple, opinionated SQLx ORM for PostgreSQL
|
|
|
|
|
</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<br/>
|
|
|
|
|
|
|
|
|
|
<div align="center">
|
|
|
|
|
<!-- Github Actions -->
|
|
|
|
|
<a href="https://github.com/phundrak/georm/actions/workflows/ci.yaml?query=branch%3Amain">
|
|
|
|
|
<img src="https://img.shields.io/github/actions/workflow/status/phundrak/georm/ci.yaml?branch=main&style=flat-square" alt="actions status" /></a>
|
|
|
|
|
<!-- 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-02-01 01:25:34 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
2025-03-02 16:07:30 +01:00
|
|
|
|
## What is Georm?
|
2025-02-01 01:25:34 +01:00
|
|
|
|
|
|
|
|
|
Georm is a quite simple ORM built around
|
|
|
|
|
[SQLx](https://crates.io/crates/sqlx) that gives access to a few
|
|
|
|
|
useful functions when interacting with a database, implementing
|
|
|
|
|
automatically the most basic SQL interactions you’re tired of writing.
|
|
|
|
|
|
2025-03-02 16:07:30 +01:00
|
|
|
|
## Why is Georm?
|
2025-02-01 01:25:34 +01:00
|
|
|
|
|
|
|
|
|
I wanted an ORM that’s easy and straightforward to use. I am aware
|
|
|
|
|
some other projects exist, such as
|
|
|
|
|
[SeaORM](https://www.sea-ql.org/SeaORM/), but they generally don’t fit
|
|
|
|
|
my needs and/or my wants of a simple interface. I ended up writing the
|
|
|
|
|
ORM I wanted to use.
|
|
|
|
|
|
2025-03-02 16:07:30 +01:00
|
|
|
|
## How is Georm?
|
2025-02-01 01:25:34 +01:00
|
|
|
|
|
|
|
|
|
I use it in a few projects, and I’m quite happy with it right now. But
|
|
|
|
|
of course, I’m open to constructive criticism and suggestions!
|
|
|
|
|
|
2025-03-02 16:07:30 +01:00
|
|
|
|
## How can I use it?
|
2025-02-01 01:25:34 +01:00
|
|
|
|
|
|
|
|
|
Georm works with SQLx, but does not re-export it itself. To get
|
|
|
|
|
started, install both Georm and SQLx in your Rust project:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
cargo add sqlx --features postgres,macros # and any other feature you might want
|
|
|
|
|
cargo add georm
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
As Georm relies heavily on the macro
|
|
|
|
|
[`query_as!`](https://docs.rs/sqlx/latest/sqlx/macro.query_as.html),
|
|
|
|
|
the `macros` feature is not optional. Declare your tables in your
|
|
|
|
|
Postgres database (you may want to use SQLx’s `migrate` feature for
|
|
|
|
|
this), and then declare their equivalent in Rust.
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
CREATE TABLE biographies (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
content TEXT NOT NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE authors (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
name VARCHAR(100) NOT NULL,
|
|
|
|
|
biography_id INT,
|
|
|
|
|
FOREIGN KEY (biography_id) REFERENCES biographies(id)
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
pub struct Author {
|
|
|
|
|
pub id: i32,
|
|
|
|
|
pub name: String,
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
To link a struct to a table in your database, derive the
|
|
|
|
|
`sqlx::FromRow` and the `georm::Georm` traits.
|
|
|
|
|
```rust
|
|
|
|
|
#[derive(sqlx::FromRow, Georm)]
|
|
|
|
|
pub struct Author {
|
|
|
|
|
pub id: i32,
|
|
|
|
|
pub name: String,
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Now, indicate with the `georm` proc-macro which table they refer to.
|
|
|
|
|
```rust
|
|
|
|
|
#[derive(sqlx::FromRow, Georm)]
|
|
|
|
|
#[georm(table = "authors")]
|
|
|
|
|
pub struct Author {
|
|
|
|
|
pub id: i32,
|
|
|
|
|
pub name: String,
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Finally, indicate with the same proc-macro which field of your struct
|
|
|
|
|
is the primary key in your database.
|
|
|
|
|
```rust
|
|
|
|
|
#[derive(sqlx::FromRow, Georm)]
|
|
|
|
|
#[georm(table = "authors")]
|
|
|
|
|
pub struct Author {
|
|
|
|
|
#[georm(id)]
|
|
|
|
|
pub id: i32,
|
|
|
|
|
pub name: String,
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Congratulations, your struct `Author` now has access to all the
|
|
|
|
|
functions described in the `Georm` trait!
|
|
|
|
|
|
2025-03-02 16:07:30 +01:00
|
|
|
|
## Entity relationship
|
2025-02-01 01:25:34 +01:00
|
|
|
|
|
|
|
|
|
It is possible to implement one-to-one, one-to-many, and many-to-many
|
|
|
|
|
relationships with Georm. This is a quick example of how a struct with
|
|
|
|
|
several relationships of different types may be declared:
|
|
|
|
|
```rust
|
|
|
|
|
#[derive(sqlx::FromRow, Georm)]
|
|
|
|
|
#[georm(
|
|
|
|
|
table = "books",
|
2025-03-02 16:07:30 +01:00
|
|
|
|
one_to_one = [
|
|
|
|
|
{ name = "draft", remote_id = "book_id", table = "drafts", entity = Draft }
|
|
|
|
|
],
|
2025-02-01 01:25:34 +01:00
|
|
|
|
one_to_many = [
|
2025-03-02 16:07:30 +01:00
|
|
|
|
{ name = "reviews", remote_id = "book_id", table = "reviews", entity = Review },
|
|
|
|
|
{ name = "reprints", remote_id = "book_id", table = "reprints", entity = Reprint }
|
2025-02-01 01:25:34 +01:00
|
|
|
|
],
|
|
|
|
|
many_to_many = [{
|
|
|
|
|
name = "genres",
|
|
|
|
|
table = "genres",
|
|
|
|
|
entity = Genre,
|
|
|
|
|
link = { table = "book_genres", from = "book_id", to = "genre_id" }
|
|
|
|
|
}]
|
|
|
|
|
)]
|
|
|
|
|
pub struct Book {
|
|
|
|
|
#[georm(id)]
|
|
|
|
|
ident: i32,
|
|
|
|
|
title: String,
|
|
|
|
|
#[georm(relation = {entity = Author, table = "authors", name = "author"})]
|
|
|
|
|
author_id: i32,
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
To read more about these features, you can refer to the [online
|
2025-02-01 01:59:40 +01:00
|
|
|
|
documentation](https://docs.rs/georm/).
|
2025-06-04 22:15:38 +02:00
|
|
|
|
|
|
|
|
|
## Roadmap / TODO
|
|
|
|
|
|
|
|
|
|
The following features are being considered for future development:
|
|
|
|
|
|
|
|
|
|
### High Priority
|
|
|
|
|
- **Transaction Support**: Add comprehensive transaction support with
|
|
|
|
|
transaction-aware CRUD methods and relationship handling for atomic
|
|
|
|
|
operations across multiple entities
|
|
|
|
|
- **Race Condition Fix**: Replace the current `create_or_update`
|
|
|
|
|
implementation with database-specific UPSERT operations (PostgreSQL
|
|
|
|
|
`ON CONFLICT`, MySQL `ON DUPLICATE KEY UPDATE`, SQLite `ON
|
|
|
|
|
CONFLICT`) to prevent race conditions
|
|
|
|
|
|
|
|
|
|
### Medium Priority
|
|
|
|
|
- **Multi-Database Support**: Extend Georm to support MySQL and SQLite
|
|
|
|
|
in addition to PostgreSQL, with database-specific optimizations and
|
|
|
|
|
dialect handling
|
|
|
|
|
- **Relationship Optimization**: Implement eager loading and N+1 query
|
|
|
|
|
prevention with circular dependency protection to dramatically
|
|
|
|
|
improve performance when working with related entities
|
|
|
|
|
- **Composite Primary Keys**: Add support for entities with multiple
|
|
|
|
|
primary key fields using auto-generated ID structs and type-safe
|
|
|
|
|
composite key handling
|
|
|
|
|
- **Soft Delete**: Implement optional soft delete functionality with
|
|
|
|
|
`deleted_at` timestamps, allowing entities to be marked as deleted
|
|
|
|
|
without physical removal
|
|
|
|
|
|
|
|
|
|
### Lower Priority
|
|
|
|
|
- **Migration Support**: Add optional migration utilities that
|
|
|
|
|
leverage SQLx's existing infrastructure for schema generation,
|
|
|
|
|
verification, and evolution
|
|
|
|
|
- **Enhanced Error Handling**: Consider implementing custom error
|
|
|
|
|
types with better categorization and operation context while
|
|
|
|
|
maintaining compatibility with SQLx errors
|
|
|
|
|
- **Many-to-Many Relationship Improvements**: Add direct methods to
|
|
|
|
|
add or remove items from many-to-many relationships without manually
|
|
|
|
|
handling the join table
|
|
|
|
|
|
|
|
|
|
### Recently Completed
|
|
|
|
|
- ✅ **Defaultable Fields**: Support for fields with database defaults
|
|
|
|
|
or auto-generated values, creating companion structs with optional
|
|
|
|
|
fields for easier entity creation
|