Skip to main content

diesel/query_dsl/
save_changes_dsl.rs

1#[cfg(any(feature = "__sqlite-shared", feature = "postgres", feature = "mysql"))]
2use crate::Table;
3use crate::associations::HasTable;
4#[cfg(any(feature = "__sqlite-shared", feature = "mysql"))]
5use crate::associations::Identifiable;
6use crate::connection::Connection;
7#[cfg(any(feature = "__sqlite-shared", feature = "mysql"))]
8use crate::dsl::Find;
9#[cfg(any(feature = "__sqlite-shared", feature = "postgres", feature = "mysql"))]
10use crate::dsl::Update;
11#[cfg(any(feature = "__sqlite-shared", feature = "postgres", feature = "mysql"))]
12use crate::expression::{MixedAggregates, ValidGrouping, is_aggregate};
13use crate::query_builder::{AsChangeset, IntoUpdateTarget};
14#[cfg(any(feature = "__sqlite-shared", feature = "mysql"))]
15use crate::query_dsl::methods::{ExecuteDsl, FindDsl};
16#[cfg(any(feature = "__sqlite-shared", feature = "postgres", feature = "mysql"))]
17use crate::query_dsl::{LoadQuery, RunQueryDsl};
18use crate::result::QueryResult;
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        + crate::expression::SelectableExpression<
45            crate::query_builder::returning::ReturningQuerySource<
46                crate::query_builder::returning::UpdateStmt,
47                Changes::Table,
48            >,
49        >,
50    <<Changes::Table as Table>::AllColumns as ValidGrouping<()>>::IsAggregate:
51        MixedAggregates<is_aggregate::No, Output = is_aggregate::No>,
52{
53    fn update_and_fetch(&mut self, changeset: Changes) -> QueryResult<Output> {
54        crate::update(changeset).set(changeset).get_result(self)
55    }
56}
57
58#[cfg(feature = "__sqlite-shared")]
59use crate::sqlite::SqliteConnection;
60
61#[cfg(feature = "__sqlite-shared")]
62impl<'b, Changes, Output> UpdateAndFetchResults<Changes, Output> for SqliteConnection
63where
64    Changes: Copy + Identifiable,
65    Changes: AsChangeset<Target = <Changes as HasTable>::Table> + IntoUpdateTarget,
66    Changes::Table: FindDsl<Changes::Id>,
67    Update<Changes, Changes>: ExecuteDsl<SqliteConnection>,
68    Find<Changes::Table, Changes::Id>: LoadQuery<'b, SqliteConnection, Output>,
69    <Changes::Table as Table>::AllColumns: ValidGrouping<()>
70        + crate::expression::SelectableExpression<
71            crate::query_builder::returning::ReturningQuerySource<
72                crate::query_builder::returning::UpdateStmt,
73                Changes::Table,
74            >,
75        >,
76    <<Changes::Table as Table>::AllColumns as ValidGrouping<()>>::IsAggregate:
77        MixedAggregates<is_aggregate::No, Output = is_aggregate::No>,
78{
79    fn update_and_fetch(&mut self, changeset: Changes) -> QueryResult<Output> {
80        crate::update(changeset).set(changeset).execute(self)?;
81        Changes::table().find(changeset.id()).get_result(self)
82    }
83}
84
85#[cfg(feature = "mysql")]
86use crate::mysql::MysqlConnection;
87
88#[cfg(feature = "mysql")]
89impl<'b, Changes, Output> UpdateAndFetchResults<Changes, Output> for MysqlConnection
90where
91    Changes: Copy + Identifiable,
92    Changes: AsChangeset<Target = <Changes as HasTable>::Table> + IntoUpdateTarget,
93    Changes::Table: FindDsl<Changes::Id>,
94    Update<Changes, Changes>: ExecuteDsl<MysqlConnection>,
95    Find<Changes::Table, Changes::Id>: LoadQuery<'b, MysqlConnection, Output>,
96    <Changes::Table as Table>::AllColumns: ValidGrouping<()>
97        + crate::expression::SelectableExpression<
98            crate::query_builder::returning::ReturningQuerySource<
99                crate::query_builder::returning::UpdateStmt,
100                Changes::Table,
101            >,
102        >,
103    <<Changes::Table as Table>::AllColumns as ValidGrouping<()>>::IsAggregate:
104        MixedAggregates<is_aggregate::No, Output = is_aggregate::No>,
105{
106    fn update_and_fetch(&mut self, changeset: Changes) -> QueryResult<Output> {
107        crate::update(changeset).set(changeset).execute(self)?;
108        Changes::table().find(changeset.id()).get_result(self)
109    }
110}
111
112/// Sugar for types which implement both `AsChangeset` and `Identifiable`
113///
114/// On backends which support the `RETURNING` keyword,
115/// `foo.save_changes(&conn)` is equivalent to
116/// `update(&foo).set(&foo).get_result(&conn)`.
117/// On other backends, two queries will be executed.
118///
119/// # Example
120///
121/// ```rust
122/// # include!("../doctest_setup.rs");
123/// # use schema::animals;
124/// #
125/// #[derive(Queryable, Debug, PartialEq)]
126/// struct Animal {
127///     id: i32,
128///     species: String,
129///     legs: i32,
130///     name: Option<String>,
131/// }
132///
133/// #[derive(AsChangeset, Identifiable)]
134/// #[diesel(table_name = animals)]
135/// struct AnimalForm<'a> {
136///     id: i32,
137///     name: &'a str,
138/// }
139///
140/// # fn main() {
141/// #     run_test();
142/// # }
143/// #
144/// # fn run_test() -> QueryResult<()> {
145/// #     use self::animals::dsl::*;
146/// #     let connection = &mut establish_connection();
147/// let form = AnimalForm {
148///     id: 2,
149///     name: "Super scary",
150/// };
151/// let changed_animal = form.save_changes(connection)?;
152/// let expected_animal = Animal {
153///     id: 2,
154///     species: String::from("spider"),
155///     legs: 8,
156///     name: Some(String::from("Super scary")),
157/// };
158/// assert_eq!(expected_animal, changed_animal);
159/// #     Ok(())
160/// # }
161/// ```
162pub trait SaveChangesDsl<Conn> {
163    /// See the trait documentation.
164    fn save_changes<T>(self, connection: &mut Conn) -> QueryResult<T>
165    where
166        Self: Sized,
167        Conn: UpdateAndFetchResults<Self, T>,
168    {
169        connection.update_and_fetch(self)
170    }
171}
172
173impl<T, Conn> SaveChangesDsl<Conn> for T where
174    T: Copy + AsChangeset<Target = <T as HasTable>::Table> + IntoUpdateTarget
175{
176}