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