mirror of
				https://github.com/Phundrak/georm.git
				synced 2025-11-04 01:11:10 +00:00 
			
		
		
		
	feat: enable transaction support via sqlx::Executor
				
					
				
			This commit abstracts the database operations to use the generic `sqlx::Executor` trait instead of a concrete `&sqlx::PgPool`. This change allows all generated methods (find, create, update, delete, and relationships) to be executed within a `sqlx::Transaction`, in addition to a connection pool. This is a crucial feature for ensuring atomic operations and data consistency. The public-facing traits `Georm` and `Defaultable` have been updated to require `sqlx::Executor`, and the documentation has been updated to reflect this new capability.
This commit is contained in:
		
							parent
							
								
									3307aa679d
								
							
						
					
					
						commit
						49c7d86102
					
				
							
								
								
									
										51
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								README.md
									
									
									
									
									
								
							@ -38,6 +38,7 @@ Georm is a lightweight, opinionated Object-Relational Mapping (ORM) library buil
 | 
				
			|||||||
### Key Features
 | 
					### Key Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **Type Safety**: Compile-time verified SQL queries using SQLx macros
 | 
					- **Type Safety**: Compile-time verified SQL queries using SQLx macros
 | 
				
			||||||
 | 
					- **Flexible Executor**: Works with both `PgPool` and `Transaction` for atomic operations.
 | 
				
			||||||
- **Zero Runtime Cost**: No reflection or runtime query building
 | 
					- **Zero Runtime Cost**: No reflection or runtime query building
 | 
				
			||||||
- **Simple API**: Intuitive derive macros for common operations
 | 
					- **Simple API**: Intuitive derive macros for common operations
 | 
				
			||||||
- **Relationship Support**: One-to-one, one-to-many, and many-to-many relationships
 | 
					- **Relationship Support**: One-to-one, one-to-many, and many-to-many relationships
 | 
				
			||||||
