Skip to main content
sqlx — это асинхронный SQL-инструментарий, полностью написанный на Rust, со встроенным пулом соединений и поддержкой миграций. В этом руководстве вы создадите таблицу todos с помощью миграции sqlx-cli и реализуете обработку запросов на создание, чтение, обновление и удаление в небольшом сервере на axum. Все соединения защищены TLS с полной проверкой сертификата.

Предварительные требования

  • Rust версии 1.94 или выше. Это руководство проверено на Rust 1.98.1, sqlx 0.9.0, sqlx-cli 0.9.0 и axum 0.8.9.
  • Аккаунт ClickHouse Cloud
  • psql для создания базы данных. Вместо этого можно выполнить оператор CREATE DATABASE в консоли SQL.

Создание сервиса ClickHouse Managed Postgres

В консоли ClickHouse Cloud нажмите New service и выберите Postgres. Через несколько минут инстанс будет готов. Пошаговые инструкции приведены в кратком руководстве.

Получение сведений о подключении

Нажмите Подключение на левой боковой панели вашего сервиса. Оставьте выбранным вариант Directly и включите Use SSL. Теперь URL подключения на вкладке url заканчивается на sslmode=verify-full&sslrootcert=<service-name>-ca-certificate.pem, а в качестве порта указан 5432. Нажмите Download CA‑сертификат. Скачать его можно и позже — в разделе Settings → CA‑сертификат. Этот сертификат уникален для вашего инстанса, поэтому драйвер может с его помощью убедиться, что взаимодействует именно с вашим сервером. В этом руководстве и приложение, и sqlx-cli подключаются к Postgres напрямую через порт 5432. Сервер axum — долгоживущий процесс, а PgPool поддерживает собственный пул соединений, поэтому дополнительный пулер перед Postgres не нужен. Миграции всегда следует выполнять через прямое соединение. Если вы запускаете много инстансов приложения, см. раздел Использование PgBouncer.

Настройка проекта

Создайте проект и добавьте sqlx с драйвером Postgres, средой выполнения Tokio и TLS-бэкендом rustls, а также axum и несколько вспомогательных крейтов:
Установите sqlx-cli с тем же TLS-бэкендом:
На macOS используйте rustls, а не native-tlsClickHouse Managed Postgres принимает только TLS 1.3. На macOS бэкенд native-tls использует системную библиотеку Secure Transport, которая не поддерживает TLS 1.3. Обычная установка cargo install sqlx-cli использует native-tls, и в результате любая команда завершается ошибкой error occurred while attempting to establish a TLS connection: One or more parameters passed to a function were not valid. (или bad protocol version). То же касается приложения, собранного с возможностью tls-native-tls. На Linux native-tls использует OpenSSL и подключение устанавливается, однако из ca-certificate.pem загружается только первый сертификат. rustls поддерживает TLS 1.3 на всех платформах и загружает все сертификаты из файла.
Добавляете в существующее приложение?Пропустите cargo new. В своём проекте выполните приведённые выше команды cargo add и cargo install, затем перейдите к разделу Настройка соединения. Если приложение использует sqlx с SQLite:
  • Удалите sqlite из возможностей sqlx в Cargo.toml и замените SqlitePool на PgPool.
  • Замените плейсхолдеры ? на $1, $2 и так далее.
  • Перепишите миграции на синтаксис Postgres, например bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY вместо INTEGER PRIMARY KEY AUTOINCREMENT.

Настройка соединения

Переместите скачанный CA‑сертификат в каталог проекта и переименуйте его в ca-certificate.pem. Создайте базу данных для приложения. Замените <PASSWORD> и хост значениями из модального окна Подключение:
Создайте файл .env в корне проекта или измените DATABASE_URL в уже существующем файле так, чтобы этот параметр указывал на базу данных guide_rust. И приложение, и sqlx-cli считывают DATABASE_URL из этого файла:
.env
sqlx считывает sslmode и sslrootcert из URL. В режиме verify-full он проверяет, что сертификат сервера подписан CA вашего инстанса и соответствует имени хоста. Путь sslrootcert разрешается относительно каталога, из которого вы запускаете команды. Не добавляйте .env в систему контроля версий, а если пароль содержит такие символы, как @, / или #, закодируйте их в percent-encoding.
Всегда указывайте sslmode=verify-fullЕсли sslmode не задан, sqlx использует prefer: соединение шифруется, но принимается любой сертификат, даже если указан sslrootcert. В режиме verify-full при отсутствующем или неверном CA‑сертификате возникает ошибка UnknownIssuer, а если хост не соответствует сертификату (например, указан IP-адрес) — ошибка NotValidForNameContext (certificate not valid for name в sqlx-cli). При неверном пути sslrootcert возникает ошибка No such file or directory.

Создание таблицы с помощью миграции

