diff --git a/tavern/src/database.rs b/tavern/src/database.rs index 4216e8d..ab79730 100644 --- a/tavern/src/database.rs +++ b/tavern/src/database.rs @@ -299,70 +299,82 @@ impl Database pub async fn get_tales_summary(&self, categories: &[String]) -> Result> { - let mut tales = Vec::new(); + let mut tales = Vec::new(); - // Start a read-only transaction. - let mut tx = self.pool.begin().await?; + // Start a read-only transaction. + let mut tx = self.pool.begin().await?; - // Dynamically build the query. - let mut query = String::from( - "SELECT - t.title, - t.slug, - t.summary, - t.author, - t.publish_date, - GROUP_CONCAT(tg.name, ',') AS tags - FROM tales AS t - LEFT JOIN tale_tags AS tt ON t.slug = tt.tale_slug - LEFT JOIN tags AS tg ON tt.tag_id = tg.id" - ); + // Build the base query string with placeholders for categories in the EXISTS clause. + // We add a parameter for categories length to check if filtering is needed. + let mut query = String::from( + "SELECT + t.title, + t.slug, + t.summary, + t.author, + t.publish_date, + GROUP_CONCAT(tg_all.name, ',') AS tags + FROM tales AS t + LEFT JOIN tale_tags AS tt_all ON t.slug = tt_all.tale_slug + LEFT JOIN tags AS tg_all ON tt_all.tag_id = tg_all.id + WHERE (? = 0 OR EXISTS ( + SELECT 1 FROM tale_tags tt_filter + JOIN tags tg_filter ON tt_filter.tag_id = tg_filter.id + WHERE tt_filter.tale_slug = t.slug + AND tg_filter.name IN (" + ); - if !categories.is_empty() - { - query.push_str(" WHERE tg.name IN ("); - let placeholders: Vec<_> = - (0..categories.len()).map(|_| "?").collect(); - query.push_str(&placeholders.join(", ")); - query.push(')'); - } + // Add placeholders for category names in EXISTS IN clause + if !categories.is_empty() { + let placeholders: Vec<_> = (0..categories.len()).map(|_| "?").collect(); + query.push_str(&placeholders.join(", ")); + } else { + // No categories, so dummy placeholder to satisfy SQL syntax + query.push_str("NULL"); + } + query.push_str("))) GROUP BY t.slug ORDER BY t.publish_date DESC"); - query.push_str(" GROUP BY t.slug ORDER BY t.publish_date DESC"); + // Prepare query with sqlx + let mut q = sqlx::query(&query); - let mut q = sqlx::query(&query); - for cat in categories - { - q = q.bind(cat); - } + // Bind the length of categories for the (? = 0) check + q = q.bind(categories.len() as i64); - let rows = q.fetch_all(&mut *tx).await?; - let current_time = chrono::Utc::now().naive_utc(); + // Bind the category names if any + for cat in categories { + q = q.bind(cat); + } - for row in rows - { - let date_str: String = row.try_get("publish_date")?; - let publish_date = chrono::NaiveDateTime::parse_from_str(&date_str, "%Y-%m-%d %H:%M:%S") - .map_err(|e| sqlx::Error::Decode(e.into()))?; + // Execute the query + let rows = q.fetch_all(&mut *tx).await?; + let current_time = chrono::Utc::now().naive_utc(); - // Only give tales that are ready to be published. - if current_time >= publish_date - { - let tags_str: Option = row.try_get("tags")?; - let tags = tags_str.map(|s| s.split(',').map(String::from).collect()) - .unwrap_or_default(); + for row in rows { + let date_str: String = row.try_get("publish_date")?; + let publish_date = chrono::NaiveDateTime::parse_from_str(&date_str, "%Y-%m-%d %H:%M:%S") + .map_err(|e| sqlx::Error::Decode(e.into()))?; - tales.push(Lore { title: row.try_get("title")?, - slug: row.try_get("slug")?, - summary: row.try_get("summary")?, - author: row.try_get("author")?, - publish_date, - tags }); - } - } + // Only include tales that are ready to be published. + if current_time >= publish_date { + let tags_str: Option = row.try_get("tags")?; + let tags = tags_str + .map(|s| s.split(',').map(String::from).collect()) + .unwrap_or_default(); - tx.commit().await?; // Explicit commit, even for read transactions. + tales.push(Lore { + title: row.try_get("title")?, + slug: row.try_get("slug")?, + summary: row.try_get("summary")?, + author: row.try_get("author")?, + publish_date, + tags, + }); + } + } - Ok(tales) + tx.commit().await?; // Explicit commit, even for read transactions. + + Ok(tales) } #[cfg(any(not(feature = "publisher"), feature = "tester"))]