1pub mod author;
44pub mod collection;
45pub mod file_meta;
46pub mod filter;
47pub mod platform;
48pub mod post;
49pub mod tag;
50
51use cached::Cached;
52pub use countable::{Countable, Totalled};
53pub use paginate::Paginate;
54pub use sortable::{SortDir, Sortable};
55
56use std::{
57 fmt::{Debug, Display},
58 rc::Rc,
59};
60
61use rusqlite::ToSql;
62
63use crate::{
64 manager::{PostArchiverConnection, PostArchiverManager},
65 utils::macros::AsTable,
66};
67
68pub trait FromQuery: Sized {
77 type Based: AsTable;
78 fn select_sql() -> String;
80 fn from_row(row: &rusqlite::Row) -> Result<Self, rusqlite::Error>;
82}
83
84pub trait Query: Sized {
94 type Wrapper<T>;
97 type Based: AsTable;
99 fn query_with_context<T: FromQuery<Based = Self::Based>>(
102 self,
103 sql: RawSql<T>,
104 ) -> crate::error::Result<Self::Wrapper<T>>;
105
106 fn query<T: FromQuery<Based = Self::Based>>(self) -> crate::error::Result<Self::Wrapper<T>> {
108 let sql: RawSql<T> = RawSql::new();
109 self.query_with_context(sql)
110 }
111}
112
113pub trait BaseFilter: Sized {
119 type Based: AsTable;
120 fn update_sql<T: FromQuery<Based = Self::Based>>(&self, sql: RawSql<T>) -> RawSql<T>;
122 fn queryer(&self) -> &Queryer<'_, impl PostArchiverConnection>;
124
125 fn count(&self) -> crate::error::Result<u64> {
126 let sql = RawSql::<Self::Based>::new();
127 let sql = self.update_sql(sql);
128 let (sql, params) = sql.build_count_sql();
129
130 let cache_key = (
131 sql.clone(),
132 params
133 .iter()
134 .map(|p| p.to_string())
135 .collect::<Vec<_>>()
136 .join(","),
137 );
138
139 let cached_count = self
140 .queryer()
141 .manager
142 .caches
143 .lock()
144 .unwrap()
145 .counts
146 .cache_get(&cache_key)
147 .cloned();
148 match cached_count {
149 Some(cached_count) => Ok(cached_count),
150 None => {
151 let count = self.queryer().count(&sql, params)?;
152 self.queryer()
153 .manager
154 .caches
155 .lock()
156 .unwrap()
157 .counts
158 .cache_set(cache_key, count);
159 Ok(count)
160 }
161 }
162 }
163}
164
165pub trait ToSqlAndEq: ToSql + Display {}
166impl<T: ToSql + Display> ToSqlAndEq for T {}
167
168pub type Param = Rc<dyn ToSqlAndEq>;
169
170#[derive(Default, Clone)]
177pub struct RawSql<T> {
178 pub where_clause: (Vec<String>, Vec<Param>),
180 pub order_clause: Vec<String>,
182 pub limit_clause: Option<[u64; 2]>,
184 _phantom: std::marker::PhantomData<T>,
185}
186
187impl<T> Debug for RawSql<T> {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 f.debug_struct("RawSql")
190 .field("where_clause", &self.where_clause.0)
191 .field("order_clause", &self.order_clause)
192 .field("limit_clause", &self.limit_clause)
193 .finish()
194 }
195}
196
197impl<T> RawSql<T> {
198 pub fn new() -> Self {
200 Self {
201 where_clause: (Vec::new(), Vec::new()),
202 order_clause: Vec::new(),
203 limit_clause: None,
204 _phantom: std::marker::PhantomData,
205 }
206 }
207
208 pub fn build_generic_sql(&self) -> (String, Vec<Param>) {
210 let mut params = self.where_clause.1.clone();
211 let where_sql = if self.where_clause.0.is_empty() {
212 "".to_string()
213 } else {
214 format!("WHERE {}", self.where_clause.0.join(" AND "))
215 };
216 let order_sql = if self.order_clause.is_empty() {
217 "".to_string()
218 } else {
219 format!("ORDER BY {}", self.order_clause.join(", "))
220 };
221 let limit_sql = if let Some([limit, offset]) = self.limit_clause {
222 params.push(Rc::new(limit));
223 params.push(Rc::new(offset));
224 "LIMIT ? OFFSET ?".to_string()
225 } else {
226 "".to_string()
227 };
228 (format!("{} {} {}", where_sql, order_sql, limit_sql), params)
229 }
230}
231
232impl<T: FromQuery> RawSql<T> {
233 pub fn build_sql(&self) -> (String, Vec<Param>) {
235 let (clause, params) = self.build_generic_sql();
236 let sql = format!("{} {}", T::select_sql(), clause);
237 (sql, params)
238 }
239
240 pub fn build_count_sql(&self) -> (String, Vec<Param>) {
242 let where_sql = if self.where_clause.0.is_empty() {
243 "".to_string()
244 } else {
245 format!("WHERE {}", self.where_clause.0.join(" AND "))
246 };
247 let sql = format!(
248 "SELECT COUNT(*) FROM {} {}",
249 T::Based::TABLE_NAME,
250 where_sql
251 );
252 (sql, self.where_clause.1.clone())
253 }
254}
255
256pub struct Queryer<'a, C> {
261 pub manager: &'a PostArchiverManager<C>,
262}
263
264impl<T> Debug for Queryer<'_, T> {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266 f.debug_struct("Queryer").finish()
267 }
268}
269
270impl<'a, C: PostArchiverConnection> Queryer<'a, C> {
271 pub fn new(manager: &'a PostArchiverManager<C>) -> Self {
272 Self { manager }
273 }
274
275 pub fn fetch<Q: FromQuery>(
277 &self,
278 sql: &str,
279 params: Vec<Param>,
280 ) -> crate::error::Result<Vec<Q>> {
281 let mut stmt = self.manager.conn().prepare_cached(sql)?;
282 let params = params
283 .iter()
284 .map(|p| p.as_ref() as &dyn ToSql)
285 .collect::<Vec<_>>();
286 let rows = stmt.query_map(params.as_slice(), Q::from_row)?;
287 rows.collect::<Result<_, _>>().map_err(Into::into)
288 }
289
290 pub fn count(&self, sql: &str, params: Vec<Param>) -> crate::error::Result<u64> {
292 let mut stmt = self.manager.conn().prepare_cached(sql)?;
293 let params = params
294 .iter()
295 .map(|p| p.as_ref() as &dyn ToSql)
296 .collect::<Vec<_>>();
297 Ok(stmt.query_row(params.as_slice(), |row| row.get(0))?)
298 }
299}
300
301pub use paginate::*;
303pub mod paginate {
304 use crate::{
305 manager::PostArchiverConnection,
306 query::{Query, RawSql},
307 };
308
309 use super::{BaseFilter, FromQuery};
310
311 pub trait Paginate: Sized {
315 fn pagination(self, limit: u64, page: u64) -> Paginated<Self>;
318 }
319
320 impl<T: Query> Paginate for T {
321 fn pagination(self, limit: u64, page: u64) -> Paginated<Self> {
322 Paginated {
323 inner: self,
324 limit,
325 page,
326 }
327 }
328 }
329
330 #[derive(Debug)]
334 pub struct Paginated<Q> {
335 inner: Q,
336 limit: u64,
337 page: u64,
338 }
339
340 impl<Q: Query> Query for Paginated<Q> {
341 type Wrapper<T> = Q::Wrapper<T>;
342 type Based = Q::Based;
343
344 fn query_with_context<T: FromQuery<Based = Self::Based>>(
345 self,
346 mut sql: RawSql<T>,
347 ) -> crate::error::Result<Self::Wrapper<T>> {
348 sql.limit_clause = Some([self.limit, self.limit * self.page]);
349 self.inner.query_with_context(sql)
350 }
351 }
352
353 impl<Q: BaseFilter> BaseFilter for Paginated<Q> {
354 type Based = Q::Based;
355
356 fn update_sql<T: FromQuery<Based = Self::Based>>(&self, sql: RawSql<T>) -> RawSql<T> {
357 self.inner.update_sql(sql)
358 }
359
360 fn queryer(&self) -> &crate::query::Queryer<'_, impl PostArchiverConnection> {
361 self.inner.queryer()
362 }
363 }
364}
365
366pub use countable::*;
367pub mod countable {
368 use serde::{Deserialize, Serialize};
369
370 use crate::{
371 manager::PostArchiverConnection,
372 query::{Query, RawSql},
373 };
374
375 use super::{BaseFilter, FromQuery};
376
377 pub trait Countable: Sized {
381 fn with_total(self) -> WithTotal<Self> {
385 WithTotal { inner: self }
386 }
387 }
388
389 impl<T: Query> Countable for T {}
390
391 #[derive(Debug)]
394 pub struct WithTotal<Q> {
395 inner: Q,
396 }
397
398 impl<Q: Query + BaseFilter> Query for WithTotal<Q> {
399 type Wrapper<T> = Totalled<Q::Wrapper<T>>;
400 type Based = <Q as Query>::Based;
401
402 fn query_with_context<T: FromQuery<Based = Self::Based>>(
403 self,
404 sql: RawSql<T>,
405 ) -> crate::error::Result<Self::Wrapper<T>> {
406 let total = self.inner.count()?;
407 let items = self.inner.query_with_context(sql)?;
408 Ok(Totalled { items, total })
409 }
410 }
411
412 impl<T: BaseFilter> BaseFilter for WithTotal<T> {
413 type Based = T::Based;
414
415 fn update_sql<U: FromQuery<Based = Self::Based>>(&self, sql: RawSql<U>) -> RawSql<U> {
416 self.inner.update_sql(sql)
417 }
418
419 fn queryer(&self) -> &crate::query::Queryer<'_, impl PostArchiverConnection> {
420 self.inner.queryer()
421 }
422 }
423
424 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
427 pub struct Totalled<T> {
428 pub items: T,
430 pub total: u64,
432 }
433}
434
435pub use sortable::*;
436pub mod sortable {
437 use std::fmt::Display;
438
439 use crate::{
440 manager::PostArchiverConnection,
441 query::{Query, RawSql},
442 };
443
444 pub trait Sortable: Sized {
449 type SortField;
451 fn sort(self, field: Self::SortField, dir: SortDir) -> Sorted<Self, Self::SortField> {
453 Sorted {
454 inner: self,
455 field,
456 dir,
457 }
458 }
459 fn sort_random(self) -> Sorted<Self, Random> {
461 Sorted {
462 inner: self,
463 field: Random,
464 dir: SortDir::Asc, }
466 }
467 }
468
469 #[derive(Debug)]
471 pub struct Sorted<Q, F> {
472 inner: Q,
473 field: F,
474 dir: SortDir,
475 }
476
477 #[derive(Debug)]
479 pub struct Random;
480
481 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
483 pub enum SortDir {
484 #[default]
486 Asc,
487 Desc,
489 }
490
491 impl SortDir {
492 pub fn as_sql(self) -> &'static str {
493 match self {
494 SortDir::Asc => "ASC",
495 SortDir::Desc => "DESC",
496 }
497 }
498 }
499
500 impl<Q: Query, U: Display> Query for Sorted<Q, U> {
501 type Wrapper<T> = Q::Wrapper<T>;
502 type Based = Q::Based;
503 fn query_with_context<T: FromQuery<Based = Self::Based>>(
504 self,
505 mut sql: RawSql<T>,
506 ) -> crate::error::Result<Self::Wrapper<T>> {
507 sql.order_clause
508 .push(format!("{} {}", self.field, self.dir.as_sql()));
509 self.inner.query_with_context(sql)
510 }
511 }
512
513 impl<Q: Query> Query for Sorted<Q, Random> {
514 type Wrapper<T> = Q::Wrapper<T>;
515 type Based = Q::Based;
516 fn query_with_context<T: FromQuery<Based = Self::Based>>(
517 self,
518 mut sql: RawSql<T>,
519 ) -> crate::error::Result<Self::Wrapper<T>> {
520 sql.order_clause.push("RANDOM()".to_string());
521 self.inner.query_with_context(sql)
522 }
523 }
524
525 impl<Q: BaseFilter, U> BaseFilter for Sorted<Q, U> {
526 type Based = Q::Based;
527
528 fn update_sql<T: FromQuery<Based = Self::Based>>(&self, sql: RawSql<T>) -> RawSql<T> {
529 self.inner.update_sql(sql)
530 }
531
532 fn queryer(&self) -> &super::Queryer<'_, impl PostArchiverConnection> {
533 self.inner.queryer()
534 }
535 }
536
537 impl<T: Sortable> Sortable for WithTotal<T> {
538 type SortField = T::SortField;
539 }
540 impl<T: Sortable> Sortable for Paginated<T> {
541 type SortField = T::SortField;
542 }
543 impl<T: Sortable, U> Sortable for Sorted<T, U> {
544 type SortField = T::SortField;
545 }
546
547 #[macro_export]
548 macro_rules! impl_sortable {
549 ($query_type:ident ($sort_field_enum:ident) {
550 $($field:ident: $column:expr),*
551 }) => {
552 pub enum $sort_field_enum {
553 $($field),*
554 }
555
556 impl std::fmt::Display for $sort_field_enum {
557 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
558 match self {
559 $(Self::$field => write!(f, "{}", $column)),*
560 }
561 }
562 }
563
564 impl<C> $crate::query::sortable::Sortable for $query_type<'_, C> {
565 type SortField = $sort_field_enum;
566 }
567 };
568 }
569 pub use impl_sortable;
570
571 use super::{countable::WithTotal, paginate::Paginated, BaseFilter, FromQuery};
572}