diesel/query_dsl/
save_changes_dsl.rs

1use crate::associations::HasTable;
2#[cfg(any(feature = "sqlite", feature = "mysql"))]
3use crate::associations::Identifiable;
4use crate::connection::Connection;
5#[cfg(any(feature = "sqlite", feature = "mysql"))]
6use crate::dsl::Find;
7#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
8use crate::dsl::Update;
9#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
10use crate::expression::{is_aggregate, MixedAggregates, ValidGrouping};
11use crate::query_builder::{AsChangeset, IntoUpdateTarget};
12#[cfg(any(feature = "sqlite", feature = "mysql"))]
13use crate::query_dsl::methods::{ExecuteDsl, FindDsl};
14#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
15use crate::query_dsl::{LoadQuery, RunQueryDsl};
16use crate::result::QueryResult;
17#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
18use crate::Table;
19
20/// A trait defining how to update a record and fetch the updated entry
21/// on a certain backend.
22///
23/// The only case where it is required to work with this trait is while
24/// implementing a new connection type.
25/// Otherwise use [`SaveChangesDsl`]
26///
27/// For implementing this trait for a custom backend:
28/// * The `Changes` generic parameter represents the changeset that should be stored
29/// * The `Output` generic parameter represents the type of the response.
30pub trait UpdateAndFetchResults<Changes, Output>: Connection {
31    /// See the traits documentation.
32    fn update_and_fetch(&mut self, changeset: Changes) -> QueryResult<Output>;
33}
34
35#[cfg(feature = "postgres")]
36use crate::pg::PgConnection;
37
38#[cfg(feature = "postgres")]
39impl<'b, Changes, Output> UpdateAndFetchResults<Changes, Output> for PgConnection
40where
41    Changes: Copy + AsChangeset<Target = <Changes as HasTable>::Table> + IntoUpdateTarget,
42    Update<Changes, Changes>: LoadQuery<'b, PgConnection, Output>,
43    <Changes::Table as Table>::AllColumns: ValidGrouping<()>,
44    <<Changes::Table as Table>::AllColumns as ValidGrouping<()>>::IsAggregate:
45        MixedAggregates<is_aggregate::No, Output = is_aggregate::No>,
46{
47    fn update_and_fetch(&mut self, changeset: Changes) -> QueryResult<Output> {
48        crate::update(changeset).set(changeset).get_result(self)
49    }
50}
51
52#[cfg(feature = "sqlite")]
53use crate::sqlite::SqliteConnection;
54
55#[cfg(feature = "sqlite")]
56impl<'b, Changes, Output> UpdateAndFetchResults<Changes, Output> for SqliteConnection
57where
58    Changes: Copy + Identifiable,
59    Changes: AsChangeset<Target = <Changes as HasTable>::Table> + IntoUpdateTarget,
60    Changes::Table: FindDsl<Changes::Id>,
61    Update<Changes, Changes>: ExecuteDsl<SqliteConnection>,
62    Find<Changes::Table, Changes::Id>: LoadQuery<'b, SqliteConnection, Output>,
63    <Changes::Table as Table>::AllColumns: ValidGrouping<()>,
64    <<Changes::Table as Table>::AllColumns as ValidGrouping<()>>::IsAggregate:
65        MixedAggregates<is_aggregate::No, Output = is_aggregate::No>,
66{
67    fn update_and_fetch(&mut self, changeset: Changes) -> QueryResult<Output> {
68        crate::update(changeset).set(changeset).execute(self)?;
69        Changes::table().find(changeset.id()).get_result(self)
70    }
71}
72
73#[cfg(feature = "mysql")]
74use crate::mysql::MysqlConnection;
75
76#[cfg(feature = "mysql")]
77impl<'b, Changes, Output> UpdateAndFetchResults<Changes, Output> for MysqlConnection
78where
79    Changes: Copy + Identifiable,
80    Changes: AsChangeset<Target = <Changes as HasTable>::Table> + IntoUpdateTarget,
81    Changes::Table: FindDsl<Changes::Id>,
82    Update<Changes, Changes>: ExecuteDsl<MysqlConnection>,
83    Find<Changes::Table, Changes::Id>: LoadQuery<'b, MysqlConnection, Output>,
84    <Changes::Table as Table>::AllColumns: ValidGrouping<()>,
85    <<Changes::Table as Table>::AllColumns as ValidGrouping<()>>::IsAggregate:
86        MixedAggregates<is_aggregate::No, Output = is_aggregate::No>,
87{
88    fn update_and_fetch(&mut self, changeset: Changes) -> QueryResult<Output> {
89        crate::update(changeset).set(changeset).execute(self)?;
90        Changes::table().find(changeset.id()).get_result(self)
91    }
92}
93
94/// Sugar for types which implement both `AsChangeset` and `Identifiable`
95///
96/// On backends which support the `RETURNING` keyword,
97/// `foo.save_changes(&conn)` is equivalent to
98/// `update(&foo).set(&foo).get_result(&conn)`.
99/// On other backends, two queries will be executed.
100///
101/// # Example
102///
103/// ```rust
104/// # include!("../doctest_setup.rs");
105/// # use schema::animals;
106/// #
107/// #[derive(Queryable, Debug, PartialEq)]
108/// struct Animal {
109///    id: i32,
110///    species: String,
111///    legs: i32,
112///    name: Option<String>,
113/// }
114///
115/// #[derive(AsChangeset, Identifiable)]
116/// #[diesel(table_name = animals)]
117/// struct AnimalForm<'a> {
118///     id: i32,
119///     name: &'a str,
120/// }
121///
122/// # fn main() {
123/// #     run_test();
124/// # }
125/// #
126/// # fn run_test() -> QueryResult<()> {
127/// #     use self::animals::dsl::*;
128/// #     let connection = &mut establish_connection();
129/// let form = AnimalForm { id: 2, name: "Super scary" };
130/// let changed_animal = form.save_changes(connection)?;
131/// let expected_animal = Animal {
132///     id: 2,
133///     species: String::from("spider"),
134///     legs: 8,
135///     name: Some(String::from("Super scary")),
136/// };
137/// assert_eq!(expected_animal, changed_animal);
138/// #     Ok(())
139/// # }
140/// ```
141pub trait SaveChangesDsl<Conn> {
142    /// See the trait documentation.
143    fn save_changes<T>(self, connection: &mut Conn) -> QueryResult<T>
144    where
145        Self: Sized,
146        Conn: UpdateAndFetchResults<Self, T>,
147    {
148        connection.update_and_fetch(self)
149    }
150}
151
152impl<T, Conn> SaveChangesDsl<Conn> for T where
153    T: Copy + AsChangeset<Target = <T as HasTable>::Table> + IntoUpdateTarget
154{
155}