Перейти к содержанию

Создание диалекта: гарантии, тестирование и наблюдаемость

Шаги 11–13 создания диалекта: что hare делает за базу с меньшими гарантиями, тестирование диалекта и наблюдение за ним.

11. Что hare делает для базы без гарантий

У базы нет hare
Транзакций Отклоняет atomic(); свои записи из нескольких команд выполняет по одной команде.
Точек сохранения Присоединяет вложенный atomic() к транзакции, которая откатывается целиком, если вложенный блок завершился ошибкой.
Генерируемых ключей Отклоняет при привязке модель, чей первичный ключ генерирует база, — или берёт её ключи из серии до вставки (takes_keys_before_insert).
UPDATE / DELETE сохранённых строк Отклоняет каждую такую команду до отправки; весь каскад проверяется до первой записи, поэтому база без транзакций никогда не остаётся с половиной каскада.
Связанных подзапросов Пишет EXISTS, связанный равенствами колонок, как проверку принадлежности; любой другой связанный подзапрос отклоняет до отправки.
Внешних ключей Выполняет on_delete и PROTECT в Python; отклоняет связь с db_constraint=True при привязке её модели.
Ограничений уникальности Отклоняет модель, объявившую уникальность, при привязке, а миграцию, объявляющую её, — до её первой команды.
ON CONFLICT Отклоняет параметры конфликтов bulk_create() до отправки SQL (supports_insert_conflicts).
RETURNING UPDATE не читает изменённые вычисляемые колонки; значения, которые база даёт записанной строке, остаются в объекте непрочитанными (их читает refresh_from_db()); returning() отклоняется.
Числа записанных строк update(), delete() и bulk_update() возвращают None; модель с Meta.optimistic_lock_field отклоняется при привязке (reports_written_row_count).
Блокировок строк Отклоняет select_for_update().
Отложенных ограничений TableClearing.defer_cascade_foreign_keys() даёт UnSupportedError — настоящее удаление через защиту PROTECT в том же каскаде нельзя отложить.
Первичного ключа Поддерживает модели без ключа, как описано выше.

Features.checks_foreign_keys_per_cascade_step, checks_restrict_at_statement_end и cascade_depth_limit описывают, как ведёт себя собственный каскад базы, чтобы hare знал, когда откладывать его или выполнять самому. Клиент, у которого в возможностях задан cascade_depth_limit, даёт CascadeDepthLimitError из hare.exceptions — или свой подкласс, как SqliteTriggerRecursionLimitError у SQLite, — когда база останавливает каскад на этой глубине; delete() перехватывает её, откатывает попытку и проходит каскад в Python.

12. Тестирование

Собственный набор тестов hare работает на диалекте: установите пакет (его точка входа регистрирует драйвер) и направьте на него HARE_TEST_DB (colstore+colstore-client://.../test_{}, место {} заполняется при каждом запуске) — выполнится каждый тест, который допускают возможности диалекта. Тест того, чего у базы может не быть, помечается нужными ему возможностями:

from hare.contrib.test import requires_features

@requires_features(supports_transactions=True)
async def test_rollback(db): ...

@requires_features(supports_foreign_keys=True)
async def test_cascade_in_the_database(db): ...

tests/dialects/test_dialect_contract.py проверяет каждый зарегистрированный диалект и драйвер: имена, схемы, возможности, то, что каждая часть построена для своего диалекта, реестры, редактор схемы, чтение схемы и то, что SQL строится одинаково при повторе. Между тестами база очищается через TableClearing.clear_tables().

13. Наблюдение и скорость

otel_system_name — значение db.system.name в OpenTelemetry (postgresql, clickhouse или other_sql для базы, которой OpenTelemetry имени не даёт). Чтение строк на Rust зависит от native_python_types клиента, а не от имени диалекта. Каждая команда, которую выполняет клиент, доходит до Observers — QueryExecuted, обёртки запросов, журнал медленных запросов — через декоратор, описанный в разделе о клиенте; клиент, который его пропускает, невидим для capture_queries, поиска N+1 и OpenTelemetry.