prax_query/
traits.rs

1//! Core traits for the query builder.
2
3use std::future::Future;
4use std::pin::Pin;
5
6use crate::error::QueryResult;
7use crate::filter::Filter;
8
9/// A model that can be queried.
10pub trait Model: Sized + Send + Sync {
11    /// The name of the model (used for table name).
12    const MODEL_NAME: &'static str;
13
14    /// The name of the database table.
15    const TABLE_NAME: &'static str;
16
17    /// The primary key column name(s).
18    const PRIMARY_KEY: &'static [&'static str];
19
20    /// All column names for this model.
21    const COLUMNS: &'static [&'static str];
22}
23
24/// A database view that can be queried (read-only).
25///
26/// Views are similar to models but only support read operations.
27/// They cannot be inserted into, updated, or deleted from directly.
28pub trait View: Sized + Send + Sync {
29    /// The name of the view.
30    const VIEW_NAME: &'static str;
31
32    /// The name of the database view.
33    const DB_VIEW_NAME: &'static str;
34
35    /// All column names for this view.
36    const COLUMNS: &'static [&'static str];
37
38    /// Whether this is a materialized view.
39    const IS_MATERIALIZED: bool;
40}
41
42/// A materialized view that supports refresh operations.
43pub trait MaterializedView: View {
44    /// Whether concurrent refresh is supported.
45    const SUPPORTS_CONCURRENT_REFRESH: bool = true;
46}
47
48/// A type that can be converted into a filter.
49pub trait IntoFilter {
50    /// Convert this type into a filter.
51    fn into_filter(self) -> Filter;
52}
53
54impl IntoFilter for Filter {
55    fn into_filter(self) -> Filter {
56        self
57    }
58}
59
60impl<F: FnOnce() -> Filter> IntoFilter for F {
61    fn into_filter(self) -> Filter {
62        self()
63    }
64}
65
66/// A query that can be executed.
67pub trait Executable {
68    /// The output type of the query.
69    type Output;
70
71    /// Execute the query and return the result.
72    fn exec(self) -> impl Future<Output = QueryResult<Self::Output>> + Send;
73}
74
75/// A boxed future for async operations.
76pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
77
78/// The query engine abstraction.
79///
80/// This trait defines how queries are executed against a database.
81/// Different implementations can be provided for different databases
82/// (PostgreSQL, MySQL, SQLite, etc.).
83pub trait QueryEngine: Send + Sync + Clone + 'static {
84    /// Execute a SELECT query and return rows.
85    fn query_many<T: Model + Send + 'static>(
86        &self,
87        sql: &str,
88        params: Vec<crate::filter::FilterValue>,
89    ) -> BoxFuture<'_, QueryResult<Vec<T>>>;
90
91    /// Execute a SELECT query expecting one result.
92    fn query_one<T: Model + Send + 'static>(
93        &self,
94        sql: &str,
95        params: Vec<crate::filter::FilterValue>,
96    ) -> BoxFuture<'_, QueryResult<T>>;
97
98    /// Execute a SELECT query expecting zero or one result.
99    fn query_optional<T: Model + Send + 'static>(
100        &self,
101        sql: &str,
102        params: Vec<crate::filter::FilterValue>,
103    ) -> BoxFuture<'_, QueryResult<Option<T>>>;
104
105    /// Execute an INSERT query and return the created row.
106    fn execute_insert<T: Model + Send + 'static>(
107        &self,
108        sql: &str,
109        params: Vec<crate::filter::FilterValue>,
110    ) -> BoxFuture<'_, QueryResult<T>>;
111
112    /// Execute an UPDATE query and return affected rows.
113    fn execute_update<T: Model + Send + 'static>(
114        &self,
115        sql: &str,
116        params: Vec<crate::filter::FilterValue>,
117    ) -> BoxFuture<'_, QueryResult<Vec<T>>>;
118
119    /// Execute a DELETE query and return affected rows count.
120    fn execute_delete(
121        &self,
122        sql: &str,
123        params: Vec<crate::filter::FilterValue>,
124    ) -> BoxFuture<'_, QueryResult<u64>>;
125
126    /// Execute a raw SQL query.
127    fn execute_raw(
128        &self,
129        sql: &str,
130        params: Vec<crate::filter::FilterValue>,
131    ) -> BoxFuture<'_, QueryResult<u64>>;
132
133    /// Get a count of records.
134    fn count(
135        &self,
136        sql: &str,
137        params: Vec<crate::filter::FilterValue>,
138    ) -> BoxFuture<'_, QueryResult<u64>>;
139
140    /// Refresh a materialized view.
141    ///
142    /// For PostgreSQL, this executes `REFRESH MATERIALIZED VIEW`.
143    /// For MSSQL, this rebuilds the indexed view.
144    /// For databases that don't support materialized views, this returns an error.
145    fn refresh_materialized_view(
146        &self,
147        view_name: &str,
148        concurrently: bool,
149    ) -> BoxFuture<'_, QueryResult<()>> {
150        let view_name = view_name.to_string();
151        Box::pin(async move {
152            let _ = (view_name, concurrently);
153            Err(crate::error::QueryError::unsupported(
154                "Materialized view refresh is not supported by this database",
155            ))
156        })
157    }
158}
159
160/// Query engine extension for view operations.
161pub trait ViewQueryEngine: QueryEngine {
162    /// Query rows from a view.
163    fn query_view_many<V: View + Send + 'static>(
164        &self,
165        sql: &str,
166        params: Vec<crate::filter::FilterValue>,
167    ) -> BoxFuture<'_, QueryResult<Vec<V>>>;
168
169    /// Query a single row from a view.
170    fn query_view_optional<V: View + Send + 'static>(
171        &self,
172        sql: &str,
173        params: Vec<crate::filter::FilterValue>,
174    ) -> BoxFuture<'_, QueryResult<Option<V>>>;
175
176    /// Count rows in a view.
177    fn count_view(
178        &self,
179        sql: &str,
180        params: Vec<crate::filter::FilterValue>,
181    ) -> BoxFuture<'_, QueryResult<u64>> {
182        self.count(sql, params)
183    }
184}
185
186/// A model accessor that provides query operations.
187///
188/// This is typically generated by the proc-macro for each model.
189pub trait ModelAccessor<E: QueryEngine>: Send + Sync {
190    /// The model type.
191    type Model: Model;
192
193    /// Get the query engine.
194    fn engine(&self) -> &E;
195
196    /// Start a find_many query.
197    fn find_many(&self) -> crate::operations::FindManyOperation<E, Self::Model>;
198
199    /// Start a find_unique query.
200    fn find_unique(&self) -> crate::operations::FindUniqueOperation<E, Self::Model>;
201
202    /// Start a find_first query.
203    fn find_first(&self) -> crate::operations::FindFirstOperation<E, Self::Model>;
204
205    /// Start a create operation.
206    fn create(
207        &self,
208        data: <Self::Model as CreateData>::Data,
209    ) -> crate::operations::CreateOperation<E, Self::Model>
210    where
211        Self::Model: CreateData;
212
213    /// Start an update operation.
214    fn update(&self) -> crate::operations::UpdateOperation<E, Self::Model>;
215
216    /// Start a delete operation.
217    fn delete(&self) -> crate::operations::DeleteOperation<E, Self::Model>;
218
219    /// Start an upsert operation.
220    fn upsert(
221        &self,
222        create: <Self::Model as CreateData>::Data,
223        update: <Self::Model as UpdateData>::Data,
224    ) -> crate::operations::UpsertOperation<E, Self::Model>
225    where
226        Self::Model: CreateData + UpdateData;
227
228    /// Count records matching a filter.
229    fn count(&self) -> crate::operations::CountOperation<E, Self::Model>;
230}
231
232/// Data for creating a new record.
233pub trait CreateData: Model {
234    /// The type that holds create data.
235    type Data: Send + Sync;
236}
237
238/// Data for updating an existing record.
239pub trait UpdateData: Model {
240    /// The type that holds update data.
241    type Data: Send + Sync;
242}
243
244/// Data for upserting a record.
245pub trait UpsertData: CreateData + UpdateData {}
246
247impl<T: CreateData + UpdateData> UpsertData for T {}
248
249/// Trait for models that support eager loading of relations.
250pub trait WithRelations: Model {
251    /// The type of include specification.
252    type Include;
253
254    /// The type of select specification.
255    type Select;
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    struct TestModel;
263
264    impl Model for TestModel {
265        const MODEL_NAME: &'static str = "TestModel";
266        const TABLE_NAME: &'static str = "test_models";
267        const PRIMARY_KEY: &'static [&'static str] = &["id"];
268        const COLUMNS: &'static [&'static str] = &["id", "name", "email"];
269    }
270
271    #[test]
272    fn test_model_trait() {
273        assert_eq!(TestModel::MODEL_NAME, "TestModel");
274        assert_eq!(TestModel::TABLE_NAME, "test_models");
275        assert_eq!(TestModel::PRIMARY_KEY, &["id"]);
276    }
277
278    #[test]
279    fn test_into_filter() {
280        let filter = Filter::Equals("id".into(), crate::filter::FilterValue::Int(1));
281        let converted = filter.clone().into_filter();
282        assert_eq!(converted, filter);
283    }
284}