2025-10-18 03:26:42 +00:00
|
|
|
from typing import AsyncIterator, ClassVar, List, Optional
|
|
|
|
|
|
2025-10-18 05:44:36 +00:00
|
|
|
from common import ApiModel
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-01-13 07:18:25 +00:00
|
|
|
|
2025-10-18 05:44:36 +00:00
|
|
|
class Person(ApiModel):
|
2025-10-18 03:26:42 +00:00
|
|
|
KEYS: ClassVar[List[str]] = ["id", "name"]
|
2024-05-17 09:09:03 +00:00
|
|
|
|
2024-05-20 10:09:57 +00:00
|
|
|
id: int = -1
|
2024-01-13 07:18:25 +00:00
|
|
|
name: str
|
|
|
|
|
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-01-13 07:18:25 +00:00
|
|
|
async def create(conn):
|
2025-10-18 03:26:42 +00:00
|
|
|
await conn.execute(
|
|
|
|
|
"""
|
2024-01-13 08:44:07 +00:00
|
|
|
CREATE TABLE IF NOT EXISTS Person (
|
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
|
name TEXT UNIQUE
|
2025-10-18 03:26:42 +00:00
|
|
|
);"""
|
|
|
|
|
)
|
2025-10-19 02:12:28 +00:00
|
|
|
# Useful indexes for search and pagination
|
|
|
|
|
await conn.execute("CREATE INDEX IF NOT EXISTS idx_person_name ON Person(name);")
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-04-25 02:03:30 +00:00
|
|
|
|
2024-05-13 03:59:46 +00:00
|
|
|
async def search_by_name(conn, name: str) -> AsyncIterator[Person]:
|
2025-10-18 03:26:42 +00:00
|
|
|
async with conn.execute(
|
|
|
|
|
"""
|
2024-05-04 04:22:19 +00:00
|
|
|
SELECT id, name
|
|
|
|
|
FROM Person
|
|
|
|
|
WHERE name LIKE ?
|
2025-10-18 03:26:42 +00:00
|
|
|
""",
|
|
|
|
|
(f"%{name}%",),
|
|
|
|
|
) as cursor:
|
2024-05-04 04:22:19 +00:00
|
|
|
async for row in cursor:
|
|
|
|
|
yield Person(id=row[0], name=row[1])
|
|
|
|
|
|
2025-10-18 03:26:42 +00:00
|
|
|
|
|
|
|
|
async def get_by_name(conn, name: str) -> Optional[Person]:
|
|
|
|
|
cursor = await conn.execute(
|
|
|
|
|
"""
|
2024-04-25 02:03:30 +00:00
|
|
|
SELECT id, name
|
|
|
|
|
FROM Person
|
|
|
|
|
WHERE name = ?
|
2025-10-18 03:26:42 +00:00
|
|
|
""",
|
|
|
|
|
(name,),
|
|
|
|
|
)
|
2024-04-25 02:03:30 +00:00
|
|
|
row = await cursor.fetchone()
|
|
|
|
|
if not row:
|
|
|
|
|
return None
|
|
|
|
|
return Person(id=row[0], name=row[1])
|
2024-01-13 07:18:25 +00:00
|
|
|
|
2025-10-18 03:26:42 +00:00
|
|
|
|
|
|
|
|
async def get_by_id(conn, id: int) -> Optional[Person]:
|
|
|
|
|
cursor = await conn.execute(
|
|
|
|
|
"""
|
2024-01-13 08:44:07 +00:00
|
|
|
SELECT id, name
|
|
|
|
|
FROM Person
|
|
|
|
|
WHERE id = ?
|
2025-10-18 03:26:42 +00:00
|
|
|
""",
|
|
|
|
|
(id,),
|
|
|
|
|
)
|
2024-01-13 08:44:07 +00:00
|
|
|
row = await cursor.fetchone()
|
|
|
|
|
if not row:
|
|
|
|
|
return None
|
|
|
|
|
return Person(id=row[0], name=row[1])
|
|
|
|
|
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2025-10-19 02:12:28 +00:00
|
|
|
async def get_by_ids(conn, ids: List[int]) -> dict[int, Person]:
|
|
|
|
|
"""Fetch many persons in a single query. Returns a dict id->Person.
|
|
|
|
|
|
|
|
|
|
If ids is empty, returns {}.
|
|
|
|
|
"""
|
|
|
|
|
if not ids:
|
|
|
|
|
return {}
|
|
|
|
|
placeholders = ",".join(["?"] * len(ids))
|
|
|
|
|
query = f"""
|
|
|
|
|
SELECT id, name
|
|
|
|
|
FROM Person
|
|
|
|
|
WHERE id IN ({placeholders})
|
|
|
|
|
"""
|
|
|
|
|
result: dict[int, Person] = {}
|
|
|
|
|
async with conn.execute(query, ids) as cursor:
|
|
|
|
|
async for row in cursor:
|
|
|
|
|
p = Person(id=row[0], name=row[1])
|
|
|
|
|
result[p.id] = p
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2024-05-13 03:59:46 +00:00
|
|
|
async def get_all(conn) -> AsyncIterator[Person]:
|
2025-10-18 03:26:42 +00:00
|
|
|
async with conn.execute(
|
|
|
|
|
"""
|
2024-01-13 08:44:07 +00:00
|
|
|
SELECT id, name
|
|
|
|
|
FROM Person
|
2025-10-18 03:26:42 +00:00
|
|
|
"""
|
|
|
|
|
) as cursor:
|
2024-01-13 08:44:07 +00:00
|
|
|
async for row in cursor:
|
|
|
|
|
yield Person(id=row[0], name=row[1])
|
|
|
|
|
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2025-10-18 05:44:36 +00:00
|
|
|
async def get_all_paged(conn, after_id: Optional[int], limit: int) -> AsyncIterator[Person]:
|
|
|
|
|
after = after_id if after_id is not None else -1
|
|
|
|
|
async with conn.execute(
|
|
|
|
|
"""
|
|
|
|
|
SELECT id, name
|
|
|
|
|
FROM Person
|
|
|
|
|
WHERE id > ?
|
|
|
|
|
ORDER BY id
|
|
|
|
|
LIMIT ?
|
|
|
|
|
""",
|
|
|
|
|
(after, limit),
|
|
|
|
|
) as cursor:
|
|
|
|
|
async for row in cursor:
|
|
|
|
|
yield Person(id=row[0], name=row[1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def search_by_name_paged(
|
|
|
|
|
conn, name: str, after_id: Optional[int], limit: int
|
|
|
|
|
) -> AsyncIterator[Person]:
|
|
|
|
|
after = after_id if after_id is not None else -1
|
|
|
|
|
async with conn.execute(
|
|
|
|
|
"""
|
|
|
|
|
SELECT id, name
|
|
|
|
|
FROM Person
|
|
|
|
|
WHERE name LIKE ? AND id > ?
|
|
|
|
|
ORDER BY id
|
|
|
|
|
LIMIT ?
|
|
|
|
|
""",
|
|
|
|
|
(f"%{name}%", after, limit),
|
|
|
|
|
) as cursor:
|
|
|
|
|
async for row in cursor:
|
|
|
|
|
yield Person(id=row[0], name=row[1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def count_all(conn) -> int:
|
|
|
|
|
cursor = await conn.execute(
|
|
|
|
|
"""
|
|
|
|
|
SELECT COUNT(1)
|
|
|
|
|
FROM Person
|
|
|
|
|
"""
|
|
|
|
|
)
|
|
|
|
|
row = await cursor.fetchone()
|
|
|
|
|
return int(row[0]) if row else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def count_by_name(conn, name: str) -> int:
|
|
|
|
|
cursor = await conn.execute(
|
|
|
|
|
"""
|
|
|
|
|
SELECT COUNT(1)
|
|
|
|
|
FROM Person
|
|
|
|
|
WHERE name LIKE ?
|
|
|
|
|
""",
|
|
|
|
|
(f"%{name}%",),
|
|
|
|
|
)
|
|
|
|
|
row = await cursor.fetchone()
|
|
|
|
|
return int(row[0]) if row else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def compute_prev_cursor(conn, first_id: int, limit: int, name: Optional[str] = None) -> Optional[str]:
|
|
|
|
|
"""Compute a prevCursor string for paginated persons, respecting optional name LIKE filter."""
|
|
|
|
|
if limit <= 0:
|
|
|
|
|
return None
|
|
|
|
|
if name:
|
|
|
|
|
query = """
|
|
|
|
|
SELECT id
|
|
|
|
|
FROM Person
|
|
|
|
|
WHERE name LIKE ? AND id < ?
|
|
|
|
|
ORDER BY id DESC
|
|
|
|
|
LIMIT ?
|
|
|
|
|
"""
|
|
|
|
|
params = (f"%{name}%", first_id, limit)
|
|
|
|
|
else:
|
|
|
|
|
query = """
|
|
|
|
|
SELECT id
|
|
|
|
|
FROM Person
|
|
|
|
|
WHERE id < ?
|
|
|
|
|
ORDER BY id DESC
|
|
|
|
|
LIMIT ?
|
|
|
|
|
"""
|
|
|
|
|
params = (first_id, limit)
|
|
|
|
|
|
|
|
|
|
async with conn.execute(query, params) as c:
|
|
|
|
|
prev_ids = [row[0] async for row in c]
|
|
|
|
|
if len(prev_ids) == limit and prev_ids:
|
|
|
|
|
return str(min(prev_ids) - 1)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
2024-04-28 03:37:01 +00:00
|
|
|
async def insert_person(conn, person: Person) -> Person:
|
2025-10-18 03:26:42 +00:00
|
|
|
cursor = await conn.execute(
|
|
|
|
|
"""
|
2024-01-13 08:44:07 +00:00
|
|
|
INSERT INTO Person (name)
|
|
|
|
|
VALUES (?)
|
2025-10-18 03:26:42 +00:00
|
|
|
""",
|
|
|
|
|
(person.name,),
|
|
|
|
|
)
|
2024-01-13 08:44:07 +00:00
|
|
|
person.id = cursor.lastrowid
|
2025-10-18 03:26:42 +00:00
|
|
|
return person
|