Zum Inhalt

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

  1. Beim Speichern wird die Vorlage mit SQLGlot (PostgreSQL-Dialekt) geparst und muss eine Anweisung sein, die ein SELECT oder WITH … SELECT ist. DML, DDL, mehrere Anweisungen und CTEs, die ein INSERT … RETURNING einschmuggeln, werden mit einer 422 abgelehnt, die das Problem benennt.
  2. Beim Fetch wird jeder {{ … }}-Platzhalter zu einem positionalen Bind-Parameter ($1, $2, …); Werte werden nie in den SQL-Text interpoliert. Die Anweisung läuft in BEGIN TRANSACTION READ ONLY mit einem statement_timeout von 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-Tabelle ctx_<name> in Regeln und Transformern). Werte werden in JSON-freundliche Typen gewandelt: numeric und Decimal zu Zahlen, Datums-/Zeitwerte zu ISO-Strings, UUID zu Text.
  • Skalar — die Abfrage muss genau eine Spalte liefern; der Wert der ersten Zeile wird zu context.<name>. Keine Zeilen ist ein empty_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 / MCP test_context_source probelaufen 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/fetch erneut 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.