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}