diff --git a/app/api/taxonomies.py b/app/api/taxonomies.py index 78ff2ab..9aaf891 100644 --- a/app/api/taxonomies.py +++ b/app/api/taxonomies.py @@ -15,14 +15,14 @@ from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel, ConfigDict -from sqlalchemy import and_, exists, func, select +from sqlalchemy import and_, case, exists, func, or_, select from sqlalchemy.orm import Session from app.auth import require_api_key from app.db import get_session from app.models.movie import Movie, MovieTag from app.models.movie_playback_source import MoviePlaybackSource -from app.models.performer import Performer +from app.models.performer import Performer, PerformerAlias from app.models.playback_source import PlaybackSource from app.models.scene import ScenePerformer, SceneTag from app.models.studio import Studio @@ -30,6 +30,37 @@ from app.models.tag import Tag from app.models.tag_vertical_count import TagVerticalCount from app.verticals import VERTICALS + +def _performer_name_patterns(q: str) -> list[str]: + qn = " ".join(q.lower().split()) + pats = [f"%{qn}%"] + words = qn.split(" ") + if len(words) == 2: + pats.append(f"%{words[1]} {words[0]}%") + return pats + + +def _performer_name_rank(q: str): + """0 = trafienie w nazwie, 1 = tylko w aliasie; sortujemy po tym przed liczba scen, + inaczej "anna" dawalo na gorze duze nazwiska z aliasem "Adrianna".""" + return case((or_(*[Performer.name_normalized.ilike(p) for p in _performer_name_patterns(q)]), 0), else_=1) + + +def _performer_name_clause(q: str): + """Szukanie wykonawcy: podciag nazwy ALBO aliasu, a dla dwoch slow tez w odwrotnej + kolejnosci. Zgloszenie 436121fb: "Takase Rina" (japonska kolejnosc) nie znajdowalo + "Rina Takase" i wygladalo, jakby jej nie bylo. Obie kolumny maja indeksy trigramowe.""" + pats = _performer_name_patterns(q) + return or_( + *[Performer.name_normalized.ilike(p) for p in pats], + exists( + select(1).where( + PerformerAlias.performer_id == Performer.id, + or_(*[PerformerAlias.alias_normalized.ilike(p) for p in pats]), + ) + ), + ) + log = logging.getLogger(__name__) router = APIRouter(tags=["taxonomies"], dependencies=[Depends(require_api_key)]) @@ -294,14 +325,15 @@ def list_performers( .join(v_count, v_count.c.performer_id == Performer.id) ) if q: - base = base.where(Performer.name_normalized.ilike(f"%{q.lower()}%")) + base = base.where(_performer_name_clause(q)) total = session.execute( select(func.count()).select_from(base.subquery()) ).scalar_one() + rank = [_performer_name_rank(q)] if q else [] if order in ("scene_count", "popular"): - ordered = base.order_by(v_count.c.c.desc(), Performer.canonical_name.asc()) + ordered = base.order_by(*rank, v_count.c.c.desc(), Performer.canonical_name.asc()) else: - ordered = base.order_by(Performer.canonical_name.asc()) + ordered = base.order_by(*rank, Performer.canonical_name.asc()) rows = session.execute( ordered.offset((page - 1) * per_page).limit(per_page) ).all() @@ -323,18 +355,19 @@ def list_performers( # 3M scene_performers + EXISTS playback per-request. Patrz migracja 0019. base = select(Performer) if q: - base = base.where(Performer.name_normalized.ilike(f"%{q.lower()}%")) + base = base.where(_performer_name_clause(q)) total = session.execute( select(func.count()).select_from(base.subquery()) ).scalar_one() + rank = [_performer_name_rank(q)] if q else [] if order in ("scene_count", "popular"): ordered = base.order_by( - Performer.scene_count.desc(), Performer.canonical_name.asc() + *rank, Performer.scene_count.desc(), Performer.canonical_name.asc() ) else: - ordered = base.order_by(Performer.canonical_name.asc()) + ordered = base.order_by(*rank, Performer.canonical_name.asc()) perfs_page = session.execute( ordered.offset((page - 1) * per_page).limit(per_page)