Поиск расхождений¶
hare drift [app_labels...] [--connection ALIAS] [--schema SCHEMA] сравнивает текущую схему базы с
моделями: печатает каждое расхождение — операцию, которая его устранит, неизвестную таблицу или колонку,
колонку, которую не вернёт ни одна операция, — и завершается с кодом 1, если оно есть, и с кодом 0, если
нет. Без --connection проверяется первое настроенное подключение, без меток приложений — каждое
приложение на нём.
Вызов из кода¶
Команда — тонкая обёртка над публичной функцией библиотеки; вызывайте её напрямую (например, из проверки работоспособности или в CI), а не через командную строку:
from hare.migrations.drift import detect_drift_for_alias
async def detect_drift_for_alias(
apps: Apps,
apps_config: dict[str, dict[str, Any]],
connection_alias: str,
*,
schema: str | None = None,
app_labels: list[str] | None = None,
journal_tables: Collection[str] = (),
) -> DriftResult: ...
result = await detect_drift_for_alias(Hare.apps, apps_config, "default")
if result.has_drift:
for operation in result.operations:
print(operation.describe())
print(result.untracked_tables, result.untracked_columns, result.mismatched_columns)
Проверяется каждое приложение на подключении connection_alias: приложение из apps_config, у которого
default_connection равно connection_alias, а также приложение, зарегистрированное только во время
работы (например, через Hare.register_live_models()), модели которого привязаны к connection_alias.
app_labels сужает проверку до этих приложений. Модель с Meta.managed = False (в том числе
зарегистрированная во время работы) никогда не сравнивается, но её таблица считается известной и поэтому
не попадает в неизвестные; в тени есть только её таблица, для связей на неё, — без её триггеров,
представлений и других объектов (триггер INSTEAD OF модели над представлением на таблице не
создаётся). Колонка IDENTITY вне первичного ключа сохраняется в тени как обычная колонка,
чтобы связь с to_field могла на неё ссылаться. Модель без Meta.schema всегда ищется в схеме
подключения по умолчанию (current_schema() в PostgreSQL, то есть первая существующая схема в
search_path) — там, где её
таблицу создаёт migrate; модель с Meta.schema ищется в своей схеме — в своей подключённой базе в
SQLite, в своей базе сервера в ClickHouse. schema только выбирает схему, в которой ищутся неизвестные
таблицы (при None — схема по умолчанию), — подключённую базу в SQLite, базу сервера в ClickHouse. Схемы и
расширения, которые нужны только моделям приложений вне проверяемых, никогда не сообщаются. journal_tables
называет таблицы журналов миграций помимо hare_migrations — MigrationRecorder(table_name=...) миграций,
которые приложение выполняет само (Миграции без файлов): они считаются известными, а не неотслеживаемыми
(hare drift --journal-table NAME в командной строке). Ошибка ConfigurationError — для неизвестного
подключения, неизвестного элемента app_labels или подключения, которым не пользуется ни одно
приложение.
Смотрите DriftResult.operations/.untracked_tables/.untracked_columns/.mismatched_columns или
просто свойство .has_drift.
Что сравнивается¶
Схема, которую объявляют модели, строится рядом с базой — в тени, тем же SQL, который пишет
generate_schemas(), — затем база и тень читаются одним и тем же интроспектором и сравниваются по частям.
Обе стороны печатает сама база, поэтому в схеме, только что созданной migrate или generate_schemas(),
расхождений никогда нет, какими бы словами база ни переписала объявление, а любое изменение, сделанное
вручную, видно: типа колонки, допустимости NULL или значения по умолчанию (и даты, и документа JSON),
выражения индекса или условия частичного индекса, CHECK, тела или условия триггера, запроса
представления.
Тень¶
Тень удаляется в конце, что бы ни случилось, — и после ошибки:
| База | Тень | Нужные права |
|---|---|---|
| PostgreSQL | схема hare_shadow_<hex> — и hare_shadow_<hex>_<схема> для каждой другой схемы моделей — на отдельном подключении, первой в пути поиска, в транзакции, которая в конце откатывается: ничего из неё не фиксируется |
CREATE на базу (CREATE SCHEMA) |
| SQLite | база в памяти, открытая с настройками подключения, — и база в памяти под именем каждой базы, которую подключает соединение | никаких |
| ClickHouse | база hare_shadow_<hex> — и hare_shadow_<hex>_<база> для каждой другой базы моделей — на каждом сервере cluster, удаляется через DROP DATABASE ... SYNC; см. ниже |
CREATE DATABASE и DROP DATABASE |
На ClickHouse база тени — движка Replicated с путём Keeper /hare_shadow_<hex>/..., если такова база,
которую она заменяет, и таблица Replicated* со своим путём Keeper получает его под тем же префиксом — ни
одна таблица тени не становится репликой настоящей.
Для базы, у диалекта которой тени нет, — UnSupportedError.
Части и операции¶
Каждая часть сопоставляется по своей таблице и имени — имена объектов hare даёт сам (явные имена), поэтому объект, переименованный вручную, — это удаление объекта базы и добавление объявленного, а не угаданное переименование. Часть, которая есть только в тени, только в базе или читается на них по-разному, возвращается операцией, которая сделала бы базу такой, как объявляют модели, — построенной из объявления модели:
| Часть | Нет в базе | Есть только в базе | Читается иначе |
|---|---|---|---|
| таблица | CreateModel |
неизвестная таблица | — |
| колонка | AddField |
неизвестная колонка | AlterField |
| внешний ключ | AlterField его поля |
AlterField его поля |
AlterField его поля |
индекс самого поля (db_index, unique) |
AlterField |
— | AlterField |
запись Meta.indexes |
AddIndex |
— | RemoveIndex + AddIndex |
запись Meta.constraints |
AddConstraint |
— | RemoveConstraint + AddConstraint |
| индекс или ограничение, которых не объявляет ни одна модель | — | RemoveIndex, RemoveConstraint для уникального индекса или ограничения |
— |
| триггер, политика, последовательность | AddTrigger/AddPolicy/AddSequence |
RemoveTrigger/... |
AlterTrigger/... |
| представление, материализованное представление, функция, словарь, объявленные моделью | AddView/... |
— | AlterView/... |
| право | AddGrant |
RemoveGrant |
— |
| партиция | AddPartition |
RemovePartition |
RemovePartition + AddPartition |
| остальные параметры таблицы, комментарий таблицы | — | — | AlterModelOptions |
| защита строк | — | — | AlterRowLevelSecurity |
тип ENUM |
CreateEnumType |
— | AlterEnumType |
| схема, расширение | CreateSchema, CreateExtension |
— | — |
Комментарий колонки — часть колонки: другой даёт AlterField; SQLite хранит комментарии таблицы и
колонок внутри CREATE TABLE, откуда их читают drift и inspectdb.
Хранение таблицы — запись диалекта подключения в Meta.table_options;
у партиционированной таблицы, у которой
различаются только партиции, они добавляются и удаляются, у таблицы другой стратегии или ключа —
AlterModelOptions. Сами партиции никогда не считаются отдельными таблицами — ни в drift, ни в
inspectdb. Таблица, партиционированная так, как не объявляет ни одно партиционирование hare (по
выражению, с партицией, партиционированной в свою очередь), отличается от любой модели: её
AlterModelOptions.
Промежуточная таблица ManyToManyField, которой управляет hare, если её нет, или в ней нет одной из
колонок ключей или её ключа UNIQUE, — это AddField поля; если она отличается иначе — индексом ключа,
FOREIGN KEY, — его AlterField.
Два различия, которые не вернёт ни одна операция, — это ColumnMismatch (app_label, model_name,
table, column, detail) в .mismatched_columns: таблица, PRIMARY KEY которой содержит не те
колонки, что первичный ключ модели («is not in the table's PRIMARY KEY, the model's primary key holds
it»; в ClickHouse — PRIMARY KEY (id, v) при ключе модели id), и внешний ключ, объявленный через
swappable(), который ссылается на таблицу модели, на которую настройка указывала раньше, — его строки
называют строки той таблицы.
Из готового состояния: detect_drift()¶
Функция, которую она оборачивает, принимает уже построенное состояние: