Skip to content

Commit

Permalink
Merge pull request #4019 from dessalines/optional_querybuilder_error
Browse files Browse the repository at this point in the history
When using `.optional()`, convert a `QueryBuilderError` into `None`
  • Loading branch information
weiznich authored May 18, 2024
2 parents 0830d05 + 1062278 commit 3c7b7c4
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 6 deletions.
5 changes: 4 additions & 1 deletion diesel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,10 @@ pub mod prelude {
#[doc(inline)]
pub use crate::query_source::{Column, JoinTo, QuerySource, Table};
#[doc(inline)]
pub use crate::result::{ConnectionError, ConnectionResult, OptionalExtension, QueryResult};
pub use crate::result::{
ConnectionError, ConnectionResult, OptionalEmptyChangesetExtension, OptionalExtension,
QueryResult,
};
#[doc(inline)]
pub use diesel_derives::table_proc as table;

Expand Down
5 changes: 2 additions & 3 deletions diesel/src/query_builder/update_statement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::query_builder::where_clause::*;
use crate::query_dsl::methods::{BoxedDsl, FilterDsl};
use crate::query_dsl::RunQueryDsl;
use crate::query_source::Table;
use crate::result::EmptyChangeset;
use crate::result::Error::QueryBuilderError;
use crate::{query_builder::*, QuerySource};

Expand Down Expand Up @@ -198,9 +199,7 @@ where
{
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
if self.values.is_noop(out.backend())? {
return Err(QueryBuilderError(
"There are no changes to save. This query cannot be built".into(),
));
return Err(QueryBuilderError(Box::new(EmptyChangeset)));
}

out.unsafe_to_cache_prepared();
Expand Down
42 changes: 42 additions & 0 deletions diesel/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,31 @@ impl<T> OptionalExtension<T> for QueryResult<T> {
}
}

/// See the [method documentation](OptionalEmptyChangesetExtension::optional_empty_changeset).
pub trait OptionalEmptyChangesetExtension<T> {
/// By default, Diesel treats an empty update as a `QueryBuilderError`. This method will
/// convert that error into `None`.
///
/// # Example
///
/// ```rust
/// use diesel::{QueryResult, OptionalEmptyChangesetExtension, result::Error::QueryBuilderError, result::EmptyChangeset};
/// let result: QueryResult<i32> = Err(QueryBuilderError(Box::new(EmptyChangeset)));
/// assert_eq!(Ok(None), result.optional_empty_changeset());
/// ```
fn optional_empty_changeset(self) -> Result<Option<T>, Error>;
}

impl<T> OptionalEmptyChangesetExtension<T> for QueryResult<T> {
fn optional_empty_changeset(self) -> Result<Option<T>, Error> {
match self {
Ok(value) => Ok(Some(value)),
Err(Error::QueryBuilderError(e)) if e.is::<EmptyChangeset>() => Ok(None),
Err(e) => Err(e),
}
}
}

impl From<NulError> for ConnectionError {
fn from(e: NulError) -> Self {
ConnectionError::InvalidCString(e)
Expand Down Expand Up @@ -413,3 +438,20 @@ impl fmt::Display for UnexpectedEndOfRow {
}

impl StdError for UnexpectedEndOfRow {}

/// Expected when an update has no changes to save.
///
/// When using `optional_empty_changeset`, this error is turned into `None`.
#[derive(Debug, Clone, Copy)]
pub struct EmptyChangeset;

impl fmt::Display for EmptyChangeset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"There are no changes to save. This query cannot be built"
)
}
}

impl StdError for EmptyChangeset {}
23 changes: 21 additions & 2 deletions diesel_tests/tests/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,6 @@ fn sql_syntax_is_correct_when_option_field_comes_mixed_with_non_option() {
}

#[test]
#[should_panic(expected = "There are no changes to save.")]
fn update_with_no_changes() {
#[derive(AsChangeset)]
#[diesel(table_name = users)]
Expand All @@ -228,10 +227,30 @@ fn update_with_no_changes() {
name: None,
hair_color: None,
};
update(users::table)
let update_result = update(users::table).set(&changes).execute(connection);
assert!(update_result.is_err());
}

#[test]
fn update_with_optional_empty_changeset() {
#[derive(AsChangeset)]
#[diesel(table_name = users)]
struct Changes {
name: Option<String>,
hair_color: Option<String>,
}

let connection = &mut connection();
let changes = Changes {
name: None,
hair_color: None,
};
let update_result = update(users::table)
.set(&changes)
.execute(connection)
.optional_empty_changeset()
.unwrap();
assert_eq!(None, update_result);
}

#[test]
Expand Down

0 comments on commit 3c7b7c4

Please sign in to comment.