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