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 type that can be converted into a filter.
25pub trait IntoFilter {
26    /// Convert this type into a filter.
27    fn into_filter(self) -> Filter;
28}
29
30impl IntoFilter for Filter {
31    fn into_filter(self) -> Filter {
32        self
33    }
34}
35
36impl<F: FnOnce() -> Filter> IntoFilter for F {
37    fn into_filter(self) -> Filter {
38        self()
39    }
40}
41
42/// A query that can be executed.
43pub trait Executable {
44    /// The output type of the query.
45    type Output;
46
47    /// Execute the query and return the result.
48    fn exec(self) -> impl Future<Output = QueryResult<Self::Output>> + Send;
49}
50
51/// A boxed future for async operations.
52pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
53
54/// The query engine abstraction.
55///
56/// This trait defines how queries are executed against a database.
57/// Different implementations can be provided for different databases
58/// (PostgreSQL, MySQL, SQLite, etc.).
59pub trait QueryEngine: Send + Sync + Clone + 'static {
60    /// Execute a SELECT query and return rows.
61    fn query_many<T: Model + Send + 'static>(
62        &self,
63        sql: &str,
64        params: Vec<crate::filter::FilterValue>,
65    ) -> BoxFuture<'_, QueryResult<Vec<T>>>;
66
67    /// Execute a SELECT query expecting one result.
68    fn query_one<T: Model + Send + 'static>(
69        &self,
70        sql: &str,
71        params: Vec<crate::filter::FilterValue>,
72    ) -> BoxFuture<'_, QueryResult<T>>;
73
74    /// Execute a SELECT query expecting zero or one result.
75    fn query_optional<T: Model + Send + 'static>(
76        &self,
77        sql: &str,
78        params: Vec<crate::filter::FilterValue>,
79    ) -> BoxFuture<'_, QueryResult<Option<T>>>;
80
81    /// Execute an INSERT query and return the created row.
82    fn execute_insert<T: Model + Send + 'static>(
83        &self,
84        sql: &str,
85        params: Vec<crate::filter::FilterValue>,
86    ) -> BoxFuture<'_, QueryResult<T>>;
87
88    /// Execute an UPDATE query and return affected rows.
89    fn execute_update<T: Model + Send + 'static>(
90        &self,
91        sql: &str,
92        params: Vec<crate::filter::FilterValue>,
93    ) -> BoxFuture<'_, QueryResult<Vec<T>>>;
94
95    /// Execute a DELETE query and return affected rows count.
96    fn execute_delete(
97        &self,
98        sql: &str,
99        params: Vec<crate::filter::FilterValue>,
100    ) -> BoxFuture<'_, QueryResult<u64>>;
101
102    /// Execute a raw SQL query.
103    fn execute_raw(
104        &self,
105        sql: &str,
106        params: Vec<crate::filter::FilterValue>,
107    ) -> BoxFuture<'_, QueryResult<u64>>;
108
109    /// Get a count of records.
110    fn count(
111        &self,
112        sql: &str,
113        params: Vec<crate::filter::FilterValue>,
114    ) -> BoxFuture<'_, QueryResult<u64>>;
115}
116
117/// A model accessor that provides query operations.
118///
119/// This is typically generated by the proc-macro for each model.
120pub trait ModelAccessor<E: QueryEngine>: Send + Sync {
121    /// The model type.
122    type Model: Model;
123
124    /// Get the query engine.
125    fn engine(&self) -> &E;
126
127    /// Start a find_many query.
128    fn find_many(&self) -> crate::operations::FindManyOperation<E, Self::Model>;
129
130    /// Start a find_unique query.
131    fn find_unique(&self) -> crate::operations::FindUniqueOperation<E, Self::Model>;
132
133    /// Start a find_first query.
134    fn find_first(&self) -> crate::operations::FindFirstOperation<E, Self::Model>;
135
136    /// Start a create operation.
137    fn create(&self, data: <Self::Model as CreateData>::Data) -> crate::operations::CreateOperation<E, Self::Model>
138    where
139        Self::Model: CreateData;
140
141    /// Start an update operation.
142    fn update(&self) -> crate::operations::UpdateOperation<E, Self::Model>;
143
144    /// Start a delete operation.
145    fn delete(&self) -> crate::operations::DeleteOperation<E, Self::Model>;
146
147    /// Start an upsert operation.
148    fn upsert(
149        &self,
150        create: <Self::Model as CreateData>::Data,
151        update: <Self::Model as UpdateData>::Data,
152    ) -> crate::operations::UpsertOperation<E, Self::Model>
153    where
154        Self::Model: CreateData + UpdateData;
155
156    /// Count records matching a filter.
157    fn count(&self) -> crate::operations::CountOperation<E, Self::Model>;
158}
159
160/// Data for creating a new record.
161pub trait CreateData: Model {
162    /// The type that holds create data.
163    type Data: Send + Sync;
164}
165
166/// Data for updating an existing record.
167pub trait UpdateData: Model {
168    /// The type that holds update data.
169    type Data: Send + Sync;
170}
171
172/// Data for upserting a record.
173pub trait UpsertData: CreateData + UpdateData {}
174
175impl<T: CreateData + UpdateData> UpsertData for T {}
176
177/// Trait for models that support eager loading of relations.
178pub trait WithRelations: Model {
179    /// The type of include specification.
180    type Include;
181
182    /// The type of select specification.
183    type Select;
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    struct TestModel;
191
192    impl Model for TestModel {
193        const MODEL_NAME: &'static str = "TestModel";
194        const TABLE_NAME: &'static str = "test_models";
195        const PRIMARY_KEY: &'static [&'static str] = &["id"];
196        const COLUMNS: &'static [&'static str] = &["id", "name", "email"];
197    }
198
199    #[test]
200    fn test_model_trait() {
201        assert_eq!(TestModel::MODEL_NAME, "TestModel");
202        assert_eq!(TestModel::TABLE_NAME, "test_models");
203        assert_eq!(TestModel::PRIMARY_KEY, &["id"]);
204    }
205
206    #[test]
207    fn test_into_filter() {
208        let filter = Filter::Equals("id".into(), crate::filter::FilterValue::Int(1));
209        let converted = filter.clone().into_filter();
210        assert_eq!(converted, filter);
211    }
212}
213