Перейти к содержимому
falang.ruEN

Базы данных

Учётные данные для базы данных напрямую подключают сценарий к базе Postgres или MySQL — читать и писать строки можно через типизированные действия, а для всего, что они не покрывают, есть запасной вариант — сырой SQL. После синхронизации схемы редактор «знает» таблицы подключённой базы: узел «Select» на таблице orders предлагает orders в списке таблиц, результат типизирован по строкам, а Monaco подсказывает названия колонок при вводе фильтра.

Учётные данные для Postgres или MySQL добавляются так же, как для любой другой интеграции, — одной строкой подключения: postgres://user:pass@host:5432/db или mysql://user:pass@host:3306/db, а не отдельными полями хоста/порта/пользователя/пароля, потому что dev- и production-база обычно — два совершенно разных сервера, а не один и тот же с другим паролем; строка подключения — это ещё и то, что уже даёт любой драйвер, ORM или консоль облачного провайдера. Рядом с ней — настройка ssl (prefer/require/disable). Как и любые учётные данные в движке, строка подключения хранится отдельно для dev- и prod-окружений и шифруется перед сохранением — редактор и «Синхронизировать структуру» всегда используют dev-строку, опубликованный (production) запуск — prod.

Нажмите «Синхронизировать структуру» на учётных данных, чтобы редактор прочитал реальные таблицы и колонки базы. Это одно подключение (по dev-строке), чтение схемы и её сохранение — после этого живого обращения к базе при редактировании узла больше не происходит. Перезапускайте синхронизацию всякий раз, когда схема меняется (добавлена колонка, переименована таблица), чтобы вернуть картину редактора в актуальное состояние; если забыть — ничего не ломается сразу, узел, ссылающийся на уже не совпадающую таблицу, просто упадёт с обычной ошибкой базы данных при следующем запуске.

После синхронизации у каждой таблицы появляются четыре типа, которыми редактор пользуется автоматически: Row (все колонки, как они приходят из запроса), Insert (все колонки, но те, у которых есть значение по умолчанию или допустим NULL, становятся необязательными), Patch (все колонки необязательны — для частичного обновления) и Where (см. ниже). Типы колонок сопоставляются ожидаемо — целые и дробные числа в числа, текст/даты/UUID в строки, булевы в булевы, JSON-колонки и всё нераспознанное — в разрешающий тип «any».

У каждого диалекта (Postgres, MySQL, SQLite) одинаковый набор из шести действий:

  • Select — возвращает все подходящие строки массивом Row. Принимает необязательный фильтр where, orderBy (например, created_at desc) и limit.

  • Select one — как Select, но возвращает одну строку Row и падает с ошибкой, если ничего не найдено; если «ни одной строки» — нормальный, ожидаемый результат, а не ошибка, используйте Select с лимитом 1.

  • Insert — вставляет одну строку (типизированную как Insert) и возвращает её обратно как Row, включая любые колонки, заполненные самой базой (автоинкрементный id, метка времени по умолчанию).

  • Update — применяет Patch ко всем строкам, подходящим под where, и возвращает число изменённых строк. where обязателен и не может быть пустым — случайно обновить всю таблицу, оставив фильтр пустым, невозможно.

  • Delete — то же обязательное непустое where, что и у Update, возвращает число удалённых строк.

  • Query — запасной вариант для join’ов, агрегатов и всего остального, что не выражается пятью действиями выше: сырой SQL-запрос плюс отдельный список параметров, подставляемых через плейсхолдеры $1/$2 (Postgres) или ? (MySQL/SQLite). Сам текст SQL никогда не подставляется со значениями напрямую — параметры всегда передаются отдельно, та же защита от SQL-инъекций, что даёт любой API с параметризованными запросами.

where — небольшой язык фильтров, а не просто равенство. Голое значение у колонки означает «равно» (а null — IS NULL); для чего-то другого дайте этой колонке объект с одним или несколькими операторами — eq, ne, gt, gte, lt, lte, in, notIn, isNull, а для текстовых колонок ещё like/ ilike/startsWith/contains. Несколько колонок по умолчанию объединяются через AND; чтобы задать альтернативы, оберните их в явный массив or (или and) — их можно вкладывать друг в друга:

// status равен "new" или "paid", и total больше 100
{ status: { in: ['new', 'paid'] }, total: { gt: 100 } }
// id равен 1 или 2
{ or: [{ id: 1 }, { id: 2 }] }
// note содержит "urgent" без учёта регистра (Postgres)
{ note: { ilike: '%urgent%' } }

Каждое действие с базой данных выполняется как отдельный шаг устойчивого выполнения сценария и, как любой другой шаг, автоматически повторяется при ошибке — сбой сети, оборванное соединение, кратковременно недоступная база. Обычно это именно то, что нужно: неудачная запись считается «строка не создана», и повтор доводит дело до конца. Единственный граничный случай, о котором стоит знать, — запись, которую база данных на самом деле зафиксировала, но подтверждение которой не успело вернуться до обрыва соединения: повтор в этом редком случае может вставить или обновить одну и ту же строку дважды. Если в таблицу пишет сценарий, который может повториться, добавьте ей уникальное ограничение (или отдельную колонку с ключом идемпотентности), чтобы повторная попытка отклонялась самой базой данных, а не молча выполнялась второй раз.

SQLite доступен как третий диалект, работающий с обычным файлом (или базой в памяти) вместо отдельного сервера — не нужно запускать ещё один сервис, что удобно для локальной разработки, самостоятельного развёртывания на одном сервере с примонтированным томом и для тестов. Это не production-выбор для развёртывания, которое масштабирует runner-поды вверх и вниз: собственная файловая система runner-пода не сохраняется между перезапусками, поэтому файл SQLite внутри пода не переживёт масштабирование до нуля. Для всего, что должно пережить под, написавший данные, используйте Postgres или MySQL.