1use crate::clause::{Limit, Offset, OrderBy, Where};
4use crate::eager::{
5 EagerLoader, IncludePath, build_aliased_column_parts, build_join_clause, find_relationship,
6};
7use crate::expr::{Dialect, Expr};
8use crate::join::Join;
9use crate::subquery::SelectQuery;
10use asupersync::{Cx, Outcome};
11use sqlmodel_core::{Connection, Model, RelationshipKind, Value};
12use std::marker::PhantomData;
13
14type ParentFieldsFn = fn() -> &'static [sqlmodel_core::FieldInfo];
15
16fn sti_discriminator_filter<M: Model>() -> Option<Expr> {
17 let inh = M::inheritance();
18 match (inh.discriminator_column, inh.discriminator_value) {
19 (Some(col), Some(val)) => Some(Expr::qualified(M::TABLE_NAME, col).eq(val)),
20 _ => None,
21 }
22}
23
24fn joined_inheritance_parent<M: Model>() -> Option<(&'static str, ParentFieldsFn)> {
25 let inh = M::inheritance();
26 if inh.strategy != sqlmodel_core::InheritanceStrategy::Joined {
27 return None;
28 }
29 let parent = inh.parent?;
30 let parent_fields_fn = inh.parent_fields_fn?;
31 Some((parent, parent_fields_fn))
32}
33
34fn joined_inheritance_join<M: Model>() -> Option<Join> {
35 let (parent_table, _parent_fields_fn) = joined_inheritance_parent::<M>()?;
36
37 let pks = M::PRIMARY_KEY;
39 if pks.is_empty() {
40 return None;
41 }
42
43 let mut on = Expr::qualified(M::TABLE_NAME, pks[0]).eq(Expr::qualified(parent_table, pks[0]));
44 for pk in &pks[1..] {
45 on = on.and(Expr::qualified(M::TABLE_NAME, *pk).eq(Expr::qualified(parent_table, *pk)));
46 }
47
48 Some(Join::inner(parent_table, on))
49}
50
51fn joined_inheritance_select_columns<M: Model>() -> Option<Vec<String>> {
52 let (parent_table, parent_fields_fn) = joined_inheritance_parent::<M>()?;
53
54 let child_cols: Vec<&str> = M::fields().iter().map(|f| f.column_name).collect();
55 let parent_cols: Vec<&str> = parent_fields_fn().iter().map(|f| f.column_name).collect();
56
57 let mut parts = Vec::new();
58 parts.extend(build_aliased_column_parts(M::TABLE_NAME, &child_cols));
59 parts.extend(build_aliased_column_parts(parent_table, &parent_cols));
60 Some(parts)
61}
62
63#[derive(Debug, Clone)]
68#[allow(dead_code)] struct EagerJoinInfo {
70 relationship_name: &'static str,
72 related_table: &'static str,
74 kind: RelationshipKind,
76 nested: Vec<IncludePath>,
78}
79
80#[derive(Debug, Clone)]
85pub struct Select<M: Model> {
86 columns: Vec<String>,
88 where_clause: Option<Where>,
90 order_by: Vec<OrderBy>,
92 joins: Vec<Join>,
94 limit: Option<Limit>,
96 offset: Option<Offset>,
98 group_by: Vec<String>,
100 having: Option<Where>,
102 distinct: bool,
104 for_update: bool,
106 eager_loader: Option<EagerLoader<M>>,
108 _marker: PhantomData<M>,
110}
111
112impl<M: Model> Select<M> {
113 pub fn new() -> Self {
115 Self {
116 columns: Vec::new(),
117 where_clause: None,
118 order_by: Vec::new(),
119 joins: Vec::new(),
120 limit: None,
121 offset: None,
122 group_by: Vec::new(),
123 having: None,
124 distinct: false,
125 for_update: false,
126 eager_loader: None,
127 _marker: PhantomData,
128 }
129 }
130
131 pub fn columns(mut self, cols: &[&str]) -> Self {
133 self.columns = cols.iter().map(|&s| s.to_string()).collect();
134 self
135 }
136
137 pub fn filter(mut self, expr: Expr) -> Self {
139 self.where_clause = Some(match self.where_clause {
140 Some(existing) => existing.and(expr),
141 None => Where::new(expr),
142 });
143 self
144 }
145
146 pub fn or_filter(mut self, expr: Expr) -> Self {
148 self.where_clause = Some(match self.where_clause {
149 Some(existing) => existing.or(expr),
150 None => Where::new(expr),
151 });
152 self
153 }
154
155 pub fn order_by(mut self, order: OrderBy) -> Self {
157 self.order_by.push(order);
158 self
159 }
160
161 pub fn join(mut self, join: Join) -> Self {
163 self.joins.push(join);
164 self
165 }
166
167 pub fn limit(mut self, n: u64) -> Self {
169 self.limit = Some(Limit(n));
170 self
171 }
172
173 pub fn offset(mut self, n: u64) -> Self {
175 self.offset = Some(Offset(n));
176 self
177 }
178
179 pub fn group_by(mut self, cols: &[&str]) -> Self {
181 self.group_by.extend(cols.iter().map(|&s| s.to_string()));
182 self
183 }
184
185 pub fn having(mut self, expr: Expr) -> Self {
187 self.having = Some(match self.having {
188 Some(existing) => existing.and(expr),
189 None => Where::new(expr),
190 });
191 self
192 }
193
194 pub fn distinct(mut self) -> Self {
196 self.distinct = true;
197 self
198 }
199
200 pub fn for_update(mut self) -> Self {
202 self.for_update = true;
203 self
204 }
205
206 pub fn eager(mut self, loader: EagerLoader<M>) -> Self {
217 self.eager_loader = Some(loader);
218 self
219 }
220
221 #[must_use]
236 pub fn polymorphic_joined<Child: Model>(mut self) -> PolymorphicJoinedSelect<M, Child> {
237 self.columns = polymorphic_joined_select_columns::<M, Child>();
238 if let Some(join) = polymorphic_joined_left_join::<M, Child>() {
239 self.joins.push(join);
240 }
241
242 PolymorphicJoinedSelect {
243 select: self,
244 _marker: PhantomData,
245 }
246 }
247
248 #[must_use]
252 pub fn polymorphic_joined2<C1: Model, C2: Model>(
253 mut self,
254 ) -> PolymorphicJoinedSelect2<M, C1, C2> {
255 self.columns = polymorphic_joined_select_columns2::<M, C1, C2>();
256 if let Some(join) = polymorphic_joined_left_join::<M, C1>() {
257 self.joins.push(join);
258 }
259 if let Some(join) = polymorphic_joined_left_join::<M, C2>() {
260 self.joins.push(join);
261 }
262
263 PolymorphicJoinedSelect2 {
264 select: self,
265 _marker: PhantomData,
266 }
267 }
268
269 #[must_use]
273 pub fn polymorphic_joined3<C1: Model, C2: Model, C3: Model>(
274 mut self,
275 ) -> PolymorphicJoinedSelect3<M, C1, C2, C3> {
276 self.columns = polymorphic_joined_select_columns3::<M, C1, C2, C3>();
277 if let Some(join) = polymorphic_joined_left_join::<M, C1>() {
278 self.joins.push(join);
279 }
280 if let Some(join) = polymorphic_joined_left_join::<M, C2>() {
281 self.joins.push(join);
282 }
283 if let Some(join) = polymorphic_joined_left_join::<M, C3>() {
284 self.joins.push(join);
285 }
286
287 PolymorphicJoinedSelect3 {
288 select: self,
289 _marker: PhantomData,
290 }
291 }
292
293 #[tracing::instrument(level = "trace", skip(self))]
297 fn build_eager_with_dialect(
298 &self,
299 dialect: Dialect,
300 ) -> (String, Vec<Value>, Vec<EagerJoinInfo>) {
301 let mut sql = String::new();
302 let mut params = Vec::new();
303 let mut join_info = Vec::new();
304 let mut where_clause = self.where_clause.clone();
305 let mut joins = self.joins.clone();
306
307 if let Some(expr) = sti_discriminator_filter::<M>() {
309 where_clause = Some(match where_clause {
310 Some(existing) => existing.and(expr),
311 None => Where::new(expr),
312 });
313 }
314
315 if let Some(join) = joined_inheritance_join::<M>() {
316 joins.insert(0, join);
317 }
318
319 let parent_cols: Vec<&str> = M::fields().iter().map(|f| f.column_name).collect();
321
322 sql.push_str("SELECT ");
324 if self.distinct {
325 sql.push_str("DISTINCT ");
326 }
327
328 let mut col_parts = Vec::new();
330 for col in &parent_cols {
331 col_parts.push(format!(
332 "{}.{} AS {}__{}",
333 M::TABLE_NAME,
334 col,
335 M::TABLE_NAME,
336 col
337 ));
338 }
339
340 if let Some((parent_table, parent_fields_fn)) = joined_inheritance_parent::<M>() {
343 let parent_cols: Vec<&str> = parent_fields_fn().iter().map(|f| f.column_name).collect();
344 col_parts.extend(build_aliased_column_parts(parent_table, &parent_cols));
345 }
346
347 if let Some(loader) = &self.eager_loader {
349 for include in loader.includes() {
350 if let Some(rel) = find_relationship::<M>(include.relationship) {
351 join_info.push(EagerJoinInfo {
352 relationship_name: include.relationship,
353 related_table: rel.related_table,
354 kind: rel.kind,
355 nested: include.nested.clone(),
356 });
357
358 let related_cols: Vec<&str> = (rel.related_fields_fn)()
361 .iter()
362 .map(|f| f.column_name)
363 .collect();
364 col_parts.extend(build_aliased_column_parts(rel.related_table, &related_cols));
365 }
366 }
367 }
368
369 sql.push_str(&col_parts.join(", "));
370
371 sql.push_str(" FROM ");
373 sql.push_str(M::TABLE_NAME);
374
375 if let Some(loader) = &self.eager_loader {
377 for include in loader.includes() {
378 if let Some(rel) = find_relationship::<M>(include.relationship) {
379 let (join_sql, join_params) =
380 build_join_clause(M::TABLE_NAME, rel, params.len());
381 sql.push_str(&join_sql);
382 params.extend(join_params);
383 }
384 }
385 }
386
387 for join in &joins {
389 sql.push_str(&join.build_with_dialect(dialect, &mut params, 0));
390 }
391
392 if let Some(where_clause) = &where_clause {
394 let (where_sql, where_params) = where_clause.build_with_dialect(dialect, params.len());
395 sql.push_str(" WHERE ");
396 sql.push_str(&where_sql);
397 params.extend(where_params);
398 }
399
400 if !self.group_by.is_empty() {
402 sql.push_str(" GROUP BY ");
403 sql.push_str(&self.group_by.join(", "));
404 }
405
406 if let Some(having) = &self.having {
408 let (having_sql, having_params) = having.build_with_dialect(dialect, params.len());
409 sql.push_str(" HAVING ");
410 sql.push_str(&having_sql);
411 params.extend(having_params);
412 }
413
414 if !self.order_by.is_empty() {
416 sql.push_str(" ORDER BY ");
417 let order_strs: Vec<_> = self
418 .order_by
419 .iter()
420 .map(|o| o.build(dialect, &mut params, 0))
421 .collect();
422 sql.push_str(&order_strs.join(", "));
423 }
424
425 if let Some(Limit(n)) = self.limit {
427 sql.push_str(&format!(" LIMIT {}", n));
428 }
429
430 if let Some(Offset(n)) = self.offset {
432 sql.push_str(&format!(" OFFSET {}", n));
433 }
434
435 (sql, params, join_info)
436 }
437
438 #[tracing::instrument(level = "debug", skip(self, cx, conn))]
462 pub async fn all_eager<C: Connection>(
463 self,
464 cx: &Cx,
465 conn: &C,
466 ) -> Outcome<Vec<M>, sqlmodel_core::Error> {
467 if !self.eager_loader.as_ref().is_some_and(|e| e.has_includes()) {
469 tracing::trace!("No eager loading configured, falling back to regular all()");
470 return self.all(cx, conn).await;
471 }
472
473 let (sql, params, join_info) = self.build_eager_with_dialect(conn.dialect());
474
475 tracing::debug!(
476 table = M::TABLE_NAME,
477 includes = join_info.len(),
478 "Executing eager loading query"
479 );
480 tracing::trace!(sql = %sql, "Eager SQL");
481
482 let rows = conn.query(cx, &sql, ¶ms).await;
483
484 rows.and_then(|rows| {
485 tracing::debug!(row_count = rows.len(), "Processing eager query results");
486
487 let mut seen_pks = std::collections::HashSet::new();
489 let mut models = Vec::with_capacity(rows.len());
490
491 for row in &rows {
492 let parent_row = row.subset_by_prefix(M::TABLE_NAME);
494
495 if parent_row.is_empty() {
497 tracing::warn!(
498 table = M::TABLE_NAME,
499 "Row has no columns with parent table prefix"
500 );
501 match M::from_row(row) {
503 Ok(model) => {
504 models.push(model);
505 }
506 Err(e) => {
507 tracing::debug!(error = %e, "Failed to parse model from row");
508 return Outcome::Err(e);
509 }
510 }
511 continue;
512 }
513
514 match M::from_row(&parent_row) {
516 Ok(model) => {
517 let pk = model.primary_key_value();
519 let pk_hash = {
520 use std::hash::{Hash, Hasher};
521 let mut hasher = std::collections::hash_map::DefaultHasher::new();
522 format!("{:?}", pk).hash(&mut hasher);
524 hasher.finish()
525 };
526
527 if seen_pks.insert(pk_hash) {
528 models.push(model);
529 }
530 }
531 Err(e) => {
532 tracing::debug!(error = %e, "Failed to parse model from prefixed row");
533 return Outcome::Err(e);
534 }
535 }
536 }
537
538 tracing::debug!(
539 unique_models = models.len(),
540 "Eager loading complete (deduplicated)"
541 );
542 Outcome::Ok(models)
543 })
544 }
545
546 pub fn build(&self) -> (String, Vec<Value>) {
548 self.build_with_dialect(Dialect::default())
549 }
550
551 pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
553 let mut sql = String::new();
554 let mut params = Vec::new();
555 let mut where_clause = self.where_clause.clone();
556 let mut joins = self.joins.clone();
557
558 if let Some(expr) = sti_discriminator_filter::<M>() {
560 where_clause = Some(match where_clause {
561 Some(existing) => existing.and(expr),
562 None => Where::new(expr),
563 });
564 }
565
566 if let Some(join) = joined_inheritance_join::<M>() {
567 joins.insert(0, join);
568 }
569
570 sql.push_str("SELECT ");
572 if self.distinct {
573 sql.push_str("DISTINCT ");
574 }
575
576 if let Some(cols) = joined_inheritance_select_columns::<M>() {
577 sql.push_str(&cols.join(", "));
578 } else if self.columns.is_empty() {
579 sql.push('*');
580 } else {
581 sql.push_str(&self.columns.join(", "));
582 }
583
584 sql.push_str(" FROM ");
586 sql.push_str(M::TABLE_NAME);
587
588 for join in &joins {
590 sql.push_str(&join.build_with_dialect(dialect, &mut params, 0));
591 }
592
593 if let Some(where_clause) = &where_clause {
595 let (where_sql, where_params) = where_clause.build_with_dialect(dialect, params.len());
596 sql.push_str(" WHERE ");
597 sql.push_str(&where_sql);
598 params.extend(where_params);
599 }
600
601 if !self.group_by.is_empty() {
603 sql.push_str(" GROUP BY ");
604 sql.push_str(&self.group_by.join(", "));
605 }
606
607 if let Some(having) = &self.having {
609 let (having_sql, having_params) = having.build_with_dialect(dialect, params.len());
610 sql.push_str(" HAVING ");
611 sql.push_str(&having_sql);
612 params.extend(having_params);
613 }
614
615 if !self.order_by.is_empty() {
617 sql.push_str(" ORDER BY ");
618 let order_strs: Vec<_> = self
619 .order_by
620 .iter()
621 .map(|o| o.build(dialect, &mut params, 0))
622 .collect();
623 sql.push_str(&order_strs.join(", "));
624 }
625
626 if let Some(Limit(n)) = self.limit {
628 sql.push_str(&format!(" LIMIT {}", n));
629 }
630
631 if let Some(Offset(n)) = self.offset {
633 sql.push_str(&format!(" OFFSET {}", n));
634 }
635
636 if self.for_update {
638 sql.push_str(" FOR UPDATE");
639 }
640
641 (sql, params)
642 }
643
644 pub fn into_exists(self) -> Expr {
666 Expr::exists_query(self.into_query())
667 }
668
669 pub fn into_exists_with_dialect(self, dialect: Dialect) -> Expr {
673 let (sql, params) = self.build_exists_subquery_with_dialect(dialect);
674 Expr::exists(sql, params)
675 }
676
677 pub fn into_not_exists(self) -> Expr {
698 Expr::not_exists_query(self.into_query())
699 }
700
701 pub fn into_not_exists_with_dialect(self, dialect: Dialect) -> Expr {
703 let (sql, params) = self.build_exists_subquery_with_dialect(dialect);
704 Expr::not_exists(sql, params)
705 }
706
707 pub fn into_lateral_join(
733 self,
734 alias: impl Into<String>,
735 join_type: crate::JoinType,
736 on: Expr,
737 ) -> crate::Join {
738 crate::Join::lateral_query(join_type, self.into_query(), alias, on)
739 }
740
741 pub fn into_lateral_join_with_dialect(
743 self,
744 alias: impl Into<String>,
745 join_type: crate::JoinType,
746 on: Expr,
747 dialect: Dialect,
748 ) -> crate::Join {
749 let (sql, params) = self.into_query().build_with_dialect(dialect);
750 crate::Join::lateral(join_type, sql, alias, on, params)
751 }
752
753 fn into_query(self) -> SelectQuery {
755 let Select {
756 columns,
757 where_clause,
758 order_by,
759 joins,
760 limit,
761 offset,
762 group_by,
763 having,
764 distinct,
765 for_update,
766 eager_loader: _,
767 _marker: _,
768 } = self;
769
770 let mut where_clause = where_clause;
771 if let Some(expr) = sti_discriminator_filter::<M>() {
772 where_clause = Some(match where_clause {
773 Some(existing) => existing.and(expr),
774 None => Where::new(expr),
775 });
776 }
777
778 let mut joins = joins;
779 if let Some(join) = joined_inheritance_join::<M>() {
780 joins.insert(0, join);
781 }
782
783 SelectQuery {
784 table: M::TABLE_NAME.to_string(),
785 columns,
786 where_clause,
787 order_by,
788 joins,
789 limit,
790 offset,
791 group_by,
792 having,
793 distinct,
794 for_update,
795 }
796 }
797
798 fn build_exists_subquery_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
799 let mut sql = String::new();
800 let mut params = Vec::new();
801 let mut where_clause = self.where_clause.clone();
802 let mut joins = self.joins.clone();
803
804 if let Some(expr) = sti_discriminator_filter::<M>() {
805 where_clause = Some(match where_clause {
806 Some(existing) => existing.and(expr),
807 None => Where::new(expr),
808 });
809 }
810
811 if let Some(join) = joined_inheritance_join::<M>() {
812 joins.insert(0, join);
813 }
814
815 sql.push_str("SELECT 1 FROM ");
817 sql.push_str(M::TABLE_NAME);
818
819 for join in &joins {
821 sql.push_str(&join.build_with_dialect(dialect, &mut params, 0));
822 }
823
824 if let Some(where_clause) = &where_clause {
826 let (where_sql, where_params) = where_clause.build_with_dialect(dialect, params.len());
827 sql.push_str(" WHERE ");
828 sql.push_str(&where_sql);
829 params.extend(where_params);
830 }
831
832 if !self.group_by.is_empty() {
834 sql.push_str(" GROUP BY ");
835 sql.push_str(&self.group_by.join(", "));
836 }
837
838 if let Some(having) = &self.having {
840 let (having_sql, having_params) = having.build_with_dialect(dialect, params.len());
841 sql.push_str(" HAVING ");
842 sql.push_str(&having_sql);
843 params.extend(having_params);
844 }
845
846 (sql, params)
849 }
850
851 pub async fn all<C: Connection>(
853 self,
854 cx: &Cx,
855 conn: &C,
856 ) -> Outcome<Vec<M>, sqlmodel_core::Error> {
857 let (sql, params) = self.build_with_dialect(conn.dialect());
858 let rows = conn.query(cx, &sql, ¶ms).await;
859
860 rows.and_then(|rows| {
861 let mut models = Vec::with_capacity(rows.len());
862 for row in &rows {
863 match M::from_row(row) {
864 Ok(model) => models.push(model),
865 Err(e) => return Outcome::Err(e),
866 }
867 }
868 Outcome::Ok(models)
869 })
870 }
871
872 pub async fn first<C: Connection>(
874 self,
875 cx: &Cx,
876 conn: &C,
877 ) -> Outcome<Option<M>, sqlmodel_core::Error> {
878 let query = self.limit(1);
879 let (sql, params) = query.build_with_dialect(conn.dialect());
880 let row = conn.query_one(cx, &sql, ¶ms).await;
881
882 row.and_then(|opt_row| match opt_row {
883 Some(row) => match M::from_row(&row) {
884 Ok(model) => Outcome::Ok(Some(model)),
885 Err(e) => Outcome::Err(e),
886 },
887 None => Outcome::Ok(None),
888 })
889 }
890
891 pub async fn one<C: Connection>(self, cx: &Cx, conn: &C) -> Outcome<M, sqlmodel_core::Error> {
893 match self.one_or_none(cx, conn).await {
894 Outcome::Ok(Some(model)) => Outcome::Ok(model),
895 Outcome::Ok(None) => Outcome::Err(sqlmodel_core::Error::Custom(
896 "Expected one row, found none".to_string(),
897 )),
898 Outcome::Err(e) => Outcome::Err(e),
899 Outcome::Cancelled(r) => Outcome::Cancelled(r),
900 Outcome::Panicked(p) => Outcome::Panicked(p),
901 }
902 }
903
904 pub async fn one_or_none<C: Connection>(
906 self,
907 cx: &Cx,
908 conn: &C,
909 ) -> Outcome<Option<M>, sqlmodel_core::Error> {
910 let mut query = self;
913 query.limit = Some(Limit(2));
914 let (sql, params) = query.build_with_dialect(conn.dialect());
915 let rows = conn.query(cx, &sql, ¶ms).await;
916
917 rows.and_then(|rows| match rows.len() {
918 0 => Outcome::Ok(None),
919 1 => match M::from_row(&rows[0]) {
920 Ok(model) => Outcome::Ok(Some(model)),
921 Err(e) => Outcome::Err(e),
922 },
923 n => Outcome::Err(sqlmodel_core::Error::Custom(format!(
924 "Expected zero or one row, found {n}"
925 ))),
926 })
927 }
928
929 pub async fn count<C: Connection>(
931 self,
932 cx: &Cx,
933 conn: &C,
934 ) -> Outcome<u64, sqlmodel_core::Error> {
935 let mut count_query = self;
936 count_query.columns = vec!["COUNT(*) as count".to_string()];
937 count_query.order_by.clear();
938 count_query.limit = None;
939 count_query.offset = None;
940
941 let (sql, params) = count_query.build_with_dialect(conn.dialect());
942 let row = conn.query_one(cx, &sql, ¶ms).await;
943
944 row.and_then(|opt_row| match opt_row {
945 Some(row) => match row.get_named::<i64>("count") {
946 Ok(count) => Outcome::Ok(count as u64),
947 Err(e) => Outcome::Err(e),
948 },
949 None => Outcome::Ok(0),
950 })
951 }
952
953 pub async fn exists<C: Connection>(
955 self,
956 cx: &Cx,
957 conn: &C,
958 ) -> Outcome<bool, sqlmodel_core::Error> {
959 let count = self.count(cx, conn).await;
960 count.map(|n| n > 0)
961 }
962}
963
964impl<M: Model> Default for Select<M> {
965 fn default() -> Self {
966 Self::new()
967 }
968}
969
970fn polymorphic_joined_left_join<Base: Model, Child: Model>() -> Option<Join> {
971 let pks = Base::PRIMARY_KEY;
973 if pks.is_empty() {
974 return None;
975 }
976
977 let mut on =
978 Expr::qualified(Base::TABLE_NAME, pks[0]).eq(Expr::qualified(Child::TABLE_NAME, pks[0]));
979 for pk in &pks[1..] {
980 on = on.and(
981 Expr::qualified(Base::TABLE_NAME, *pk).eq(Expr::qualified(Child::TABLE_NAME, *pk)),
982 );
983 }
984
985 Some(Join::left(Child::TABLE_NAME, on))
986}
987
988fn polymorphic_joined_select_columns<Base: Model, Child: Model>() -> Vec<String> {
989 let base_cols: Vec<&str> = Base::fields().iter().map(|f| f.column_name).collect();
990 let child_cols: Vec<&str> = Child::fields().iter().map(|f| f.column_name).collect();
991
992 let mut parts = Vec::new();
993 parts.extend(build_aliased_column_parts(Base::TABLE_NAME, &base_cols));
994 parts.extend(build_aliased_column_parts(Child::TABLE_NAME, &child_cols));
995 parts
996}
997
998fn polymorphic_joined_select_columns2<Base: Model, C1: Model, C2: Model>() -> Vec<String> {
999 let base_cols: Vec<&str> = Base::fields().iter().map(|f| f.column_name).collect();
1000 let c1_cols: Vec<&str> = C1::fields().iter().map(|f| f.column_name).collect();
1001 let c2_cols: Vec<&str> = C2::fields().iter().map(|f| f.column_name).collect();
1002
1003 let mut parts = Vec::new();
1004 parts.extend(build_aliased_column_parts(Base::TABLE_NAME, &base_cols));
1005 parts.extend(build_aliased_column_parts(C1::TABLE_NAME, &c1_cols));
1006 parts.extend(build_aliased_column_parts(C2::TABLE_NAME, &c2_cols));
1007 parts
1008}
1009
1010fn polymorphic_joined_select_columns3<Base: Model, C1: Model, C2: Model, C3: Model>() -> Vec<String>
1011{
1012 let base_cols: Vec<&str> = Base::fields().iter().map(|f| f.column_name).collect();
1013 let c1_cols: Vec<&str> = C1::fields().iter().map(|f| f.column_name).collect();
1014 let c2_cols: Vec<&str> = C2::fields().iter().map(|f| f.column_name).collect();
1015 let c3_cols: Vec<&str> = C3::fields().iter().map(|f| f.column_name).collect();
1016
1017 let mut parts = Vec::new();
1018 parts.extend(build_aliased_column_parts(Base::TABLE_NAME, &base_cols));
1019 parts.extend(build_aliased_column_parts(C1::TABLE_NAME, &c1_cols));
1020 parts.extend(build_aliased_column_parts(C2::TABLE_NAME, &c2_cols));
1021 parts.extend(build_aliased_column_parts(C3::TABLE_NAME, &c3_cols));
1022 parts
1023}
1024
1025#[derive(Debug, Clone, PartialEq)]
1027pub enum PolymorphicJoined<Base: Model, Child: Model> {
1028 Base(Base),
1029 Child(Child),
1030}
1031
1032#[derive(Debug, Clone)]
1036pub struct PolymorphicJoinedSelect<Base: Model, Child: Model> {
1037 select: Select<Base>,
1038 _marker: PhantomData<Child>,
1039}
1040
1041impl<Base: Model, Child: Model> PolymorphicJoinedSelect<Base, Child> {
1042 #[must_use]
1044 pub fn filter(mut self, expr: Expr) -> Self {
1045 self.select = self.select.filter(expr);
1046 self
1047 }
1048
1049 #[must_use]
1051 pub fn order_by(mut self, order: OrderBy) -> Self {
1052 self.select = self.select.order_by(order);
1053 self
1054 }
1055
1056 #[must_use]
1058 pub fn limit(mut self, n: u64) -> Self {
1059 self.select = self.select.limit(n);
1060 self
1061 }
1062
1063 #[must_use]
1065 pub fn offset(mut self, n: u64) -> Self {
1066 self.select = self.select.offset(n);
1067 self
1068 }
1069
1070 pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
1072 self.select.build_with_dialect(dialect)
1073 }
1074
1075 #[tracing::instrument(level = "debug", skip(self, cx, conn))]
1077 pub async fn all<C: Connection>(
1078 self,
1079 cx: &Cx,
1080 conn: &C,
1081 ) -> Outcome<Vec<PolymorphicJoined<Base, Child>>, sqlmodel_core::Error> {
1082 let inh_base = Base::inheritance();
1084 if inh_base.strategy != sqlmodel_core::InheritanceStrategy::Joined
1085 || inh_base.parent.is_some()
1086 {
1087 return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1088 "polymorphic_joined requires a joined-inheritance base model; got strategy={:?}, parent={:?} for {}",
1089 inh_base.strategy,
1090 inh_base.parent,
1091 Base::TABLE_NAME
1092 )));
1093 }
1094
1095 let inh_child = Child::inheritance();
1096 if inh_child.strategy != sqlmodel_core::InheritanceStrategy::Joined
1097 || inh_child.parent != Some(Base::TABLE_NAME)
1098 {
1099 return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1100 "polymorphic_joined requires a joined-inheritance child with parent={}; got strategy={:?}, parent={:?} for {}",
1101 Base::TABLE_NAME,
1102 inh_child.strategy,
1103 inh_child.parent,
1104 Child::TABLE_NAME
1105 )));
1106 }
1107
1108 if Base::PRIMARY_KEY.is_empty() {
1109 return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1110 "polymorphic_joined requires base model {} to have a primary key",
1111 Base::TABLE_NAME
1112 )));
1113 }
1114
1115 let (sql, params) = self.select.build_with_dialect(conn.dialect());
1116 tracing::debug!(
1117 sql = %sql,
1118 base = Base::TABLE_NAME,
1119 child = Child::TABLE_NAME,
1120 "Executing polymorphic joined SELECT"
1121 );
1122
1123 let rows = conn.query(cx, &sql, ¶ms).await;
1124 rows.and_then(|rows| {
1125 let mut out = Vec::with_capacity(rows.len());
1126 for row in rows {
1127 if row.prefix_is_all_null(Child::TABLE_NAME) {
1128 match Base::from_row(&row) {
1129 Ok(b) => out.push(PolymorphicJoined::Base(b)),
1130 Err(e) => return Outcome::Err(e),
1131 }
1132 } else {
1133 match Child::from_row(&row) {
1134 Ok(c) => out.push(PolymorphicJoined::Child(c)),
1135 Err(e) => return Outcome::Err(e),
1136 }
1137 }
1138 }
1139 Outcome::Ok(out)
1140 })
1141 }
1142}
1143
1144#[derive(Debug, Clone, PartialEq)]
1146pub enum PolymorphicJoined2<Base: Model, C1: Model, C2: Model> {
1147 Base(Base),
1148 C1(C1),
1149 C2(C2),
1150}
1151
1152#[derive(Debug, Clone)]
1156pub struct PolymorphicJoinedSelect2<Base: Model, C1: Model, C2: Model> {
1157 select: Select<Base>,
1158 _marker: PhantomData<(C1, C2)>,
1159}
1160
1161impl<Base: Model, C1: Model, C2: Model> PolymorphicJoinedSelect2<Base, C1, C2> {
1162 #[must_use]
1164 pub fn filter(mut self, expr: Expr) -> Self {
1165 self.select = self.select.filter(expr);
1166 self
1167 }
1168
1169 #[must_use]
1171 pub fn order_by(mut self, order: OrderBy) -> Self {
1172 self.select = self.select.order_by(order);
1173 self
1174 }
1175
1176 #[must_use]
1178 pub fn limit(mut self, n: u64) -> Self {
1179 self.select = self.select.limit(n);
1180 self
1181 }
1182
1183 #[must_use]
1185 pub fn offset(mut self, n: u64) -> Self {
1186 self.select = self.select.offset(n);
1187 self
1188 }
1189
1190 pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
1192 self.select.build_with_dialect(dialect)
1193 }
1194
1195 #[tracing::instrument(level = "debug", skip(self, cx, conn))]
1197 pub async fn all<C: Connection>(
1198 self,
1199 cx: &Cx,
1200 conn: &C,
1201 ) -> Outcome<Vec<PolymorphicJoined2<Base, C1, C2>>, sqlmodel_core::Error> {
1202 let inh_base = Base::inheritance();
1203 if inh_base.strategy != sqlmodel_core::InheritanceStrategy::Joined
1204 || inh_base.parent.is_some()
1205 {
1206 return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1207 "polymorphic_joined2 requires a joined-inheritance base model; got strategy={:?}, parent={:?} for {}",
1208 inh_base.strategy,
1209 inh_base.parent,
1210 Base::TABLE_NAME
1211 )));
1212 }
1213
1214 for (child_table, inh_child) in [
1215 (C1::TABLE_NAME, C1::inheritance()),
1216 (C2::TABLE_NAME, C2::inheritance()),
1217 ] {
1218 if inh_child.strategy != sqlmodel_core::InheritanceStrategy::Joined
1219 || inh_child.parent != Some(Base::TABLE_NAME)
1220 {
1221 return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1222 "polymorphic_joined2 requires joined-inheritance children with parent={}; got strategy={:?}, parent={:?} for {}",
1223 Base::TABLE_NAME,
1224 inh_child.strategy,
1225 inh_child.parent,
1226 child_table
1227 )));
1228 }
1229 }
1230
1231 if Base::PRIMARY_KEY.is_empty() {
1232 return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1233 "polymorphic_joined2 requires base model {} to have a primary key",
1234 Base::TABLE_NAME
1235 )));
1236 }
1237
1238 let (sql, params) = self.select.build_with_dialect(conn.dialect());
1239 tracing::debug!(
1240 sql = %sql,
1241 base = Base::TABLE_NAME,
1242 c1 = C1::TABLE_NAME,
1243 c2 = C2::TABLE_NAME,
1244 "Executing polymorphic joined2 SELECT"
1245 );
1246
1247 let rows = conn.query(cx, &sql, ¶ms).await;
1248 rows.and_then(|rows| {
1249 let mut out = Vec::with_capacity(rows.len());
1250 for row in rows {
1251 let has_c1 = !row.prefix_is_all_null(C1::TABLE_NAME);
1252 let has_c2 = !row.prefix_is_all_null(C2::TABLE_NAME);
1253 if has_c1 && has_c2 {
1254 return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1255 "polymorphic_joined2 ambiguous row: both {} and {} prefixes are non-NULL",
1256 C1::TABLE_NAME,
1257 C2::TABLE_NAME
1258 )));
1259 }
1260
1261 if has_c2 {
1262 match C2::from_row(&row) {
1263 Ok(c) => out.push(PolymorphicJoined2::C2(c)),
1264 Err(e) => return Outcome::Err(e),
1265 }
1266 } else if has_c1 {
1267 match C1::from_row(&row) {
1268 Ok(c) => out.push(PolymorphicJoined2::C1(c)),
1269 Err(e) => return Outcome::Err(e),
1270 }
1271 } else {
1272 match Base::from_row(&row) {
1273 Ok(b) => out.push(PolymorphicJoined2::Base(b)),
1274 Err(e) => return Outcome::Err(e),
1275 }
1276 }
1277 }
1278 Outcome::Ok(out)
1279 })
1280 }
1281}
1282
1283#[derive(Debug, Clone, PartialEq)]
1285pub enum PolymorphicJoined3<Base: Model, C1: Model, C2: Model, C3: Model> {
1286 Base(Base),
1287 C1(C1),
1288 C2(C2),
1289 C3(C3),
1290}
1291
1292#[derive(Debug, Clone)]
1296pub struct PolymorphicJoinedSelect3<Base: Model, C1: Model, C2: Model, C3: Model> {
1297 select: Select<Base>,
1298 _marker: PhantomData<(C1, C2, C3)>,
1299}
1300
1301impl<Base: Model, C1: Model, C2: Model, C3: Model> PolymorphicJoinedSelect3<Base, C1, C2, C3> {
1302 #[must_use]
1304 pub fn filter(mut self, expr: Expr) -> Self {
1305 self.select = self.select.filter(expr);
1306 self
1307 }
1308
1309 #[must_use]
1311 pub fn order_by(mut self, order: OrderBy) -> Self {
1312 self.select = self.select.order_by(order);
1313 self
1314 }
1315
1316 #[must_use]
1318 pub fn limit(mut self, n: u64) -> Self {
1319 self.select = self.select.limit(n);
1320 self
1321 }
1322
1323 #[must_use]
1325 pub fn offset(mut self, n: u64) -> Self {
1326 self.select = self.select.offset(n);
1327 self
1328 }
1329
1330 pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
1332 self.select.build_with_dialect(dialect)
1333 }
1334
1335 #[tracing::instrument(level = "debug", skip(self, cx, conn))]
1337 pub async fn all<C: Connection>(
1338 self,
1339 cx: &Cx,
1340 conn: &C,
1341 ) -> Outcome<Vec<PolymorphicJoined3<Base, C1, C2, C3>>, sqlmodel_core::Error> {
1342 let inh_base = Base::inheritance();
1343 if inh_base.strategy != sqlmodel_core::InheritanceStrategy::Joined
1344 || inh_base.parent.is_some()
1345 {
1346 return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1347 "polymorphic_joined3 requires a joined-inheritance base model; got strategy={:?}, parent={:?} for {}",
1348 inh_base.strategy,
1349 inh_base.parent,
1350 Base::TABLE_NAME
1351 )));
1352 }
1353
1354 for (child_table, inh_child) in [
1355 (C1::TABLE_NAME, C1::inheritance()),
1356 (C2::TABLE_NAME, C2::inheritance()),
1357 (C3::TABLE_NAME, C3::inheritance()),
1358 ] {
1359 if inh_child.strategy != sqlmodel_core::InheritanceStrategy::Joined
1360 || inh_child.parent != Some(Base::TABLE_NAME)
1361 {
1362 return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1363 "polymorphic_joined3 requires joined-inheritance children with parent={}; got strategy={:?}, parent={:?} for {}",
1364 Base::TABLE_NAME,
1365 inh_child.strategy,
1366 inh_child.parent,
1367 child_table
1368 )));
1369 }
1370 }
1371
1372 if Base::PRIMARY_KEY.is_empty() {
1373 return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1374 "polymorphic_joined3 requires base model {} to have a primary key",
1375 Base::TABLE_NAME
1376 )));
1377 }
1378
1379 let (sql, params) = self.select.build_with_dialect(conn.dialect());
1380 tracing::debug!(
1381 sql = %sql,
1382 base = Base::TABLE_NAME,
1383 c1 = C1::TABLE_NAME,
1384 c2 = C2::TABLE_NAME,
1385 c3 = C3::TABLE_NAME,
1386 "Executing polymorphic joined3 SELECT"
1387 );
1388
1389 let rows = conn.query(cx, &sql, ¶ms).await;
1390 rows.and_then(|rows| {
1391 let mut out = Vec::with_capacity(rows.len());
1392 for row in rows {
1393 let has_c1 = !row.prefix_is_all_null(C1::TABLE_NAME);
1394 let has_c2 = !row.prefix_is_all_null(C2::TABLE_NAME);
1395 let has_c3 = !row.prefix_is_all_null(C3::TABLE_NAME);
1396
1397 let mut matched_children = Vec::new();
1398 if has_c1 {
1399 matched_children.push(C1::TABLE_NAME);
1400 }
1401 if has_c2 {
1402 matched_children.push(C2::TABLE_NAME);
1403 }
1404 if has_c3 {
1405 matched_children.push(C3::TABLE_NAME);
1406 }
1407 if matched_children.len() > 1 {
1408 return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1409 "polymorphic_joined3 ambiguous row: multiple child prefixes are non-NULL: {}",
1410 matched_children.join(", ")
1411 )));
1412 }
1413
1414 if has_c1 {
1415 match C1::from_row(&row) {
1416 Ok(c) => out.push(PolymorphicJoined3::C1(c)),
1417 Err(e) => return Outcome::Err(e),
1418 }
1419 } else if has_c2 {
1420 match C2::from_row(&row) {
1421 Ok(c) => out.push(PolymorphicJoined3::C2(c)),
1422 Err(e) => return Outcome::Err(e),
1423 }
1424 } else if has_c3 {
1425 match C3::from_row(&row) {
1426 Ok(c) => out.push(PolymorphicJoined3::C3(c)),
1427 Err(e) => return Outcome::Err(e),
1428 }
1429 } else {
1430 match Base::from_row(&row) {
1431 Ok(b) => out.push(PolymorphicJoined3::Base(b)),
1432 Err(e) => return Outcome::Err(e),
1433 }
1434 }
1435 }
1436 Outcome::Ok(out)
1437 })
1438 }
1439}
1440
1441#[cfg(test)]
1442mod tests {
1443 use super::*;
1444 use crate::JoinType;
1445 use sqlmodel_core::{
1446 Error, FieldInfo, InheritanceInfo, InheritanceStrategy, Result, Row, Value,
1447 };
1448
1449 #[derive(Debug, Clone)]
1450 struct Hero;
1451
1452 impl Model for Hero {
1453 const TABLE_NAME: &'static str = "heroes";
1454 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1455
1456 fn fields() -> &'static [FieldInfo] {
1457 &[]
1458 }
1459
1460 fn to_row(&self) -> Vec<(&'static str, Value)> {
1461 Vec::new()
1462 }
1463
1464 fn from_row(_row: &Row) -> Result<Self> {
1465 Err(Error::Custom("not used in tests".to_string()))
1466 }
1467
1468 fn primary_key_value(&self) -> Vec<Value> {
1469 Vec::new()
1470 }
1471
1472 fn is_new(&self) -> bool {
1473 true
1474 }
1475 }
1476
1477 #[derive(Debug, Clone)]
1478 struct StiManager;
1479
1480 impl Model for StiManager {
1481 const TABLE_NAME: &'static str = "employees";
1483 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1484
1485 fn fields() -> &'static [FieldInfo] {
1486 &[]
1487 }
1488
1489 fn to_row(&self) -> Vec<(&'static str, Value)> {
1490 Vec::new()
1491 }
1492
1493 fn from_row(_row: &Row) -> Result<Self> {
1494 Err(Error::Custom("not used in tests".to_string()))
1495 }
1496
1497 fn primary_key_value(&self) -> Vec<Value> {
1498 Vec::new()
1499 }
1500
1501 fn is_new(&self) -> bool {
1502 true
1503 }
1504
1505 fn inheritance() -> InheritanceInfo {
1506 InheritanceInfo {
1507 strategy: InheritanceStrategy::None,
1508 parent: Some("employees"),
1509 parent_fields_fn: None,
1510 discriminator_column: Some("type_"),
1511 discriminator_value: Some("manager"),
1512 }
1513 }
1514 }
1515
1516 #[test]
1517 fn build_collects_params_across_joins_where_having() {
1518 let query = Select::<Hero>::new()
1519 .join(Join::inner(
1520 "teams",
1521 Expr::qualified("teams", "active").eq(true),
1522 ))
1523 .filter(Expr::col("age").gt(18))
1524 .group_by(&["team_id"])
1525 .having(Expr::col("count").gt(1));
1526
1527 let (sql, params) = query.build();
1528
1529 assert_eq!(
1530 sql,
1531 "SELECT * FROM heroes INNER JOIN teams ON \"teams\".\"active\" = $1 WHERE \"age\" > $2 GROUP BY team_id HAVING \"count\" > $3"
1532 );
1533 assert_eq!(
1534 params,
1535 vec![Value::Bool(true), Value::Int(18), Value::Int(1)]
1536 );
1537 }
1538
1539 #[test]
1540 fn test_select_all_columns() {
1541 let query = Select::<Hero>::new();
1542 let (sql, params) = query.build();
1543
1544 assert_eq!(sql, "SELECT * FROM heroes");
1545 assert!(params.is_empty());
1546 }
1547
1548 #[test]
1549 fn test_sti_child_select_adds_discriminator_filter() {
1550 let query = Select::<StiManager>::new();
1551 let (sql, params) = query.build();
1552 assert_eq!(
1553 sql,
1554 "SELECT * FROM employees WHERE \"employees\".\"type_\" = $1"
1555 );
1556 assert_eq!(params, vec![Value::Text("manager".to_string())]);
1557 }
1558
1559 #[test]
1560 fn test_sti_child_select_ands_discriminator_with_user_filter() {
1561 let query = Select::<StiManager>::new().filter(Expr::col("active").eq(true));
1562 let (sql, params) = query.build();
1563 assert_eq!(
1564 sql,
1565 "SELECT * FROM employees WHERE \"active\" = $1 AND \"employees\".\"type_\" = $2"
1566 );
1567 assert_eq!(
1568 params,
1569 vec![Value::Bool(true), Value::Text("manager".to_string())]
1570 );
1571 }
1572
1573 #[derive(Debug, Clone)]
1574 struct JoinedParent;
1575
1576 impl Model for JoinedParent {
1577 const TABLE_NAME: &'static str = "persons";
1578 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1579
1580 fn fields() -> &'static [FieldInfo] {
1581 static FIELDS: &[FieldInfo] = &[
1582 FieldInfo::new("id", "id", sqlmodel_core::SqlType::BigInt).primary_key(true),
1583 FieldInfo::new("name", "name", sqlmodel_core::SqlType::Text),
1584 ];
1585 FIELDS
1586 }
1587
1588 fn to_row(&self) -> Vec<(&'static str, Value)> {
1589 Vec::new()
1590 }
1591
1592 fn from_row(_row: &Row) -> Result<Self> {
1593 Err(Error::Custom("not used in tests".to_string()))
1594 }
1595
1596 fn primary_key_value(&self) -> Vec<Value> {
1597 Vec::new()
1598 }
1599
1600 fn is_new(&self) -> bool {
1601 true
1602 }
1603 }
1604
1605 #[derive(Debug, Clone)]
1606 struct JoinedChild;
1607
1608 impl Model for JoinedChild {
1609 const TABLE_NAME: &'static str = "employees";
1610 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1611
1612 fn fields() -> &'static [FieldInfo] {
1613 static FIELDS: &[FieldInfo] = &[
1614 FieldInfo::new("id", "id", sqlmodel_core::SqlType::BigInt).primary_key(true),
1615 FieldInfo::new("dept", "department", sqlmodel_core::SqlType::Text),
1616 ];
1617 FIELDS
1618 }
1619
1620 fn to_row(&self) -> Vec<(&'static str, Value)> {
1621 Vec::new()
1622 }
1623
1624 fn from_row(_row: &Row) -> Result<Self> {
1625 Err(Error::Custom("not used in tests".to_string()))
1626 }
1627
1628 fn primary_key_value(&self) -> Vec<Value> {
1629 Vec::new()
1630 }
1631
1632 fn is_new(&self) -> bool {
1633 true
1634 }
1635
1636 fn inheritance() -> InheritanceInfo {
1637 InheritanceInfo {
1638 strategy: InheritanceStrategy::Joined,
1639 parent: Some("persons"),
1640 parent_fields_fn: Some(<JoinedParent as Model>::fields),
1641 discriminator_column: None,
1642 discriminator_value: None,
1643 }
1644 }
1645 }
1646
1647 #[test]
1648 fn test_joined_inheritance_child_select_projects_parent_and_joins() {
1649 let query = Select::<JoinedChild>::new();
1650 let (sql, params) = query.build();
1651
1652 assert!(params.is_empty());
1653 assert!(sql.starts_with("SELECT "));
1654 assert!(sql.contains("employees.id AS employees__id"));
1655 assert!(sql.contains("employees.department AS employees__department"));
1656 assert!(sql.contains("persons.id AS persons__id"));
1657 assert!(sql.contains("persons.name AS persons__name"));
1658 assert!(sql.contains(
1659 "FROM employees INNER JOIN persons ON \"employees\".\"id\" = \"persons\".\"id\""
1660 ));
1661 }
1662
1663 #[test]
1664 fn test_select_specific_columns() {
1665 let query = Select::<Hero>::new().columns(&["id", "name", "power"]);
1666 let (sql, params) = query.build();
1667
1668 assert_eq!(sql, "SELECT id, name, power FROM heroes");
1669 assert!(params.is_empty());
1670 }
1671
1672 #[test]
1673 fn test_select_distinct() {
1674 let query = Select::<Hero>::new().columns(&["team_id"]).distinct();
1675 let (sql, params) = query.build();
1676
1677 assert_eq!(sql, "SELECT DISTINCT team_id FROM heroes");
1678 assert!(params.is_empty());
1679 }
1680
1681 #[test]
1682 fn test_select_with_simple_filter() {
1683 let query = Select::<Hero>::new().filter(Expr::col("active").eq(true));
1684 let (sql, params) = query.build();
1685
1686 assert_eq!(sql, "SELECT * FROM heroes WHERE \"active\" = $1");
1687 assert_eq!(params, vec![Value::Bool(true)]);
1688 }
1689
1690 #[test]
1691 fn test_select_with_multiple_and_filters() {
1692 let query = Select::<Hero>::new()
1693 .filter(Expr::col("active").eq(true))
1694 .filter(Expr::col("age").gt(18));
1695 let (sql, params) = query.build();
1696
1697 assert_eq!(
1698 sql,
1699 "SELECT * FROM heroes WHERE \"active\" = $1 AND \"age\" > $2"
1700 );
1701 assert_eq!(params, vec![Value::Bool(true), Value::Int(18)]);
1702 }
1703
1704 #[test]
1705 fn test_select_with_or_filter() {
1706 let query = Select::<Hero>::new()
1707 .filter(Expr::col("role").eq("warrior"))
1708 .or_filter(Expr::col("role").eq("mage"));
1709 let (sql, params) = query.build();
1710
1711 assert_eq!(
1712 sql,
1713 "SELECT * FROM heroes WHERE \"role\" = $1 OR \"role\" = $2"
1714 );
1715 assert_eq!(
1716 params,
1717 vec![
1718 Value::Text("warrior".to_string()),
1719 Value::Text("mage".to_string())
1720 ]
1721 );
1722 }
1723
1724 #[test]
1725 fn test_select_with_order_by_asc() {
1726 let query = Select::<Hero>::new().order_by(OrderBy::asc(Expr::col("name")));
1727 let (sql, params) = query.build();
1728
1729 assert_eq!(sql, "SELECT * FROM heroes ORDER BY \"name\" ASC");
1730 assert!(params.is_empty());
1731 }
1732
1733 #[test]
1734 fn test_select_with_order_by_desc() {
1735 let query = Select::<Hero>::new().order_by(OrderBy::desc(Expr::col("created_at")));
1736 let (sql, params) = query.build();
1737
1738 assert_eq!(sql, "SELECT * FROM heroes ORDER BY \"created_at\" DESC");
1739 assert!(params.is_empty());
1740 }
1741
1742 #[test]
1743 fn test_select_with_multiple_order_by() {
1744 let query = Select::<Hero>::new()
1745 .order_by(OrderBy::asc(Expr::col("team_id")))
1746 .order_by(OrderBy::asc(Expr::col("name")));
1747 let (sql, params) = query.build();
1748
1749 assert_eq!(
1750 sql,
1751 "SELECT * FROM heroes ORDER BY \"team_id\" ASC, \"name\" ASC"
1752 );
1753 assert!(params.is_empty());
1754 }
1755
1756 #[test]
1757 fn test_select_with_limit() {
1758 let query = Select::<Hero>::new().limit(10);
1759 let (sql, params) = query.build();
1760
1761 assert_eq!(sql, "SELECT * FROM heroes LIMIT 10");
1762 assert!(params.is_empty());
1763 }
1764
1765 #[test]
1766 fn test_select_with_offset() {
1767 let query = Select::<Hero>::new().offset(20);
1768 let (sql, params) = query.build();
1769
1770 assert_eq!(sql, "SELECT * FROM heroes OFFSET 20");
1771 assert!(params.is_empty());
1772 }
1773
1774 #[test]
1775 fn test_select_with_limit_and_offset() {
1776 let query = Select::<Hero>::new().limit(10).offset(20);
1777 let (sql, params) = query.build();
1778
1779 assert_eq!(sql, "SELECT * FROM heroes LIMIT 10 OFFSET 20");
1780 assert!(params.is_empty());
1781 }
1782
1783 #[test]
1784 fn test_select_with_group_by() {
1785 let query = Select::<Hero>::new()
1786 .columns(&["team_id", "COUNT(*) as count"])
1787 .group_by(&["team_id"]);
1788 let (sql, params) = query.build();
1789
1790 assert_eq!(
1791 sql,
1792 "SELECT team_id, COUNT(*) as count FROM heroes GROUP BY team_id"
1793 );
1794 assert!(params.is_empty());
1795 }
1796
1797 #[test]
1798 fn test_select_with_multiple_group_by() {
1799 let query = Select::<Hero>::new()
1800 .columns(&["team_id", "role", "COUNT(*) as count"])
1801 .group_by(&["team_id", "role"]);
1802 let (sql, params) = query.build();
1803
1804 assert_eq!(
1805 sql,
1806 "SELECT team_id, role, COUNT(*) as count FROM heroes GROUP BY team_id, role"
1807 );
1808 assert!(params.is_empty());
1809 }
1810
1811 #[test]
1812 fn test_select_with_for_update() {
1813 let query = Select::<Hero>::new()
1814 .filter(Expr::col("id").eq(1))
1815 .for_update();
1816 let (sql, params) = query.build();
1817
1818 assert_eq!(sql, "SELECT * FROM heroes WHERE \"id\" = $1 FOR UPDATE");
1819 assert_eq!(params, vec![Value::Int(1)]);
1820 }
1821
1822 #[test]
1823 fn test_select_inner_join() {
1824 let query = Select::<Hero>::new().join(Join::inner(
1825 "teams",
1826 Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
1827 ));
1828 let (sql, _) = query.build();
1829
1830 assert!(sql.contains("INNER JOIN teams ON"));
1831 }
1832
1833 #[test]
1834 fn test_select_left_join() {
1835 let query = Select::<Hero>::new().join(Join::left(
1836 "teams",
1837 Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
1838 ));
1839 let (sql, _) = query.build();
1840
1841 assert!(sql.contains("LEFT JOIN teams ON"));
1842 }
1843
1844 #[test]
1845 fn test_select_right_join() {
1846 let query = Select::<Hero>::new().join(Join::right(
1847 "teams",
1848 Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
1849 ));
1850 let (sql, _) = query.build();
1851
1852 assert!(sql.contains("RIGHT JOIN teams ON"));
1853 }
1854
1855 #[test]
1856 fn test_select_multiple_joins() {
1857 let query = Select::<Hero>::new()
1858 .join(Join::inner(
1859 "teams",
1860 Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
1861 ))
1862 .join(Join::left(
1863 "powers",
1864 Expr::qualified("heroes", "id").eq(Expr::qualified("powers", "hero_id")),
1865 ));
1866 let (sql, _) = query.build();
1867
1868 assert!(sql.contains("INNER JOIN teams ON"));
1869 assert!(sql.contains("LEFT JOIN powers ON"));
1870 }
1871
1872 #[test]
1873 fn test_select_complex_query() {
1874 let query = Select::<Hero>::new()
1875 .columns(&["heroes.id", "heroes.name", "teams.name as team_name"])
1876 .distinct()
1877 .join(Join::inner(
1878 "teams",
1879 Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
1880 ))
1881 .filter(Expr::col("active").eq(true))
1882 .filter(Expr::col("level").gt(10))
1883 .group_by(&["heroes.id", "heroes.name", "teams.name"])
1884 .having(Expr::col("score").gt(100))
1885 .order_by(OrderBy::desc(Expr::col("level")))
1886 .limit(50)
1887 .offset(0);
1888 let (sql, params) = query.build();
1889
1890 assert!(sql.starts_with(
1891 "SELECT DISTINCT heroes.id, heroes.name, teams.name as team_name FROM heroes"
1892 ));
1893 assert!(sql.contains("INNER JOIN teams ON"));
1894 assert!(sql.contains("WHERE"));
1895 assert!(sql.contains("GROUP BY"));
1896 assert!(sql.contains("HAVING"));
1897 assert!(sql.contains("ORDER BY"));
1898 assert!(sql.contains("LIMIT 50"));
1899 assert!(sql.contains("OFFSET 0"));
1900
1901 assert_eq!(params.len(), 3);
1904 }
1905
1906 #[test]
1907 fn test_select_default() {
1908 let query = Select::<Hero>::default();
1909 let (sql, _) = query.build();
1910 assert_eq!(sql, "SELECT * FROM heroes");
1911 }
1912
1913 #[test]
1914 fn test_select_clone() {
1915 let query = Select::<Hero>::new()
1916 .filter(Expr::col("id").eq(1))
1917 .limit(10);
1918 let cloned = query.clone();
1919
1920 let (sql1, params1) = query.build();
1921 let (sql2, params2) = cloned.build();
1922
1923 assert_eq!(sql1, sql2);
1924 assert_eq!(params1, params2);
1925 }
1926
1927 use sqlmodel_core::RelationshipInfo;
1932
1933 #[derive(Debug, Clone)]
1935 struct EagerTeam;
1936
1937 impl Model for EagerTeam {
1938 const TABLE_NAME: &'static str = "teams";
1939 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1940
1941 fn fields() -> &'static [FieldInfo] {
1942 static FIELDS: &[FieldInfo] = &[
1943 FieldInfo::new("id", "id", sqlmodel_core::SqlType::BigInt),
1944 FieldInfo::new("name", "name", sqlmodel_core::SqlType::Text),
1945 ];
1946 FIELDS
1947 }
1948
1949 fn to_row(&self) -> Vec<(&'static str, Value)> {
1950 Vec::new()
1951 }
1952
1953 fn from_row(_row: &Row) -> Result<Self> {
1954 Err(Error::Custom("not used in tests".to_string()))
1955 }
1956
1957 fn primary_key_value(&self) -> Vec<Value> {
1958 Vec::new()
1959 }
1960
1961 fn is_new(&self) -> bool {
1962 true
1963 }
1964 }
1965
1966 #[derive(Debug, Clone)]
1968 struct EagerHero;
1969
1970 impl Model for EagerHero {
1971 const TABLE_NAME: &'static str = "heroes";
1972 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1973 const RELATIONSHIPS: &'static [RelationshipInfo] =
1974 &[
1975 RelationshipInfo::new("team", "teams", RelationshipKind::ManyToOne)
1976 .related_fields(EagerTeam::fields)
1977 .local_key("team_id"),
1978 ];
1979
1980 fn fields() -> &'static [FieldInfo] {
1981 static FIELDS: &[FieldInfo] = &[
1982 FieldInfo::new("id", "id", sqlmodel_core::SqlType::BigInt),
1983 FieldInfo::new("name", "name", sqlmodel_core::SqlType::Text),
1984 FieldInfo::new("team_id", "team_id", sqlmodel_core::SqlType::BigInt),
1985 ];
1986 FIELDS
1987 }
1988
1989 fn to_row(&self) -> Vec<(&'static str, Value)> {
1990 Vec::new()
1991 }
1992
1993 fn from_row(_row: &Row) -> Result<Self> {
1994 Err(Error::Custom("not used in tests".to_string()))
1995 }
1996
1997 fn primary_key_value(&self) -> Vec<Value> {
1998 Vec::new()
1999 }
2000
2001 fn is_new(&self) -> bool {
2002 true
2003 }
2004 }
2005
2006 #[test]
2007 fn test_select_with_eager_loader() {
2008 let loader = EagerLoader::<EagerHero>::new().include("team");
2009 let query = Select::<EagerHero>::new().eager(loader);
2010
2011 assert!(query.eager_loader.is_some());
2013 assert!(query.eager_loader.as_ref().unwrap().has_includes());
2014 }
2015
2016 #[test]
2017 fn test_select_eager_generates_join() {
2018 let loader = EagerLoader::<EagerHero>::new().include("team");
2019 let query = Select::<EagerHero>::new().eager(loader);
2020
2021 let (sql, params, join_info) = query.build_eager_with_dialect(Dialect::default());
2022
2023 assert!(sql.contains("LEFT JOIN teams"));
2025 assert!(sql.contains("heroes.team_id = teams.id"));
2026
2027 assert!(sql.contains("heroes.id AS heroes__id"));
2029 assert!(sql.contains("heroes.name AS heroes__name"));
2030 assert!(sql.contains("heroes.team_id AS heroes__team_id"));
2031
2032 assert!(sql.contains("teams.id AS teams__id"));
2034 assert!(sql.contains("teams.name AS teams__name"));
2035
2036 assert_eq!(join_info.len(), 1);
2038 assert!(params.is_empty());
2039 }
2040
2041 #[test]
2042 fn test_select_eager_with_filter() {
2043 let loader = EagerLoader::<EagerHero>::new().include("team");
2044 let query = Select::<EagerHero>::new()
2045 .eager(loader)
2046 .filter(Expr::col("active").eq(true));
2047
2048 let (sql, params, _) = query.build_eager_with_dialect(Dialect::default());
2049
2050 assert!(sql.contains("LEFT JOIN teams"));
2051 assert!(sql.contains("WHERE"));
2052 assert!(sql.contains("\"active\" = $1"));
2053 assert_eq!(params, vec![Value::Bool(true)]);
2054 }
2055
2056 #[test]
2057 fn test_select_eager_with_order_and_limit() {
2058 let loader = EagerLoader::<EagerHero>::new().include("team");
2059 let query = Select::<EagerHero>::new()
2060 .eager(loader)
2061 .order_by(OrderBy::asc(Expr::col("name")))
2062 .limit(10)
2063 .offset(5);
2064
2065 let (sql, _, _) = query.build_eager_with_dialect(Dialect::default());
2066
2067 assert!(sql.contains("LEFT JOIN teams"));
2068 assert!(sql.contains("ORDER BY"));
2069 assert!(sql.contains("LIMIT 10"));
2070 assert!(sql.contains("OFFSET 5"));
2071 }
2072
2073 #[test]
2074 fn test_select_eager_no_includes_fallback() {
2075 let loader = EagerLoader::<EagerHero>::new();
2077 let query = Select::<EagerHero>::new().eager(loader);
2078
2079 assert!(query.eager_loader.is_some());
2082 assert!(!query.eager_loader.as_ref().unwrap().has_includes());
2083 }
2084
2085 #[test]
2086 fn test_select_eager_distinct() {
2087 let loader = EagerLoader::<EagerHero>::new().include("team");
2088 let query = Select::<EagerHero>::new().eager(loader).distinct();
2089
2090 let (sql, _, _) = query.build_eager_with_dialect(Dialect::default());
2091
2092 assert!(sql.starts_with("SELECT DISTINCT"));
2093 }
2094
2095 #[test]
2098 fn test_select_into_exists() {
2099 let exists_expr = Select::<Hero>::new()
2101 .filter(Expr::raw("orders.customer_id = customers.id"))
2102 .into_exists();
2103
2104 let mut params = Vec::new();
2105 let sql = exists_expr.build(&mut params, 0);
2106
2107 assert_eq!(
2109 sql,
2110 "EXISTS (SELECT 1 FROM heroes WHERE orders.customer_id = customers.id)"
2111 );
2112 }
2113
2114 #[test]
2115 fn test_select_into_not_exists() {
2116 let not_exists_expr = Select::<Hero>::new()
2118 .filter(Expr::raw("orders.customer_id = customers.id"))
2119 .into_not_exists();
2120
2121 let mut params = Vec::new();
2122 let sql = not_exists_expr.build(&mut params, 0);
2123
2124 assert_eq!(
2125 sql,
2126 "NOT EXISTS (SELECT 1 FROM heroes WHERE orders.customer_id = customers.id)"
2127 );
2128 }
2129
2130 #[test]
2131 fn test_select_into_exists_with_params() {
2132 let exists_expr = Select::<Hero>::new()
2134 .filter(Expr::col("status").eq("active"))
2135 .into_exists();
2136
2137 let mut params = Vec::new();
2138 let sql = exists_expr.build(&mut params, 0);
2139
2140 assert_eq!(sql, "EXISTS (SELECT 1 FROM heroes WHERE \"status\" = $1)");
2141 assert_eq!(params.len(), 1);
2142 assert_eq!(params[0], Value::Text("active".to_string()));
2143 }
2144
2145 #[test]
2146 fn test_select_into_exists_propagates_dialect_mysql() {
2147 let exists_expr = Select::<Hero>::new()
2148 .filter(Expr::col("status").eq("active"))
2149 .into_exists();
2150
2151 let mut params = Vec::new();
2152 let sql = exists_expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
2153
2154 assert_eq!(sql, "EXISTS (SELECT 1 FROM heroes WHERE `status` = ?)");
2155 assert_eq!(params, vec![Value::Text("active".to_string())]);
2156 }
2157
2158 #[test]
2159 fn test_select_into_exists_with_join() {
2160 let exists_expr = Select::<Hero>::new()
2162 .join(Join::inner(
2163 "teams",
2164 Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
2165 ))
2166 .filter(Expr::col("active").eq(true))
2167 .into_exists();
2168
2169 let mut params = Vec::new();
2170 let sql = exists_expr.build(&mut params, 0);
2171
2172 assert!(sql.starts_with("EXISTS (SELECT 1 FROM heroes"));
2173 assert!(sql.contains("INNER JOIN teams ON"));
2174 assert!(sql.contains("WHERE"));
2175 }
2176
2177 #[test]
2178 fn test_select_into_exists_omits_order_by_limit() {
2179 let exists_expr = Select::<Hero>::new()
2182 .filter(Expr::col("active").eq(true))
2183 .order_by(OrderBy::asc(Expr::col("name")))
2184 .limit(10)
2185 .offset(5)
2186 .into_exists();
2187
2188 let mut params = Vec::new();
2189 let sql = exists_expr.build(&mut params, 0);
2190
2191 assert!(!sql.contains("ORDER BY"));
2193 assert!(!sql.contains("LIMIT"));
2194 assert!(!sql.contains("OFFSET"));
2195 assert_eq!(sql, "EXISTS (SELECT 1 FROM heroes WHERE \"active\" = $1)");
2196 }
2197
2198 #[test]
2199 fn test_exists_in_outer_query() {
2200 let has_heroes = Select::<Hero>::new()
2202 .filter(Expr::raw("heroes.team_id = teams.id"))
2203 .into_exists();
2204
2205 let query = Select::<EagerTeam>::new().filter(Expr::col("active").eq(true).and(has_heroes));
2206 let (sql, params) = query.build_with_dialect(Dialect::default());
2207
2208 assert_eq!(
2209 sql,
2210 "SELECT * FROM teams WHERE \"active\" = $1 AND EXISTS (SELECT 1 FROM heroes WHERE heroes.team_id = teams.id)"
2211 );
2212 assert_eq!(params, vec![Value::Bool(true)]);
2213 }
2214
2215 #[test]
2216 fn test_lateral_join_propagates_dialect_sqlite() {
2217 let lateral = Select::<Hero>::new()
2218 .filter(Expr::col("status").eq("active"))
2219 .into_lateral_join("recent", JoinType::Left, Expr::raw("TRUE"));
2220
2221 let query = Select::<Hero>::new()
2222 .filter(Expr::col("active").eq(true))
2223 .join(lateral);
2224
2225 let (sql, params) = query.build_with_dialect(Dialect::Sqlite);
2226
2227 assert!(sql.contains(
2228 "LEFT JOIN LATERAL (SELECT * FROM heroes WHERE \"status\" = ?1) AS recent ON TRUE"
2229 ));
2230 assert!(sql.contains("WHERE \"active\" = ?2"));
2231 assert_eq!(params.len(), 2);
2232 assert_eq!(params[0], Value::Text("active".to_string()));
2233 assert_eq!(params[1], Value::Bool(true));
2234 }
2235}