georm/src/georm.rs
Lucien Cartier-Tilet a7696270da
docs: rewrite documentation for core traits and library
Completely rewrite and expand documentation for Georm’s core
functionality with detailed explanations, examples, and implementation
details.

Changes:
- Rewrite lib.rs with comprehensive library documentation covering all
  features
- Add extensive Georm trait documentation with method-specific details
- Add detailed Defaultable trait documentation with usage patterns

The documentation now provides complete coverage of:
- All CRUD operations with database behavior details
- Composite key support and generated ID structs
- Defaultable field patterns and companion struct generation
- Relationship modeling (field-level and struct-level)
- Error handling and performance characteristics
- PostgreSQL-specific features and optimizations
2025-06-09 22:41:43 +02:00

371 lines
14 KiB
Rust

/// Core database operations trait for Georm entities.
///
/// This trait is automatically implemented by the `#[derive(Georm)]` macro and provides
/// all essential CRUD operations for database entities. The trait is generic over the
/// primary key type `Id`, which can be a simple type (e.g., `i32`) or a generated
/// composite key struct (e.g., `UserRoleId`).
///
/// ## Generated Implementation
///
/// When you derive `Georm` on a struct, this trait is automatically implemented with
/// PostgreSQL-optimized queries that use:
/// - **Prepared statements** for security and performance
/// - **RETURNING clause** to capture database-generated values
/// - **ON CONFLICT** for efficient upsert operations
/// - **Compile-time verification** via SQLx macros
///
/// ## Method Categories
///
/// ### Static Methods (Query Operations)
/// - [`find_all`] - Retrieve all entities from the table
/// - [`find`] - Retrieve a single entity by primary key
/// - [`delete_by_id`] - Delete an entity by primary key
///
/// ### Instance Methods (Mutation Operations)
/// - [`create`] - Insert a new entity into the database
/// - [`update`] - Update an existing entity in the database
/// - [`create_or_update`] - Upsert (insert or update) an entity
/// - [`delete`] - Delete this entity from the database
/// - [`get_id`] - Get the primary key of this entity
///
/// ## Usage Examples
///
/// ```ignore
/// use georm::Georm;
///
/// #[derive(Georm)]
/// #[georm(table = "users")]
/// struct User {
/// #[georm(id)]
/// id: i32,
/// username: String,
/// email: String,
/// }
///
/// // Static methods
/// let all_users = User::find_all(&pool).await?;
/// let user = User::find(&pool, &1).await?;
/// let deleted_count = User::delete_by_id(&pool, &1).await?;
///
/// // Instance methods
/// let new_user = User { id: 0, username: "alice".into(), email: "alice@example.com".into() };
/// let created = new_user.create(&pool).await?;
/// let updated = created.update(&pool).await?;
/// let id = updated.get_id();
/// let deleted_count = updated.delete(&pool).await?;
/// ```
///
/// ## Composite Key Support
///
/// For entities with composite primary keys, the `Id` type parameter becomes a generated
/// struct following the pattern `{EntityName}Id`:
///
/// ```ignore
/// #[derive(Georm)]
/// #[georm(table = "user_roles")]
/// struct UserRole {
/// #[georm(id)]
/// user_id: i32,
/// #[georm(id)]
/// role_id: i32,
/// assigned_at: chrono::DateTime<chrono::Utc>,
/// }
///
/// // Generated: pub struct UserRoleId { pub user_id: i32, pub role_id: i32 }
/// // Trait: impl Georm<UserRoleId> for UserRole
///
/// let id = UserRoleId { user_id: 1, role_id: 2 };
/// let user_role = UserRole::find(&pool, &id).await?;
/// ```
///
/// ## Error Handling
///
/// All methods return `sqlx::Result<T>` and may fail due to:
/// - Database connection issues
/// - Constraint violations (unique, foreign key, etc.)
/// - Invalid queries (though most are caught at compile time)
/// - Missing records (for operations expecting existing data)
///
/// [`find_all`]: Georm::find_all
/// [`find`]: Georm::find
/// [`create`]: Georm::create
/// [`update`]: Georm::update
/// [`create_or_update`]: Georm::create_or_update
/// [`delete`]: Georm::delete
/// [`delete_by_id`]: Georm::delete_by_id
/// [`get_id`]: Georm::get_id
pub trait Georm<Id> {
/// Retrieve all entities from the database table.
///
/// This method executes a `SELECT * FROM table_name` query and returns all records
/// as a vector of entities. The results are not paginated or filtered.
///
/// # Returns
/// - `Ok(Vec<Self>)` - All entities in the table (may be empty)
/// - `Err(sqlx::Error)` - Database connection or query execution errors
///
/// # Performance Notes
/// - Returns all records in memory - consider pagination for large tables
/// - Uses prepared statements for optimal performance
/// - No built-in ordering - results may vary between calls
///
/// # Examples
/// ```ignore
/// let all_users = User::find_all(&pool).await?;
/// println!("Found {} users", all_users.len());
/// ```
///
/// # Errors
/// Returns `sqlx::Error` for database connection issues, permission problems,
/// or if the table doesn't exist.
fn find_all(
pool: &sqlx::PgPool,
) -> impl ::std::future::Future<Output = ::sqlx::Result<Vec<Self>>> + Send
where
Self: Sized;
/// Find a single entity by its primary key.
///
/// This method executes a `SELECT * FROM table_name WHERE primary_key = $1` query
/// (or equivalent for composite keys) and returns the matching entity if found.
///
/// # Parameters
/// - `pool` - Database connection pool
/// - `id` - Primary key value (simple type or composite key struct)
///
/// # Returns
/// - `Ok(Some(Self))` - Entity found and returned
/// - `Ok(None)` - No entity with the given ID exists
/// - `Err(sqlx::Error)` - Database connection or query execution errors
///
/// # Examples
/// ```ignore
/// // Simple primary key
/// let user = User::find(&pool, &1).await?;
///
/// // Composite primary key
/// let id = UserRoleId { user_id: 1, role_id: 2 };
/// let user_role = UserRole::find(&pool, &id).await?;
/// ```
///
/// # Errors
/// Returns `sqlx::Error` for database connection issues, type conversion errors,
/// or query execution problems. Note that not finding a record is not an error
/// - it returns `Ok(None)`.
fn find(
pool: &sqlx::PgPool,
id: &Id,
) -> impl std::future::Future<Output = sqlx::Result<Option<Self>>> + Send
where
Self: Sized;
/// Insert this entity as a new record in the database.
///
/// This method executes an `INSERT INTO table_name (...) VALUES (...) RETURNING *`
/// query and returns the newly created entity with any database-generated values
/// (such as auto-increment IDs, default timestamps, etc.).
///
/// # Parameters
/// - `pool` - Database connection pool
///
/// # Returns
/// - `Ok(Self)` - The entity as it exists in the database after insertion
/// - `Err(sqlx::Error)` - Database constraint violations or connection errors
///
/// # Database Behavior
/// - Uses `RETURNING *` to capture database-generated values
/// - Respects database defaults for fields marked `#[georm(defaultable)]`
/// - Triggers and database-side modifications are reflected in the returned entity
///
/// # Examples
/// ```ignore
/// let new_user = User { id: 0, username: "alice".into(), email: "alice@example.com".into() };
/// let created_user = new_user.create(&pool).await?;
/// println!("Created user with ID: {}", created_user.id);
/// ```
///
/// # Errors
/// Returns `sqlx::Error` for:
/// - Unique constraint violations
/// - Foreign key constraint violations
/// - NOT NULL constraint violations
/// - Database connection issues
/// - Permission problems
fn create(
&self,
pool: &sqlx::PgPool,
) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
where
Self: Sized;
/// Update an existing entity in the database.
///
/// This method executes an `UPDATE table_name SET ... WHERE primary_key = ... RETURNING *`
/// query using the entity's current primary key to locate the record to update.
///
/// # Parameters
/// - `pool` - Database connection pool
///
/// # Returns
/// - `Ok(Self)` - The entity as it exists in the database after the update
/// - `Err(sqlx::Error)` - Database errors or if no matching record exists
///
/// # Database Behavior
/// - Uses `RETURNING *` to capture any database-side changes
/// - Updates all fields, not just changed ones
/// - Triggers and database-side modifications are reflected in the returned entity
/// - Fails if no record with the current primary key exists
///
/// # Examples
/// ```ignore
/// let mut user = User::find(&pool, &1).await?.unwrap();
/// user.email = "newemail@example.com".into();
/// let updated_user = user.update(&pool).await?;
/// ```
///
/// # Errors
/// Returns `sqlx::Error` for:
/// - No matching record found (record was deleted by another process)
/// - Constraint violations (unique, foreign key, etc.)
/// - Database connection issues
/// - Permission problems
fn update(
&self,
pool: &sqlx::PgPool,
) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
where
Self: Sized;
/// Insert or update this entity using PostgreSQL's upsert functionality.
///
/// This method executes an `INSERT ... ON CONFLICT (...) DO UPDATE SET ... RETURNING *`
/// query that atomically inserts the entity if it doesn't exist, or updates it if
/// a record with the same primary key already exists.
///
/// # Parameters
/// - `pool` - Database connection pool
///
/// # Returns
/// - `Ok(Self)` - The final entity state in the database (inserted or updated)
/// - `Err(sqlx::Error)` - Database connection or constraint violation errors
///
/// # Database Behavior
/// - Uses PostgreSQL's `ON CONFLICT` for true atomic upsert
/// - More efficient than separate find-then-create-or-update logic
/// - Uses `RETURNING *` to capture the final state
/// - Conflict resolution is based on the primary key constraint
///
/// # Examples
/// ```ignore
/// let user = User { id: 1, username: "alice".into(), email: "alice@example.com".into() };
/// let final_user = user.create_or_update(&pool).await?;
/// // Will insert if ID 1 doesn't exist, update if it does
/// ```
///
/// # Errors
/// Returns `sqlx::Error` for:
/// - Non-primary-key constraint violations
/// - Database connection issues
/// - Permission problems
fn create_or_update(
&self,
pool: &sqlx::PgPool,
) -> impl ::std::future::Future<Output = sqlx::Result<Self>>
where
Self: Sized;
/// Delete this entity from the database.
///
/// This method executes a `DELETE FROM table_name WHERE primary_key = ...` query
/// using this entity's primary key to identify the record to delete.
///
/// # Parameters
/// - `pool` - Database connection pool
///
/// # Returns
/// - `Ok(u64)` - Number of rows affected (0 if entity didn't exist, 1 if deleted)
/// - `Err(sqlx::Error)` - Database connection or constraint violation errors
///
/// # Database Behavior
/// - Uses the entity's current primary key for deletion
/// - Returns 0 if no matching record exists (not an error)
/// - May fail due to foreign key constraints if other records reference this entity
///
/// # Examples
/// ```ignore
/// let user = User::find(&pool, &1).await?.unwrap();
/// let deleted_count = user.delete(&pool).await?;
/// assert_eq!(deleted_count, 1);
/// ```
///
/// # Errors
/// Returns `sqlx::Error` for:
/// - Foreign key constraint violations (referenced by other tables)
/// - Database connection issues
/// - Permission problems
fn delete(
&self,
pool: &sqlx::PgPool,
) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
/// Delete an entity by its primary key without needing an entity instance.
///
/// This method executes a `DELETE FROM table_name WHERE primary_key = ...` query
/// using the provided ID to identify the record to delete.
///
/// # Parameters
/// - `pool` - Database connection pool
/// - `id` - Primary key value (simple type or composite key struct)
///
/// # Returns
/// - `Ok(u64)` - Number of rows affected (0 if entity didn't exist, 1 if deleted)
/// - `Err(sqlx::Error)` - Database connection or constraint violation errors
///
/// # Database Behavior
/// - More efficient than `find().delete()` when you only have the ID
/// - Returns 0 if no matching record exists (not an error)
/// - May fail due to foreign key constraints if other records reference this entity
///
/// # Examples
/// ```ignore
/// // Simple primary key
/// let deleted_count = User::delete_by_id(&pool, &1).await?;
///
/// // Composite primary key
/// let id = UserRoleId { user_id: 1, role_id: 2 };
/// let deleted_count = UserRole::delete_by_id(&pool, &id).await?;
/// ```
///
/// # Errors
/// Returns `sqlx::Error` for:
/// - Foreign key constraint violations (referenced by other tables)
/// - Database connection issues
/// - Permission problems
fn delete_by_id(
pool: &sqlx::PgPool,
id: &Id,
) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
/// Get the primary key of this entity.
///
/// For entities with simple primary keys, this returns the ID value directly.
/// For entities with composite primary keys, this returns an owned instance of
/// the generated `{EntityName}Id` struct.
///
/// # Returns
/// - Simple keys: The primary key value (e.g., `i32`, `String`)
/// - Composite keys: Generated ID struct (e.g., `UserRoleId`)
///
/// # Examples
/// ```ignore
/// // Simple primary key
/// let user = User { id: 42, username: "alice".into(), email: "alice@example.com".into() };
/// let id = user.get_id(); // Returns 42
///
/// // Composite primary key
/// let user_role = UserRole { user_id: 1, role_id: 2, assigned_at: now };
/// let id = user_role.get_id(); // Returns UserRoleId { user_id: 1, role_id: 2 }
/// ```
fn get_id(&self) -> Id;
}