1use crate::{
2 ConnectionTrait, DbErr, EntityTrait, Select, SelectFive, SelectFour, SelectSix, SelectThree,
3 SelectTwo, Selector, SelectorRaw, SelectorTrait, Topology,
4};
5use sea_query::{Expr, SelectStatement};
6
7#[async_trait::async_trait]
9pub trait SelectExt {
11 #[doc(hidden)]
14 fn exists_query(self) -> SelectStatement;
15 async fn exists<C>(self, db: &C) -> Result<bool, DbErr>
17 where
18 C: ConnectionTrait,
19 Self: Send + Sized,
20 {
21 let stmt = self.exists_query();
22 Ok(db.query_one(&stmt).await?.is_some())
23 }
24}
25
26fn into_exists_query(mut stmt: SelectStatement) -> SelectStatement {
27 stmt.clear_selects();
28 stmt.expr(Expr::cust("1"));
30 stmt.reset_limit();
31 stmt.reset_offset();
32 stmt.clear_order_by();
33 stmt
34}
35
36impl<S> SelectExt for Selector<S>
37where
38 S: SelectorTrait,
39{
40 fn exists_query(self) -> SelectStatement {
41 into_exists_query(self.query)
42 }
43}
44
45#[async_trait::async_trait]
46impl<S> SelectExt for SelectorRaw<S>
47where
48 S: SelectorTrait,
49{
50 fn exists_query(self) -> SelectStatement {
51 let stmt = self.stmt;
52 let sub_query_sql = stmt.sql.trim().trim_end_matches(';').trim();
53 let exists_sql = format!("1 FROM ({sub_query_sql}) AS sub_query LIMIT 1");
54
55 let mut query = SelectStatement::new();
56 query.expr(if let Some(values) = stmt.values {
57 Expr::cust_with_values(exists_sql, values.0)
58 } else {
59 Expr::cust(exists_sql)
60 });
61 query
62 }
63}
64
65impl<E> SelectExt for Select<E>
66where
67 E: EntityTrait,
68{
69 fn exists_query(self) -> SelectStatement {
70 into_exists_query(self.query)
71 }
72}
73
74impl<E, F> SelectExt for SelectTwo<E, F>
75where
76 E: EntityTrait,
77 F: EntityTrait,
78{
79 fn exists_query(self) -> SelectStatement {
80 into_exists_query(self.query)
81 }
82}
83
84impl<E, F, G, TOP> SelectExt for SelectThree<E, F, G, TOP>
85where
86 E: EntityTrait,
87 F: EntityTrait,
88 G: EntityTrait,
89 TOP: Topology,
90{
91 fn exists_query(self) -> SelectStatement {
92 into_exists_query(self.query)
93 }
94}
95
96impl<E, F, G, H, TOP> SelectExt for SelectFour<E, F, G, H, TOP>
97where
98 E: EntityTrait,
99 F: EntityTrait,
100 G: EntityTrait,
101 H: EntityTrait,
102 TOP: Topology,
103{
104 fn exists_query(self) -> SelectStatement {
105 into_exists_query(self.query)
106 }
107}
108
109impl<E, F, G, H, I, TOP> SelectExt for SelectFive<E, F, G, H, I, TOP>
110where
111 E: EntityTrait,
112 F: EntityTrait,
113 G: EntityTrait,
114 H: EntityTrait,
115 I: EntityTrait,
116 TOP: Topology,
117{
118 fn exists_query(self) -> SelectStatement {
119 into_exists_query(self.query)
120 }
121}
122
123impl<E, F, G, H, I, J, TOP> SelectExt for SelectSix<E, F, G, H, I, J, TOP>
124where
125 E: EntityTrait,
126 F: EntityTrait,
127 G: EntityTrait,
128 H: EntityTrait,
129 I: EntityTrait,
130 J: EntityTrait,
131 TOP: Topology,
132{
133 fn exists_query(self) -> SelectStatement {
134 into_exists_query(self.query)
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::SelectExt;
141 use crate::entity::prelude::*;
142 use crate::{DbBackend, QueryOrder, QuerySelect, Statement, tests_cfg::*};
143
144 #[test]
145 fn exists_query_select_basic() {
146 let stmt = fruit::Entity::find().exists_query();
147 let sql = DbBackend::Postgres.build(&stmt).to_string();
148 assert_eq!(sql, r#"SELECT 1 FROM "fruit""#);
149 }
150
151 #[test]
152 fn exists_query_select_strips_limit_offset_order() {
153 let stmt = fruit::Entity::find()
154 .filter(fruit::Column::Id.gt(1))
155 .order_by_asc(fruit::Column::Id)
156 .limit(2)
157 .offset(4)
158 .exists_query();
159
160 let sql = DbBackend::Postgres.build(&stmt).to_string();
161 assert_eq!(sql, r#"SELECT 1 FROM "fruit" WHERE "fruit"."id" > 1"#);
162 }
163
164 #[test]
165 fn exists_query_selector_basic() {
166 let stmt = fruit::Entity::find()
167 .into_model::<fruit::Model>()
168 .exists_query();
169
170 let sql = DbBackend::Postgres.build(&stmt).to_string();
171 assert_eq!(sql, r#"SELECT 1 FROM "fruit""#);
172 }
173
174 #[test]
175 fn exists_query_selector_complex() {
176 let stmt = fruit::Entity::find()
177 .filter(fruit::Column::Id.gt(1))
178 .order_by_desc(fruit::Column::Id)
179 .limit(2)
180 .offset(4)
181 .into_model::<fruit::Model>()
182 .exists_query();
183
184 let sql = DbBackend::Postgres.build(&stmt).to_string();
185 assert_eq!(sql, r#"SELECT 1 FROM "fruit" WHERE "fruit"."id" > 1"#);
186 }
187
188 #[test]
189 fn exists_query_selector_raw_simple() {
190 let raw_stmt =
191 Statement::from_string(DbBackend::Postgres, r#"SELECT "fruit"."id" FROM "fruit""#);
192 let stmt = fruit::Entity::find().from_raw_sql(raw_stmt).exists_query();
193
194 let sql = DbBackend::Postgres.build(&stmt).to_string();
195 assert_eq!(
196 sql,
197 r#"SELECT 1 FROM (SELECT "fruit"."id" FROM "fruit") AS sub_query LIMIT 1"#
198 );
199 }
200
201 #[test]
202 fn exists_query_selector_raw_complex() {
203 let raw_stmt = Statement::from_string(
204 DbBackend::Postgres,
205 r#"SELECT "fruit"."id" FROM "fruit" WHERE "fruit"."id" > 1 ORDER BY "fruit"."id" DESC LIMIT 5 OFFSET 2"#,
206 );
207 let stmt = fruit::Entity::find().from_raw_sql(raw_stmt).exists_query();
208
209 let sql = DbBackend::Postgres.build(&stmt).to_string();
210 assert_eq!(
211 sql,
212 r#"SELECT 1 FROM (SELECT "fruit"."id" FROM "fruit" WHERE "fruit"."id" > 1 ORDER BY "fruit"."id" DESC LIMIT 5 OFFSET 2) AS sub_query LIMIT 1"#
213 );
214 }
215
216 #[test]
217 fn exists_query_select_two_simple() {
218 let stmt = cake::Entity::find()
219 .find_also_related(fruit::Entity)
220 .exists_query();
221
222 let sql = DbBackend::Postgres.build(&stmt).to_string();
223 assert_eq!(
224 sql,
225 r#"SELECT 1 FROM "cake" LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#
226 );
227 }
228
229 #[test]
230 fn exists_query_select_two_complex() {
231 let stmt = cake::Entity::find()
232 .find_also_related(fruit::Entity)
233 .filter(cake::Column::Id.gt(1))
234 .order_by_desc(cake::Column::Id)
235 .limit(2)
236 .offset(4)
237 .exists_query();
238
239 let sql = DbBackend::Postgres.build(&stmt).to_string();
240 assert_eq!(
241 sql,
242 [
243 r#"SELECT 1 FROM "cake""#,
244 r#"LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#,
245 r#"WHERE "cake"."id" > 1"#,
246 ]
247 .join(" ")
248 );
249 }
250
251 #[test]
252 fn exists_query_select_three_simple() {
253 let stmt = cake_filling::Entity::find()
254 .find_also_related(cake::Entity)
255 .find_also(cake_filling::Entity, filling::Entity)
256 .exists_query();
257
258 let sql = DbBackend::Postgres.build(&stmt).to_string();
259 assert_eq!(
260 sql,
261 [
262 r#"SELECT 1 FROM "cake_filling""#,
263 r#"LEFT JOIN "cake" ON "cake_filling"."cake_id" = "cake"."id""#,
264 r#"LEFT JOIN "filling" ON "cake_filling"."filling_id" = "filling"."id""#,
265 ]
266 .join(" ")
267 );
268 }
269
270 #[test]
271 fn exists_query_select_three_complex() {
272 let stmt = cake_filling::Entity::find()
273 .find_also_related(cake::Entity)
274 .find_also(cake_filling::Entity, filling::Entity)
275 .filter(cake_filling::Column::CakeId.gt(1))
276 .order_by_desc(cake_filling::Column::CakeId)
277 .limit(2)
278 .offset(4)
279 .exists_query();
280
281 let sql = DbBackend::Postgres.build(&stmt).to_string();
282 assert_eq!(
283 sql,
284 [
285 r#"SELECT 1 FROM "cake_filling""#,
286 r#"LEFT JOIN "cake" ON "cake_filling"."cake_id" = "cake"."id""#,
287 r#"LEFT JOIN "filling" ON "cake_filling"."filling_id" = "filling"."id""#,
288 r#"WHERE "cake_filling"."cake_id" > 1"#,
289 ]
290 .join(" ")
291 );
292 }
293
294 #[test]
295 fn exists_query_select_four_simple() {
296 let stmt = cake_filling::Entity::find()
297 .find_also_related(cake::Entity)
298 .find_also(cake_filling::Entity, filling::Entity)
299 .find_also(filling::Entity, ingredient::Entity)
300 .exists_query();
301
302 let sql = DbBackend::Postgres.build(&stmt).to_string();
303 assert_eq!(
304 sql,
305 [
306 r#"SELECT 1 FROM "cake_filling""#,
307 r#"LEFT JOIN "cake" ON "cake_filling"."cake_id" = "cake"."id""#,
308 r#"LEFT JOIN "filling" ON "cake_filling"."filling_id" = "filling"."id""#,
309 r#"LEFT JOIN "ingredient" ON "filling"."id" = "ingredient"."filling_id""#,
310 ]
311 .join(" ")
312 );
313 }
314
315 #[test]
316 fn exists_query_select_four_complex() {
317 let stmt = cake_filling::Entity::find()
318 .find_also_related(cake::Entity)
319 .find_also(cake_filling::Entity, filling::Entity)
320 .find_also(filling::Entity, ingredient::Entity)
321 .filter(cake_filling::Column::CakeId.gt(1))
322 .order_by_desc(cake_filling::Column::CakeId)
323 .limit(2)
324 .offset(4)
325 .exists_query();
326
327 let sql = DbBackend::Postgres.build(&stmt).to_string();
328 assert_eq!(
329 sql,
330 [
331 r#"SELECT 1 FROM "cake_filling""#,
332 r#"LEFT JOIN "cake" ON "cake_filling"."cake_id" = "cake"."id""#,
333 r#"LEFT JOIN "filling" ON "cake_filling"."filling_id" = "filling"."id""#,
334 r#"LEFT JOIN "ingredient" ON "filling"."id" = "ingredient"."filling_id""#,
335 r#"WHERE "cake_filling"."cake_id" > 1"#,
336 ]
337 .join(" ")
338 );
339 }
340
341 #[test]
342 fn exists_query_select_five_simple() {
343 let stmt = cake_filling::Entity::find()
344 .find_also_related(cake::Entity)
345 .find_also(cake_filling::Entity, filling::Entity)
346 .find_also(filling::Entity, ingredient::Entity)
347 .find_also(cake_filling::Entity, cake_filling_price::Entity)
348 .exists_query();
349
350 let sql = DbBackend::Postgres.build(&stmt).to_string();
351 assert_eq!(
352 sql,
353 [
354 r#"SELECT 1 FROM "cake_filling""#,
355 r#"LEFT JOIN "cake" ON "cake_filling"."cake_id" = "cake"."id""#,
356 r#"LEFT JOIN "filling" ON "cake_filling"."filling_id" = "filling"."id""#,
357 r#"LEFT JOIN "ingredient" ON "filling"."id" = "ingredient"."filling_id""#,
358 r#"LEFT JOIN "public"."cake_filling_price" ON "cake_filling"."cake_id" = "cake_filling_price"."cake_id" AND "cake_filling"."filling_id" = "cake_filling_price"."filling_id""#,
359 ]
360 .join(" ")
361 );
362 }
363
364 #[test]
365 fn exists_query_select_five_complex() {
366 let stmt = cake_filling::Entity::find()
367 .find_also_related(cake::Entity)
368 .find_also(cake_filling::Entity, filling::Entity)
369 .find_also(filling::Entity, ingredient::Entity)
370 .find_also(cake_filling::Entity, cake_filling_price::Entity)
371 .filter(cake_filling::Column::CakeId.gt(1))
372 .order_by_desc(cake_filling::Column::CakeId)
373 .limit(2)
374 .offset(4)
375 .exists_query();
376
377 let sql = DbBackend::Postgres.build(&stmt).to_string();
378 assert_eq!(
379 sql,
380 [
381 r#"SELECT 1 FROM "cake_filling""#,
382 r#"LEFT JOIN "cake" ON "cake_filling"."cake_id" = "cake"."id""#,
383 r#"LEFT JOIN "filling" ON "cake_filling"."filling_id" = "filling"."id""#,
384 r#"LEFT JOIN "ingredient" ON "filling"."id" = "ingredient"."filling_id""#,
385 r#"LEFT JOIN "public"."cake_filling_price" ON "cake_filling"."cake_id" = "cake_filling_price"."cake_id" AND "cake_filling"."filling_id" = "cake_filling_price"."filling_id""#,
386 r#"WHERE "cake_filling"."cake_id" > 1"#,
387 ]
388 .join(" ")
389 );
390 }
391
392 #[test]
393 fn exists_query_select_six_simple() {
394 let stmt = cake_filling::Entity::find()
395 .find_also_related(cake::Entity)
396 .find_also(cake_filling::Entity, filling::Entity)
397 .find_also(filling::Entity, ingredient::Entity)
398 .find_also(cake_filling::Entity, cake_filling_price::Entity)
399 .find_also(filling::Entity, cake_compact::Entity)
400 .exists_query();
401
402 let sql = DbBackend::Postgres.build(&stmt).to_string();
403 assert_eq!(
404 sql,
405 [
406 r#"SELECT 1 FROM "cake_filling""#,
407 r#"LEFT JOIN "cake" ON "cake_filling"."cake_id" = "cake"."id""#,
408 r#"LEFT JOIN "filling" ON "cake_filling"."filling_id" = "filling"."id""#,
409 r#"LEFT JOIN "ingredient" ON "filling"."id" = "ingredient"."filling_id""#,
410 r#"LEFT JOIN "public"."cake_filling_price" ON "cake_filling"."cake_id" = "cake_filling_price"."cake_id" AND "cake_filling"."filling_id" = "cake_filling_price"."filling_id""#,
411 r#"LEFT JOIN "cake_filling" ON "filling"."id" = "cake_filling"."filling_id""#,
412 r#"LEFT JOIN "cake" ON "cake_filling"."cake_id" = "cake"."id""#,
413 ]
414 .join(" ")
415 );
416 }
417
418 #[test]
419 fn exists_query_select_six_complex() {
420 let stmt = cake_filling::Entity::find()
421 .find_also_related(cake::Entity)
422 .find_also(cake_filling::Entity, filling::Entity)
423 .find_also(filling::Entity, ingredient::Entity)
424 .find_also(cake_filling::Entity, cake_filling_price::Entity)
425 .find_also(filling::Entity, cake_compact::Entity)
426 .filter(cake_filling::Column::CakeId.gt(1))
427 .order_by_desc(cake_filling::Column::CakeId)
428 .limit(2)
429 .offset(4)
430 .exists_query();
431
432 let sql = DbBackend::Postgres.build(&stmt).to_string();
433 assert_eq!(
434 sql,
435 [
436 r#"SELECT 1 FROM "cake_filling""#,
437 r#"LEFT JOIN "cake" ON "cake_filling"."cake_id" = "cake"."id""#,
438 r#"LEFT JOIN "filling" ON "cake_filling"."filling_id" = "filling"."id""#,
439 r#"LEFT JOIN "ingredient" ON "filling"."id" = "ingredient"."filling_id""#,
440 r#"LEFT JOIN "public"."cake_filling_price" ON "cake_filling"."cake_id" = "cake_filling_price"."cake_id" AND "cake_filling"."filling_id" = "cake_filling_price"."filling_id""#,
441 r#"LEFT JOIN "cake_filling" ON "filling"."id" = "cake_filling"."filling_id""#,
442 r#"LEFT JOIN "cake" ON "cake_filling"."cake_id" = "cake"."id""#,
443 r#"WHERE "cake_filling"."cake_id" > 1"#,
444 ]
445 .join(" ")
446 );
447 }
448}