feat(feed): accent-insensitive search (unaccent)
Feed text search used plain ILIKE, which is case- but not diacritic-insensitive, so 'tiesto' missed the many titles spelled 'Tiësto' — a search that ingested ~45 results showed only ~12. Enable the postgres unaccent extension (migration 0029) and wrap both sides of the title/ channel match in unaccent(), so 'tiesto' now matches 'Tiësto'. Applies to feed, count and facets alike.
This commit is contained in:
parent
a528e2c366
commit
a7a72c4c7e
2 changed files with 40 additions and 2 deletions
31
backend/alembic/versions/0029_unaccent_search.py
Normal file
31
backend/alembic/versions/0029_unaccent_search.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
"""accent-insensitive feed search (unaccent)
|
||||||
|
|
||||||
|
Revision ID: 0029_unaccent_search
|
||||||
|
Revises: 0028_live_search_provenance
|
||||||
|
Create Date: 2026-06-29
|
||||||
|
|
||||||
|
Enables the PostgreSQL `unaccent` contrib extension so the feed's text search can match
|
||||||
|
regardless of diacritics — e.g. searching "tiesto" finds titles spelled "Tiësto". The feed
|
||||||
|
query wraps both sides in unaccent(); this migration just makes the function available.
|
||||||
|
`unaccent` ships with the official postgres image (postgresql-contrib). The DB role is a
|
||||||
|
superuser in our deployments, so CREATE EXTENSION succeeds; IF NOT EXISTS keeps it idempotent.
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision: str = "0029_unaccent_search"
|
||||||
|
down_revision: Union[str, None] = "0028_live_search_provenance"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.execute("CREATE EXTENSION IF NOT EXISTS unaccent")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# Intentionally left in place: other code/queries may rely on unaccent() and dropping a
|
||||||
|
# contrib extension is rarely what's wanted on a downgrade. (The feed query reverts with
|
||||||
|
# the code, not this migration.)
|
||||||
|
pass
|
||||||
|
|
@ -205,8 +205,15 @@ def _filtered_query(
|
||||||
) + timedelta(days=1)
|
) + timedelta(days=1)
|
||||||
query = query.where(Video.published_at < end)
|
query = query.where(Video.published_at < end)
|
||||||
if q:
|
if q:
|
||||||
like = f"%{q}%"
|
# Accent-insensitive: unaccent() both sides so "tiesto" matches "Tiësto" (ILIKE alone
|
||||||
query = query.where(or_(Video.title.ilike(like), Channel.title.ilike(like)))
|
# is case- but not diacritic-insensitive). unaccent is enabled by migration 0029.
|
||||||
|
like = func.unaccent(f"%{q}%")
|
||||||
|
query = query.where(
|
||||||
|
or_(
|
||||||
|
func.unaccent(Video.title).ilike(like),
|
||||||
|
func.unaccent(Channel.title).ilike(like),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if tags:
|
if tags:
|
||||||
# AND across tag categories (e.g. language AND topic narrows), OR within a
|
# AND across tag categories (e.g. language AND topic narrows), OR within a
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue