Skip to main content

prax_query/
query.rs

1//! Query builder entry point.
2
3use crate::filter::{Filter, FilterValue};
4use crate::operations::*;
5use crate::traits::{Model, QueryEngine};
6
7/// The main query builder that provides access to all query operations.
8///
9/// This is typically not used directly - instead, use the generated
10/// model accessors (e.g., `client.user()`).
11pub struct QueryBuilder<E: QueryEngine, M: Model> {
12    engine: E,
13    _model: std::marker::PhantomData<M>,
14}
15
16impl<E: QueryEngine, M: Model + crate::row::FromRow> QueryBuilder<E, M> {
17    /// Create a new query builder.
18    pub fn new(engine: E) -> Self {
19        Self {
20            engine,
21            _model: std::marker::PhantomData,
22        }
23    }
24
25    /// Start a find_many query.
26    pub fn find_many(&self) -> FindManyOperation<E, M> {
27        FindManyOperation::new(self.engine.clone())
28    }
29
30    /// Start a find_unique query.
31    pub fn find_unique(&self) -> FindUniqueOperation<E, M> {
32        FindUniqueOperation::new(self.engine.clone())
33    }
34
35    /// Start a find_first query.
36    pub fn find_first(&self) -> FindFirstOperation<E, M> {
37        FindFirstOperation::new(self.engine.clone())
38    }
39
40    /// Start a create operation.
41    pub fn create(&self) -> CreateOperation<E, M> {
42        CreateOperation::new(self.engine.clone())
43    }
44
45    /// Start an update operation.
46    pub fn update(&self) -> UpdateOperation<E, M> {
47        UpdateOperation::new(self.engine.clone())
48    }
49
50    /// Start a delete operation.
51    pub fn delete(&self) -> DeleteOperation<E, M> {
52        DeleteOperation::new(self.engine.clone())
53    }
54
55    /// Start an upsert operation.
56    pub fn upsert(&self) -> UpsertOperation<E, M> {
57        UpsertOperation::new(self.engine.clone())
58    }
59
60    /// Start a count operation.
61    pub fn count(&self) -> CountOperation<E, M> {
62        CountOperation::new(self.engine.clone())
63    }
64
65    /// Execute a raw SQL query.
66    pub async fn raw(&self, sql: &str, params: Vec<FilterValue>) -> crate::error::QueryResult<u64> {
67        self.engine.execute_raw(sql, params).await
68    }
69
70    /// Find a record by ID.
71    ///
72    /// This is a convenience method for `find_unique().r#where(id::equals(id))`.
73    pub fn find_by_id(&self, id: impl Into<FilterValue>) -> FindUniqueOperation<E, M> {
74        let pk = *M::PRIMARY_KEY.first().unwrap_or(&"id");
75        self.find_unique()
76            .r#where(Filter::Equals(pk.into(), id.into()))
77    }
78}
79
80impl<E: QueryEngine, M: Model> Clone for QueryBuilder<E, M> {
81    fn clone(&self) -> Self {
82        Self {
83            engine: self.engine.clone(),
84            _model: std::marker::PhantomData,
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::error::QueryError;
93
94    struct TestModel;
95
96    impl Model for TestModel {
97        const MODEL_NAME: &'static str = "TestModel";
98        const TABLE_NAME: &'static str = "test_models";
99        const PRIMARY_KEY: &'static [&'static str] = &["id"];
100        const COLUMNS: &'static [&'static str] = &["id", "name", "email"];
101    }
102
103    impl crate::row::FromRow for TestModel {
104        fn from_row(_row: &impl crate::row::RowRef) -> Result<Self, crate::row::RowError> {
105            Ok(TestModel)
106        }
107    }
108
109    #[derive(Clone)]
110    struct MockEngine;
111
112    impl QueryEngine for MockEngine {
113        fn dialect(&self) -> &dyn crate::dialect::SqlDialect {
114            &crate::dialect::Postgres
115        }
116
117        fn query_many<T: Model + crate::row::FromRow + Send + 'static>(
118            &self,
119            _sql: &str,
120            _params: Vec<FilterValue>,
121        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<Vec<T>>> {
122            Box::pin(async { Ok(Vec::new()) })
123        }
124
125        fn query_one<T: Model + crate::row::FromRow + Send + 'static>(
126            &self,
127            _sql: &str,
128            _params: Vec<FilterValue>,
129        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<T>> {
130            Box::pin(async { Err(QueryError::not_found("test")) })
131        }
132
133        fn query_optional<T: Model + crate::row::FromRow + Send + 'static>(
134            &self,
135            _sql: &str,
136            _params: Vec<FilterValue>,
137        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<Option<T>>> {
138            Box::pin(async { Ok(None) })
139        }
140
141        fn execute_insert<T: Model + crate::row::FromRow + Send + 'static>(
142            &self,
143            _sql: &str,
144            _params: Vec<FilterValue>,
145        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<T>> {
146            Box::pin(async { Err(QueryError::not_found("test")) })
147        }
148
149        fn execute_update<T: Model + crate::row::FromRow + Send + 'static>(
150            &self,
151            _sql: &str,
152            _params: Vec<FilterValue>,
153        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<Vec<T>>> {
154            Box::pin(async { Ok(Vec::new()) })
155        }
156
157        fn execute_delete(
158            &self,
159            _sql: &str,
160            _params: Vec<FilterValue>,
161        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<u64>> {
162            Box::pin(async { Ok(0) })
163        }
164
165        fn execute_raw(
166            &self,
167            _sql: &str,
168            _params: Vec<FilterValue>,
169        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<u64>> {
170            Box::pin(async { Ok(0) })
171        }
172
173        fn count(
174            &self,
175            _sql: &str,
176            _params: Vec<FilterValue>,
177        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<u64>> {
178            Box::pin(async { Ok(0) })
179        }
180    }
181
182    #[test]
183    fn test_query_builder_find_many() {
184        let qb = QueryBuilder::<MockEngine, TestModel>::new(MockEngine);
185        let op = qb.find_many();
186        let (sql, _) = op.build_sql(&crate::dialect::Postgres);
187        assert!(sql.contains("SELECT * FROM test_models"));
188    }
189
190    #[test]
191    fn test_query_builder_find_by_id() {
192        let qb = QueryBuilder::<MockEngine, TestModel>::new(MockEngine);
193        let op = qb.find_by_id(1i32);
194        let (sql, params) = op.build_sql(&crate::dialect::Postgres);
195        assert!(sql.contains("WHERE"));
196        assert!(sql.contains(r#""id" = $1"#));
197        assert_eq!(params.len(), 1);
198    }
199}