perf: denormalize content-warning filter to manga_content_warnings
The content-warning include/exclude filter (and its count) ran a correlated page_content_warnings->pages->chapters join per candidate manga — O(mangas * pages) on every filtered list. Add a manga_content_warnings(manga_id, warning) table holding each manga's deduped warning union, maintained by triggers that recompute an affected manga's set (delete+reinsert of the tiny five-label set, robust across page/chapter/manga cascade deletes), backfilled in the migration. The filter is now a single indexed lookup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -127,6 +127,46 @@ async fn list_filters_by_content_warning(pool: PgPool) {
|
||||
assert_eq!(ids, want);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn denormalized_warnings_track_chapter_deletion(pool: PgPool) {
|
||||
// The denormalized manga_content_warnings table must stay in sync when the
|
||||
// underlying pages disappear. Deleting the only chapter (which cascade-
|
||||
// deletes its pages and their page_content_warnings) must drop the manga
|
||||
// from the include filter.
|
||||
let h = common::harness(pool.clone());
|
||||
let gory = seed_manga(&pool, "Gory", &[&["gore"]]).await;
|
||||
|
||||
// Present in the denorm table and matched by the filter.
|
||||
let count: i64 =
|
||||
sqlx::query_scalar("SELECT count(*) FROM manga_content_warnings WHERE manga_id = $1")
|
||||
.bind(gory)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(count, 1, "warning denormalized on insert");
|
||||
assert_eq!(list_ids(&h.app, "cw_include=gore").await, vec![gory.to_string()]);
|
||||
|
||||
// Delete the chapter → cascade removes pages + page_content_warnings →
|
||||
// trigger recomputes the (now empty) set for this manga.
|
||||
sqlx::query("DELETE FROM chapters WHERE manga_id = $1")
|
||||
.bind(gory)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let count: i64 =
|
||||
sqlx::query_scalar("SELECT count(*) FROM manga_content_warnings WHERE manga_id = $1")
|
||||
.bind(gory)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(count, 0, "warning removed after chapter (and pages) deleted");
|
||||
assert!(
|
||||
list_ids(&h.app, "cw_include=gore").await.is_empty(),
|
||||
"manga no longer matches the include filter"
|
||||
);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn list_rejects_unknown_warning(pool: PgPool) {
|
||||
let h = common::harness(pool.clone());
|
||||
|
||||
Reference in New Issue
Block a user