1use std::marker::PhantomData;
4
5use crate::error::QueryResult;
6use crate::filter::Filter;
7use crate::pagination::Pagination;
8use crate::traits::{MaterializedView, QueryEngine, View};
9use crate::types::{OrderBy, Select};
10
11#[allow(dead_code)]
28pub struct ViewFindManyOperation<E: QueryEngine, V: View> {
29 engine: E,
30 filter: Filter,
31 order_by: OrderBy,
32 pagination: Pagination,
33 select: Select,
34 distinct: Option<Vec<String>>,
35 _view: PhantomData<V>,
36}
37
38impl<E: QueryEngine, V: View> ViewFindManyOperation<E, V> {
39 pub fn new(engine: E) -> Self {
41 Self {
42 engine,
43 filter: Filter::None,
44 order_by: OrderBy::none(),
45 pagination: Pagination::new(),
46 select: Select::All,
47 distinct: None,
48 _view: PhantomData,
49 }
50 }
51
52 pub fn r#where(mut self, filter: impl Into<Filter>) -> Self {
54 let new_filter = filter.into();
55 self.filter = self.filter.and_then(new_filter);
56 self
57 }
58
59 pub fn order_by(mut self, order: impl Into<OrderBy>) -> Self {
61 self.order_by = order.into();
62 self
63 }
64
65 pub fn skip(mut self, n: u64) -> Self {
67 self.pagination = self.pagination.skip(n);
68 self
69 }
70
71 pub fn take(mut self, n: u64) -> Self {
73 self.pagination = self.pagination.take(n);
74 self
75 }
76
77 pub fn select(mut self, select: impl Into<Select>) -> Self {
79 self.select = select.into();
80 self
81 }
82
83 pub fn distinct(mut self, columns: impl IntoIterator<Item = impl Into<String>>) -> Self {
85 self.distinct = Some(columns.into_iter().map(Into::into).collect());
86 self
87 }
88
89 pub fn cursor(mut self, cursor: crate::pagination::Cursor) -> Self {
91 self.pagination = self.pagination.cursor(cursor);
92 self
93 }
94
95 pub fn build_sql(&self) -> (String, Vec<crate::filter::FilterValue>) {
97 let (where_sql, params) = self.filter.to_sql(0);
98
99 let mut sql = String::new();
100
101 sql.push_str("SELECT ");
103 if let Some(ref cols) = self.distinct {
104 sql.push_str("DISTINCT ON (");
105 sql.push_str(&cols.join(", "));
106 sql.push_str(") ");
107 }
108 sql.push_str(&self.select.to_sql());
109
110 sql.push_str(" FROM ");
112 sql.push_str(V::DB_VIEW_NAME);
113
114 if !self.filter.is_none() {
116 sql.push_str(" WHERE ");
117 sql.push_str(&where_sql);
118 }
119
120 if !self.order_by.is_empty() {
122 sql.push_str(" ORDER BY ");
123 sql.push_str(&self.order_by.to_sql());
124 }
125
126 let pagination_sql = self.pagination.to_sql();
128 if !pagination_sql.is_empty() {
129 sql.push(' ');
130 sql.push_str(&pagination_sql);
131 }
132
133 (sql, params)
134 }
135
136 pub async fn exec(self) -> QueryResult<Vec<V>>
141 where
142 V: Send + 'static + serde::de::DeserializeOwned,
143 {
144 let (sql, _params) = self.build_sql();
145 let _ = sql;
148 Ok(Vec::new()) }
150}
151
152pub struct ViewFindFirstOperation<E: QueryEngine, V: View> {
154 inner: ViewFindManyOperation<E, V>,
155}
156
157impl<E: QueryEngine, V: View> ViewFindFirstOperation<E, V> {
158 pub fn new(engine: E) -> Self {
160 Self {
161 inner: ViewFindManyOperation::new(engine).take(1),
162 }
163 }
164
165 pub fn r#where(mut self, filter: impl Into<Filter>) -> Self {
167 self.inner = self.inner.r#where(filter);
168 self
169 }
170
171 pub fn order_by(mut self, order: impl Into<OrderBy>) -> Self {
173 self.inner = self.inner.order_by(order);
174 self
175 }
176
177 pub fn build_sql(&self) -> (String, Vec<crate::filter::FilterValue>) {
179 self.inner.build_sql()
180 }
181
182 pub async fn exec(self) -> QueryResult<Option<V>>
184 where
185 V: Send + 'static + serde::de::DeserializeOwned,
186 {
187 let results = self.inner.exec().await?;
188 Ok(results.into_iter().next())
189 }
190}
191
192pub struct ViewCountOperation<E: QueryEngine, V: View> {
194 engine: E,
195 filter: Filter,
196 _view: PhantomData<V>,
197}
198
199impl<E: QueryEngine, V: View> ViewCountOperation<E, V> {
200 pub fn new(engine: E) -> Self {
202 Self {
203 engine,
204 filter: Filter::None,
205 _view: PhantomData,
206 }
207 }
208
209 pub fn r#where(mut self, filter: impl Into<Filter>) -> Self {
211 self.filter = self.filter.and_then(filter.into());
212 self
213 }
214
215 pub fn build_sql(&self) -> (String, Vec<crate::filter::FilterValue>) {
217 let (where_sql, params) = self.filter.to_sql(0);
218
219 let mut sql = format!("SELECT COUNT(*) FROM {}", V::DB_VIEW_NAME);
220
221 if !self.filter.is_none() {
222 sql.push_str(" WHERE ");
223 sql.push_str(&where_sql);
224 }
225
226 (sql, params)
227 }
228
229 pub async fn exec(self) -> QueryResult<u64> {
231 let (sql, params) = self.build_sql();
232 self.engine.count(&sql, params).await
233 }
234}
235
236pub struct RefreshMaterializedViewOperation<E: QueryEngine, V: MaterializedView> {
260 engine: E,
261 concurrently: bool,
262 _view: PhantomData<V>,
263}
264
265impl<E: QueryEngine, V: MaterializedView> RefreshMaterializedViewOperation<E, V> {
266 pub fn new(engine: E) -> Self {
268 Self {
269 engine,
270 concurrently: false,
271 _view: PhantomData,
272 }
273 }
274
275 pub fn concurrently(mut self) -> Self {
280 self.concurrently = V::SUPPORTS_CONCURRENT_REFRESH;
281 self
282 }
283
284 pub async fn exec(self) -> QueryResult<()> {
286 self.engine
287 .refresh_materialized_view(V::DB_VIEW_NAME, self.concurrently)
288 .await
289 }
290}
291
292pub struct ViewQueryBuilder<E: QueryEngine, V: View> {
297 engine: E,
298 _view: PhantomData<V>,
299}
300
301impl<E: QueryEngine, V: View> ViewQueryBuilder<E, V> {
302 pub fn new(engine: E) -> Self {
304 Self {
305 engine,
306 _view: PhantomData,
307 }
308 }
309
310 pub fn find_many(&self) -> ViewFindManyOperation<E, V> {
312 ViewFindManyOperation::new(self.engine.clone())
313 }
314
315 pub fn find_first(&self) -> ViewFindFirstOperation<E, V> {
317 ViewFindFirstOperation::new(self.engine.clone())
318 }
319
320 pub fn count(&self) -> ViewCountOperation<E, V> {
322 ViewCountOperation::new(self.engine.clone())
323 }
324}
325
326impl<E: QueryEngine, V: MaterializedView> ViewQueryBuilder<E, V> {
327 pub fn refresh(&self) -> RefreshMaterializedViewOperation<E, V> {
331 RefreshMaterializedViewOperation::new(self.engine.clone())
332 }
333}
334
335impl<E: QueryEngine, V: View> Clone for ViewQueryBuilder<E, V> {
336 fn clone(&self) -> Self {
337 Self {
338 engine: self.engine.clone(),
339 _view: PhantomData,
340 }
341 }
342}
343
344pub trait ViewAccessor<E: QueryEngine>: Send + Sync {
348 type View: View;
350
351 fn engine(&self) -> &E;
353
354 fn find_many(&self) -> ViewFindManyOperation<E, Self::View>;
356
357 fn find_first(&self) -> ViewFindFirstOperation<E, Self::View>;
359
360 fn count(&self) -> ViewCountOperation<E, Self::View>;
362}
363
364pub trait MaterializedViewAccessor<E: QueryEngine>: ViewAccessor<E>
366where
367 Self::View: MaterializedView,
368{
369 fn refresh(&self) -> RefreshMaterializedViewOperation<E, Self::View>;
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376 use crate::error::QueryError;
377 use crate::filter::FilterValue;
378 use crate::traits::BoxFuture;
379
380 struct TestView;
381
382 impl View for TestView {
383 const VIEW_NAME: &'static str = "TestView";
384 const DB_VIEW_NAME: &'static str = "test_view";
385 const COLUMNS: &'static [&'static str] = &["id", "user_id", "post_count"];
386 const IS_MATERIALIZED: bool = false;
387 }
388
389 struct TestMaterializedView;
390
391 impl View for TestMaterializedView {
392 const VIEW_NAME: &'static str = "TestMaterializedView";
393 const DB_VIEW_NAME: &'static str = "test_materialized_view";
394 const COLUMNS: &'static [&'static str] = &["id", "stats"];
395 const IS_MATERIALIZED: bool = true;
396 }
397
398 impl MaterializedView for TestMaterializedView {
399 const SUPPORTS_CONCURRENT_REFRESH: bool = true;
400 }
401
402 #[derive(Clone)]
403 struct MockEngine;
404
405 impl QueryEngine for MockEngine {
406 fn query_many<T: crate::traits::Model + Send + 'static>(
407 &self,
408 _sql: &str,
409 _params: Vec<FilterValue>,
410 ) -> BoxFuture<'_, QueryResult<Vec<T>>> {
411 Box::pin(async { Ok(Vec::new()) })
412 }
413
414 fn query_one<T: crate::traits::Model + Send + 'static>(
415 &self,
416 _sql: &str,
417 _params: Vec<FilterValue>,
418 ) -> BoxFuture<'_, QueryResult<T>> {
419 Box::pin(async { Err(QueryError::not_found("test")) })
420 }
421
422 fn query_optional<T: crate::traits::Model + Send + 'static>(
423 &self,
424 _sql: &str,
425 _params: Vec<FilterValue>,
426 ) -> BoxFuture<'_, QueryResult<Option<T>>> {
427 Box::pin(async { Ok(None) })
428 }
429
430 fn execute_insert<T: crate::traits::Model + Send + 'static>(
431 &self,
432 _sql: &str,
433 _params: Vec<FilterValue>,
434 ) -> BoxFuture<'_, QueryResult<T>> {
435 Box::pin(async { Err(QueryError::not_found("test")) })
436 }
437
438 fn execute_update<T: crate::traits::Model + Send + 'static>(
439 &self,
440 _sql: &str,
441 _params: Vec<FilterValue>,
442 ) -> BoxFuture<'_, QueryResult<Vec<T>>> {
443 Box::pin(async { Ok(Vec::new()) })
444 }
445
446 fn execute_delete(
447 &self,
448 _sql: &str,
449 _params: Vec<FilterValue>,
450 ) -> BoxFuture<'_, QueryResult<u64>> {
451 Box::pin(async { Ok(0) })
452 }
453
454 fn execute_raw(
455 &self,
456 _sql: &str,
457 _params: Vec<FilterValue>,
458 ) -> BoxFuture<'_, QueryResult<u64>> {
459 Box::pin(async { Ok(0) })
460 }
461
462 fn count(&self, _sql: &str, _params: Vec<FilterValue>) -> BoxFuture<'_, QueryResult<u64>> {
463 Box::pin(async { Ok(42) })
464 }
465
466 fn refresh_materialized_view(
467 &self,
468 view_name: &str,
469 concurrently: bool,
470 ) -> BoxFuture<'_, QueryResult<()>> {
471 let view_name = view_name.to_string();
472 Box::pin(async move {
473 let _ = (view_name, concurrently);
474 Ok(())
475 })
476 }
477 }
478
479 #[test]
482 fn test_view_find_many_basic() {
483 let op = ViewFindManyOperation::<MockEngine, TestView>::new(MockEngine);
484 let (sql, params) = op.build_sql();
485
486 assert_eq!(sql, "SELECT * FROM test_view");
487 assert!(params.is_empty());
488 }
489
490 #[test]
491 fn test_view_find_many_with_filter() {
492 let op = ViewFindManyOperation::<MockEngine, TestView>::new(MockEngine)
493 .r#where(Filter::Gte("post_count".into(), FilterValue::Int(10)));
494
495 let (sql, params) = op.build_sql();
496
497 assert!(sql.contains("WHERE"));
498 assert!(sql.contains("post_count"));
499 assert_eq!(params.len(), 1);
500 }
501
502 #[test]
503 fn test_view_find_many_with_pagination() {
504 let op = ViewFindManyOperation::<MockEngine, TestView>::new(MockEngine)
505 .skip(10)
506 .take(20);
507
508 let (sql, _) = op.build_sql();
509
510 assert!(sql.contains("LIMIT 20"));
511 assert!(sql.contains("OFFSET 10"));
512 }
513
514 #[test]
515 fn test_view_find_many_with_order() {
516 use crate::types::OrderByField;
517
518 let op = ViewFindManyOperation::<MockEngine, TestView>::new(MockEngine)
519 .order_by(OrderByField::desc("post_count"));
520
521 let (sql, _) = op.build_sql();
522
523 assert!(sql.contains("ORDER BY post_count DESC"));
524 }
525
526 #[test]
527 fn test_view_find_many_with_distinct() {
528 let op =
529 ViewFindManyOperation::<MockEngine, TestView>::new(MockEngine).distinct(["user_id"]);
530
531 let (sql, _) = op.build_sql();
532
533 assert!(sql.contains("DISTINCT ON (user_id)"));
534 }
535
536 #[test]
539 fn test_view_find_first_has_limit_1() {
540 let op = ViewFindFirstOperation::<MockEngine, TestView>::new(MockEngine);
541 let (sql, _) = op.build_sql();
542
543 assert!(sql.contains("LIMIT 1"));
544 }
545
546 #[test]
547 fn test_view_find_first_with_filter() {
548 let op = ViewFindFirstOperation::<MockEngine, TestView>::new(MockEngine)
549 .r#where(Filter::Equals("user_id".into(), FilterValue::Int(1)));
550
551 let (sql, params) = op.build_sql();
552
553 assert!(sql.contains("WHERE"));
554 assert!(sql.contains("user_id"));
555 assert!(sql.contains("LIMIT 1"));
556 assert_eq!(params.len(), 1);
557 }
558
559 #[test]
562 fn test_view_count_basic() {
563 let op = ViewCountOperation::<MockEngine, TestView>::new(MockEngine);
564 let (sql, params) = op.build_sql();
565
566 assert_eq!(sql, "SELECT COUNT(*) FROM test_view");
567 assert!(params.is_empty());
568 }
569
570 #[test]
571 fn test_view_count_with_filter() {
572 let op = ViewCountOperation::<MockEngine, TestView>::new(MockEngine)
573 .r#where(Filter::Gte("post_count".into(), FilterValue::Int(5)));
574
575 let (sql, params) = op.build_sql();
576
577 assert!(sql.contains("WHERE"));
578 assert!(sql.contains("post_count"));
579 assert_eq!(params.len(), 1);
580 }
581
582 #[tokio::test]
583 async fn test_view_count_exec() {
584 let op = ViewCountOperation::<MockEngine, TestView>::new(MockEngine);
585 let result = op.exec().await;
586
587 assert!(result.is_ok());
588 assert_eq!(result.unwrap(), 42); }
590
591 #[test]
594 fn test_refresh_materialized_view_default() {
595 let op =
596 RefreshMaterializedViewOperation::<MockEngine, TestMaterializedView>::new(MockEngine);
597
598 assert!(!op.concurrently);
599 }
600
601 #[test]
602 fn test_refresh_materialized_view_concurrently() {
603 let op =
604 RefreshMaterializedViewOperation::<MockEngine, TestMaterializedView>::new(MockEngine)
605 .concurrently();
606
607 assert!(op.concurrently);
608 }
609
610 #[tokio::test]
611 async fn test_refresh_materialized_view_exec() {
612 let op =
613 RefreshMaterializedViewOperation::<MockEngine, TestMaterializedView>::new(MockEngine);
614 let result = op.exec().await;
615
616 assert!(result.is_ok());
617 }
618
619 #[test]
622 fn test_view_query_builder_find_many() {
623 let builder = ViewQueryBuilder::<MockEngine, TestView>::new(MockEngine);
624 let op = builder.find_many();
625 let (sql, _) = op.build_sql();
626
627 assert!(sql.contains("SELECT * FROM test_view"));
628 }
629
630 #[test]
631 fn test_view_query_builder_find_first() {
632 let builder = ViewQueryBuilder::<MockEngine, TestView>::new(MockEngine);
633 let op = builder.find_first();
634 let (sql, _) = op.build_sql();
635
636 assert!(sql.contains("LIMIT 1"));
637 }
638
639 #[test]
640 fn test_view_query_builder_count() {
641 let builder = ViewQueryBuilder::<MockEngine, TestView>::new(MockEngine);
642 let op = builder.count();
643 let (sql, _) = op.build_sql();
644
645 assert!(sql.contains("COUNT(*)"));
646 }
647
648 #[test]
649 fn test_materialized_view_query_builder_refresh() {
650 let builder = ViewQueryBuilder::<MockEngine, TestMaterializedView>::new(MockEngine);
651 let _op = builder.refresh();
652 }
654
655 #[test]
656 fn test_view_query_builder_clone() {
657 let builder = ViewQueryBuilder::<MockEngine, TestView>::new(MockEngine);
658 let _cloned = builder.clone();
659 }
660
661 #[test]
664 fn test_view_trait_constants() {
665 assert_eq!(TestView::VIEW_NAME, "TestView");
666 assert_eq!(TestView::DB_VIEW_NAME, "test_view");
667 assert_eq!(TestView::COLUMNS, &["id", "user_id", "post_count"]);
668 assert!(!TestView::IS_MATERIALIZED);
669 }
670
671 #[test]
672 fn test_materialized_view_trait_constants() {
673 assert_eq!(TestMaterializedView::VIEW_NAME, "TestMaterializedView");
674 assert!(TestMaterializedView::IS_MATERIALIZED);
675 assert!(TestMaterializedView::SUPPORTS_CONCURRENT_REFRESH);
676 }
677}