@ -116,13 +117,16 @@ pub struct Post {
 | 
				
			|||||||
use sqlx::PgPool;
 | 
					use sqlx::PgPool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn example(pool: &PgPool) -> sqlx::Result<()> {
 | 
					async fn example(pool: &PgPool) -> sqlx::Result<()> {
 | 
				
			||||||
 | 
					    // Start a transaction
 | 
				
			||||||
 | 
					    let mut tx = pool.begin().await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Create an author
 | 
					    // Create an author
 | 
				
			||||||
    let author = Author {
 | 
					    let author = Author {
 | 
				
			||||||
        id: 0, // Will be auto-generated
 | 
					        id: 0, // Will be auto-generated
 | 
				
			||||||
        name: "Jane Doe".to_string(),
 | 
					        name: "Jane Doe".to_string(),
 | 
				
			||||||
        email: "jane@example.com".to_string(),
 | 
					        email: "jane@example.com".to_string(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    let author = author.create(pool).await?;
 | 
					    let author = author.create(&mut *tx).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Create a post
 | 
					    // Create a post
 | 
				
			||||||
    let post = Post {
 | 
					    let post = Post {
 | 
				
			||||||
@ -133,9 +137,12 @@ async fn example(pool: &PgPool) -> sqlx::Result<()> {
 | 
				
			|||||||
        author_id: author.id,
 | 
					        author_id: author.id,
 | 
				
			||||||
        created_at: chrono::Utc::now(),
 | 
					        created_at: chrono::Utc::now(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    let post = post.create(pool).await?;
 | 
					    let post = post.create(&mut *tx).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Find all posts
 | 
					    // Commit the transaction
 | 
				
			||||||
 | 
					    tx.commit().await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Find all posts (using the pool directly)
 | 
				
			||||||
    let all_posts = Post::find_all(pool).await?;
 | 
					    let all_posts = Post::find_all(pool).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Get the post's author
 | 
					    // Get the post's author
 | 
				
			||||||
@ -521,22 +528,41 @@ pub struct Post {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Core Operations
 | 
					### Core Operations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
All entities implementing `Georm<Id>` get these methods:
 | 
					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:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```rust
 | 
					```rust
 | 
				
			||||||
// Query operations
 | 
					// Query operations
 | 
				
			||||||
Post::find_all(pool).await?;              // Find all posts
 | 
					Post::find_all(executor).await?;
 | 
				
			||||||
Post::find(pool, &post_id).await?;        // Find by ID
 | 
					Post::find(executor, &post_id).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Mutation operations
 | 
					// Mutation operations
 | 
				
			||||||
post.create(pool).await?;                 // Insert new record
 | 
					post.create(executor).await?;
 | 
				
			||||||
post.update(pool).await?;                 // Update existing record
 | 
					post.update(executor).await?;
 | 
				
			||||||
post.create_or_update(pool).await?;       // Upsert operation
 | 
					post.create_or_update(executor).await?;
 | 
				
			||||||
post.delete(pool).await?;                 // Delete this record
 | 
					post.delete(executor).await?;
 | 
				
			||||||
Post::delete_by_id(pool, &post_id).await?; // Delete by ID
 | 
					Post::delete_by_id(executor, &post_id).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Utility
 | 
					// Utility
 | 
				
			||||||
post.get_id();                            // Get entity ID
 | 
					post.get_id();
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Defaultable Operations
 | 
					### Defaultable Operations
 | 
				
			||||||
@ -624,7 +650,6 @@ cargo run help # For a list of all available actions
 | 
				
			|||||||
## Roadmap
 | 
					## Roadmap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### High Priority
 | 
					### High Priority
 | 
				
			||||||
- **Transaction Support**: Comprehensive transaction handling with atomic operations
 | 
					 | 
				
			||||||
- **Simplified Relationship Syntax**: Remove redundant table/remote_id specifications by inferring them from target entity metadata
 | 
					- **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
 | 
					- **Multi-Database Support**: MySQL and SQLite support with feature flags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -61,7 +61,8 @@ async fn create_comment(
 | 
				
			|||||||
    pool: &sqlx::PgPool,
 | 
					    pool: &sqlx::PgPool,
 | 
				
			||||||
) -> Result {
 | 
					) -> Result {
 | 
				
			||||||
    let prompt = "Who is creating the comment?";
 | 
					    let prompt = "Who is creating the comment?";
 | 
				
			||||||
    let user = User::get_user_by_username_or_select(username.as_deref(), prompt, pool).await?;
 | 
					    let mut tx = pool.begin().await?;
 | 
				
			||||||
 | 
					    let user = User::get_user_by_username_or_select(username.as_deref(), prompt, &mut *tx).await?;
 | 
				
			||||||
    let content = match text {
 | 
					    let content = match text {
 | 
				
			||||||
        Some(text) => text,
 | 
					        Some(text) => text,
 | 
				
			||||||
        None => inquire::Text::new("Content of the comment:")
 | 
					        None => inquire::Text::new("Content of the comment:")
 | 
				
			||||||
@ -73,29 +74,33 @@ async fn create_comment(
 | 
				
			|||||||
        content,
 | 
					        content,
 | 
				
			||||||
        id: None,
 | 
					        id: None,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    let comment = comment.create(pool).await?;
 | 
					    let comment = comment.create(&mut *tx).await?;
 | 
				
			||||||
 | 
					    tx.commit().await?;
 | 
				
			||||||
    println!("Successfuly created comment:\n{comment}");
 | 
					    println!("Successfuly created comment:\n{comment}");
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn remove_comment(id: Option<i32>, pool: &sqlx::PgPool) -> Result {
 | 
					async fn remove_comment(id: Option<i32>, pool: &sqlx::PgPool) -> Result {
 | 
				
			||||||
    let prompt = "Select the comment to remove:";
 | 
					    let prompt = "Select the comment to remove:";
 | 
				
			||||||
 | 
					    let mut tx = pool.begin().await?;
 | 
				
			||||||
    let comment = match id {
 | 
					    let comment = match id {
 | 
				
			||||||
        Some(id) => Comment::find(pool, &id)
 | 
					        Some(id) => Comment::find(&mut *tx, &id)
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
            .map_err(UserInputError::DatabaseError)?
 | 
					            .map_err(UserInputError::DatabaseError)?
 | 
				
			||||||
            .ok_or(UserInputError::CommentDoesNotExist)?,
 | 
					            .ok_or(UserInputError::CommentDoesNotExist)?,
 | 
				
			||||||
        None => Comment::select_comment(prompt, pool).await?,
 | 
					        None => Comment::select_comment(prompt, &mut *tx).await?,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    comment.delete(pool).await?;
 | 
					    comment.delete(&mut *tx).await?;
 | 
				
			||||||
 | 
					    tx.commit().await?;
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn remove_user_comment(username: Option<String>, pool: &sqlx::PgPool) -> Result {
 | 
					async fn remove_user_comment(username: Option<String>, pool: &sqlx::PgPool) -> Result {
 | 
				
			||||||
 | 
					    let mut tx = pool.begin().await?;
 | 
				
			||||||
    let prompt = "Select user whose comment you want to delete:";
 | 
					    let prompt = "Select user whose comment you want to delete:";
 | 
				
			||||||
    let user = User::get_user_by_username_or_select(username.as_deref(), prompt, pool).await?;
 | 
					    let user = User::get_user_by_username_or_select(username.as_deref(), prompt, &mut *tx).await?;
 | 
				
			||||||
    let comments: HashMap<String, Comment> = user
 | 
					    let comments: HashMap<String, Comment> = user
 | 
				
			||||||
        .get_comments(pool)
 | 
					        .get_comments(&mut *tx)
 | 
				
			||||||
        .await?
 | 
					        .await?
 | 
				
			||||||
        .into_iter()
 | 
					        .into_iter()
 | 
				
			||||||
        .map(|comment| (comment.content.clone(), comment))
 | 
					        .map(|comment| (comment.content.clone(), comment))
 | 
				
			||||||
@ -105,7 +110,8 @@ async fn remove_user_comment(username: Option<String>, pool: &sqlx::PgPool) -> R
 | 
				
			|||||||
            .prompt()
 | 
					            .prompt()
 | 
				
			||||||
            .map_err(UserInputError::InquireError)?;
 | 
					            .map_err(UserInputError::InquireError)?;
 | 
				
			||||||
    let comment: &Comment = comments.get(&selected_comment_content).unwrap();
 | 
					    let comment: &Comment = comments.get(&selected_comment_content).unwrap();
 | 
				
			||||||
    comment.delete(pool).await?;
 | 
					    comment.delete(&mut *tx).await?;
 | 
				
			||||||
 | 
					    tx.commit().await?;
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -53,16 +53,17 @@ async fn follow_user(
 | 
				
			|||||||
    followed: Option<String>,
 | 
					    followed: Option<String>,
 | 
				
			||||||
    pool: &sqlx::PgPool,
 | 
					    pool: &sqlx::PgPool,
 | 
				
			||||||
) -> Result {
 | 
					) -> Result {
 | 
				
			||||||
 | 
					    let mut tx = pool.begin().await?;
 | 
				
			||||||
    let follower = User::get_user_by_username_or_select(
 | 
					    let follower = User::get_user_by_username_or_select(
 | 
				
			||||||
        follower.as_deref(),
 | 
					        follower.as_deref(),
 | 
				
			||||||
        "Select who will be following someone:",
 | 
					        "Select who will be following someone:",
 | 
				
			||||||
        pool,
 | 
					        &mut *tx,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .await?;
 | 
					    .await?;
 | 
				
			||||||
    let followed = User::get_user_by_username_or_select(
 | 
					    let followed = User::get_user_by_username_or_select(
 | 
				
			||||||
        followed.as_deref(),
 | 
					        followed.as_deref(),
 | 
				
			||||||
        "Select who will be followed:",
 | 
					        "Select who will be followed:",
 | 
				
			||||||
        pool,
 | 
					        &mut *tx,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .await?;
 | 
					    .await?;
 | 
				
			||||||
    let follow = FollowerDefault {
 | 
					    let follow = FollowerDefault {
 | 
				
			||||||
@ -70,17 +71,22 @@ async fn follow_user(
 | 
				
			|||||||
        follower: follower.id,
 | 
					        follower: follower.id,
 | 
				
			||||||
        followed: followed.id,
 | 
					        followed: followed.id,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    follow.create(pool).await?;
 | 
					    follow.create(&mut *tx).await?;
 | 
				
			||||||
 | 
					    tx.commit().await?;
 | 
				
			||||||
    println!("User {follower} now follows {followed}");
 | 
					    println!("User {follower} now follows {followed}");
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn unfollow_user(follower: Option<String>, pool: &sqlx::PgPool) -> Result {
 | 
					async fn unfollow_user(follower: Option<String>, pool: &sqlx::PgPool) -> Result {
 | 
				
			||||||
    let follower =
 | 
					    let mut tx = pool.begin().await?;
 | 
				
			||||||
        User::get_user_by_username_or_select(follower.as_deref(), "Select who is following", pool)
 | 
					    let follower = User::get_user_by_username_or_select(
 | 
				
			||||||
            .await?;
 | 
					        follower.as_deref(),
 | 
				
			||||||
 | 
					        "Select who is following",
 | 
				
			||||||
 | 
					        &mut *tx,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .await?;
 | 
				
			||||||
    let followed_list: HashMap<String, User> = follower
 | 
					    let followed_list: HashMap<String, User> = follower
 | 
				
			||||||
        .get_followed(pool)
 | 
					        .get_followed(&mut *tx)
 | 
				
			||||||
        .await?
 | 
					        .await?
 | 
				
			||||||
        .iter()
 | 
					        .iter()
 | 
				
			||||||
        .map(|person| (person.username.clone(), person.clone()))
 | 
					        .map(|person| (person.username.clone(), person.clone()))
 | 
				
			||||||
@ -97,8 +103,9 @@ async fn unfollow_user(follower: Option<String>, pool: &sqlx::PgPool) -> Result
 | 
				
			|||||||
        follower.id,
 | 
					        follower.id,
 | 
				
			||||||
        followed.id
 | 
					        followed.id
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .execute(pool)
 | 
					    .execute(&mut *tx)
 | 
				
			||||||
    .await?;
 | 
					    .await?;
 | 
				
			||||||
 | 
					    tx.commit().await?;
 | 
				
			||||||
    println!("User {follower} unfollowed {followed}");
 | 
					    println!("User {follower} unfollowed {followed}");
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -18,8 +18,11 @@ pub struct Comment {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Comment {
 | 
					impl Comment {
 | 
				
			||||||
    pub async fn select_comment(prompt: &str, pool: &sqlx::PgPool) -> Result<Self> {
 | 
					    pub async fn select_comment<'e, E>(prompt: &str, executor: E) -> Result<Self>
 | 
				
			||||||
        let comments: HashMap<String, Self> = Self::find_all(pool)
 | 
					    where
 | 
				
			||||||
 | 
					        E: sqlx::Executor<'e, Database = sqlx::Postgres>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        let comments: HashMap<String, Self> = Self::find_all(executor)
 | 
				
			||||||
            .await?
 | 
					            .await?
 | 
				
			||||||
            .into_iter()
 | 
					            .into_iter()
 | 
				
			||||||
            .map(|comment| (comment.content.clone(), comment))
 | 
					            .map(|comment| (comment.content.clone(), comment))
 | 
				
			||||||
 | 
				
			|||||||
@ -38,7 +38,10 @@ impl Profile {
 | 
				
			|||||||
        self.bio.clone().unwrap_or_default()
 | 
					        self.bio.clone().unwrap_or_default()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn try_new(user_id: i32, pool: &sqlx::PgPool) -> Result<Self> {
 | 
					    pub async fn try_new<'e, E>(user_id: i32, executor: E) -> Result<Self>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        E: sqlx::Executor<'e, Database = sqlx::Postgres>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        let profile = ProfileDefault {
 | 
					        let profile = ProfileDefault {
 | 
				
			||||||
            user_id,
 | 
					            user_id,
 | 
				
			||||||
            id: None,
 | 
					            id: None,
 | 
				
			||||||
@ -46,20 +49,23 @@ impl Profile {
 | 
				
			|||||||
            display_name: None,
 | 
					            display_name: None,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        profile
 | 
					        profile
 | 
				
			||||||
            .create(pool)
 | 
					            .create(executor)
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
            .map_err(UserInputError::DatabaseError)
 | 
					            .map_err(UserInputError::DatabaseError)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn update_interactive(
 | 
					    pub async fn update_interactive<'e, E>(
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
        display_name: Option<String>,
 | 
					        display_name: Option<String>,
 | 
				
			||||||
        bio: Option<String>,
 | 
					        bio: Option<String>,
 | 
				
			||||||
        pool: &sqlx::PgPool,
 | 
					        executor: E
 | 
				
			||||||
    ) -> Result<Self> {
 | 
					    ) -> Result<Self>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        E: sqlx::Executor<'e, Database = sqlx::Postgres>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        self.display_name = display_name;
 | 
					        self.display_name = display_name;
 | 
				
			||||||
        self.bio = bio;
 | 
					        self.bio = bio;
 | 
				
			||||||
        self.update(pool)
 | 
					        self.update(executor)
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
            .map_err(UserInputError::DatabaseError)
 | 
					            .map_err(UserInputError::DatabaseError)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -50,8 +50,11 @@ impl From<&str> for UserDefault {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl User {
 | 
					impl User {
 | 
				
			||||||
    async fn select_user(prompt: &str, pool: &sqlx::PgPool) -> Result<Self> {
 | 
					    async fn select_user<'e, E>(prompt: &str, executor: E) -> Result<Self>
 | 
				
			||||||
        let users: HashMap<String, Self> = Self::find_all(pool)
 | 
					    where
 | 
				
			||||||
 | 
					        E: sqlx::Executor<'e, Database = sqlx::Postgres>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        let users: HashMap<String, Self> = Self::find_all(executor)
 | 
				
			||||||
            .await?
 | 
					            .await?
 | 
				
			||||||
            .into_iter()
 | 
					            .into_iter()
 | 
				
			||||||
            .map(|user| (user.username.clone(), user))
 | 
					            .map(|user| (user.username.clone(), user))
 | 
				
			||||||
@ -63,41 +66,50 @@ impl User {
 | 
				
			|||||||
        Ok(user.clone())
 | 
					        Ok(user.clone())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn get_user_by_id_or_select(
 | 
					    pub async fn get_user_by_id_or_select<'e, E>(
 | 
				
			||||||
        id: Option<i32>,
 | 
					        id: Option<i32>,
 | 
				
			||||||
        prompt: &str,
 | 
					        prompt: &str,
 | 
				
			||||||
        pool: &sqlx::PgPool,
 | 
					        executor: E
 | 
				
			||||||
    ) -> Result<Self> {
 | 
					    ) -> Result<Self>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        E: sqlx::Executor<'e, Database = sqlx::Postgres>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        let user = match id {
 | 
					        let user = match id {
 | 
				
			||||||
            Some(id) => Self::find(pool, &id)
 | 
					            Some(id) => Self::find(executor, &id)
 | 
				
			||||||
                .await?
 | 
					                .await?
 | 
				
			||||||
                .ok_or(UserInputError::UserDoesNotExist)?,
 | 
					                .ok_or(UserInputError::UserDoesNotExist)?,
 | 
				
			||||||
            None => Self::select_user(prompt, pool).await?,
 | 
					            None => Self::select_user(prompt, executor).await?,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        Ok(user)
 | 
					        Ok(user)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn get_user_by_username_or_select(
 | 
					    pub async fn get_user_by_username_or_select<'e, E>(
 | 
				
			||||||
        username: Option<&str>,
 | 
					        username: Option<&str>,
 | 
				
			||||||
        prompt: &str,
 | 
					        prompt: &str,
 | 
				
			||||||
        pool: &sqlx::PgPool,
 | 
					        executor: E,
 | 
				
			||||||
    ) -> Result<Self> {
 | 
					    ) -> Result<Self>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        E: sqlx::Executor<'e, Database = sqlx::Postgres>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        let user = match username {
 | 
					        let user = match username {
 | 
				
			||||||
            Some(username) => Self::find_by_username(username, pool)
 | 
					            Some(username) => Self::find_by_username(username, executor)
 | 
				
			||||||
                .await?
 | 
					                .await?
 | 
				
			||||||
                .ok_or(UserInputError::UserDoesNotExist)?,
 | 
					                .ok_or(UserInputError::UserDoesNotExist)?,
 | 
				
			||||||
            None => Self::select_user(prompt, pool).await?,
 | 
					            None => Self::select_user(prompt, executor).await?,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        Ok(user)
 | 
					        Ok(user)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn find_by_username(username: &str, pool: &sqlx::PgPool) -> Result<Option<Self>> {
 | 
					    pub async fn find_by_username<'e, E>(username: &str, executor: E) -> Result<Option<Self>>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        E: sqlx::Executor<'e, Database = sqlx::Postgres>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        sqlx::query_as!(
 | 
					        sqlx::query_as!(
 | 
				
			||||||
            Self,
 | 
					            Self,
 | 
				
			||||||
            "SELECT * FROM Users u WHERE u.username = $1",
 | 
					            "SELECT * FROM Users u WHERE u.username = $1",
 | 
				
			||||||
            username
 | 
					            username
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .fetch_optional(pool)
 | 
					        .fetch_optional(executor)
 | 
				
			||||||
        .await
 | 
					        .await
 | 
				
			||||||
        .map_err(UserInputError::DatabaseError)
 | 
					        .map_err(UserInputError::DatabaseError)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -116,7 +128,8 @@ impl User {
 | 
				
			|||||||
        Ok(user)
 | 
					        Ok(user)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn update_profile(id: Option<i32>, pool: &sqlx::PgPool) -> Result<(User, Profile)> {
 | 
					    pub async fn update_profile(id: Option<i32>, pool: &sqlx::PgPool) -> Result<(User, Profile)>
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        let prompt = "Select the user whose profile you want to update";
 | 
					        let prompt = "Select the user whose profile you want to update";
 | 
				
			||||||
        let user = Self::get_user_by_id_or_select(id, prompt, pool).await?;
 | 
					        let user = Self::get_user_by_id_or_select(id, prompt, pool).await?;
 | 
				
			||||||
        let profile = match user.get_profile(pool).await? {
 | 
					        let profile = match user.get_profile(pool).await? {
 | 
				
			||||||
 | 
				
			|||||||
@ -94,7 +94,10 @@ fn generate_defaultable_trait_impl(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    quote! {
 | 
					    quote! {
 | 
				
			||||||
        impl ::georm::Defaultable<#id_type, #struct_name> for #defaultable_struct_name {
 | 
					        impl ::georm::Defaultable<#id_type, #struct_name> for #defaultable_struct_name {
 | 
				
			||||||
            async fn create(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<#struct_name> {
 | 
					            async fn create<'e, E>(&self, mut executor: E) -> ::sqlx::Result<#struct_name>
 | 
				
			||||||
 | 
					            where
 | 
				
			||||||
 | 
					                E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
                let mut dynamic_fields = Vec::new();
 | 
					                let mut dynamic_fields = Vec::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                #(#field_checks)*
 | 
					                #(#field_checks)*
 | 
				
			||||||
@ -121,7 +124,7 @@ fn generate_defaultable_trait_impl(
 | 
				
			|||||||
                // Then bind defaultable fields that have values
 | 
					                // Then bind defaultable fields that have values
 | 
				
			||||||
                #(#bind_checks)*
 | 
					                #(#bind_checks)*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                query_builder.fetch_one(pool).await
 | 
					                query_builder.fetch_one(executor).await
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -71,8 +71,11 @@ WHERE local.{} = $1",
 | 
				
			|||||||
            value.local.id
 | 
					            value.local.id
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        quote! {
 | 
					        quote! {
 | 
				
			||||||
            pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Vec<#entity>> {
 | 
					            pub async fn #function<'e, E>(&self, mut executor: E) -> ::sqlx::Result<Vec<#entity>>
 | 
				
			||||||
                ::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await
 | 
					            where
 | 
				
			||||||
 | 
					                E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(executor).await
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -172,8 +172,11 @@ impl From<&GeormField> for proc_macro2::TokenStream {
 | 
				
			|||||||
            quote! { fetch_one }
 | 
					            quote! { fetch_one }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        quote! {
 | 
					        quote! {
 | 
				
			||||||
            pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<#return_type> {
 | 
					            pub async fn #function<'e, E>(&self, mut executor: E) -> ::sqlx::Result<#return_type>
 | 
				
			||||||
                ::sqlx::query_as!(#entity, #query, self.#local_ident).#fetch(pool).await
 | 
					            where
 | 
				
			||||||
 | 
					                E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ::sqlx::query_as!(#entity, #query, self.#local_ident).#fetch(executor).await
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -45,8 +45,11 @@ impl From<&SimpleRelationship<OneToOne>> for proc_macro2::TokenStream {
 | 
				
			|||||||
        let entity = &value.entity;
 | 
					        let entity = &value.entity;
 | 
				
			||||||
        let function = value.make_function_name();
 | 
					        let function = value.make_function_name();
 | 
				
			||||||
        quote! {
 | 
					        quote! {
 | 
				
			||||||
            pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Option<#entity>> {
 | 
					            pub async fn #function<'e, E>(&self, mut executor: E) -> ::sqlx::Result<Option<#entity>>
 | 
				
			||||||
                ::sqlx::query_as!(#entity, #query, self.get_id()).fetch_optional(pool).await
 | 
					            where
 | 
				
			||||||
 | 
					                E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ::sqlx::query_as!(#entity, #query, self.get_id()).fetch_optional(executor).await
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -58,8 +61,11 @@ impl From<&SimpleRelationship<OneToMany>> for proc_macro2::TokenStream {
 | 
				
			|||||||
        let entity = &value.entity;
 | 
					        let entity = &value.entity;
 | 
				
			||||||
        let function = value.make_function_name();
 | 
					        let function = value.make_function_name();
 | 
				
			||||||
        quote! {
 | 
					        quote! {
 | 
				
			||||||
            pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Vec<#entity>> {
 | 
					            pub async fn #function<'e, E>(&self, mut executor: E) -> ::sqlx::Result<Vec<#entity>>
 | 
				
			||||||
                ::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await
 | 
					            where
 | 
				
			||||||
 | 
					                E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(executor).await
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -21,13 +21,16 @@ pub fn generate_create_query(table_name: &str, fields: &[GeormField]) -> proc_ma
 | 
				
			|||||||
        placeholders.join(", ")
 | 
					        placeholders.join(", ")
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    quote! {
 | 
					    quote! {
 | 
				
			||||||
        async fn create(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
 | 
					        async fn create<'e, E>(&self, mut executor: E) -> ::sqlx::Result<Self>
 | 
				
			||||||
 | 
					        where
 | 
				
			||||||
 | 
					            E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            ::sqlx::query_as!(
 | 
					            ::sqlx::query_as!(
 | 
				
			||||||
                Self,
 | 
					                Self,
 | 
				
			||||||
                #query,
 | 
					                #query,
 | 
				
			||||||
                #(self.#field_idents),*
 | 
					                #(self.#field_idents),*
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .fetch_one(pool)
 | 
					            .fetch_one(executor)
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -24,16 +24,22 @@ pub fn generate_delete_query(table: &str, id: &IdType) -> proc_macro2::TokenStre
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
    let delete_string = format!("DELETE FROM {table} WHERE {where_clause}");
 | 
					    let delete_string = format!("DELETE FROM {table} WHERE {where_clause}");
 | 
				
			||||||
    quote! {
 | 
					    quote! {
 | 
				
			||||||
        async fn delete_by_id(pool: &::sqlx::PgPool, id: &#id_type) -> ::sqlx::Result<u64> {
 | 
					        async fn delete<'e, E>(&self, mut executor: E) -> ::sqlx::Result<u64>
 | 
				
			||||||
 | 
					        where
 | 
				
			||||||
 | 
					            E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Self::delete_by_id(executor, &self.get_id()).await
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        async fn delete_by_id<'e, E>(mut executor: E, id: &#id_type) -> ::sqlx::Result<u64>
 | 
				
			||||||
 | 
					        where
 | 
				
			||||||
 | 
					            E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            let rows_affected = ::sqlx::query!(#delete_string, #query_args)
 | 
					            let rows_affected = ::sqlx::query!(#delete_string, #query_args)
 | 
				
			||||||
                .execute(pool)
 | 
					                .execute(executor)
 | 
				
			||||||
                .await?
 | 
					                .await?
 | 
				
			||||||
                .rows_affected();
 | 
					                .rows_affected();
 | 
				
			||||||
            Ok(rows_affected)
 | 
					            Ok(rows_affected)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        async fn delete(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<u64> {
 | 
					 | 
				
			||||||
            Self::delete_by_id(pool, &self.get_id()).await
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,8 +4,11 @@ use quote::quote;
 | 
				
			|||||||
pub fn generate_find_all_query(table: &str) -> proc_macro2::TokenStream {
 | 
					pub fn generate_find_all_query(table: &str) -> proc_macro2::TokenStream {
 | 
				
			||||||
    let find_string = format!("SELECT * FROM {table}");
 | 
					    let find_string = format!("SELECT * FROM {table}");
 | 
				
			||||||
    quote! {
 | 
					    quote! {
 | 
				
			||||||
        async fn find_all(pool: &::sqlx::PgPool) -> ::sqlx::Result<Vec<Self>> {
 | 
					        async fn find_all<'e, E>(mut executor: E) -> ::sqlx::Result<Vec<Self>>
 | 
				
			||||||
            ::sqlx::query_as!(Self, #find_string).fetch_all(pool).await
 | 
					        where
 | 
				
			||||||
 | 
					            E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ::sqlx::query_as!(Self, #find_string).fetch_all(executor).await
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -18,10 +21,13 @@ pub fn generate_find_query(table: &str, id: &IdType) -> proc_macro2::TokenStream
 | 
				
			|||||||
        } => {
 | 
					        } => {
 | 
				
			||||||
            let find_string = format!("SELECT * FROM {table} WHERE {} = $1", field_name);
 | 
					            let find_string = format!("SELECT * FROM {table} WHERE {} = $1", field_name);
 | 
				
			||||||
            quote! {
 | 
					            quote! {
 | 
				
			||||||
                async fn find(pool: &::sqlx::PgPool, id: &#field_type) -> ::sqlx::Result<Option<Self>> {
 | 
					                async fn find<'e, E>(mut executor: E, id: &#field_type) -> ::sqlx::Result<Option<Self>>
 | 
				
			||||||
 | 
					                where
 | 
				
			||||||
 | 
					                    E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
                    ::sqlx::query_as!(Self, #find_string, id)
 | 
					                    ::sqlx::query_as!(Self, #find_string, id)
 | 
				
			||||||
                    .fetch_optional(pool)
 | 
					                        .fetch_optional(executor)
 | 
				
			||||||
                    .await
 | 
					                        .await
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -36,10 +42,13 @@ pub fn generate_find_query(table: &str, id: &IdType) -> proc_macro2::TokenStream
 | 
				
			|||||||
                fields.iter().map(|field| field.name.clone()).collect();
 | 
					                fields.iter().map(|field| field.name.clone()).collect();
 | 
				
			||||||
            let find_string = format!("SELECT * FROM {table} WHERE {id_match_string}");
 | 
					            let find_string = format!("SELECT * FROM {table} WHERE {id_match_string}");
 | 
				
			||||||
            quote! {
 | 
					            quote! {
 | 
				
			||||||
                async fn find(pool: &::sqlx::PgPool, id: &#field_type) -> ::sqlx::Result<Option<Self>> {
 | 
					                async fn find<'e, E>(mut executor: E, id: &#field_type) -> ::sqlx::Result<Option<Self>>
 | 
				
			||||||
 | 
					                where
 | 
				
			||||||
 | 
					                    E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
                    ::sqlx::query_as!(Self, #find_string, #(id.#id_members),*)
 | 
					                    ::sqlx::query_as!(Self, #find_string, #(id.#id_members),*)
 | 
				
			||||||
                    .fetch_optional(pool)
 | 
					                        .fetch_optional(executor)
 | 
				
			||||||
                    .await
 | 
					                        .await
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -28,14 +28,17 @@ pub fn generate_update_query(table_name: &str, fields: &[GeormField]) -> proc_ma
 | 
				
			|||||||
        where_clauses.join(" AND ")
 | 
					        where_clauses.join(" AND ")
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    quote! {
 | 
					    quote! {
 | 
				
			||||||
        async fn update(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
 | 
					        async fn update<'e, E>(&self, mut executor: E) -> ::sqlx::Result<Self>
 | 
				
			||||||
 | 
					        where
 | 
				
			||||||
 | 
					            E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            ::sqlx::query_as!(
 | 
					            ::sqlx::query_as!(
 | 
				
			||||||
                Self,
 | 
					                Self,
 | 
				
			||||||
                #query,
 | 
					                #query,
 | 
				
			||||||
                #(self.#update_idents),*,
 | 
					                #(self.#update_idents),*,
 | 
				
			||||||
                #(self.#id_idents),*
 | 
					                #(self.#id_idents),*
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .fetch_one(pool)
 | 
					            .fetch_one(executor)
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -44,13 +44,16 @@ pub fn generate_upsert_query(
 | 
				
			|||||||
    let field_idents: Vec<syn::Ident> = fields.iter().map(|f| f.ident.clone()).collect();
 | 
					    let field_idents: Vec<syn::Ident> = fields.iter().map(|f| f.ident.clone()).collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    quote! {
 | 
					    quote! {
 | 
				
			||||||
        async fn create_or_update(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
 | 
					        async fn create_or_update<'e, E>(&self, mut executor: E) -> ::sqlx::Result<Self>
 | 
				
			||||||
 | 
					        where
 | 
				
			||||||
 | 
					            E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            ::sqlx::query_as!(
 | 
					            ::sqlx::query_as!(
 | 
				
			||||||
                Self,
 | 
					                Self,
 | 
				
			||||||
                #upsert_string,
 | 
					                #upsert_string,
 | 
				
			||||||
                #(self.#field_idents),*
 | 
					                #(self.#field_idents),*
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .fetch_one(pool)
 | 
					            .fetch_one(executor)
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					use sqlx::{Executor, Postgres};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Trait for creating entities with database defaults and auto-generated values.
 | 
					/// Trait for creating entities with database defaults and auto-generated values.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// This trait is automatically implemented on generated companion structs for entities
 | 
					/// This trait is automatically implemented on generated companion structs for entities
 | 
				
			||||||
@ -269,10 +271,11 @@ pub trait Defaultable<Id, Entity> {
 | 
				
			|||||||
    /// };
 | 
					    /// };
 | 
				
			||||||
    /// let created = post_default.create(&pool).await?;
 | 
					    /// let created = post_default.create(&pool).await?;
 | 
				
			||||||
    /// ```
 | 
					    /// ```
 | 
				
			||||||
    fn create(
 | 
					    fn create<'e, E>(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        pool: &sqlx::PgPool,
 | 
					        executor: E,
 | 
				
			||||||
    ) -> impl std::future::Future<Output = sqlx::Result<Entity>> + Send
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<Entity>> + Send
 | 
				
			||||||
    where
 | 
					    where
 | 
				
			||||||
        Self: Sized;
 | 
					        Self: Sized,
 | 
				
			||||||
 | 
					        E: Executor<'e, Database = Postgres>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										57
									
								
								src/georm.rs
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								src/georm.rs
									
									
									
									
									
								
							@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					use sqlx::{Executor, Postgres};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Core database operations trait for Georm entities.
 | 
					/// Core database operations trait for Georm entities.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// This trait is automatically implemented by the `#[derive(Georm)]` macro and provides
 | 
					/// This trait is automatically implemented by the `#[derive(Georm)]` macro and provides
 | 
				
			||||||
@ -118,11 +120,12 @@ pub trait Georm<Id> {
 | 
				
			|||||||
    /// # Errors
 | 
					    /// # Errors
 | 
				
			||||||
    /// Returns `sqlx::Error` for database connection issues, permission problems,
 | 
					    /// Returns `sqlx::Error` for database connection issues, permission problems,
 | 
				
			||||||
    /// or if the table doesn't exist.
 | 
					    /// or if the table doesn't exist.
 | 
				
			||||||
    fn find_all(
 | 
					    fn find_all<'e, E>(
 | 
				
			||||||
        pool: &sqlx::PgPool,
 | 
					        executor: E,
 | 
				
			||||||
    ) -> impl ::std::future::Future<Output = ::sqlx::Result<Vec<Self>>> + Send
 | 
					    ) -> impl ::std::future::Future<Output = ::sqlx::Result<Vec<Self>>> + Send
 | 
				
			||||||
    where
 | 
					    where
 | 
				
			||||||
        Self: Sized;
 | 
					        Self: Sized,
 | 
				
			||||||
 | 
					        E: Executor<'e, Database = Postgres>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Find a single entity by its primary key.
 | 
					    /// Find a single entity by its primary key.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
@ -152,12 +155,13 @@ pub trait Georm<Id> {
 | 
				
			|||||||
    /// Returns `sqlx::Error` for database connection issues, type conversion 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
 | 
					    /// or query execution problems. Note that not finding a record is not an error
 | 
				
			||||||
    /// - it returns `Ok(None)`.
 | 
					    /// - it returns `Ok(None)`.
 | 
				
			||||||
    fn find(
 | 
					    fn find<'e, E>(
 | 
				
			||||||
        pool: &sqlx::PgPool,
 | 
					        executor: E,
 | 
				
			||||||
        id: &Id,
 | 
					        id: &Id,
 | 
				
			||||||
    ) -> impl std::future::Future<Output = sqlx::Result<Option<Self>>> + Send
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<Option<Self>>> + Send
 | 
				
			||||||
    where
 | 
					    where
 | 
				
			||||||
        Self: Sized;
 | 
					        Self: Sized,
 | 
				
			||||||
 | 
					        E: Executor<'e, Database = Postgres>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Insert this entity as a new record in the database.
 | 
					    /// Insert this entity as a new record in the database.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
@ -187,16 +191,17 @@ pub trait Georm<Id> {
 | 
				
			|||||||
    /// # Errors
 | 
					    /// # Errors
 | 
				
			||||||
    /// Returns `sqlx::Error` for:
 | 
					    /// Returns `sqlx::Error` for:
 | 
				
			||||||
    /// - Unique constraint violations
 | 
					    /// - Unique constraint violations
 | 
				
			||||||
    /// - Foreign key constraint violations  
 | 
					    /// - Foreign key constraint violations
 | 
				
			||||||
    /// - NOT NULL constraint violations
 | 
					    /// - NOT NULL constraint violations
 | 
				
			||||||
    /// - Database connection issues
 | 
					    /// - Database connection issues
 | 
				
			||||||
    /// - Permission problems
 | 
					    /// - Permission problems
 | 
				
			||||||
    fn create(
 | 
					    fn create<'e, E>(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        pool: &sqlx::PgPool,
 | 
					        executor: E,
 | 
				
			||||||
    ) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
 | 
				
			||||||
    where
 | 
					    where
 | 
				
			||||||
        Self: Sized;
 | 
					        Self: Sized,
 | 
				
			||||||
 | 
					        E: Executor<'e, Database = Postgres>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Update an existing entity in the database.
 | 
					    /// Update an existing entity in the database.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
@ -229,12 +234,13 @@ pub trait Georm<Id> {
 | 
				
			|||||||
    /// - Constraint violations (unique, foreign key, etc.)
 | 
					    /// - Constraint violations (unique, foreign key, etc.)
 | 
				
			||||||
    /// - Database connection issues
 | 
					    /// - Database connection issues
 | 
				
			||||||
    /// - Permission problems
 | 
					    /// - Permission problems
 | 
				
			||||||
    fn update(
 | 
					    fn update<'e, E>(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        pool: &sqlx::PgPool,
 | 
					        executor: E,
 | 
				
			||||||
    ) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
 | 
				
			||||||
    where
 | 
					    where
 | 
				
			||||||
        Self: Sized;
 | 
					        Self: Sized,
 | 
				
			||||||
 | 
					        E: Executor<'e, Database = Postgres>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Insert or update this entity using PostgreSQL's upsert functionality.
 | 
					    /// Insert or update this entity using PostgreSQL's upsert functionality.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
@ -267,12 +273,13 @@ pub trait Georm<Id> {
 | 
				
			|||||||
    /// - Non-primary-key constraint violations
 | 
					    /// - Non-primary-key constraint violations
 | 
				
			||||||
    /// - Database connection issues
 | 
					    /// - Database connection issues
 | 
				
			||||||
    /// - Permission problems
 | 
					    /// - Permission problems
 | 
				
			||||||
    fn create_or_update(
 | 
					    fn create_or_update<'e, E>(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        pool: &sqlx::PgPool,
 | 
					        executor: E,
 | 
				
			||||||
    ) -> impl ::std::future::Future<Output = sqlx::Result<Self>>
 | 
					    ) -> impl ::std::future::Future<Output = sqlx::Result<Self>>
 | 
				
			||||||
    where
 | 
					    where
 | 
				
			||||||
        Self: Sized;
 | 
					        Self: Sized,
 | 
				
			||||||
 | 
					        E: Executor<'e, Database = Postgres>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Delete this entity from the database.
 | 
					    /// Delete this entity from the database.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
@ -303,10 +310,12 @@ pub trait Georm<Id> {
 | 
				
			|||||||
    /// - Foreign key constraint violations (referenced by other tables)
 | 
					    /// - Foreign key constraint violations (referenced by other tables)
 | 
				
			||||||
    /// - Database connection issues
 | 
					    /// - Database connection issues
 | 
				
			||||||
    /// - Permission problems
 | 
					    /// - Permission problems
 | 
				
			||||||
    fn delete(
 | 
					    fn delete<'e, E>(
 | 
				
			||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        pool: &sqlx::PgPool,
 | 
					        executor: E,
 | 
				
			||||||
    ) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        E: Executor<'e, Database = Postgres>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Delete an entity by its primary key without needing an entity instance.
 | 
					    /// Delete an entity by its primary key without needing an entity instance.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
@ -341,10 +350,12 @@ pub trait Georm<Id> {
 | 
				
			|||||||
    /// - Foreign key constraint violations (referenced by other tables)
 | 
					    /// - Foreign key constraint violations (referenced by other tables)
 | 
				
			||||||
    /// - Database connection issues
 | 
					    /// - Database connection issues
 | 
				
			||||||
    /// - Permission problems
 | 
					    /// - Permission problems
 | 
				
			||||||
    fn delete_by_id(
 | 
					    fn delete_by_id<'e, E>(
 | 
				
			||||||
        pool: &sqlx::PgPool,
 | 
					        executor: E,
 | 
				
			||||||
        id: &Id,
 | 
					        id: &Id,
 | 
				
			||||||
    ) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        E: Executor<'e, Database = Postgres>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Get the primary key of this entity.
 | 
					    /// Get the primary key of this entity.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
@ -362,7 +373,7 @@ pub trait Georm<Id> {
 | 
				
			|||||||
    /// let user = User { id: 42, username: "alice".into(), email: "alice@example.com".into() };
 | 
					    /// let user = User { id: 42, username: "alice".into(), email: "alice@example.com".into() };
 | 
				
			||||||
    /// let id = user.get_id(); // Returns 42
 | 
					    /// let id = user.get_id(); // Returns 42
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// // Composite primary key  
 | 
					    /// // Composite primary key
 | 
				
			||||||
    /// let user_role = UserRole { user_id: 1, role_id: 2, assigned_at: now };
 | 
					    /// 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 }
 | 
					    /// let id = user_role.get_id(); // Returns UserRoleId { user_id: 1, role_id: 2 }
 | 
				
			||||||
    /// ```
 | 
					    /// ```
 | 
				
			||||||
 | 
				
			|||||||
@ -200,7 +200,9 @@
 | 
				
			|||||||
//! }
 | 
					//! }
 | 
				
			||||||
//!
 | 
					//!
 | 
				
			||||||
//! impl Defaultable<i32, Product> for ProductDefault {
 | 
					//! impl Defaultable<i32, Product> for ProductDefault {
 | 
				
			||||||
//!     async fn create(&self, pool: &sqlx::PgPool) -> sqlx::Result<Product>;
 | 
					//!     async fn create<'e, E>(&self, pool: E) -> sqlx::Result<Post>
 | 
				
			||||||
 | 
					//!     where
 | 
				
			||||||
 | 
					//!         E: sqlx::Executor<'e, Database = sqlx::Postgres>;
 | 
				
			||||||
//! }
 | 
					//! }
 | 
				
			||||||
//! ```
 | 
					//! ```
 | 
				
			||||||
//!
 | 
					//!
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
use georm::Georm;
 | 
					use georm::Georm;
 | 
				
			||||||
use sqlx::types::BigDecimal;
 | 
					use sqlx::{Postgres, types::BigDecimal};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Georm, PartialEq, Eq, Default)]
 | 
					#[derive(Debug, Georm, PartialEq, Eq, Default)]
 | 
				
			||||||
#[georm(
 | 
					#[georm(
 | 
				
			||||||
@ -123,9 +123,12 @@ pub struct Product {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
impl Product {
 | 
					impl Product {
 | 
				
			||||||
    #[allow(dead_code)]
 | 
					    #[allow(dead_code)]
 | 
				
			||||||
    pub async fn find_by_name(name: String, pool: &sqlx::PgPool) -> ::sqlx::Result<Self> {
 | 
					    pub async fn find_by_name<'e, E>(name: String, executor: E) -> ::sqlx::Result<Self>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        E: sqlx::Executor<'e, Database = Postgres>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        ::sqlx::query_as!(Self, "SELECT * FROM products WHERE name = $1", name)
 | 
					        ::sqlx::query_as!(Self, "SELECT * FROM products WHERE name = $1", name)
 | 
				
			||||||
            .fetch_one(pool)
 | 
					            .fetch_one(executor)
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user