Создайте файл миграции:
Откройте новый файл в каталоге migrations и добавьте определение таблицы:
migrations/<timestamp>_create_todos.sql
Примените её:
sqlx migrate run записывает применённые миграции в таблицу _sqlx_migrations и при последующих вызовах выполняет только новые. Добавьте каталог migrations в репозиторий вместе с кодом. Если же нужно применять миграции при запуске приложения, вызовите sqlx::migrate!().run(&pool).await? в main — при условии, что пул использует прямое соединение.

Создание API

Замените содержимое src/main.rs небольшим сервером на axum, который выполняет по одному запросу на каждый маршрут. В существующем приложении создайте пул так же, как в main, и добавьте структуру Todo, обработчики и маршруты в свой маршрутизатор:
src/main.rs
Значения всегда передаются через .bind как параметры ($1) и никогда не подставляются в строку SQL напрямую. Каждая уникальная строка SQL подготавливается один раз на соединение и хранится в кэше операторов ёмкостью 100 операторов; вытесняемые из кэша операторы sqlx закрывает.
Запросы с проверкой на этапе компиляцииВ этом руководстве используется функция query_as, которая проверяет SQL во время выполнения, поэтому для cargo build база данных не требуется. Макросы query! и query_as!, напротив, сверяют SQL и типы столбцов с базой данных на этапе компиляции. Во время сборки они считывают DATABASE_URL из .env, поэтому эта переменная должна указывать на прямое соединение. Чтобы собирать проект без базы данных, например в CI, выполните cargo sqlx prepare, закоммитьте сгенерированный каталог .sqlx и установите SQLX_OFFLINE=true.

Запуск и проверка

Запустите сервер:
Первая строка — это вывод pg_stat_ssl, подтверждающий, что сеанс зашифрован. Во втором терминале создайте две задачи, отметьте первую как выполненную и удалите вторую:
Каждый запрос возвращает затронутую строку. Параметр -w '\n' лишь добавляет перевод строки после каждого ответа:
Откройте http://localhost:3117/todos в браузере, чтобы увидеть список оставшихся задач:
Чтобы просмотреть данные в консоли, откройте консоль SQL на левой боковой панели вашего сервиса, разверните guide_rust, затем public и выберите таблицу todos. В соседней таблице _sqlx_migrations хранится история миграций. В таблице todos осталась одна строка:

Использование PgBouncer

Чтобы подключить приложение через встроенный PgBouncer, выберите via PgBouncer в модальном окне Подключение и укажите порт 6432 в DATABASE_URL приложения. CA‑сертификат используется тот же. Например, оставьте в .env прямое соединение для sqlx-cli, а переменную задавайте при запуске приложения:
При работе через PgBouncer pg_stat_ssl показывает собственное соединение PgBouncer с Postgres, поэтому проверка выводит none. Соединение вашего приложения с PgBouncer при этом всё равно проверяется: с неверным CA‑сертификатом подключение точно так же завершится ошибкой UnknownIssuer. PgBouncer работает в режиме пула транзакций, поэтому каждая транзакция может выполняться в отдельном соединении с Postgres. При использовании sqlx учитывайте следующее:
  • Для подключения требуется .extra_float_digits(None), как в main.rs. По умолчанию sqlx передаёт extra_float_digits как параметр запуска, и PgBouncer отклоняет соединение с ошибкой unsupported startup parameter: extra_float_digits. По той же причине PgConnectOptions::options завершается ошибкой unsupported startup parameter in options.
  • Подготовленные операторы работают с настройками по умолчанию. Операторы, вытесняемые из кэша, sqlx освобождает с помощью сообщения Close на уровне протокола, и PgBouncer корректно его обрабатывает, в том числе внутри транзакций. Не задавайте statement_cache_capacity(0): в этом нет необходимости, а при отключённом кэше sqlx по-прежнему подготавливает именованные операторы, но никогда их не закрывает.
  • Не используйте .persistent(false) для запросов, выполняемых вне транзакции. В этом случае sqlx подготавливает безымянный оператор и выполняет его отдельным обращением к серверу, которое PgBouncer может направить в другое соединение с Postgres. В результате запрос может завершиться ошибкой unnamed prepared statement does not exist или незаметно выполнить оператор другого клиента и вернуть неверные результаты.
  • Настройки сеанса, заданные через SET, в том числе в хуке after_connect пула, не закрепляются за вашим соединением и могут повлиять на других клиентов. Вместо этого используйте SET LOCAL внутри транзакции.
  • Миграции необходимо выполнять через прямое соединение. sqlx-cli и макросы query! не могут подключиться через PgBouncer, поскольку всегда передают extra_float_digits. sqlx::migrate! захватывает рекомендательную блокировку уровня сеанса (pg_advisory_lock), а при работе через PgBouncer команда её снятия может попасть в другое соединение с Postgres. Тогда блокировка так и остаётся на одном из соединений пула, и последующие миграции, в том числе через прямое соединение, зависают, пока PgBouncer не закроет это соединение.

Дальнейшие шаги