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¶
- При сохранении шаблон разбирается SQLGlot (диалект PostgreSQL) и
должен быть одним оператором
SELECTилиWITH … SELECT. DML, DDL, несколько операторов и CTE, протаскивающиеINSERT … RETURNING, отклоняются с 422, называющей проблему. - При сборе каждый плейсхолдер
{{ … }}становится позиционным bind-параметром ($1,$2, …); значения никогда не интерполируются в текст SQL. Оператор выполняется внутриBEGIN TRANSACTION READ ONLYсоstatement_timeout15 с. На подключение — тайм-аут 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>(таблица DuckDBctx_<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/ MCPtest_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 |
Фильтр ничего не нашёл, а цель обязательна | Проверить значения плейсхолдеров в контексте сессии; пометить цель необязательной, если отсутствие допустимо. |