From 59eb96b9c88825c428941a9f5bb25f26aa05480c Mon Sep 17 00:00:00 2001 From: Lucien Cartier-Tilet Date: Sat, 1 Feb 2025 00:43:47 +0100 Subject: [PATCH] docs: improve documentation of georm --- georm-macros/src/georm/ir.rs | 11 -- georm-macros/src/lib.rs | 70 ---------- src/lib.rs | 249 +++++++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 81 deletions(-) diff --git a/georm-macros/src/georm/ir.rs b/georm-macros/src/georm/ir.rs index 6abfeb0..9045db2 100644 --- a/georm-macros/src/georm/ir.rs +++ b/georm-macros/src/georm/ir.rs @@ -44,16 +44,6 @@ pub struct M2MLink { pub to: String, } -//#[georm( -// table = "users", -// many_to_many = [ -// { -// name = friends, -// entity: User, -// link = { table = "user_friendships", from: "user1", to "user2" } -// } -// ] -//)] #[derive(deluxe::ParseMetaItem)] pub struct M2MRelationship { pub name: String, @@ -134,7 +124,6 @@ struct GeormFieldAttributes { pub relation: Option, } -// #[georm(relation = { name = profile, id = "id", entity = Profile, nullable })] #[derive(deluxe::ParseMetaItem, Clone, Debug)] pub struct O2ORelationship { pub entity: syn::Type, diff --git a/georm-macros/src/lib.rs b/georm-macros/src/lib.rs index a21b78a..d47ad04 100644 --- a/georm-macros/src/lib.rs +++ b/georm-macros/src/lib.rs @@ -1,73 +1,3 @@ -//! Creates ORM functionality for ``SQLx`` with `PostgreSQL`. -//! -//! This crate provides the trait implementation `Georm` which -//! generates the following ``SQLx`` queries: -//! - find an entity by id -//! -//! SQL query: `SELECT * FROM ... WHERE = ...` -//! - insert an entity into the database -//! -//! SQL query: `INSERT INTO ... (...) VALUES (...) RETURNING *` -//! - update an entity in the database -//! -//! SQL query: `UPDATE ... SET ... WHERE = ... RETURNING *` -//! - delete an entity from the database using its id or an id -//! provided by the interface’s user -//! -//! SQL query: `DELETE FROM ... WHERE = ...` -//! - update an entity or create it if it does not already exist in -//! the database -//! -//! This macro relies on the trait `Georm` found in the `georm` -//! crate. -//! -//! To use this macro, you need to add it to the derives of the -//! struct. You will also need to define its identifier -//! -//! # Usage -//! -//! Add `#[georm(table = "my_table_name")]` atop of the structure, -//! after the `Georm` derive. -//! -//! ## Entity Identifier -//! You will also need to add `#[georm(id)]` atop of the field of your -//! struct that will be used as the identifier of your entity. -//! -//! ## Column Name -//! If the name of a field does not match the name of its related -//! column, you can use `#[georm(column = "...")]` to specify the -//! correct value. -//! -//! ```ignore -//! #[derive(Georm)] -//! #[georm(table = "users")] -//! pub struct User { -//! #[georm(id)] -//! id: String, -//! #[georm(column = "name")] -//! username: String, -//! created_at: Timestampz, -//! last_updated: Timestampz, -//! } -//! ``` -//! -//! With the example of the `User` struct, this links it to the -//! `users` table of the connected database. It will use `Users.id` to -//! uniquely identify a user entity. -//! -//! # Limitations -//! ## ID -//! For now, only one identifier is supported. It does not have to be -//! a primary key, but it is strongly encouraged to use Georm ID on a -//! unique and non-null column of your database schema. -//! -//! ## Database type -//! -//! For now, only the ``PostgreSQL`` syntax is supported. If you use -//! another database that uses the same syntax, you’re in luck! -//! Otherwise, pull requests to add additional syntaxes are most -//! welcome. - mod georm; use georm::georm_derive_macro2; diff --git a/src/lib.rs b/src/lib.rs index 64dffd8..3a24a72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,252 @@ +//! # Georm +//! +//! ## Introduction +//! +//! Georm is a simple, opinionated SQLx ORM for PostgreSQL. +//! +//! To automatically implement the `Georm` trait, you need at least: +//! - to derive the `Georm` and `sqlx::FromRow` traits +//! - use the `georm` proc-macro to indicate the table in which your entity +//! lives +//! - use the `georm` proc-macro again to indicate which field of your struct is +//! the identifier of your entity. +//! +//! ## Simple usage +//! Here is a minimal use of Georm with a struct: +//! +//! ```ignore +//! #[derive(sqlx::FromRow, Georm)] +//! #[georm(table = "users")] +//! pub struct User { +//! #[georm(id)] +//! id: i32, +//! username: String, +//! hashed_password: String, +//! } +//! ``` +//! +//! The `User` type will now have access to all the functions declared in the +//! `Georm` trait. +//! +//! ## One-to-one relationships +//! +//! You can then create relationships between different entities. For instance, +//! you can use an identifier of another entity as a link to that other entity. +//! +//! ```ignore +//! #[derive(sqlx::FromRow, Georm)] +//! #[georm(table = "profiles")] +//! pub struct Profile { +//! #[georm(id)] +//! id: i32, +//! #[georm( +//! relation = { +//! entity = User, +//! name = "user", +//! table = "users", +//! remote_id = "id", +//! nullable = false +//! }) +//! ] +//! user_id: i32, +//! display_name: String, +//! } +//! ``` +//! +//! This will give access to the `Profile::get_user(&self, pool: &sqlx::PgPool) +//! -> User` method. +//! +//! Here is an explanation of what these different values mean: +//! +//! | Value Name | Explanation | Default value | +//! |------------|-----------------------------------------------------------------------------------------|---------------| +//! | entity | Rust type of the entity found in the database | N/A | +//! | name | Name of the remote entity within the local entity; generates a method named `get_{name}` | N/A | +//! | table | Database table where the entity is stored | N/A | +//! | remote_id | Name of the column serving as the identifier of the entity | `"id"` | +//! | nullable | Whether the relationship can be broken | `false` | +//! +//! Note that in this instance, the `remote_id` and `nullable` values can be +//! omitted as this is their default value. This below is a strict equivalent: +//! +//! ```ignore +//! #[derive(sqlx::FromRow, Georm)] +//! #[georm(table = "profiles")] +//! pub struct Profile { +//! #[georm(id)] +//! id: i32, +//! #[georm(relation = { entity = User, table = "users", name = "user" })] +//! user_id: i32, +//! display_name: String, +//! } +//! ``` +//! +//! ## One-to-many relationships +//! +//! Sometimes, our entity is the one being referenced to by multiple entities, +//! but we have no internal reference to these remote entities in our local +//! entity. Fortunately, we have a way to indicate to Georm how to find these. +//! +//! ```ignore +//! #[derive(sqlx::FromRow, Georm)] +//! #[georm(table = "posts")] +//! struct Post { +//! #[georm(id)] +//! id: i32, +//! #[georm(relation = { entity = User, table = "users", name = "user" })] +//! author_id: i32, +//! content: String +//! } +//! +//! #[derive(sqlx::FromRow, Georm)] +//! #[georm( +//! table = "users", +//! one_to_many = [{ +//! entity = Post, +//! name = "posts", +//! table = "posts", +//! remote_id = "id" +//! }] +//! )] +//! struct User { +//! #[georm(id)] +//! id: i32, +//! username: String, +//! hashed_password: String +//! } +//! ``` +//! +//! As we’ve seen earlier, the struct `Post` has access to the method +//! `Post::get_user(&self, pool: &sqlx::PgPool) -> User` thanks to the +//! proc-macro used on `author_id`. However, `User` now has also access to +//! `User::get_posts(&self, pool: &sqlx::PgPool) -> Vec`. And as you can +//! see, `one_to_many` is an array, meaning you can define several one-to-many +//! relationships for `User`. +//! +//! Here is an explanation of the values of `one_to_many`: +//! +//! | Value Name | Explanaion | Default Value | +//! |------------|------------------------------------------------------------------------------------------|---------------| +//! | entity | Rust type of the entity found in the database | N/A | +//! | name | Name of the remote entity within the local entity; generates a method named `get_{name}` | N/A | +//! | table | Database table where the entity is stored | N/A | +//! | remote_id | Name of the column serving as the identifier of the entity | `"id"` | +//! +//! As with one-to-one relationships, `remote_id` is optional. The following +//! `User` struct is strictly equivalent. +//! +//! ```ignore +//! #[derive(sqlx::FromRow, Georm)] +//! #[georm( +//! table = "users", +//! one_to_many = [{ entity = Post, name = "posts", table = "posts" }] +//! )] +//! struct User { +//! #[georm(id)] +//! id: i32, +//! username: String, +//! hashed_password: String +//! } +//! ``` +//! +//! ## Many-to-many relationships +//! +//! Many-to-many relationships between entities A and entities B with Georm rely +//! on a third table which refers to both. For instance, the following SQL code +//! describes a many-to-many relationship between books and book genre. +//! +//! ```sql +//! CREATE TABLE books ( +//! id SERIAL PRIMARY KEY, +//! title VARCHAR(100) NOT NULL +//! ); +//! +//! CREATE TABLE genres ( +//! id SERIAL PRIMARY KEY, +//! name VARCHAR(100) NOT NULL +//! ); +//! +//! CREATE TABLE books_genres ( +//! book_id INT NOT NULL, +//! genre_id INT NOT NULL, +//! PRIMARY KEY (book_id, genre_id), +//! FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE CASCADE, +//! FOREIGN KEY (genre_id) REFERENCES genres(id) ON DELETE CASCADE +//! ); +//! ``` +//! +//! The table `books_genres` is the one defining the many-to-many relationship +//! between the table `books` and the table `genres`. With Georm, this gives us +//! the following code: +//! +//! ```ignore +//! #[derive(sqlx::FromRow, Georm)] +//! #[georm( +//! table = "books", +//! many_to_many = [{ +//! name = "genres", +//! entity = Genre, +//! table = "genres", +//! remote_id = "id", +//! link = { table = "books_genres", from = "book_id", to = "genre_id" } +//! }] +//! )] +//! struct Book { +//! #[georm(id)] +//! id: i32, +//! title: String +//! } +//! +//! #[derive(sqlx::FromRow, Georm)] +//! #[georm( +//! table = "genres", +//! many_to_many = [{ +//! entity = Book, +//! name = "books", +//! table = "books", +//! remote_id = "id", +//! link = { table = "books_genres", from = "genre_id", to = "book_id" } +//! }] +//! )] +//! struct Genre { +//! #[georm(id)] +//! id: i32, +//! name: String +//! } +//! ``` +//! +//! This generates two methods: +//! - `Book::get_genres(&self, pool: &sqlx::PgPool) -> Vec` +//! - `Genre::get_books(&self, pool: &sqlx::PgPool) -> Vec` +//! +//! As you can see, `many_to_many` is also an array, meaning we can define +//! several many-to-many relationships for the same struct. +//! +//! Here is an explanation of the values behind `many_to_many`: +//! +//! | Value Name | Explanation | Default value | +//! |------------|------------------------------------------------------------------------------------------|---------------| +//! | entity | Rust type of the entity found in the database | N/A | +//! | name | Name of the remote entity within the local entity; generates a method named `get_{name}` | N/A | +//! | table | Database table where the entity is stored | N/A | +//! | remote_id | Name of the column serving as the identifier of the entity | `"id"` | +//! | link.table | Name of the many-to-many relationship table | N/A | +//! | link.from | Column of the linking table referring to this entity | N/A | +//! | link.to | Column of the linking table referring to the remote entity | N/A | +//! +//! ## Limitations +//! ### Database +//! +//! For now, Georm is limited to PostgreSQL. Other databases may be supported in +//! the future, such as Sqlite or MySQL, but that is not the case yet. +//! +//! ## Identifiers +//! +//! Identifiers, or primary keys from the point of view of the database, may +//! only be simple types recognized by SQLx. They also cannot be arrays, and +//! optionals are only supported in one-to-one relationships when explicitly +//! marked as nullables. + pub use georm_macros::Georm; pub trait Georm {