Диалекты и их возможности¶
Диалект — это язык SQL, типы колонок, команды создания и изменения схемы и системный каталог одного
вида баз данных; драйвер — то, как hare к такой базе подключается. У одного диалекта может быть
несколько драйверов — у PostgreSQL их два. engine подключения (или схема в адресе базы — это одно и то
же слово) выбирает драйвер, а вместе с ним и диалект. См. PostgreSQL:
драйверы.
| Диалект | engine / схема адреса |
Драйвер | Самая старая версия сервера |
|---|---|---|---|
sqlite |
sqlite+aiosqlite |
aiosqlite поверх стандартного модуля sqlite3 |
SQLite 3.35.5 |
postgresql |
postgresql |
rust_pg, драйвер hare на Rust |
PostgreSQL 14 |
postgresql |
postgresql+asyncpg |
asyncpg (hare-orm[asyncpg]) |
PostgreSQL 14 |
clickhouse |
clickhouse+clickhouse-connect |
clickhouse-connect, по HTTP (hare-orm[clickhouse-connect]) — ClickHouse |
ClickHouse 25.8 |
clickhouse |
clickhouse+clickhouse-driver |
clickhouse-driver, по родному протоколу поверх TCP (hare-orm[clickhouse-driver]) — ClickHouse |
ClickHouse 25.8 |
aiosqlite выполняет каждое подключение SQLite в отдельном потоке, и этот поток не удерживает
интерпретатор: программа, которая завершается с незакрытым подключением (например, из-за исключения),
выходит, а не зависает, а журнал SQLite откатывает незавершённую транзакцию.
Всё, что hare делает по-разному для разных баз, — синтаксис SQL, типы колонок, как значение записывается
и читается, команды изменения схемы, чтение существующей схемы, — решают диалект подключения и его
Features, а не сравнение названий баз. Запрос выполняется на диалекте того подключения, на котором он
работает (using(), транзакция, маршрутизатор или подключение модели по умолчанию), поэтому одну модель
можно читать и из SQLite, и из PostgreSQL в одном процессе.
Страницы каждой базы¶
Что одна база делает по-своему, описано в её папке:
- SQLite: Подключение, Функции, Что работает иначе.
- PostgreSQL: Подключение, Поля,
Индексы и таблицы, Функции,
Сходство по триграммам и
unaccent, LISTEN/NOTIFY, Что работает иначе, Тестирование. - ClickHouse: Подключение, Модели, Типы, Модификаторы запросов, Агрегаты, Объекты схемы, Транзакции и блокировки, Что работает иначе, Тестирование.
Версия сервера¶
Сразу после открытия соединения hare читает версию сервера — так, что этого не видят наблюдатели
(драйверы PostgreSQL читают server_version_num, SQLite сообщает версию своей библиотеки). Сервер старше
minimum_server_version своего диалекта отклоняется до выполнения чего-либо ещё, а соединение
закрывается:
Возможность, которая появилась только в какой-то версии, определяется по собственной версии сервера: у
подключения к PostgreSQL 14 features.supports_nulls_distinct выключено, поэтому
UniqueConstraint(..., nulls_distinct=False) на нём даёт UnSupportedError до отправки команд создания
схемы. По той же причине Hare.generate_schemas() подключается к базе, прежде чем записывать команды.
connection.dialect и connection.features¶
connection = Book.get_connection()
connection.dialect.name # "postgresql"
connection.dialect.features.supports_copy # True
connection.features.supports_nulls_distinct # True у PostgreSQL 15 и новее
connection.dialect — объект диалекта, один на диалект и общий для всех подключений, которые на нём
работают; connection.dialect.features — что поддерживает база, одинаково для всех подключений к ней.
connection.features — неизменяемый Features этого подключения: возможности диалекта вместе с тем, что
добавляет драйвер и меняет версия сервера; значения фиксируются после подключения. hare читает
connection.features везде, где у него есть подключение.
Features¶
| Поле | Что означает | sqlite |
postgresql |
postgresql+asyncpg |
clickhouse |
|---|---|---|---|---|---|
supports_transactions |
Транзакции | да | да | да | с transactions=true — см. Транзакции и блокировки |
can_rollback_ddl |
Команды изменения схемы выполняются внутри транзакции и откатываются вместе с ней | да | да | да | нет |
supports_select_for_update |
SELECT ... FOR UPDATE |
нет | да | да | нет |
supports_select_for_no_key_update |
SELECT ... FOR NO KEY UPDATE |
нет | да | да | нет |
supports_select_for_share |
SELECT ... FOR SHARE |
нет | да | да | нет |
supports_select_for_key_share |
SELECT ... FOR KEY SHARE |
нет | да | да | нет |
supports_update_limit_order_by |
UPDATE/DELETE с ORDER BY и LIMIT |
нет | нет | нет | нет |
supports_posix_regex |
Операторы фильтра с регулярными выражениями POSIX | при install_regexp_functions=True |
да | да | нет |
supports_returning |
INSERT ... RETURNING |
да | да | да | нет |
reports_written_row_count |
База сообщает, сколько строк изменила запись; без этого update(), delete() и bulk_update() возвращают None, запись пропавшей строки не обнаруживается, а модель с Meta.optimistic_lock_field отклоняется при привязке |
да | да | да | нет |
supports_insert_conflicts |
INSERT ... ON CONFLICT — bulk_create(ignore_conflicts=..., update_fields=..., on_conflict=..., on_conflict_constraint=...); без этого эти параметры отклоняются |
да | да | да | нет |
supports_two_phase_commit |
PREPARE TRANSACTION / COMMIT PREPARED |
нет | да | да | нет |
supports_listen_notify |
LISTEN / NOTIFY |
нет | да | да | нет |
inline_comments |
Комментарии таблиц и колонок записываются внутри CREATE TABLE |
да | нет | нет | да |
supports_positional_rows |
Строки execute(rows_by_position=True) читаются по номеру колонки |
да | да | да | да |
supports_streaming |
QuerySet.stream() читает строки через курсор внутри транзакции |
да | да | да | да |
streams_without_transaction |
stream() читает строки и вне транзакции — сервер отдаёт строки по мере вычисления |
нет | нет | нет | да |
execute_many_scales_poorly |
executemany() медленнее одной команды на много строк |
нет | да | нет | да |
binds_written_parameters |
Драйвер передаёт строки, собранные встроенным записывающим кодом hare, как есть | нет | да | нет | нет |
max_bind_parameters |
Наибольшее число параметров в одной команде | 32766 | 32767 | 32767 | 1000000 |
max_expression_depth |
Наибольшая глубина выражения в одной команде (None — без предела); длинный список групп ключей через OR делится на несколько команд |
1000 | None |
None |
None |
max_bulk_update_rows |
Наибольшее число строк одной команды bulk_update() (None — сколько позволяют параметры) |
None |
None |
None |
1000 |
cascade_depth_limit |
Глубина рекурсии, на которой останавливается собственный ON DELETE CASCADE базы; тогда клиент даёт CascadeDepthLimitError, а глубокий каскад доводит до конца hare — меньшим числом уровней за раз |
SQLITE_LIMIT_TRIGGER_DEPTH сборки (1000; 100 у официальной библиотеки 3.37.2) |
нет | нет | нет |
supports_nulls_distinct |
Ограничение уникальности принимает NULLS [NOT] DISTINCT |
нет | с 15 | с 15 | нет |
explain_options |
Параметры, которые принимает explain(**options) |
нет | параметры версии сервера (GENERIC_PLAN с 16, MEMORY и SERIALIZE с 17) |
так же | нет |
supports_pool_status |
Клиент сообщает состояние и метрики своего пула — get_pool_status(), метрики пула |
да | да | да | clickhouse-driver — да, clickhouse-connect — нет |
Если возможности нет, операция, которой она нужна, даёт UnSupportedError с её названием до отправки
чего-либо: select_for_update() в SQLite, Transactions.distributed() без двухфазной фиксации,
stream() без потоковой передачи.
Возможности базы¶
Остальные поля Features принадлежат самой базе: их объявляет её диалект, и у каждого её драйвера
значения одинаковы.
| Поле | Что означает | sqlite |
postgresql |
clickhouse |
|---|---|---|---|---|
supports_schemas |
Миграции создают и удаляют схемы Meta.schema (CREATE SCHEMA); без этого схема модели пишется как есть и должна существовать — присоединённая база в SQLite (см. attached_databases), база сервера в ClickHouse |
нет | да | нет |
references_other_schemas |
Внешний ключ ссылается на таблицу другой схемы; без этого связь с db_constraint=True на модель другой схемы отклоняется при привязке модели |
нет | да | да |
supports_distinct_on |
SELECT DISTINCT ON (...) — distinct("field") выполняет его; без него distinct("field") отклоняется |
нет | да | нет |
supports_grouping_sets |
GROUP BY ROLLUP/CUBE/GROUPING SETS и GROUPING() — group_by(Rollup(...)), Grouping() |
нет | да | нет |
supports_lateral |
Подзапрос LATERAL во FROM — Lateral(queryset) |
нет | да | нет |
supports_table_sample |
Выборка таблицы во FROM — sample(): TABLESAMPLE, SAMPLE ClickHouse |
нет | да | да |
supports_asof_join |
ASOF LEFT JOIN — AsofJoin(...) |
нет | нет | да |
supports_array_join |
Каждая строка повторяется с каждым элементом своего массива — ArrayJoin(...) |
нет | нет | да |
supports_merge |
MERGE — merge() |
нет | да (15+) | нет |
supports_merge_returning |
MERGE ... RETURNING — merge().returning() |
нет | да (17+) | нет |
supports_merge_not_matched_by_source |
WHEN NOT MATCHED BY SOURCE — when_not_matched_by_source() |
нет | да (17+) | нет |
supports_enum_types |
CREATE TYPE ... AS ENUM — NativeEnumField |
нет | да | нет |
supports_views |
Meta.views и операции представлений — см. Представления, функции, последовательности и доступ |
нет | да | да |
supports_materialized_views |
Meta.materialized_views, RefreshMaterializedView, refresh_materialized_view() |
нет | да | да — см. Объекты схемы |
supports_dictionaries |
Meta.dictionaries — строки таблицы, загруженные для поиска по ключу, DictGet(...) |
нет | нет | да |
supports_database_functions |
Meta.functions и операции функций |
нет | да | нет |
supports_sequences |
Meta.sequences, операции последовательностей, get_next_sequence_value() |
нет | да | нет |
supports_row_level_security |
Meta.row_level_security, Meta.policies и их операции |
нет | да | нет |
supports_grants |
Meta.grants, AddGrant/RemoveGrant |
нет | да | нет |
sorts_nulls_first |
При сортировке по возрастанию NULL по умолчанию идёт раньше всех значений |
да | нет | нет |
guarantees_returning_order |
INSERT ... RETURNING на много строк возвращает строки в порядке вставки — bulk_create(returning=True) |
нет | да | нет |
divides_integers_exactly |
/ двух целых даёт точное частное, в том числе дробь, — поле вывода выражения тогда FloatField |
нет | нет | да |
computes_integer_statistics_exactly |
Avg(), StdDev() и Variance() от целых — точные десятичные числа, которые считает база, читаются как Decimal; без этого база считает их как числа с плавающей точкой |
нет | да | нет |
computes_decimal_statistics_exactly |
Avg(), StdDev() и Variance() от десятичных — точные десятичные числа, которые считает база, читаются как Decimal; без этого база считает их как числа с плавающей точкой |
да | да | нет |
keeps_enum_columns |
Колонка IntEnumField и CharEnumField — перечислимого типа, который хранит только элементы; без этого CHECK (EnumMembersCondition) ограничивает колонку значениями элементов |
нет | нет | да |
orders_json_values_in_aggregates |
Max()/Min() значения JSON сравнивают его так, как его упорядочивает база; без этого они отклоняются |
да | да | нет |
supports_uuid_v7 |
db_default, порождающий упорядоченный по времени UUID версии 7 (UuidV7()) |
нет | с 18 | нет |
supports_conflict_constraint_names |
bulk_create(on_conflict_constraint=...) |
нет | да | нет |
supports_conflict_where |
bulk_create(conflict_where=...) |
нет | да | нет |
supports_copy |
Массовая загрузка строк не текстом SQL — bulk_create(use_copy=True): протокол COPY на PostgreSQL, двоичная вставка на ClickHouse |
нет | да | да |
copies_bulk_inserts |
bulk_create() загружает строки так и без use_copy=True — когда не обрабатывает конфликты и не читает строки обратно |
нет | нет | да |
supports_virtual_generated_columns |
Вычисляемая колонка может вычисляться при чтении (stored=False); без него колонка VIRTUAL даёт UnSupportedError до DDL |
да | да (18+) | да (ALIAS) |
supports_without_overlaps |
UniqueConstraint(without_overlaps=True) и CompositePrimaryKey(without_overlaps=True) — WITHOUT OVERLAPS у последнего поля, диапазона |
нет | да (18+) | нет |
supports_returning_old_new |
returning(old=...) у update() и merge() — RETURNING old.column |
нет | да (18+) | нет |
supports_json_table |
JSON_TABLE — JsonTable(...) |
нет | да (17+) | нет |
supports_text_search_configurations |
Полнотекстовый поиск с конфигурациями, SearchVector как значением, лексемами, весами меток, нормализацией ранга и параметрами фрагментов разметки |
нет | да | нет |
supports_full_text_index |
FullTextIndex — таблица FTS5, которую держат в согласии триггеры и из которой читает полнотекстовый поиск, и веса полей у SearchRank |
да (SQLite, собранная с FTS5) | нет (поиск по tsvector) | нет |
supports_vector_search |
Расстояния и __nearby из hare.vectors — pgvector в PostgreSQL, sqlite-vec в SQLite |
с load_sqlite_vec=true |
да (pgvector) | нет |
supports_tenant_schemas |
Схема на арендатора — tenant_schema_template, Meta.tenant_schema, TenantSchemas |
нет | да | нет |
supports_spatial |
Пространственные операторы, пути, функции и агрегаты hare.gis |
с load_spatialite=true |
да (PostGIS) | да (гео-типы — см. Типы) |
supports_geography |
GeometryField(geography=True), измеряемое на эллипсоиде |
с пространственными метаданными SpatiaLite (spatialite_metadata не none) |
да | да (измеряется на сфере) |
spatial_reference_ids |
SRID пространственных метаданных, которые принимают geography и пространственный индекс, — None для любых |
из метаданных, читаются при открытии соединения | None |
None |
supports_ordered_aggregates |
ORDER BY внутри агрегата — MakeLine(order_by=...) |
да (3.44+) | да | нет |
isolation_levels |
Уровни изоляции, на которых выполняется транзакция, — см. Уровень изоляции | SERIALIZABLE |
все четыре | REPEATABLE READ — снимок на начало транзакции |
max_identifier_length |
Наибольшая длина имени в байтах. hare держит каждое сгенерированное имя в пределах 63 байт, укорачивая более длинное с хэшем, и не регистрирует диалект с меньшим пределом | нет | 63 | нет |
creates_tables_with_forward_references |
Таблица создаётся с внешним ключом на ещё не созданную — цикл внешних ключей создаётся таблица за таблицей, без разрыва | да | нет | нет |
supports_partial_indexes |
Индекс принимает условие WHERE |
да | да | нет |
supports_correlated_aggregates |
Связанный подзапрос с Count/Sum считает каждую строку один раз — см. Связанные подзапросы |
да | да | нет |
cuts_like_pattern_at_null_byte |
LIKE читает шаблон только до первого нулевого байта, поэтому шаблон с нулевым байтом отклоняется |
да | нет | нет |
supports_deferrable_constraints |
deferrable=True у ограничения или триггера |
нет | да | нет |
supports_index_key_order |
Ключ индекса сортируется по возрастанию или убыванию | да | да | нет |
collates_in_ordering_only |
Правило сравнения (Collate()) принимается только в ключе ORDER BY — distinct() выбирает значение и упорядочивает его по этому правилу |
нет | нет | да |
supports_index_nulls_order |
Ключ индекса задаёт место NULL |
нет | да | нет |
supports_concurrent_indexes |
Операции с индексами CONCURRENTLY |
нет | да | нет |
supports_not_valid_constraints |
Ограничение добавляется как NOT VALID и проверяется позже |
нет | да | нет |
supports_extensions |
CREATE EXTENSION |
нет | да | нет |
supports_collations |
CREATE COLLATION |
нет | да | нет |
supports_foreign_keys |
Внешние ключи проверяются базой; без этого каждое действие on_delete выполняет hare, а связь с db_constraint=True отклоняется при привязке её модели — см. Модели |
да | да | нет |
supports_unique_constraints |
Уникальность проверяется базой; без этого модель, объявившая её (unique=True, UniqueConstraint, Index(unique=True)), отклоняется при привязке |
да | да | нет |
supports_recursive_union_distinct |
Рекурсивный запрос WITH добавляет строки через UNION, каждую один раз; без этого каскад по дереву строк одной модели проходит уровень за уровнем |
да | да | нет |
table_alias_hides_name |
Таблица, прочитанная под псевдонимом, отзывается только на псевдоним; без этого подзапрос к таблице, которую читает внешний запрос, берёт строки из производной таблицы | да | да | нет |
truncates_values_on_type_change |
Сужающая смена типа молча обрезает значения; hare сначала проверяет данные | нет | да | да |
alters_indexed_columns |
Колонка под индексом меняет тип или допустимость NULL; без неё hare удаляет покрывающие индексы на время изменения и создаёт их заново | да | да | нет |
binds_array_parameters |
Список передаётся одним параметром-массивом (RawSQL("... = ANY(%s)", [ids])) |
нет | да | нет |
matches_ordering_to_grouping_by_sql |
Выражение сортировки, по которому идёт и группировка, записывается точно как в GROUP BY |
нет | да | нет |
checks_foreign_keys_per_cascade_step |
Внешний ключ NO ACTION проверяется после каждого вложенного шага ON DELETE CASCADE, а не один раз в конце команды — DELETE тогда падает на строке, которую охраняет связь on_delete=PROTECT, даже если тот же каскад позже удаляет охраняющую строку, пока ограничения PROTECT не отложены |
нет | да | нет |
checks_restrict_at_statement_end |
Внешний ключ RESTRICT проверяется в конце команды, как NO ACTION, а не сразу, до продолжения собственного каскада команды |
нет | нет | нет |
supports_savepoints |
Точки сохранения — вложенный atomic() |
да | да | нет |
supports_generated_keys |
База генерирует первичный ключ (IntField(primary_key=True)); без этого такая модель отклоняется при привязке |
да | да | с Keeper |
takes_keys_before_insert |
Ключи берутся из серии до записи строк — generateSerialID |
нет | нет | да |
supports_row_updates |
UPDATE хранимых строк |
да | да | да |
supports_row_deletes |
DELETE хранимых строк |
да | да | да |
supports_recursive_queries |
Рекурсивный запрос WITH — with_recursive(); без этого он даёт UnSupportedError |
да | да | да |
reads_trailing_comment_of_insert |
Комментарий в конце INSERT ... VALUES остаётся комментарием; без этого теги запроса ставятся перед такой командой — ClickHouse читает всё после VALUES как данные |
да | да | нет |
supports_correlated_subqueries |
Связанный подзапрос; без этого EXISTS, связанный равенством колонок, пишется как проверка IN, а любой другой отклоняется |
да | да | да |
rewrites_correlated_exists |
EXISTS, связанный равенством колонок (и строки колонок составного ключа), пишется как проверка IN и там, где связанные подзапросы выполняются, — собственный EXISTS сервер не выполняет в ON соединения и над UNION DISTINCT; связанный не только равенствами там — собственный EXISTS сервера, а в остальных случаях даёт UnSupportedError |
нет | нет | да |
supports_ordered_correlated_subqueries |
Связанный подзапрос сам сортирует и срезает свои строки (Subquery(... .order_by(...)[:1])) |
да | да | нет |
orders_by_correlated_subqueries |
Связанный подзапрос выполняется в ORDER BY и рядом с WHERE; без этого строки выбираются в производной таблице, а фильтруются, сортируются и срезаются снаружи |
да | да | нет |
Каждое поле описано в hare.dialects.base.features.Features. connection.dialect.minimum_server_version
— самая старая версия сервера, с которой работает hare: SQLite 3.35.5, PostgreSQL 14, ClickHouse 25.8.
Как диалект хранит таблицу — WITHOUT ROWID в SQLite, табличное пространство, UNLOGGED, параметры
хранения и партиционирование (хеш-, списочные и
диапазонные партиции, которые добавляют и удаляют миграции) в PostgreSQL, — задаётся для каждой модели
через Meta.table_options.
Возможности одной базы¶
Возможность, которая есть только у одной базы и которую читает только её диалект, — поле подкласса
Features этого диалекта: connection.features соединения с ней — его экземпляр, а у диалекта другой
базы такого поля нет.
| Класс | Поле | Смысл | Значение |
|---|---|---|---|
SqliteFeatures |
supports_unhex |
Функция SQL unhex() — длинный __in по байтам передаётся одним массивом JSON |
с 3.41.0 |
SqliteFeatures |
supports_strict_tables |
SqliteTableOptions(strict=True) — таблицы STRICT |
с 3.37 |
SqliteFeatures |
supports_spatial_index |
SpatialiteIndex — R*Tree SpatiaLite и пространственные операторы, сужаемые через него |
с пространственными метаданными SpatiaLite |
PostgresqlFeatures |
supports_partitioned_exclusion_constraints |
Партиционированная таблица принимает ограничение-исключение | с 17 |
ClickhouseFeatures |
supports_json_type |
Значение JSON хранится собственным типом с типизированными путями — JSONField становится колонкой JSON |
да (String через clickhouse-driver) |
DialectRegistry¶
from hare.dialects.dialect_registry import DialectRegistry
DialectRegistry.get_dialect("postgresql") # объект диалекта
DialectRegistry.get_driver("postgresql+asyncpg") # драйвер, который называет engine
DialectRegistry.get_driver_for_url_scheme("sqlite") # драйвер, который выбирает схема адреса
DialectRegistry.get_dialects(), DialectRegistry.get_drivers()
Неизвестное имя или схема дают ConfigurationError со списком зарегистрированных. Встроенный диалект или
драйвер импортируется и регистрируется, только когда его называет подключение или поиск, поэтому
Hare.init() на sqlite+aiosqlite:// не импортирует ничего от PostgreSQL, а Hare.init() на
postgresql:// — ничего от SQLite; get_dialects() перечисляет встроенные диалекты первыми, всегда в
одном порядке. Имя или схема, которых нет у встроенных драйверов, а также
get_dialects()/get_drivers() загружают каждый модуль, названный в группе точек входа hare.dialects
установленного пакета, — сторонний диалект регистрирует свои драйверы при импорте:
После этого его engine и схема адреса работают так же, как встроенные. Как написать свой диалект — в
руководстве Как написать диалект.
Регистрация после Hare.init()¶
Диалект, драйвер, способ вывода выражения или запись типа диалекта, оператор фильтра из
register_lookup(), часть пути внутри значения и метод QuerySet от диалекта можно регистрировать в
любой момент. Каждая регистрация сбрасывает всё, что hare сохранил из реестров, — SQL запросов, планы
чтения строк, описания фильтров и сортировок — и заново собирает фильтры каждой привязанной модели,
поэтому следующий запрос уже использует новое; запрос, построенный раньше, выполняется со своим SQL.
Обычно регистрация происходит при импорте, до Hare.init(), когда сохранённого ещё ничего нет и сброс
ничего не стоит.
Тесты по возможностям¶
requires_features() пропускает тест, если у подключения нет
нужного ему — поля Features, атрибута диалекта или имени диалекта: