Closed LecronRu closed 2 months ago
Как раз для таких случаев сделаны функции nm.query_only
и nm.query_and_params
. Через них можно выкрутиться как-то так:
@nm.sql_fetch_all(DbUser)
def get_some_users(salary_limit: int, names: typing.Collection[str]):
if not names:
raise nm.CancelExecException
return nm.query_and_params(
f"""
SELECT rowid AS id, username, email
FROM users
WHERE salary < :salary_limit
AND username IN ({", ".join(":n_"+str(i) for i in range(len(names)))})
""",
salary_limit=salary_limit,
**{f"n_{i}": v for i, v in enumerate(names)},
)
Если выписывание таких конструкций начёт напрягать, можно приделать пару удобняшек:
def list_placeholders(prefix: str, vals: typing.Collection[typing.Any]) -> str:
return ", ".join(f":{prefix}{i}" for i in range(len(vals)))
def list_vals(
prefix: str, vals: typing.Collection[typing.Any]
) -> dict[str, typing.Any]:
return {f"{prefix}{i}": v for i, v in enumerate(vals)}
после чего оно станет поприятнее:
@nm.sql_fetch_all(DbUser)
def get_some_users(salary_limit: int, names: typing.Collection[str]):
if not names:
raise nm.CancelExecException
return nm.query_and_params(
f"""
SELECT rowid AS id, username, email
FROM users
WHERE salary < :salary_limit
and username in ({list_placeholders("n_", names)})
""",
salary_limit=salary_limit,
**list_vals("n_", names),
)
Конкретно с WHERE ... IN
у нас проблем нет, поскольку Постгрес понимает параметры, являющиеся tuple. Но для гибкой фильтрации или для гибких UPDATE-ов манипулирование текстом запроса очень пригождается. Тем не менее, какого-то повторяющегося паттерна, чтобы намутить даже хотя бы project-specific удобняшек, пока что не обнаружили.
Боюсь, что если начать прикручивать или самодельный шаблонизатор для SQL-ек, или подключить какой-то из уже имеющихся (например, jinja), можно открыть портал в ад, и через сравнительно небольшое количество итераций обнаружить, что маленькая, простая и элегантная штучка превратилась в монстра с кучей неочевидно как работающих фич и документацией на 500 страниц. Может быть, пока есть смысл сделать паузу и поднабрать статистику, что конкретно было бы наиболее востребовано, и тогда, возможно, придумать элегантное решение. Мета-плейсхолдеры – весьма неплохая идея. Есть смысл взять на заметку. Спасибо.
А вообще мне подумалось, что адаптеры для SQLite (noorm.sqlite3 и noorm.aiosqlite) уже и так имеют доп. фичу, которая без лишних телодвижений интерпретирует строки как date, datetime и Decimal.
Так что мне нравится идея приделать прозрачный, беспроблемный и понятно как работающий parameters propagation чисто для SQLite. Сделаю в релизе 0.1.3.
Сделал. Теперь (в версии 0.1.3) можно передавать коллекции в параметры. Как в именованные (в стиле :ids
), так и позиционные (в стиле ?
). Работает прозрачно, как будто движок SQLite научился такое понимать:
@nm.sql_fetch_all(
DbUser, "SELECT rowid AS id, username, email FROM users WHERE salary < :salary_limit AND name IN (:names)"
)
def get_user_by_condition(salary_limit: int, names: list[str]):
return nm.params(salary_limit=salary_limit, names=names)
(т.е. звёздочки не нужно)
Попробуйте пожалуйста, работает ли оно как ожидалось.
Done in v0.1.3
Нечасто, но встречается задача subj. Когда доступ через execute(select), запрос модифицируется до нужного количества плейсхолдеров, в соответсвии с количеством элементов последовательности (списка). Как это решить с помощью вашей библиотеки? Ведь запрос уже является частью декоратора.
Хотелось бы прозрачности, похожей на ORM. Список является одиночным параметром, а в запросе используется мета-плейсхолдер. Например: