PostgreSQL¶
Lesen Sie Referenzdaten direkt aus einer PostgreSQL-Datenbank — einer
Reporting-Replika, einem Data Warehouse, einem ERP-Exportschema — in den
Session-Kontext. Ein read-only SELECT je Source; das Ergebnis wird zur
Kontexttabelle oder zum Skalar.
Richtung: nur context.
1. Connection anlegen¶
Settings → Connections → Add Connection, Typ PostgreSQL:
| Feld | Wert |
|---|---|
| Connection DSN | postgresql://user@host:5432/dbname?sslmode=require — Benutzer, Host, Port, Datenbank und sslmode im DSN; kein Passwort |
| Password | Das Datenbankpasswort, verschlüsselt und getrennt vom DSN gespeichert |
Test öffnet eine Verbindung und führt SELECT 1 in einer
Read-only-Transaktion aus.
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-Rolle verwenden
Expecto erzwingt Lesezugriff zweifach (siehe unten), aber eine
Datenbankrolle mit SELECT nur auf die benötigten Views ist die dritte
und stärkste Schicht — legen Sie eine für Expecto an, statt einen
Anwendungsbenutzer wiederzuverwenden.
2. Context Sources deklarieren¶
{
"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" }
}
]
}
Das Query-Objekt hat genau zwei Schlüssel: kind: "sql" und sql.
Wie das SQL geschützt ist¶
- Beim Speichern wird die Vorlage mit SQLGlot (PostgreSQL-Dialekt)
geparst und muss eine Anweisung sein, die ein
SELECToderWITH … SELECTist. DML, DDL, mehrere Anweisungen und CTEs, die einINSERT … RETURNINGeinschmuggeln, werden mit einer 422 abgelehnt, die das Problem benennt. - Beim Fetch wird jeder
{{ … }}-Platzhalter zu einem positionalen Bind-Parameter ($1,$2, …); Werte werden nie in den SQL-Text interpoliert. Die Anweisung läuft inBEGIN TRANSACTION READ ONLYmit einemstatement_timeoutvon 15 s. Der Verbindungsaufbau hat 10 s Timeout.
Folgen für das Schreiben von Abfragen:
- Platzhalter ohne Anführungszeichen schreiben:
period = {{ session.period }}, nicht'{{ session.period }}'— der Wert wird gebunden, nicht eingefügt. - Ein Platzhalter kann keinen Bezeichner (Tabellen- oder Spaltenname) und kein Schlüsselwort ersetzen, nur einen Wert.
- Auf SQL-Seite casten, wenn der Spaltentyp vom gebundenen Text abweicht:
fund_id = {{ context.fund_number }}::int.
Limits¶
| Limit | Wert | Bei Überschreitung |
|---|---|---|
| Zeilen je Source | 10 000 | query_error: "Query returned more than 10000 rows; narrow it with a filter" |
| Anweisungsdauer | 15 s | query_error: "Query exceeded the 15s time limit" |
| Verbindungsaufbau | 10 s | connection_error |
Was eine Source erzeugt¶
- Tabelle — jede selektierte Spalte wird zur Spalte der Kontexttabelle
context._tables.<name>(DuckDB-Tabellectx_<name>in Regeln und Transformern). Werte werden in JSON-freundliche Typen gewandelt:numericundDecimalzu Zahlen, Datums-/Zeitwerte zu ISO-Strings,UUIDzu Text. - Skalar — die Abfrage muss genau eine Spalte liefern; der Wert der
ersten Zeile wird zu
context.<name>. Keine Zeilen ist einempty_result-Fehler.
Für ein Pflicht-Tabellenziel (laut Kontextschema des Projekts) ist ein
leeres Ergebnis ein empty_result-Fehler; für ein optionales ergibt es eine
leere Tabelle.
3. Schema, Test und Fetch¶
Die restlichen Schritte sind für jeden Kontext-Provider gleich:
- Den erzeugten Skalar bzw. die Tabelle im Kontextschema des Projekts mit
filled_by: "integration"deklarieren — siehe BuiltAPI › Ins Kontextschema einbinden. - Eine Source mit Test query /
POST /api/integration-connections/test-source/ MCPtest_context_sourceprobelaufen lassen, mit Beispielwerten wie{"context.fund_number": "F-1001"}. - Bei der Session-Erstellung läuft der Fetch im Worker, blockiert die
Pipeline bis zum Abschluss und lässt sich mit
POST /api/sessions/{id}/context/fetcherneut ausführen — siehe BuiltAPI › Was bei der Session-Erstellung passiert.
Troubleshooting¶
error_kind / Meldung |
Ursache | Abhilfe |
|---|---|---|
connection_error |
Host nicht erreichbar, Port geschlossen, TLS nötig aber sslmode fehlt, Verbindungs-Timeout |
Host/Port und sslmode im DSN prüfen; sicherstellen, dass die Datenbank Verbindungen vom Expecto-Egress akzeptiert. |
auth_error |
Falsches Passwort oder Rolle darf sich nicht anmelden | Passwort unter Edit rotieren; pg_hba.conf für die Rolle prüfen. |
query_error: "query.sql must be a single read-only SELECT" |
Die Vorlage enthält DML/DDL oder mehrere Anweisungen | Ein SELECT behalten; Schreibvorgänge woanders erledigen. |
query_error: "…produces a scalar; the query must select exactly one column" |
Eine Skalar-Source selektiert mehrere Spalten | Nur die Wertspalte selektieren. |
query_error: "…more than 10000 rows" |
Unbegrenztes Ergebnis | WHERE auf Periode/Fonds ergänzen, aggregieren oder eine engere View erzeugen. |
empty_result |
Der Filter hat nichts gefunden und das Ziel ist Pflicht | Platzhalterwerte im Session-Kontext prüfen; das Ziel optional markieren, wenn Abwesenheit legitim ist. |