clickhousedb), созданный на базе основного драйвера. Синхронный диалект поддерживает SQLAlchemy 1.4.40 и более поздние версии, включая SQLAlchemy 2.x, с акцентом на запросы Core, DDL ClickHouse, рефлексию и простые ORM-вставки. Для асинхронного диалекта требуется SQLAlchemy версии 2.0.44 или более поздней.
Установите зависимости SQLAlchemy с помощью дополнительного пакета:
Подключение через SQLAlchemy
Создайте движок, указав URL в форматеclickhousedb:// или clickhousedb+connect://:
Идентификаторы сеансов ClickHouse
По умолчанию каждое соединение из пула — как в синхронном, так и в асинхронном диалекте — генерирует собственный идентификатор сеанса ClickHouse. Если запросы через это соединение попадают в один и тот же процесс сервера ClickHouse, настройки, изменённые с помощьюSET, и временные таблицы сохраняются для этого соединения. Состояние именованного сеанса и проверка на пересечение запросов в одном сеансе локальны для процесса. В пределах одного процесса сервера пересекающийся по времени запрос от того же пользователя с тем же идентификатором сеанса не ставится в очередь, а сразу отклоняется с кодом ошибки сервера 373. Если вы задаёте фиксированный session_id, используйте pool_size=1, max_overflow=0 или сериализуйте доступ до того, как запросы попадут в ClickHouse. В ClickHouse Cloud и других развертываниях с балансировкой нагрузки запросы с одним и тем же идентификатором сеанса могут попадать на разные серверы, поэтому не используйте фиксированный session_id в качестве распределённого состояния или распределённого мьютекса.
Асинхронные соединения
Для асинхронного диалекта требуется SQLAlchemy версии 2.0.44 или новее; он использует нативныйAsyncClient из ClickHouse Connect. Установите необходимые зависимости и создайте асинхронный движок с URL clickhousedb+async://:
AsyncConnection.stream() вызывает InvalidRequestError. SQLAlchemy допускает вызов AsyncSession.stream(), однако диалект буферизует весь результат, прежде чем вернуть его. Для больших объёмов результатов используйте нативные потоковые методы AsyncClient. Исходный нативный клиент доступен через driver_connection, пока соответствующее соединение SQLAlchemy взято из пула:
client.close() или какие-либо его приватные методы жизненного цикла. Параллелизмом соединений управляет пул SQLAlchemy. Каждому соединению в пуле принадлежит один нативный асинхронный клиент, и по умолчанию коннектор aiohttp ограничен одним соединением в целом и одним соединением на хост. Чтобы переопределить эти транспортные настройки, задайте connector_limit, connector_limit_per_host или keepalive_timeout в URL или в connect_args. При pool_pre_ping=True SQLAlchemy проверяет повторно используемые соединения запросом SELECT 1 при извлечении соединения из пула.
Асинхронные вставки SQLAlchemy через executemany в настоящее время отправляют отдельный HTTP-запрос для каждого набора параметров вместо использования нативного протокола массовой вставки драйвера. Используйте этот способ только для небольших пакетов. Для больших объёмов данных применяйте описанный выше шаблон доступа через driver_connection, принадлежащий пулу, и дожидайтесь завершения client.insert() до возврата соединения SQLAlchemy в пул. Поскольку асинхронный executemany использует привязку параметров запроса, наивные значения datetime обрабатываются согласно настройке naive_datetime_binding, а не naive_datetime_insert, которая применяется в синхронном нативном executemany. Типизированные привязки SQLAlchemy DateTime64 сохраняют доли секунды как для клиентских, так и для серверных параметров. Нетипизированные параметры %s или %(name)s, передаваемые в exec_driver_sql(), по умолчанию форматируют наивные значения datetime с точностью до целых секунд. Для однозначной обработки часовых поясов используйте значения с явно указанным часовым поясом. Для нативной семантики массовой вставки используйте client.insert().
Создавайте и освобождайте асинхронный движок в том цикле событий, в котором он используется. Верните все извлечённые соединения, а затем дождитесь выполнения engine.dispose() при завершении работы и перед использованием движка из другого цикла событий. Если цикл, которому принадлежит движок, уже закрыт, перед повторным использованием дождитесь выполнения engine.dispose() в текущем цикле. aiohttp может сообщать о незакрытом транспорте, если очистка начинается уже после закрытия цикла-владельца, поэтому по возможности освобождайте движок до его передачи. pool_pre_ping=True не заменяет освобождение при переносе асинхронного движка с пулом между циклами событий. Чтобы использовать один движок в нескольких циклах событий, не удерживая привязанные к циклу соединения, задайте poolclass=NullPool. Если освобождение выполняется, пока соединение ещё не возвращено в пул, диалект закроет это соединение при его возврате или сборке мусора. Не вызывайте engine.sync_engine.dispose() из синхронного кода: там SQLAlchemy не может дождаться асинхронной очистки соединений и может лишь записать ошибку в журнал, не закрыв транспорты пула.
Параметры запроса в URL могут содержать настройки ClickHouse, параметры клиента ClickHouse Connect, такие как compression, query_limit и тайм-ауты, или параметры HTTP/TLS, такие как ca_cert. При необходимости добавьте к настройке ClickHouse префикс ch_, чтобы она принудительно обрабатывалась как настройка сервера, например ch_http_max_field_name_size=99999.
Доступные параметры клиента см. в разделе Аргументы соединения и настройки.
Синхронные вспомогательные операции SQLAlchemy, например DDL и инспекцию схемы, выполняйте через AsyncConnection.run_sync():
Настройки для отдельных запросов
Передавайте настройки ClickHouse через параметры выполнения SQLAlchemy. Настройки можно задавать на уровне движка, соединения или оператора. Если один и тот же ключ задан в нескольких местах, значение на уровне оператора имеет приоритет над значением на уровне соединения или движка.Форматы чтения для отдельных запросов
Задавайте форматы чтения ClickHouse для движка, соединения или оператора с помощью параметра выполнения SQLAlchemyquery_formats. Форматы оператора применяются первыми и переопределяют соответствующие ключи и подстановочные шаблоны соединения или движка.
Обработка ошибок
Ошибки, которые драйвер выбрасывает через соединение SQLAlchemy, представлены классами DB-API, экспортируемыми изclickhouse_connect.dbapi. Это те же самые объекты классов, что и соответствующие классы в clickhouse_connect.driver.exceptions, поэтому SQLAlchemy оборачивает их в соответствующий подкласс sqlalchemy.exc.DBAPIError. StreamFailureError является подклассом OperationalError и оборачивается в sqlalchemy.exc.OperationalError.
Если отмена на стороне вызывающего кода может прервать явный вызов AsyncConnection.invalidate(), выполняйте инвалидацию в отдельной собственной задаче и дожидайтесь её завершения, прежде чем передавать отмену дальше. Так SQLAlchemy сможет завершить служебный учёт записей соединений:
await connection.invalidate() был отменён, а connection.invalidated по-прежнему равно false, снова выполните await connection.invalidate(), чтобы завершить очистку, и только после этого используйте или закрывайте соединение.
Серверные параметры
SQLAlchemy обычно подставляет параметры на стороне клиента. Чтобы использовать серверные параметры ClickHouse, включите их при создании движка:server_side_params=True в create_async_engine().
В этом режиме каждому связанному значению должен соответствовать SQLAlchemy-тип, совместимый с ClickHouse. Поддерживаемые списки IN преобразуются в типизированные параметры ClickHouse Array. Компилятор выдаёт CompileError, если не может определить совместимый тип или безопасно обработать привязку.
Имена привязок должны быть ASCII-именами ClickHouse типа BareWord. Имена, которые начинаются и заканчиваются на $, отклоняются, поскольку основной драйвер резервирует их для параметров запроса в виде необработанных двоичных данных.
Основные запросы
Диалект поддерживает запросыSELECT в SQLAlchemy Core с JOIN, фильтрами, сортировкой, ограничением и смещением, а также DISTINCT и составные SELECT.
SQLAlchemy union(), intersect() и except_() компилируются в ClickHouse UNION DISTINCT, INTERSECT DISTINCT и EXCEPT DISTINCT. Их аналоги union_all(), intersect_all() и except_all() компилируются в соответствующие операторы ALL. Это явное сопоставление сохраняет семантику дубликатов SQLAlchemy независимо от настроек по умолчанию для операций над множествами в ClickHouse.
DELETE, требующий явного условия WHERE:
Подстановка литералов
Когда SQLAlchemy подставляет связанное значение в запрос черезliteral_binds или literal_execute, диалект использует правила экранирования ClickHouse для общих строковых типов и типов ClickHouse. Это также относится к обёрткам TypeDecorator и вариантам, выбранным с помощью with_variant(). Строковые значения сохраняют знаки процента и обратные косые черты, даже если другие связанные параметры не подставляются.
Значения Python datetime с типом SQLAlchemy ClickHouse DateTime64 сохраняют микросекунды как в параметрах на стороне клиента, так и во встроенных литералах, в том числе для значений Nullable и значений, вложенных в массивы и кортежи. ClickHouse применяет объявленную точность. Python datetime поддерживает до шести цифр в дробной части. Обычные значения DateTime форматируются с точностью до целых секунд. Для оператора text() явно укажите тип с помощью bindparam("ts", type_=DateTime64(6)), чтобы сохранить доли секунды.
Типы столбцов SQLAlchemy должны соответствовать схеме на сервере. Если объявить DateTime64 для серверного столбца типа DateTime, в запрос будут подставляться доли секунды, что может приводить к ошибкам преобразования при вставке и в сравнениях IN.
В SQLAlchemy 2.x для встроенных литералов общих типов sqlalchemy.ARRAY, содержащих элементы ClickHouse Tuple, необходимо указать dimensions=1 (или соответствующую большую размерность для вложенных массивов), чтобы SQLAlchemy рассматривал каждый кортеж как один элемент. SQLAlchemy 1.4 не поддерживает встроенные литералы для общих типов ARRAY.
Если именованный параметр даты и времени используется повторно, для сохранения долей секунды каждое его вхождение должно иметь совместимый тип привязки DateTime64. Вхождение без типа или с конфликтующим типом форматируется с точностью до целых секунд. Укажите type_=DateTime64(6) для каждого bindparam или используйте разные имена параметров с соответствующими типами.
Подсказки типов JSON
Типизированные JSON-пути объявляются через отображениеtyped_paths. Типом пути может быть класс типа ClickHouse SQLAlchemy, настроенный экземпляр или строка с именем типа ClickHouse. Строки с именами типов позволяют задавать типы, у которых нет конструктора SQLAlchemy, например Dynamic, и при этом подходят для сложных настроенных выражений типов. Они сохраняют имена в именованном Tuple.
Строки с именами типов могут содержать настроенные вложенные типы JSON, например Array(JSON(`child` UInt32)). Распознаваемые имена типов ClickHouse в таких строках регистронезависимы и выводятся в канонической капитализации. Строка должна содержать ровно одно полное выражение типа. Завершающий текст и некорректные вложенные аргументы JSON отклоняются.
Пустой Tuple() не поддерживается в качестве типизированного JSON-пути, так как ClickHouse не может сериализовать его через Native format JSON-столбца. Основной драйвер поддерживает Tuple() в столбцах запроса и вставки в любой позиции, в том числе вложенным в позиционные или именованные кортежи, внутри Array, а также как Nullable(Tuple()) там, где это включено на сервере.
typed_paths, например JSON(user_id=UInt32). Используйте typed_paths для путей с точками, пробелами, обратными кавычками, точками в кодировке %2E, а также для имён, совпадающих с параметрами конструктора. Типизированный путь с именем SKIP поддерживается через это отображение. Ключи в typed_paths и значения в skip_paths — это декодированные имена. Обратные и двойные кавычки в начале или в конце имени трактуются как обычные символы пути, а не как заранее применённое SQL-экранирование. Внутри необработанной строки типа обратные и двойные кавычки являются синтаксисом идентификаторов ClickHouse.
Можно задать до 1000 типизированных путей. max_dynamic_paths принимает значения от 0 до 10000, max_dynamic_types — от 0 до 254. Эти диапазоны действуют и внутри необработанных строк вложенных JSON-типов. Явные серверные значения по умолчанию 1024 и 32 в генерируемый DDL не включаются. Для обычных пропускаемых путей выполняется дедупликация. Строки регулярных выражений не проверяются средствами Python, поскольку ClickHouse использует синтаксис RE2. Дублирующиеся регулярные выражения сохраняются.
Обычный пропускаемый путь не может называться в точности REGEXP, так как ClickHouse резервирует этот токен для SKIP REGEXP. Имена вида REGEXP_foo остаются допустимыми. В необработанной строке JSON-типа операнд обычного SKIP должен быть одним идентификатором ClickHouse либо составным идентификатором, разделённым точками. Составной идентификатор без кавычек не может начинаться с REGEXP; заключайте первый компонент в кавычки, если он является данными пути. У SKIP REGEXP должен быть ровно один строковый литерал в одинарных кавычках. Части идентификатора заключайте в обратные или двойные кавычки, если они содержат пробелы или знаки пунктуации. Необработанные подсказки JSON-типа поддерживают Variant(...); для отдельного Variant публичного конструктора SQLAlchemy нет. Члены Variant упорядочиваются и дедуплицируются по тем же каноническим именам, которые использует ClickHouse.
Конструктор упорядочивает аргументы в той же канонической форме, которую возвращает ClickHouse. Отражённые типы, копии типов SQLAlchemy и автогенерация Alembic сохраняют эту конфигурацию.
Подстолбцы JSON
Для столбца, объявленного или представленного какJSON в ClickHouse, используйте квадратные скобки, чтобы выбирать по одному сегменту пути к подстолбцу, хранящемуся в базе:
payload["severity"] компилируется в синтаксис точечного идентификатора ClickHouse. Каждая часть заключается в кавычки отдельно, например `events`.`payload`.`severity`. При этом считывается сохранённый подстолбец JSON ClickHouse без вызова getSubcolumn. Для каждого сегмента пути последовательно применяйте [] или .subcolumn(). Каждый сегмент должен быть непустой строкой.
Передача type_ в .subcolumn() оборачивает точечный путь в SQL CAST и назначает этот тип выражению SQLAlchemy. Без type_ .subcolumn("segment") работает так же, как ["segment"].
Нетипизированный путь имеет тип Dynamic ClickHouse. ClickHouse не допускает использование значений Dynamic непосредственно в ORDER BY или GROUP BY. Передавайте type_, если подстолбец используется в них.
Для статически типизированного кода импортируйте json_subcolumn из clickhouse_connect.cc_sqlalchemy. Эта вспомогательная функция также принимает по одному сегменту за раз и сохраняет тип результата Python из type_:
request_id как ColumnElement[int].
Каждый сегмент заключается в кавычки отдельно, в том числе имена с пробелами или обратными кавычками. Обратные кавычки не делают точку литеральной при обработке JSON-путей в ClickHouse. Если включен json_type_escape_dots_in_keys, используйте кодирование ClickHouse %2E для литеральных точек в ключах. Обращайтесь к ключу с именем a.b через payload["a%2Eb"], а не через payload["a.b"].
Расширения запросов к ClickHouse
Импортируйтеselect из clickhouse_connect.cc_sqlalchemy, чтобы типизированные методы ClickHouse были доступны средствам статической проверки типов. Эти методы также доступны в стандартном sqlalchemy.select при выполнении.
Select:
Select.with_hint() в SQLAlchemy — это API подсказок для таблиц. Диалект ClickHouse не формирует подсказки для таблиц. Подходящая подсказка с wildcard или подсказка clickhousedb вызывает SAWarning и не изменяет сгенерированный SQL. Для этих секций ClickHouse используйте final(), sample(), prewhere() или limit_by().
Select.with_statement_hint() — это API для необработанных завершающих директив. Он добавляет переданный текст в конец SELECT без проверки, специфичной для ClickHouse. Этот API по-прежнему доступен для доверенного статического SQL, такого как SETTINGS max_threads=1:
GLOBAL ANY LEFT JOIN в ClickHouse можно вызывать по цепочке без вложения пользовательского FromClause:
Lambda для функций высшего порядка в ClickHouse:
values() компилируется в синтаксис табличной функции ClickHouse VALUES, в том числе при использовании в общем табличном выражении (CTE). Для формы CTE требуется SQLAlchemy 2.0.42 или более поздней версии, в которой был добавлен Values.cte().
Материализованные CTE
По умолчанию ClickHouse подставляет тело общего табличного выражения (CTE), поэтому при каждом обращении к CTE его тело выполняется заново. Передайтеmaterialized=True в .cte(), чтобы сгенерировать WITH <name> AS MATERIALIZED (...), при котором тело вычисляется один раз:
MATERIALIZED, значении enable_materialized_cte=1 и включенном analyzer. Установите enable_materialized_cte для оператора, подключения или движка, как показано в разделе Настройки для отдельных запросов. Analyzer по умолчанию включен на всех серверах, поддерживающих эту возможность, поэтому явная установка enable_analyzer=1 служит дополнительной мерой предосторожности. enable_materialized_cte — экспериментальная настройка ClickHouse. При enable_materialized_cte=0 или enable_analyzer=0 запрос успешно выполняется и возвращает те же строки. ClickHouse молча игнорирует MATERIALIZED и снова разворачивает CTE, поэтому пропущенная настройка снижает производительность без каких-либо сообщений. Для материализованных CTE требуется ClickHouse 26.3 или более поздней версии. Более старые серверы отклоняют ключевое слово с синтаксической ошибкой.
Для оператора, построенного с помощью стандартного sqlalchemy.select, вместо этого используйте cte() уровня модуля. В качестве первого аргумента она принимает оператор, а в остальном повторяет Select.cte():
ValueError, если одновременно заданы recursive=True и materialized=True.
DDL и рефлексия
ClickHouse Connect предоставляет типы данных ClickHouse, движки таблиц, конструкции для словарей, DDL для баз данных и рефлексию таблиц. Отдельные столбцыVariant отражаются через внутренний тип SQLAlchemy, а автогенерация Alembic сохраняет их канонические исходные имена типов без повторяющихся изменений типа. Столбцы Geometry и MultiPoint отражаются как публичные типы SQLAlchemy.
server_default для выражений DEFAULT, а также специфичные для диалекта атрибуты, такие как clickhouse_codec, clickhouse_ttl, clickhouse_materialized и clickhouse_alias, если они заданы.
Для строковых значений в секциях DEFAULT, MATERIALIZED, ALIAS и TTL используется экранирование строк ClickHouse. Такое же экранирование применяется к комментариям таблиц, словарей и столбцов, включая комментарии, создаваемые Alembic.
Аргументы ключа MergeTree, такие как order_by, partition_by, primary_key, sample_by и ttl, принимают столбцы SQLAlchemy, SQL-выражения, а также обычные строки.
Memory(), Log(), StripeLog(), TinyLog(), Null() и Set() можно вызывать без аргументов, и они без потерь проходят полный цикл автогенерации Alembic. Существующий аргумент-словарь по-прежнему поддерживается. Для передачи настроек движка используйте settings={...}.
SummingMergeTree и ReplicatedSummingMergeTree принимают необязательный аргумент columns, который можно передать только как именованный. Существующие позиционные аргументы сохраняют прежнее значение, поэтому SummingMergeTree("id") по-прежнему задаёт ORDER BY id.
"delta" или "(delta, n_tx)". Сервер допускает для этих столбцов только идентификаторы. Если не указывать columns, ClickHouse сам выберет столбцы для суммирования. Рефлексия и автогенерация Alembic сохраняют явно заданный список столбцов.
Вставка данных и базовое использование ORM
Поддерживаются вставки через Core и простые модели ORM. Для синхронного диалекта при массовой загрузке данных предпочтительнее использовать вставки через Core с executemany там, где это поддерживается. Для асинхронной массовой вставки используйте нативный методAsyncClient.insert(), описанный в разделе Асинхронные соединения.
executemany, сгенерированные компилятором SQLAlchemy, выполняются как одна массовая вставка в формате Native. Асинхронный executemany отправляет отдельный запрос для каждого набора параметров, как описано в разделе Асинхронные соединения. Raw SQL, а также вставки с выражениями или иной семантикой, которую нельзя безопасно перенаправить, сохраняют исходный SQL и выполняются отдельно для каждого набора параметров. Если один из последующих наборов параметров завершится с ошибкой, строки, записанные предыдущими наборами, останутся закоммиченными.
Явные многострочные операторы insert(events).values([...]) поддерживают строки в виде словарей, кортежи в порядке столбцов таблицы и SQL-выражения для отдельных строк. Именно эту форму использует Pandas to_sql(method="multi"). Строки при этом вставляются, но возвращается 0, поскольку текстовые операторы INSERT сообщают через курсор DB-API количество строк 0. SQLAlchemy определяет список столбцов по первой строке. Лишние ключи словарей в последующих строках и значения кортежей, выходящие за пределы этого списка столбцов, игнорируются. Если в одной из последующих строк отсутствует значение для выбранного столбца, компиляция завершается ошибкой. Задавайте во всех строках одинаковый набор столбцов.
При стандартных ограничениях HTTP-формы в ClickHouse 26.4 и новее server_side_params=True подходит только для небольших явных батчей — примерно до 1000 привязываемых значений с запасом для других полей. Этот предел можно повысить в конфигурации сервера. Для больших обычных батчей в синхронном диалекте передавайте строки вторым аргументом execute(), чтобы драйвер мог выполнить массовую вставку в формате Native. Для асинхронной загрузки больших объёмов данных используйте await с нативным методом AsyncClient.insert().
Миграции Alembic
ClickHouse Connect поддерживает интеграцию с Alembic для миграций схем ClickHouse. Установите её с помощью:alembic.ini использует script_location = %(here)s/alembic. Оставьте эту настройку, если каталог миграций называется alembic, или укажите в ней каталог, переданный в alembic init. Замените alembic/env.py на асинхронный пример env.py для Alembic из репозитория, затем задайте sqlalchemy.url в alembic.ini.
Импортируйте clickhouse_connect.cc_sqlalchemy.alembic в env.py Alembic, чтобы зарегистрировать интеграцию диалекта. Автогенерация поддерживает типовые изменения таблиц, включая создание и удаление таблиц, добавление/изменение/удаление столбцов, значения по умолчанию и комментарии. Для переименования таблиц и столбцов используйте ручные операции. Проверяйте каждую сгенерированную миграцию перед её применением.
Функции миграций Alembic остаются синхронными. Асинхронное окружение создаёт AsyncEngine, открывает AsyncConnection и передаёт синхронную функцию миграции в await connection.run_sync(...). Офлайн-миграции напрямую вызывают context.configure(url=..., literal_binds=True, dialect_opts={"paramstyle": "named"}) и не создают движок. Асинхронный пример env.py для Alembic из репозитория поддерживает оба варианта и считывает URL соединения из стандартного параметра конфигурации Alembic sqlalchemy.url. В нём сохранены хуки и параметры ClickHouse для Alembic из подробного примера, включая include_object, make_include_name(...), clickhouse_writer и version_table. Не используйте engine.sync_engine для запуска асинхронных миграций или освобождения их ресурсов.
Специальные для ClickHouse хелперы op.* охватывают:
- Индексы пропуска данных, включая операции добавления, материализации и удаления.
- Проекции, включая операции добавления, материализации и удаления.
- Изменение и сброс настроек таблиц семейства MergeTree.
- Создание и удаление materialized view.
- Создание, удаление и перезагрузку словарей.
Index, Column(index=True), op.create_index и op.drop_index отклоняются, чтобы избежать частичного или некорректного DDL. Используйте op.add_clickhouse_index и op.drop_clickhouse_index.
См. полный пример работы с Alembic. Пользователям, переходящим с clickhouse-sqlalchemy, также следует ознакомиться с руководством по миграции.
Область применения и ограничения
- ClickHouse не поддерживает традиционные транзакции через этот HTTP-диалект.
engine.begin()иSession.commit()организуют работу на стороне Python, но коммит и rollback на сервере ничего не меняют. UPDATE, двухфазные транзакции, последовательности,RETURNINGи расширенные уровни изоляции в этом диалекте не реализованы. При необходимости используйте явный ClickHouse SQL для серверных мутаций.Column(..., primary_key=True)задает identity объекта SQLAlchemy. Это не создает ограничение уникальности на стороне сервера. Задавайте сортировку и необязательные выражения первичного ключа через движок таблицы.- Метаданные для традиционных внешних ключей, ограничений уникальности и стандартных индексов недоступны, поскольку ClickHouse не применяет такие ограничения.
- Управление relationship в ORM, обновления unit of work, каскады, а также немедленная или отложенная загрузка relationship не входят в поддерживаемую область ORM.