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> 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    #[derive(Clone)]
104    struct MockEngine;
105
106    impl QueryEngine for MockEngine {
107        fn query_many<T: Model + Send + 'static>(
108            &self,
109            _sql: &str,
110            _params: Vec<FilterValue>,
111        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<Vec<T>>> {
112            Box::pin(async { Ok(Vec::new()) })
113        }
114
115        fn query_one<T: Model + Send + 'static>(
116            &self,
117            _sql: &str,
118            _params: Vec<FilterValue>,
119        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<T>> {
120            Box::pin(async { Err(QueryError::not_found("test")) })
121        }
122
123        fn query_optional<T: Model + Send + 'static>(
124            &self,
125            _sql: &str,
126            _params: Vec<FilterValue>,
127        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<Option<T>>> {
128            Box::pin(async { Ok(None) })
129        }
130
131        fn execute_insert<T: Model + Send + 'static>(
132            &self,
133            _sql: &str,
134            _params: Vec<FilterValue>,
135        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<T>> {
136            Box::pin(async { Err(QueryError::not_found("test")) })
137        }
138
139        fn execute_update<T: Model + Send + 'static>(
140            &self,
141            _sql: &str,
142            _params: Vec<FilterValue>,
143        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<Vec<T>>> {
144            Box::pin(async { Ok(Vec::new()) })
145        }
146
147        fn execute_delete(
148            &self,
149            _sql: &str,
150            _params: Vec<FilterValue>,
151        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<u64>> {
152            Box::pin(async { Ok(0) })
153        }
154
155        fn execute_raw(
156            &self,
157            _sql: &str,
158            _params: Vec<FilterValue>,
159        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<u64>> {
160            Box::pin(async { Ok(0) })
161        }
162
163        fn count(
164            &self,
165            _sql: &str,
166            _params: Vec<FilterValue>,
167        ) -> crate::traits::BoxFuture<'_, crate::error::QueryResult<u64>> {
168            Box::pin(async { Ok(0) })
169        }
170    }
171
172    #[test]
173    fn test_query_builder_find_many() {
174        let qb = QueryBuilder::<MockEngine, TestModel>::new(MockEngine);
175        let op = qb.find_many();
176        let (sql, _) = op.build_sql();
177        assert!(sql.contains("SELECT * FROM test_models"));
178    }
179
180    #[test]
181    fn test_query_builder_find_by_id() {
182        let qb = QueryBuilder::<MockEngine, TestModel>::new(MockEngine);
183        let op = qb.find_by_id(1i32);
184        let (sql, params) = op.build_sql();
185        assert!(sql.contains("WHERE"));
186        assert!(sql.contains("id = $1"));
187        assert_eq!(params.len(), 1);
188    }
189}