Skip to main content

post_archiver/query/
tag.rs

1//! Tag query builder and related point-query helpers.
2
3use rusqlite::OptionalExtension;
4
5use crate::{
6    manager::{PostArchiverConnection, PostArchiverManager},
7    PlatformId, Tag, TagId,
8};
9
10use super::{
11    filter::{IdFilter, TextFilter},
12    sortable::impl_sortable,
13    BaseFilter, FromQuery, Query, Queryer, RawSql,
14};
15
16/// Fluent query builder for tags.  Obtained via [`PostArchiverManager::tags()`].
17///
18/// # Available filter fields
19/// - `ids`: filter by a set of [`TagId`] values.
20/// - `name`: `LIKE` fuzzy match on the tag name.
21/// - `source`: `LIKE` fuzzy match on the source field.
22/// - `platforms`: filter by a set of [`PlatformId`] values.
23#[derive(Debug)]
24pub struct TagQuery<'a, C> {
25    queryer: Queryer<'a, C>,
26    pub ids: IdFilter<TagId>,
27    pub name: TextFilter,
28    pub source: TextFilter,
29    pub platforms: IdFilter<PlatformId>,
30}
31
32impl<'a, C: PostArchiverConnection> TagQuery<'a, C> {
33    pub fn new(manager: &'a PostArchiverManager<C>) -> Self {
34        TagQuery {
35            queryer: Queryer::new(manager),
36            ids: IdFilter::new("id"),
37            name: TextFilter::new("name"),
38            source: TextFilter::new("source"),
39            platforms: IdFilter::new("platform"),
40        }
41    }
42}
43
44impl_sortable!(TagQuery(TagSort) {
45    Id: "id",
46    Name: "name",
47    Source: "source"
48});
49
50impl<C: PostArchiverConnection> BaseFilter for TagQuery<'_, C> {
51    type Based = Tag;
52
53    fn update_sql<T: FromQuery<Based = Self::Based>>(&self, mut sql: RawSql<T>) -> RawSql<T> {
54        sql = self.ids.build_sql(sql);
55        sql = self.name.build_sql(sql);
56        sql = self.source.build_sql(sql);
57        sql = self.platforms.build_sql(sql);
58
59        sql
60    }
61
62    fn queryer(&self) -> &Queryer<'_, impl PostArchiverConnection> {
63        &self.queryer
64    }
65}
66
67impl<C: PostArchiverConnection> Query for TagQuery<'_, C> {
68    type Wrapper<T> = Vec<T>;
69    type Based = Tag;
70
71    fn query_with_context<T: FromQuery<Based = Self::Based>>(
72        self,
73        sql: RawSql<T>,
74    ) -> crate::error::Result<Self::Wrapper<T>> {
75        let sql = self.update_sql(sql);
76        let (sql, params) = sql.build_sql();
77        self.queryer.fetch(&sql, params)
78    }
79}
80
81impl<C: PostArchiverConnection> PostArchiverManager<C> {
82    /// Entry point for the tag query builder.
83    pub fn tags(&self) -> TagQuery<'_, C> {
84        TagQuery::new(self)
85    }
86
87    /// Fetch a single tag by primary key. Returns `None` if not found.
88    pub fn get_tag(&self, id: TagId) -> crate::error::Result<Option<Tag>> {
89        let mut stmt = self
90            .conn()
91            .prepare_cached("SELECT * FROM tags WHERE id = ?")?;
92        Ok(stmt.query_row([id], Tag::from_row).optional()?)
93    }
94
95    /// Find a tag ID by `name` and optional `platform` (exact match).
96    pub fn find_tag(
97        &self,
98        name: &str,
99        platform: Option<PlatformId>,
100    ) -> crate::error::Result<Option<TagId>> {
101        let mut stmt = self
102            .conn()
103            .prepare_cached("SELECT id FROM tags WHERE platform IS ? AND name = ?")?;
104        Ok(stmt
105            .query_row(rusqlite::params![platform, name], |row| row.get(0))
106            .optional()?)
107    }
108}