# 2026-03-21 — pg-search: полнотекстовый поиск по title через ILIKE + LIMIT/OFFSET. # Тестирует: пагинацию, спецсимволы в input (XSS, SQL injection attempt → безопасно через параметры). import os, psycopg2, psycopg2.extras def search(event): # «query» — основной параметр (user-friendly), «q» — алиас для совместимости. query = str(event.get("query") or event.get("q") or "")[:200] # int() может упасть если юзер прислал строку — защищаем try/except. try: limit = max(1, min(int(event.get("limit", 20)), 100)) except (TypeError, ValueError): limit = 20 try: offset = max(0, int(event.get("offset", 0))) except (TypeError, ValueError): offset = 0 conn = psycopg2.connect( host=os.environ["PGHOST"], port=int(os.environ.get("PGPORT", 5432)), dbname=os.environ["PGDATABASE"], user=os.environ["PGUSER"], password=os.environ["PGPASSWORD"], sslmode=os.environ.get("PGSSLMODE", "require"), ) try: with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: pattern = f"%{query}%" if query else "%" cur.execute( "SELECT id, title, created_at::text FROM terraform_demo_table " "WHERE title ILIKE %s ORDER BY id DESC LIMIT %s OFFSET %s", (pattern, limit, offset), ) rows = [dict(r) for r in cur.fetchall()] cur.execute("SELECT COUNT(*) FROM terraform_demo_table WHERE title ILIKE %s", (pattern,)) total = cur.fetchone()["count"] return {"rows": rows, "count": len(rows), "total": total, "q": query, "limit": limit, "offset": offset} finally: conn.close()