Skip to main content

sea_orm/query/
delete.rs

1use crate::{
2    ActiveModelTrait, ActiveValue, ColumnTrait, DbBackend, DbErr, EntityTrait, IntoActiveModel,
3    Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryTrait,
4    query::column_tuple_in_condition,
5    sea_query::{IntoValueTuple, ValueTuple},
6};
7use core::marker::PhantomData;
8use sea_query::DeleteStatement;
9
10/// Type-level entry point for `DELETE` builders, e.g.
11/// `Delete::one(model)` and `Delete::many(Entity)`. You normally call
12/// [`EntityTrait::delete`](crate::EntityTrait::delete) /
13/// [`delete_many`](crate::EntityTrait::delete_many) instead.
14#[derive(Clone, Debug)]
15pub struct Delete;
16
17/// A request to delete an [`ActiveModel`](ActiveModelTrait).
18///
19/// The primary key must be set.
20/// Otherwise, it's impossible to generate the SQL condition and find the record.
21/// In that case, [`exec`][Self::exec] will return an error and not send any queries to the database.
22///
23/// If you want to use [`QueryTrait`] and access the generated SQL query,
24/// you need to convert into [`ValidatedDeleteOne`] first.
25#[derive(Clone, Debug)]
26pub struct DeleteOne<E: EntityTrait>(pub(crate) Result<ValidatedDeleteOne<E>, DbErr>);
27
28/// A validated [`DeleteOne`] request, where the primary key is set
29/// and it's possible to generate the right SQL condition.
30#[derive(Clone, Debug)]
31pub struct ValidatedDeleteOne<E: EntityTrait> {
32    pub(crate) query: DeleteStatement,
33    pub(crate) entity: PhantomData<E>,
34}
35
36impl<E: EntityTrait> TryFrom<DeleteOne<E>> for ValidatedDeleteOne<E> {
37    type Error = DbErr;
38
39    fn try_from(value: DeleteOne<E>) -> Result<Self, Self::Error> {
40        value.0
41    }
42}
43
44impl<E: EntityTrait> DeleteOne<E> {
45    /// Check whether the primary key is set and we can proceed with the operation.
46    pub fn validate(self) -> Result<ValidatedDeleteOne<E>, DbErr> {
47        self.try_into()
48    }
49}
50
51/// Multi-row `DELETE` builder, returned by
52/// [`EntityTrait::delete_many`](crate::EntityTrait::delete_many). Add
53/// `.filter(...)` to scope which rows are deleted.
54#[derive(Clone, Debug)]
55pub struct DeleteMany<E>
56where
57    E: EntityTrait,
58{
59    pub(crate) query: DeleteStatement,
60    pub(crate) entity: PhantomData<E>,
61}
62
63impl Delete {
64    /// Delete one Model or ActiveModel
65    ///
66    /// Model
67    /// ```
68    /// use sea_orm::{DbBackend, entity::*, query::*, tests_cfg::cake};
69    ///
70    /// assert_eq!(
71    ///     Delete::one(cake::Model {
72    ///         id: 1,
73    ///         name: "Apple Pie".to_owned(),
74    ///     })
75    ///     .validate()
76    ///     .unwrap()
77    ///     .build(DbBackend::Postgres)
78    ///     .to_string(),
79    ///     r#"DELETE FROM "cake" WHERE "cake"."id" = 1"#,
80    /// );
81    /// ```
82    /// ActiveModel
83    /// ```
84    /// use sea_orm::{DbBackend, entity::*, query::*, tests_cfg::cake};
85    ///
86    /// assert_eq!(
87    ///     Delete::one(cake::ActiveModel {
88    ///         id: ActiveValue::set(1),
89    ///         name: ActiveValue::set("Apple Pie".to_owned()),
90    ///     })
91    ///     .validate()
92    ///     .unwrap()
93    ///     .build(DbBackend::Postgres)
94    ///     .to_string(),
95    ///     r#"DELETE FROM "cake" WHERE "cake"."id" = 1"#,
96    /// );
97    /// ```
98    //
99    // (non-doc comment for maintainers)
100    // Ideally, we would make this method fallible instead of stashing and delaying the error.
101    // But that's a bigger breaking change.
102    pub fn one<E, A, M>(model: M) -> DeleteOne<E>
103    where
104        E: EntityTrait,
105        A: ActiveModelTrait<Entity = E>,
106        M: IntoActiveModel<A>,
107    {
108        let model = model.into_active_model();
109        let mut myself = ValidatedDeleteOne {
110            query: DeleteStatement::new()
111                .from_table(A::Entity::default().table_ref())
112                .to_owned(),
113            entity: PhantomData,
114        };
115        // Build the SQL condition from the primary key columns.
116        for key in <A::Entity as EntityTrait>::PrimaryKey::iter() {
117            let col = key.into_column();
118            let av = model.get(col);
119            match av {
120                ActiveValue::Set(value) | ActiveValue::Unchanged(value) => {
121                    myself = myself.filter(col.eq(value));
122                }
123                ActiveValue::NotSet => {
124                    return DeleteOne(Err(DbErr::PrimaryKeyNotSet { ctx: "DeleteOne" }));
125                }
126            }
127        }
128        DeleteOne(Ok(myself))
129    }
130
131    #[doc(hidden)]
132    pub fn _one_only_for_use_by_model_ex<E: EntityTrait>(entity: E) -> ValidatedDeleteOne<E> {
133        ValidatedDeleteOne {
134            query: DeleteStatement::new()
135                .from_table(entity.table_ref())
136                .to_owned(),
137            entity: PhantomData,
138        }
139    }
140
141    /// Delete many ActiveModel
142    ///
143    /// ```
144    /// use sea_orm::{DbBackend, entity::*, query::*, tests_cfg::fruit};
145    ///
146    /// assert_eq!(
147    ///     Delete::many(fruit::Entity)
148    ///         .filter(fruit::Column::Name.contains("Apple"))
149    ///         .build(DbBackend::Postgres)
150    ///         .to_string(),
151    ///     r#"DELETE FROM "fruit" WHERE "fruit"."name" LIKE '%Apple%'"#,
152    /// );
153    /// ```
154    pub fn many<E>(entity: E) -> DeleteMany<E>
155    where
156        E: EntityTrait,
157    {
158        DeleteMany {
159            query: DeleteStatement::new()
160                .from_table(entity.table_ref())
161                .to_owned(),
162            entity: PhantomData,
163        }
164    }
165}
166
167impl<E> QueryFilter for ValidatedDeleteOne<E>
168where
169    E: EntityTrait,
170{
171    type QueryStatement = DeleteStatement;
172
173    fn query(&mut self) -> &mut DeleteStatement {
174        &mut self.query
175    }
176}
177
178impl<E> QueryFilter for DeleteMany<E>
179where
180    E: EntityTrait,
181{
182    type QueryStatement = DeleteStatement;
183
184    fn query(&mut self) -> &mut DeleteStatement {
185        &mut self.query
186    }
187}
188
189impl<E> DeleteMany<E>
190where
191    E: EntityTrait,
192{
193    /// Filter by vector of IDs by primary key
194    ///
195    /// # Panics
196    ///
197    /// Should not panic.
198    pub fn filter_by_ids<I>(mut self, values: I) -> Self
199    where
200        I: IntoIterator<Item = <E::PrimaryKey as PrimaryKeyTrait>::ValueType>,
201    {
202        self.query.cond_where(
203            column_tuple_in_condition(
204                &E::default().table_ref(),
205                &E::primary_key_identity(),
206                &values
207                    .into_iter()
208                    .map(|v| v.into_value_tuple())
209                    .collect::<Vec<_>>(),
210                DbBackend::Sqlite,
211            )
212            .expect("trait bound ensured arity"),
213        );
214        self
215    }
216
217    #[doc(hidden)]
218    /// # Panics
219    ///
220    /// Panic if `ValueTuple` arity does not match primary key
221    pub fn filter_by_value_tuples(mut self, values: &[ValueTuple], db_backend: DbBackend) -> Self {
222        self.query.cond_where(
223            column_tuple_in_condition(
224                &E::default().table_ref(),
225                &E::primary_key_identity(),
226                values,
227                db_backend,
228            )
229            .expect(""),
230        );
231        self
232    }
233}
234
235impl<E> QueryTrait for ValidatedDeleteOne<E>
236where
237    E: EntityTrait,
238{
239    type QueryStatement = DeleteStatement;
240
241    fn query(&mut self) -> &mut DeleteStatement {
242        &mut self.query
243    }
244
245    fn as_query(&self) -> &DeleteStatement {
246        &self.query
247    }
248
249    fn into_query(self) -> DeleteStatement {
250        self.query
251    }
252}
253
254impl<E> QueryTrait for DeleteMany<E>
255where
256    E: EntityTrait,
257{
258    type QueryStatement = DeleteStatement;
259
260    fn query(&mut self) -> &mut DeleteStatement {
261        &mut self.query
262    }
263
264    fn as_query(&self) -> &DeleteStatement {
265        &self.query
266    }
267
268    fn into_query(self) -> DeleteStatement {
269        self.query
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use crate::tests_cfg::{cake, fruit};
276    use crate::{DbBackend, entity::*, query::*};
277
278    #[test]
279    fn delete_1() {
280        assert_eq!(
281            Delete::one(cake::Model {
282                id: 1,
283                name: "Apple Pie".to_owned(),
284            })
285            .validate()
286            .unwrap()
287            .build(DbBackend::Postgres)
288            .to_string(),
289            r#"DELETE FROM "cake" WHERE "cake"."id" = 1"#,
290        );
291        assert_eq!(
292            Delete::one(cake::ActiveModel {
293                id: ActiveValue::set(1),
294                name: ActiveValue::set("Apple Pie".to_owned()),
295            })
296            .validate()
297            .unwrap()
298            .build(DbBackend::Postgres)
299            .to_string(),
300            r#"DELETE FROM "cake" WHERE "cake"."id" = 1"#,
301        );
302    }
303
304    #[test]
305    fn delete_2() {
306        assert_eq!(
307            Delete::many(fruit::Entity)
308                .filter(fruit::Column::Name.contains("Cheese"))
309                .build(DbBackend::Postgres)
310                .to_string(),
311            r#"DELETE FROM "fruit" WHERE "fruit"."name" LIKE '%Cheese%'"#,
312        );
313    }
314}