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(
138        &self,
139        data: <Self::Model as CreateData>::Data,
140    ) -> crate::operations::CreateOperation<E, Self::Model>
141    where
142        Self::Model: CreateData;
143
144    /// Start an update operation.
145    fn update(&self) -> crate::operations::UpdateOperation<E, Self::Model>;
146
147    /// Start a delete operation.
148    fn delete(&self) -> crate::operations::DeleteOperation<E, Self::Model>;
149
150    /// Start an upsert operation.
151    fn upsert(
152        &self,
153        create: <Self::Model as CreateData>::Data,
154        update: <Self::Model as UpdateData>::Data,
155    ) -> crate::operations::UpsertOperation<E, Self::Model>
156    where
157        Self::Model: CreateData + UpdateData;
158
159    /// Count records matching a filter.
160    fn count(&self) -> crate::operations::CountOperation<E, Self::Model>;
161}
162
163/// Data for creating a new record.
164pub trait CreateData: Model {
165    /// The type that holds create data.
166    type Data: Send + Sync;
167}
168
169/// Data for updating an existing record.
170pub trait UpdateData: Model {
171    /// The type that holds update data.
172    type Data: Send + Sync;
173}
174
175/// Data for upserting a record.
176pub trait UpsertData: CreateData + UpdateData {}
177
178impl<T: CreateData + UpdateData> UpsertData for T {}
179
180/// Trait for models that support eager loading of relations.
181pub trait WithRelations: Model {
182    /// The type of include specification.
183    type Include;
184
185    /// The type of select specification.
186    type Select;
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    struct TestModel;
194
195    impl Model for TestModel {
196        const MODEL_NAME: &'static str = "TestModel";
197        const TABLE_NAME: &'static str = "test_models";
198        const PRIMARY_KEY: &'static [&'static str] = &["id"];
199        const COLUMNS: &'static [&'static str] = &["id", "name", "email"];
200    }
201
202    #[test]
203    fn test_model_trait() {
204        assert_eq!(TestModel::MODEL_NAME, "TestModel");
205        assert_eq!(TestModel::TABLE_NAME, "test_models");
206        assert_eq!(TestModel::PRIMARY_KEY, &["id"]);
207    }
208
209    #[test]
210    fn test_into_filter() {
211        let filter = Filter::Equals("id".into(), crate::filter::FilterValue::Int(1));
212        let converted = filter.clone().into_filter();
213        assert_eq!(converted, filter);
214    }
215}