Перейти к содержанию

PostgreSQL

Читайте справочные данные прямо из базы PostgreSQL — отчётной реплики, хранилища, экспортной схемы ERP — в контекст сессии. Один read-only SELECT на источник; результат становится контекстной таблицей или скаляром.

Направление: только context.

1. Создайте подключение

Settings → Connections → Add Connection, тип PostgreSQL:

Поле Значение
Connection DSN postgresql://user@host:5432/dbname?sslmode=require — пользователь, хост, порт, база и sslmode в DSN; без пароля
Password Пароль базы, хранится зашифрованным отдельно от DSN

Test открывает соединение и выполняет SELECT 1 в read-only-транзакции.

POST /api/integration-connections
{
  "name": "ERP reporting DB",
  "type": "postgres",
  "base_url": "postgresql://reporting@db.example.com:5432/erp?sslmode=require",
  "credentials": "<password>"
}

Используйте read-only-роль

Expecto обеспечивает только чтение дважды (ниже), но роль базы с SELECT только на нужные представления — третий и самый сильный слой. Заведите отдельную роль для Expecto вместо переиспользования пользователя приложения.

2. Объявите источники контекста

{
  "sources": [
    {
      "name": "prev_postings",
      "connection_id": "<connection uuid>",
      "query": {
        "kind": "sql",
        "sql": "SELECT account, SUM(amount) AS amount FROM postings WHERE fund = {{ context.fund_number }} AND period = {{ context.prev_period }} GROUP BY account"
      },
      "produces": { "table": "prev_postings" }
    },
    {
      "name": "exchange_rate",
      "connection_id": "<connection uuid>",
      "query": {
        "kind": "sql",
        "sql": "SELECT rate FROM fx WHERE currency = 'USD' AND period = {{ session.period }}"
      },
      "produces": { "scalar": "exchange_rate" }
    }
  ]
}

У объекта запроса ровно два ключа: kind: "sql" и sql.

Как защищён SQL

  1. При сохранении шаблон разбирается SQLGlot (диалект PostgreSQL) и должен быть одним оператором SELECT или WITH … SELECT. DML, DDL, несколько операторов и CTE, протаскивающие INSERT … RETURNING, отклоняются с 422, называющей проблему.
  2. При сборе каждый плейсхолдер {{ … }} становится позиционным bind-параметром ($1, $2, …); значения никогда не интерполируются в текст SQL. Оператор выполняется внутри BEGIN TRANSACTION READ ONLY со statement_timeout 15 с. На подключение — тайм-аут 10 с.

Следствия для написания запросов:

  • Пишите плейсхолдеры без кавычек: period = {{ session.period }}, а не '{{ session.period }}' — значение биндится, а не вставляется.
  • Плейсхолдер не может заменить идентификатор (имя таблицы или колонки) или ключевое слово — только значение.
  • Приводите тип на стороне SQL, если тип колонки отличается от текста: fund_id = {{ context.fund_number }}::int.

Лимиты

Лимит Значение При превышении
Строк на источник 10 000 query_error: "Query returned more than 10000 rows; narrow it with a filter"
Время оператора 15 с query_error: "Query exceeded the 15s time limit"
Время подключения 10 с connection_error

Что производит источник

  • Таблица — каждая выбранная колонка становится колонкой контекстной таблицы context._tables.<name> (таблица DuckDB ctx_<name> в правилах и трансформерах). Значения приводятся к JSON-совместимым типам: numeric и Decimal — в числа, даты и время — в ISO-строки, UUID — в текст.
  • Скаляр — запрос должен вернуть ровно одну колонку; значение первой строки становится context.<name>. Отсутствие строк — ошибка empty_result.

Для обязательной табличной цели (по контекстной схеме проекта) пустой результат — ошибка empty_result; для необязательной — пустая таблица.

3. Схема, тест и сбор

Остальные шаги одинаковы для всех провайдеров контекста:

  • Объявите производимый скаляр/таблицу в контекстной схеме проекта с filled_by: "integration" — см. BuiltAPI › Впишите в контекстную схему.
  • Прогоните источник через Test query / POST /api/integration-connections/test-source / MCP test_context_source, передав примерные значения вроде {"context.fund_number": "F-1001"}.
  • При создании сессии сбор выполняется в воркере, блокирует пайплайн до завершения и повторяется через POST /api/sessions/{id}/context/fetch — см. BuiltAPI › Что происходит при создании сессии.

Диагностика

error_kind / сообщение Причина Что делать
connection_error Хост недоступен, порт закрыт, требуется TLS, но нет sslmode, тайм-аут подключения Проверить хост/порт и sslmode в DSN; убедиться, что база принимает подключения с исходящего адреса Expecto.
auth_error Неверный пароль или роли запрещён вход Ротировать пароль в Edit; проверить pg_hba.conf для роли.
query_error: "query.sql must be a single read-only SELECT" Шаблон содержит DML/DDL или несколько операторов Оставить один SELECT; записи делать в другом месте.
query_error: "…produces a scalar; the query must select exactly one column" Скалярный источник выбирает несколько колонок Выбирать только колонку значения.
query_error: "…more than 10000 rows" Неограниченный результат Добавить WHERE по периоду/фонду, агрегировать или сделать более узкое представление.
empty_result Фильтр ничего не нашёл, а цель обязательна Проверить значения плейсхолдеров в контексте сессии; пометить цель необязательной, если отсутствие допустимо.