kit_rs/database/
query_builder.rs

1//! Fluent query builder for Eloquent-like API
2//!
3//! Provides a chainable query interface that uses the global DB connection.
4//!
5//! # Example
6//!
7//! ```rust,ignore
8//! use crate::models::todos::{Todo, Column};
9//!
10//! // Simple query
11//! let todos = Todo::query().all().await?;
12//!
13//! // With filters
14//! let todo = Todo::query()
15//!     .filter(Column::Title.eq("test"))
16//!     .filter(Column::Id.gt(5))
17//!     .first()
18//!     .await?;
19//!
20//! // With ordering and pagination
21//! let todos = Todo::query()
22//!     .order_by_desc(Column::CreatedAt)
23//!     .limit(10)
24//!     .offset(20)
25//!     .all()
26//!     .await?;
27//! ```
28
29use sea_orm::{
30    ColumnTrait, EntityTrait, Order, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, Select,
31};
32
33use crate::database::DB;
34use crate::error::FrameworkError;
35
36/// Fluent query builder wrapper
37///
38/// Wraps SeaORM's `Select` with methods that use the global DB connection.
39/// This provides an Eloquent-like query API.
40///
41/// # Example
42///
43/// ```rust,ignore
44/// let todos = Todo::query()
45///     .filter(Column::Active.eq(true))
46///     .order_by_asc(Column::Title)
47///     .all()
48///     .await?;
49/// ```
50pub struct QueryBuilder<E>
51where
52    E: EntityTrait,
53{
54    select: Select<E>,
55}
56
57impl<E> QueryBuilder<E>
58where
59    E: EntityTrait,
60    E::Model: Send + Sync,
61{
62    /// Create a new query builder for the entity
63    pub fn new() -> Self {
64        Self {
65            select: E::find(),
66        }
67    }
68
69    /// Add a filter condition
70    ///
71    /// # Example
72    ///
73    /// ```rust,ignore
74    /// let todos = Todo::query()
75    ///     .filter(Column::Title.eq("test"))
76    ///     .filter(Column::Active.eq(true))
77    ///     .all()
78    ///     .await?;
79    /// ```
80    pub fn filter<F>(mut self, filter: F) -> Self
81    where
82        F: sea_orm::sea_query::IntoCondition,
83    {
84        self.select = self.select.filter(filter);
85        self
86    }
87
88    /// Add an order by clause (ascending)
89    ///
90    /// # Example
91    ///
92    /// ```rust,ignore
93    /// let todos = Todo::query()
94    ///     .order_by_asc(Column::Title)
95    ///     .all()
96    ///     .await?;
97    /// ```
98    pub fn order_by_asc<C>(mut self, col: C) -> Self
99    where
100        C: ColumnTrait,
101    {
102        self.select = self.select.order_by(col, Order::Asc);
103        self
104    }
105
106    /// Add an order by clause (descending)
107    ///
108    /// # Example
109    ///
110    /// ```rust,ignore
111    /// let todos = Todo::query()
112    ///     .order_by_desc(Column::CreatedAt)
113    ///     .all()
114    ///     .await?;
115    /// ```
116    pub fn order_by_desc<C>(mut self, col: C) -> Self
117    where
118        C: ColumnTrait,
119    {
120        self.select = self.select.order_by(col, Order::Desc);
121        self
122    }
123
124    /// Add an order by clause with custom order
125    ///
126    /// # Example
127    ///
128    /// ```rust,ignore
129    /// use sea_orm::Order;
130    /// let todos = Todo::query()
131    ///     .order_by(Column::Title, Order::Asc)
132    ///     .all()
133    ///     .await?;
134    /// ```
135    pub fn order_by<C>(mut self, col: C, order: Order) -> Self
136    where
137        C: ColumnTrait,
138    {
139        self.select = self.select.order_by(col, order);
140        self
141    }
142
143    /// Limit the number of results
144    ///
145    /// # Example
146    ///
147    /// ```rust,ignore
148    /// let todos = Todo::query().limit(10).all().await?;
149    /// ```
150    pub fn limit(mut self, limit: u64) -> Self {
151        self.select = self.select.limit(limit);
152        self
153    }
154
155    /// Skip a number of results (offset)
156    ///
157    /// # Example
158    ///
159    /// ```rust,ignore
160    /// // Skip first 10, get next 10
161    /// let todos = Todo::query().offset(10).limit(10).all().await?;
162    /// ```
163    pub fn offset(mut self, offset: u64) -> Self {
164        self.select = self.select.offset(offset);
165        self
166    }
167
168    /// Execute query and return all results
169    ///
170    /// # Example
171    ///
172    /// ```rust,ignore
173    /// let todos = Todo::query().all().await?;
174    /// ```
175    pub async fn all(self) -> Result<Vec<E::Model>, FrameworkError> {
176        let db = DB::connection()?;
177        self.select
178            .all(db.inner())
179            .await
180            .map_err(|e| FrameworkError::database(e.to_string()))
181    }
182
183    /// Execute query and return first result
184    ///
185    /// Returns `None` if no record matches.
186    ///
187    /// # Example
188    ///
189    /// ```rust,ignore
190    /// let todo = Todo::query()
191    ///     .filter(Column::Id.eq(1))
192    ///     .first()
193    ///     .await?;
194    /// ```
195    pub async fn first(self) -> Result<Option<E::Model>, FrameworkError> {
196        let db = DB::connection()?;
197        self.select
198            .one(db.inner())
199            .await
200            .map_err(|e| FrameworkError::database(e.to_string()))
201    }
202
203    /// Execute query and return first result or error
204    ///
205    /// Returns an error if no record matches.
206    ///
207    /// # Example
208    ///
209    /// ```rust,ignore
210    /// let todo = Todo::query()
211    ///     .filter(Column::Id.eq(1))
212    ///     .first_or_fail()
213    ///     .await?;
214    /// ```
215    pub async fn first_or_fail(self) -> Result<E::Model, FrameworkError> {
216        self.first().await?.ok_or_else(|| {
217            FrameworkError::database(format!("{} not found", std::any::type_name::<E::Model>()))
218        })
219    }
220
221    /// Count matching records
222    ///
223    /// # Example
224    ///
225    /// ```rust,ignore
226    /// let count = Todo::query()
227    ///     .filter(Column::Active.eq(true))
228    ///     .count()
229    ///     .await?;
230    /// ```
231    pub async fn count(self) -> Result<u64, FrameworkError> {
232        let db = DB::connection()?;
233        self.select
234            .count(db.inner())
235            .await
236            .map_err(|e| FrameworkError::database(e.to_string()))
237    }
238
239    /// Check if any records exist matching the query
240    ///
241    /// # Example
242    ///
243    /// ```rust,ignore
244    /// let has_active = Todo::query()
245    ///     .filter(Column::Active.eq(true))
246    ///     .exists()
247    ///     .await?;
248    /// ```
249    pub async fn exists(self) -> Result<bool, FrameworkError> {
250        Ok(self.count().await? > 0)
251    }
252
253    /// Get access to the underlying SeaORM Select for advanced queries
254    ///
255    /// Use this when you need SeaORM features not exposed by QueryBuilder.
256    ///
257    /// # Example
258    ///
259    /// ```rust,ignore
260    /// let select = Todo::query()
261    ///     .filter(Column::Active.eq(true))
262    ///     .into_select();
263    ///
264    /// // Use with SeaORM directly
265    /// let todos = select.all(db.inner()).await?;
266    /// ```
267    pub fn into_select(self) -> Select<E> {
268        self.select
269    }
270}
271
272impl<E> Default for QueryBuilder<E>
273where
274    E: EntityTrait,
275    E::Model: Send + Sync,
276{
277    fn default() -> Self {
278        Self::new()
279    }
280}