feat: deprecate create_or_update in favour of upsert

The `create_or_update` method has been deprecated and replaced by
`upsert` for clarity and consistency with common database terminology.

This commit also removes the file `src/entity.rs` which has been
forgotten in earlier commits and was no longer part of Georm.
This commit is contained in:
Lucien Cartier-Tilet 2025-08-09 15:09:22 +02:00
parent 49c7d86102
commit 5d8a1b1917
10 changed files with 25 additions and 100 deletions

View File

@ -557,7 +557,7 @@ Post::find(executor, &post_id).await?;
// Mutation operations
post.create(executor).await?;
post.update(executor).await?;
post.create_or_update(executor).await?;
post.upsert(executor).await?;
post.delete(executor).await?;
Post::delete_by_id(executor, &post_id).await?;

View File

@ -58,7 +58,7 @@ impl Profile {
&mut self,
display_name: Option<String>,
bio: Option<String>,
executor: E
executor: E,
) -> Result<Self>
where
E: sqlx::Executor<'e, Database = sqlx::Postgres>,

View File

@ -69,7 +69,7 @@ impl User {
pub async fn get_user_by_id_or_select<'e, E>(
id: Option<i32>,
prompt: &str,
executor: E
executor: E,
) -> Result<Self>
where
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
@ -128,8 +128,7 @@ impl 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 user = Self::get_user_by_id_or_select(id, prompt, pool).await?;
let profile = match user.get_profile(pool).await? {

View File

@ -44,7 +44,7 @@ pub fn generate_upsert_query(
let field_idents: Vec<syn::Ident> = fields.iter().map(|f| f.ident.clone()).collect();
quote! {
async fn create_or_update<'e, E>(&self, mut executor: E) -> ::sqlx::Result<Self>
async fn upsert<'e, E>(&self, mut executor: E) -> ::sqlx::Result<Self>
where
E: ::sqlx::Executor<'e, Database = ::sqlx::Postgres>
{

View File

@ -1,83 +0,0 @@
pub trait Georm<Id> {
/// Find all the entities in the database.
///
/// # Errors
/// Returns any error Postgres may have encountered
fn find_all(
pool: &sqlx::PgPool,
) -> impl ::std::future::Future<Output = ::sqlx::Result<Vec<Self>>> + Send
where
Self: Sized;
/// Find the entiy in the database based on its identifier.
///
/// # Errors
/// Returns any error Postgres may have encountered
fn find(
pool: &sqlx::PgPool,
id: &Id,
) -> impl std::future::Future<Output = sqlx::Result<Option<Self>>> + Send
where
Self: Sized;
/// Create the entity in the database.
///
/// # Errors
/// Returns any error Postgres may have encountered
fn create(
&self,
pool: &sqlx::PgPool,
) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
where
Self: Sized;
/// Update an entity with a matching identifier in the database.
///
/// # Errors
/// Returns any error Postgres may have encountered
fn update(
&self,
pool: &sqlx::PgPool,
) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
where
Self: Sized;
/// Update an entity with a matching identifier in the database if
/// it exists, create it otherwise.
///
/// # Errors
/// Returns any error Postgres may have encountered
fn create_or_update(
&self,
pool: &sqlx::PgPool,
) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
where
Self: Sized;
/// Delete the entity from the database if it exists.
///
/// # Returns
/// Returns the amount of rows affected by the deletion.
///
/// # Errors
/// Returns any error Postgres may have encountered
fn delete(
&self,
pool: &sqlx::PgPool,
) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
/// Delete any entity with the identifier `id`.
///
/// # Returns
/// Returns the amount of rows affected by the deletion.
///
/// # Errors
/// Returns any error Postgres may have encountered
fn delete_by_id(
pool: &sqlx::PgPool,
id: &Id,
) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
/// Returns the identifier of the entity.
fn get_id(&self) -> &Id;
}

View File

@ -26,7 +26,7 @@ use sqlx::{Executor, Postgres};
/// ### 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
/// - [`upsert`] - Upsert (insert or update) an entity
/// - [`delete`] - Delete this entity from the database
/// - [`get_id`] - Get the primary key of this entity
///
@ -92,7 +92,7 @@ use sqlx::{Executor, Postgres};
/// [`find`]: Georm::find
/// [`create`]: Georm::create
/// [`update`]: Georm::update
/// [`create_or_update`]: Georm::create_or_update
/// [`upsert`]: Georm::upsert
/// [`delete`]: Georm::delete
/// [`delete_by_id`]: Georm::delete_by_id
/// [`get_id`]: Georm::get_id
@ -264,7 +264,7 @@ pub trait Georm<Id> {
/// # Examples
/// ```ignore
/// let user = User { id: 1, username: "alice".into(), email: "alice@example.com".into() };
/// let final_user = user.create_or_update(&pool).await?;
/// let final_user = user.upsert(&pool).await?;
/// // Will insert if ID 1 doesn't exist, update if it does
/// ```
///
@ -273,13 +273,22 @@ pub trait Georm<Id> {
/// - Non-primary-key constraint violations
/// - Database connection issues
/// - Permission problems
fn upsert<'e, E>(&self, executor: E) -> impl ::std::future::Future<Output = sqlx::Result<Self>>
where
Self: Sized,
E: Executor<'e, Database = Postgres>;
#[deprecated(since = "0.3.0", note = "Please use `upsert` instead")]
fn create_or_update<'e, E>(
&self,
executor: E,
) -> impl ::std::future::Future<Output = sqlx::Result<Self>>
where
Self: Sized,
E: Executor<'e, Database = Postgres>;
E: Executor<'e, Database = Postgres>,
{
self.upsert(executor)
}
/// Delete this entity from the database.
///

View File

@ -33,7 +33,7 @@
//! ### Instance Methods (called on entity objects)
//! - `entity.create(pool)` - Insert new record, returns created entity with database-generated values
//! - `entity.update(pool)` - Update existing record, returns updated entity with fresh database state
//! - `entity.create_or_update(pool)` - True PostgreSQL upsert using `ON CONFLICT`, returns final entity
//! - `entity.upsert(pool)` - True PostgreSQL upsert using `ON CONFLICT`, returns final entity
//! - `entity.delete(pool)` - Delete this record, returns affected row count
//! - `entity.get_id()` - Get reference to the entity's ID (`&Id` for simple keys, owned for composite)
//!
@ -55,7 +55,7 @@
//! Georm leverages PostgreSQL-specific features for performance and reliability:
//!
//! - **RETURNING clause**: All `INSERT` and `UPDATE` operations use `RETURNING *` to capture database-generated values (sequences, defaults, triggers)
//! - **True upserts**: `create_or_update()` uses `INSERT ... ON CONFLICT ... DO UPDATE` for atomic upsert operations
//! - **True upserts**: `upsert()` uses `INSERT ... ON CONFLICT ... DO UPDATE` for atomic upsert operations
//! - **Prepared statements**: All queries use parameter binding for security and performance
//! - **Compile-time verification**: SQLx macros verify all generated SQL against your database schema at compile time
//!

View File

@ -36,7 +36,7 @@ fn composite_key_get_id() {
}
#[sqlx::test(fixtures("composite_key"))]
async fn composite_key_create_or_update(pool: sqlx::PgPool) -> sqlx::Result<()> {
async fn composite_key_upsert(pool: sqlx::PgPool) -> sqlx::Result<()> {
let new_user_role = UserRole {
user_id: 5,
role_id: 2,
@ -44,7 +44,7 @@ async fn composite_key_create_or_update(pool: sqlx::PgPool) -> sqlx::Result<()>
};
// This will test the upsert query generation bug
let result = new_user_role.create_or_update(&pool).await?;
let result = new_user_role.upsert(&pool).await?;
assert_eq!(5, result.user_id);
assert_eq!(2, result.role_id);

View File

@ -52,7 +52,7 @@ async fn upsert_handles_generated_fields(pool: sqlx::PgPool) -> sqlx::Result<()>
let mut modified_product = product.clone();
modified_product.price = BigDecimal::from(1200);
let upserted = modified_product.create_or_update(&pool).await?;
let upserted = modified_product.upsert(&pool).await?;
// price is updated
assert_eq!(upserted.price, BigDecimal::from(1200));

View File

@ -115,7 +115,7 @@ async fn should_create_if_does_not_exist(pool: sqlx::PgPool) -> sqlx::Result<()>
name: "Miura Kentaro".into(),
..Default::default()
};
author.create_or_update(&pool).await?;
author.upsert(&pool).await?;
let all_authors = Author::find_all(&pool).await?;
assert_eq!(1, all_authors.len());
Ok(())
@ -130,7 +130,7 @@ async fn should_update_if_exist(pool: sqlx::PgPool) -> sqlx::Result<()> {
name: "Miura Kentaro".into(),
..Default::default()
};
author.create_or_update(&pool).await?;
author.upsert(&pool).await?;
let mut all_authors = Author::find_all(&pool).await?;
all_authors.sort();
assert_eq!(3, all_authors.len());