1pub mod author;
34pub mod collection;
35pub mod file_meta;
36pub mod filter;
37pub mod platform;
38pub mod post;
39pub mod tag;
40
41pub use countable::{Countable, PageResult};
42pub use paginate::Paginate;
43pub use sortable::{SortDir, Sortable};
44
45use std::{fmt::Debug, rc::Rc};
46
47use rusqlite::ToSql;
48
49use crate::{
50 manager::{PostArchiverConnection, PostArchiverManager},
51 utils::macros::AsTable,
52};
53
54pub trait FromQuery: Sized {
55 type Based: AsTable;
56 fn select_sql() -> String;
57 fn from_row(row: &rusqlite::Row) -> Result<Self, rusqlite::Error>;
58}
59
60pub trait Query: Sized {
64 type Wrapper<T>;
65 type Based: AsTable;
66 fn query_with_context<T: FromQuery<Based = Self::Based>>(
68 self,
69 sql: RawSql<T>,
70 ) -> crate::error::Result<Self::Wrapper<T>>;
71
72 fn query<T: FromQuery<Based = Self::Based>>(self) -> crate::error::Result<Self::Wrapper<T>> {
73 let sql: RawSql<T> = RawSql::new();
74 self.query_with_context(sql)
75 }
76}
77
78pub trait BaseFilter: Sized {
79 type Based: AsTable;
80 fn update_sql<T: FromQuery<Based = Self::Based>>(&self, sql: RawSql<T>) -> RawSql<T>;
81 fn queryer(&self) -> &Queryer<'_, impl PostArchiverConnection>;
82
83 fn count(&self) -> crate::error::Result<u64> {
84 let sql = RawSql::<Self::Based>::new();
85 let sql = self.update_sql(sql);
86 let (sql, params) = sql.build_count_sql();
87 self.queryer().count(&sql, params)
88 }
89}
90
91pub type Param = Rc<dyn ToSql>;
92
93#[derive(Default, Clone)]
94pub struct RawSql<T> {
95 pub where_clause: (Vec<String>, Vec<Param>),
96 pub order_clause: Vec<String>,
97 pub limit_clause: Option<[u64; 2]>,
98 _phantom: std::marker::PhantomData<T>,
99}
100
101impl<T> Debug for RawSql<T> {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 f.debug_struct("RawSql")
104 .field("where_clause", &self.where_clause.0)
105 .field("order_clause", &self.order_clause)
106 .field("limit_clause", &self.limit_clause)
107 .finish()
108 }
109}
110
111impl<T> RawSql<T> {
112 pub fn new() -> Self {
113 Self {
114 where_clause: (Vec::new(), Vec::new()),
115 order_clause: Vec::new(),
116 limit_clause: None,
117 _phantom: std::marker::PhantomData,
118 }
119 }
120
121 pub fn build_generic_sql(&self) -> (String, Vec<Param>) {
122 let mut params = self.where_clause.1.clone();
123 let where_sql = if self.where_clause.0.is_empty() {
124 "".to_string()
125 } else {
126 format!("WHERE {}", self.where_clause.0.join(" AND "))
127 };
128 let order_sql = if self.order_clause.is_empty() {
129 "".to_string()
130 } else {
131 format!("ORDER BY {}", self.order_clause.join(", "))
132 };
133 let limit_sql = if let Some([limit, offset]) = self.limit_clause {
134 params.push(Rc::new(limit));
135 params.push(Rc::new(offset));
136 "LIMIT ? OFFSET ?".to_string()
137 } else {
138 "".to_string()
139 };
140 (format!("{} {} {}", where_sql, order_sql, limit_sql), params)
141 }
142}
143
144impl<T: FromQuery> RawSql<T> {
145 pub fn build_sql(&self) -> (String, Vec<Param>) {
146 let (clause, params) = self.build_generic_sql();
147 let sql = format!("{} {}", T::select_sql(), clause);
148 (sql, params)
149 }
150
151 pub fn build_count_sql(&self) -> (String, Vec<Param>) {
152 let where_sql = if self.where_clause.0.is_empty() {
153 "".to_string()
154 } else {
155 format!("WHERE {}", self.where_clause.0.join(" AND "))
156 };
157 let sql = format!(
158 "SELECT COUNT(*) FROM {} {}",
159 T::Based::TABLE_NAME,
160 where_sql
161 );
162 (sql, self.where_clause.1.clone())
163 }
164}
165
166pub struct Queryer<'a, C> {
167 pub manager: &'a PostArchiverManager<C>,
168}
169
170impl<T> Debug for Queryer<'_, T> {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 f.debug_struct("Queryer").finish()
173 }
174}
175
176impl<'a, C: PostArchiverConnection> Queryer<'a, C> {
177 pub fn new(manager: &'a PostArchiverManager<C>) -> Self {
178 Self { manager }
179 }
180
181 pub fn fetch<Q: FromQuery>(
182 &self,
183 sql: &str,
184 params: Vec<Param>,
185 ) -> crate::error::Result<Vec<Q>> {
186 let mut stmt = self.manager.conn().prepare_cached(sql)?;
187 let params = params
188 .iter()
189 .map(|p| p.as_ref() as &dyn ToSql)
190 .collect::<Vec<_>>();
191 let rows = stmt.query_map(params.as_slice(), Q::from_row)?;
192 rows.collect::<Result<_, _>>().map_err(Into::into)
193 }
194
195 pub fn count(&self, sql: &str, params: Vec<Param>) -> crate::error::Result<u64> {
196 let mut stmt = self.manager.conn().prepare_cached(sql)?;
197 let params = params
198 .iter()
199 .map(|p| p.as_ref() as &dyn ToSql)
200 .collect::<Vec<_>>();
201 Ok(stmt.query_row(params.as_slice(), |row| row.get(0))?)
202 }
203}
204
205pub use paginate::*;
207pub mod paginate {
208 use crate::{
209 manager::PostArchiverConnection,
210 query::{Query, RawSql},
211 };
212
213 use super::{BaseFilter, FromQuery};
214
215 pub trait Paginate: Sized {
216 fn pagination(self, limit: u64, page: u64) -> Paginated<Self>;
217 }
218
219 impl<T: Query> Paginate for T {
220 fn pagination(self, limit: u64, page: u64) -> Paginated<Self> {
221 Paginated {
222 inner: self,
223 limit,
224 page,
225 }
226 }
227 }
228
229 #[derive(Debug)]
233 pub struct Paginated<Q> {
234 inner: Q,
235 limit: u64,
236 page: u64,
237 }
238
239 impl<Q: Query> Query for Paginated<Q> {
240 type Wrapper<T> = Q::Wrapper<T>;
241 type Based = Q::Based;
242
243 fn query_with_context<T: FromQuery<Based = Self::Based>>(
244 self,
245 mut sql: RawSql<T>,
246 ) -> crate::error::Result<Self::Wrapper<T>> {
247 sql.limit_clause = Some([self.limit, self.limit * self.page]);
248 self.inner.query_with_context(sql)
249 }
250 }
251
252 impl<Q: BaseFilter> BaseFilter for Paginated<Q> {
253 type Based = Q::Based;
254
255 fn update_sql<T: FromQuery<Based = Self::Based>>(&self, sql: RawSql<T>) -> RawSql<T> {
256 self.inner.update_sql(sql)
257 }
258
259 fn queryer(&self) -> &crate::query::Queryer<'_, impl PostArchiverConnection> {
260 self.inner.queryer()
261 }
262 }
263}
264
265pub use countable::*;
266pub mod countable {
267 use crate::{
268 manager::PostArchiverConnection,
269 query::{Query, RawSql},
270 };
271
272 use super::{BaseFilter, FromQuery};
273
274 pub trait Countable: Sized {
275 fn with_total(self) -> WithTotal<Self> {
277 WithTotal { inner: self }
278 }
279 }
280
281 impl<T: Query> Countable for T {}
282
283 #[derive(Debug)]
284 pub struct WithTotal<Q> {
285 inner: Q,
286 }
287
288 impl<Q: Query + BaseFilter> Query for WithTotal<Q> {
289 type Wrapper<T> = PageResult<Q::Wrapper<T>>;
290 type Based = <Q as Query>::Based;
291
292 fn query_with_context<T: FromQuery<Based = Self::Based>>(
293 self,
294 sql: RawSql<T>,
295 ) -> crate::error::Result<Self::Wrapper<T>> {
296 let total = self.inner.count()?;
297 let items = self.inner.query_with_context(sql)?;
298 Ok(PageResult { items, total })
299 }
300 }
301
302 impl<T: BaseFilter> BaseFilter for WithTotal<T> {
303 type Based = T::Based;
304
305 fn update_sql<U: FromQuery<Based = Self::Based>>(&self, sql: RawSql<U>) -> RawSql<U> {
306 self.inner.update_sql(sql)
307 }
308
309 fn queryer(&self) -> &crate::query::Queryer<'_, impl PostArchiverConnection> {
310 self.inner.queryer()
311 }
312 }
313
314 #[derive(Debug, Clone)]
316 pub struct PageResult<T> {
317 pub items: T,
318 pub total: u64,
320 }
321}
322
323pub use sortable::*;
324pub mod sortable {
325 use std::fmt::Display;
326
327 use crate::{
328 manager::PostArchiverConnection,
329 query::{Query, RawSql},
330 };
331
332 pub trait Sortable: Sized {
333 type SortField;
334 fn sort(self, field: Self::SortField, dir: SortDir) -> Sorted<Self, Self::SortField> {
335 Sorted {
336 inner: self,
337 field,
338 dir,
339 }
340 }
341 fn sort_random(self) -> Sorted<Self, Random> {
342 Sorted {
343 inner: self,
344 field: Random,
345 dir: SortDir::Asc, }
347 }
348 }
349
350 #[derive(Debug)]
351 pub struct Sorted<Q, F> {
352 inner: Q,
353 field: F,
354 dir: SortDir,
355 }
356
357 #[derive(Debug)]
358 pub struct Random;
359
360 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
362 pub enum SortDir {
363 #[default]
364 Asc,
365 Desc,
366 }
367
368 impl SortDir {
369 pub fn as_sql(self) -> &'static str {
370 match self {
371 SortDir::Asc => "ASC",
372 SortDir::Desc => "DESC",
373 }
374 }
375 }
376
377 impl<Q: Query, U: Display> Query for Sorted<Q, U> {
378 type Wrapper<T> = Q::Wrapper<T>;
379 type Based = Q::Based;
380 fn query_with_context<T: FromQuery<Based = Self::Based>>(
381 self,
382 mut sql: RawSql<T>,
383 ) -> crate::error::Result<Self::Wrapper<T>> {
384 sql.order_clause
385 .push(format!("{} {}", self.field, self.dir.as_sql()));
386 self.inner.query_with_context(sql)
387 }
388 }
389
390 impl<Q: Query> Query for Sorted<Q, Random> {
391 type Wrapper<T> = Q::Wrapper<T>;
392 type Based = Q::Based;
393 fn query_with_context<T: FromQuery<Based = Self::Based>>(
394 self,
395 mut sql: RawSql<T>,
396 ) -> crate::error::Result<Self::Wrapper<T>> {
397 sql.order_clause.push("RANDOM()".to_string());
398 self.inner.query_with_context(sql)
399 }
400 }
401
402 impl<Q: BaseFilter, U> BaseFilter for Sorted<Q, U> {
403 type Based = Q::Based;
404
405 fn update_sql<T: FromQuery<Based = Self::Based>>(&self, sql: RawSql<T>) -> RawSql<T> {
406 self.inner.update_sql(sql)
407 }
408
409 fn queryer(&self) -> &super::Queryer<'_, impl PostArchiverConnection> {
410 self.inner.queryer()
411 }
412 }
413
414 impl<T: Sortable> Sortable for WithTotal<T> {
415 type SortField = T::SortField;
416 }
417 impl<T: Sortable> Sortable for Paginated<T> {
418 type SortField = T::SortField;
419 }
420 impl<T: Sortable, U> Sortable for Sorted<T, U> {
421 type SortField = T::SortField;
422 }
423
424 #[macro_export]
425 macro_rules! impl_sortable {
426 ($query_type:ident ($sort_field_enum:ident) {
427 $($field:ident: $column:expr),*
428 }) => {
429 pub enum $sort_field_enum {
430 $($field),*
431 }
432
433 impl std::fmt::Display for $sort_field_enum {
434 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
435 match self {
436 $(Self::$field => write!(f, "{}", $column)),*
437 }
438 }
439 }
440
441 impl<C> $crate::query::sortable::Sortable for $query_type<'_, C> {
442 type SortField = $sort_field_enum;
443 }
444 };
445 }
446 pub use impl_sortable;
447
448 use super::{countable::WithTotal, paginate::Paginated, BaseFilter, FromQuery};
449}