REST-Jdbc Driver

Einleitung

Der RestJdbc Treiber ermöglicht das Lesen und Schreiben von Daten aus bzw. in REST-APIs über JDBC mit SQL.

Es gelten folgende Konventionen:

  • als Datenbank wird die Verbindung jdbc:rest: verwendet
  • die JDBC-URL enthält die Basis-URL der REST-API (jdbc:rest:https://…)
  • die API wird mit Hilfe einer Spec-Datei (JSON oder YAML) spezifiziert; optional kann baseUrl in der Spec als Fallback gesetzt werden, wenn die JDBC-URL keine HTTP-URL enthält
  • als Schema wird public verwendet (Standard)
  • als Tabellen werden Entity-Namen in der Spec angegeben (z.B. users, orders)
  • die Spalten werden in der Spec definiert; standardmäßig entsprechen sie Top-Level-JSON-Feldern (für verschachtelte Felder: jsonPath)
  • der Treiber unterstützt SELECT für das Lesen und INSERT, UPDATE und DELETE für das Schreiben

Features

Die Festlegung der Details einer REST API erfolgt über eine Spec-Datei in JSON oder YAML. Diese Datei definiert mögliche Tabellen, Spalten, Typen, Primärschlüssel usw.

Sechs Authentifizierungsverfahren stehen zur Verfügung:

  • none - keine Authentifizierung
  • basic - HTTP Basic-Auth mit User und Passwort
  • bearer - Authentifizierung mit Bearer Token
  • oauth2 - OAuth 2.0 Client Credentials (Token wird automatisch geholt und erneuert)
  • apikey - API-Schlüssel als Query-Parameter oder HTTP-Header
  • clientcert - Mutual TLS mit Client-Zertifikat (Keystore)

Ein Lesen mit einem SELECT über den Treiber wird auf ein HTTP GET mit einer JSON-Antwort abgebildet. Dabei wird ein Array in der JSON-Antwort erwartet, dass die Ergebniszeilen enthält.

Der Treiber unterstützt verschiedene Paginierungsverfahren

  • none - kein Paginierung
  • offset - Paginierung mit Limit und Offset
  • page - Paginierung mit Angabe einer Seitennummer
  • nextLink - OData-/REST-Paginierung über eine opaque URL in der JSON-Antwort (z. B. @odata.nextLink)

Das Einfügen von Datensätzen mit INSERT, resultiert in einem HTTP POST.

Das Löschen von Datensätzen kann mit DELETE erfolgen. Es führt zu einem HTTP DELETE.

Und schließlich unterstützt der Treiber UPDATEs. Diese münden in HTTP PUT oder HTTP PATCH Aufrufe.

Mit Hilfe einer FILTER-Klausel können Daten bei SELECT, UPDATE oder DELETE serverseitig gefiltert werden.

Der Treiber bietet Systemtabellen für verfügbare Tabellen, Spalten und Primärschlüssel.

JDBC URL

jdbc:rest:https://api.example.com/v1

Der Teil nach jdbc:rest: ist die Basis-URL der REST-API. Er hat Vorrang vor einem optionalen baseUrl in der Spec. Fehlt die HTTP-URL in der JDBC-URL, kann baseUrl in der Spec als Fallback dienen.

Beispiel-Verbindung (Java)

Properties props = new Properties();
props.setProperty("spec", "/pfad/zu/api-spec.json");
props.setProperty("auth", "bearer");
props.setProperty("token", "mein-bearer-token");

Connection conn = DriverManager.getConnection(
    "jdbc:rest:https://api.example.com/v1", props);

Connection Properties

Property Beschreibung Erforderlich
spec Pfad zur Spec-Datei (.json, .yaml oder .yml; Dateisystem oder classpath:...) Ja
auth Auth-Verfahren: none, bearer, basic, oauth2, apikey, clientcert (siehe unten) Nein
password Passwort Bei basic
user Benutzername Bei basic
token Bearer-Token Bei bearer
tokenUrl OAuth2-Token-Endpunkt Bei oauth2 (oder tenant)
tenant Azure AD Tenant-ID oder -Name; leitet tokenUrl für Microsoft Graph ab Bei oauth2 (Alternative zu tokenUrl)
clientId OAuth2 Client ID (client_id als Alias) Bei oauth2
clientSecret OAuth2 Client Secret (client_secret als Alias) Bei oauth2
scope OAuth2 Scope (z. B. https://graph.microsoft.com/.default) Bei oauth2, falls von der API verlangt
oauth2GrantType client_credentials (Standard) oder device_code Bei oauth2
deviceCodeUrl OAuth2 Device-Code-Endpunkt Bei device_code (oder tenant)
apiKey API-Schlüssel Bei apikey
apiKeyLocation Übergabe: query (Standard) oder header Bei apikey
apiKeyParam Name des Query-Parameters oder HTTP-Headers (Standard: apiKey) Bei apikey
keyStore Pfad zum Keystore mit Client-Zertifikat und Private Key Bei clientcert
keyStorePassword Passwort für den Keystore Bei clientcert
keyStoreType Keystore-Typ (Standard: PKCS12) Nein
keyPassword Passwort für den Private Key, falls abweichend von keyStorePassword Nein
requestIntervalMs Mindestabstand zwischen HTTP-Requests in Millisekunden (0 = aus) Nein
retryOn429 Bei HTTP 429 automatisch erneut versuchen (Standard: true) Nein
maxRetries Maximale Wiederholungen bei HTTP 429 (Standard: 5) Nein
stdoutlog Debug-Ausgabe auf stdout: cursor, http, rows oder all (kombinierbar mit +, z. B. cursor+http+rows) Nein

Property-Namen sind case-insensitive (z. B. funktionieren auch apikey, requestintervalms, stdoutlog).

Spec-Pfad:

/pfad/absolut/api-spec.json
/pfad/absolut/api-spec.yaml
classpath:de/softquadrat/jdbc/rest/mock-spec.json

Authentifizierung

Auth-Verfahren und Zugangsdaten werden über Connection Properties konfiguriert.

Die Property auth legt das Verfahren fest: none, bearer, basic, oauth2, apikey oder clientcert. Ist auth nicht gesetzt, wird automatisch erkannt:

  • user und password gesetzt → basic
  • nur token gesetzt → bearer
  • nur apiKey gesetzt → apikey
  • nur keyStore gesetzt → clientcert
  • sonst → none

Die Client-Zertifikat-Authentifizierung erfolgt auf der TLS-Ebene (Mutual TLS). Sie lässt sich mit bearer oder basic kombinieren, wenn die API sowohl mTLS als auch einen Authorization-Header verlangt.

Die Prüfung des Server-Zertifikats nutzt den JVM-Standard-Truststore (cacerts bzw. -Djavax.net.ssl.trustStore=...). Separate Truststore-Properties gibt es im Treiber nicht.

Keine Authentifizierung

props.setProperty("auth", "none");

Oder einfach keine Auth-Properties setzen.

Bearer Token

props.setProperty("auth", "bearer");
props.setProperty("token", "eyJhbGciOiJIUzI1NiIs...");

Der Treiber sendet: Authorization: Bearer <token>

OAuth 2.0 Client Credentials

Für APIs wie Microsoft Graph mit Anwendungsberechtigungen (App-only, z. B. /users, /groups):

props.setProperty("auth", "oauth2");
props.setProperty("oauth2GrantType", "client_credentials"); // Standard, kann entfallen
props.setProperty("tenant", "Ihre-Tenant-ID");
props.setProperty("clientId", "Ihre-App-Client-ID");
props.setProperty("clientSecret", "Ihr-Client-Secret");
props.setProperty("scope", "https://graph.microsoft.com/.default");

Alternativ mit explizitem Token-Endpunkt statt tenant:

props.setProperty("auth", "oauth2");
props.setProperty("tokenUrl", "https://login.microsoftonline.com/Ihre-Tenant-ID/oauth2/v2.0/token");
props.setProperty("clientId", "Ihre-App-Client-ID");
props.setProperty("clientSecret", "Ihr-Client-Secret");
props.setProperty("scope", "https://graph.microsoft.com/.default");

Der Treiber holt beim Verbindungsaufbau ein Access Token per POST (grant_type=client_credentials) und sendet es als Authorization: Bearer …. Vor Ablauf (expires_in, mit 60 Sekunden Puffer) wird das Token automatisch erneuert.

OAuth 2.0 Device Code (delegiert)

Für delegierte Berechtigungen mit Benutzer-Anmeldung (z. B. Microsoft Graph /me/messages). Der Treiber zeigt URL und Code an, wartet auf die Anmeldung und nutzt danach Refresh Tokens:

props.setProperty("auth", "oauth2");
props.setProperty("oauth2GrantType", "device_code");
props.setProperty("tenant", "Ihre-Tenant-ID");
props.setProperty("clientId", "Ihre-App-Client-ID");
props.setProperty("scope", "Mail.Read offline_access");

clientSecret ist optional (Public Client). offline_access ermöglicht Token-Erneuerung per Refresh Token.

Siehe auch: Microsoft Graph-Beispiel.

HTTP Basic

props.setProperty("auth", "basic");
props.setProperty("user", "api-user");
props.setProperty("password", "geheim");

Der Treiber sendet: Authorization: Basic <base64(user:password)>

API-Schlüssel

Mit auth=apikey wird der Schlüssel entweder als Query-Parameter oder als HTTP-Header gesendet. Die Property apiKeyLocation legt das fest (query oder header, Standard: query). apiKeyParam ist der Name des Parameters bzw. Headers (Standard: apiKey).

Query-Parameter

Für APIs, die den Schlüssel in der URL erwarten (z. B. OpenWeather mit appid):

props.setProperty("auth", "apikey");
props.setProperty("apiKey", "Ihr-API-Schlüssel");
props.setProperty("apiKeyLocation", "query");
props.setProperty("apiKeyParam", "appid");

Der Treiber hängt bei jedem Request appid=Ihr-API-Schlüssel an die URL an. apiKeyLocation=query kann entfallen, da es der Standard ist.

Siehe auch: OpenWeatherMap-Beispiel.

HTTP-Header

Für APIs, die den Schlüssel im Header erwarten (z. B. Header-Name apikey):

props.setProperty("auth", "apikey");
props.setProperty("apiKey", "Ihr-API-Schlüssel");
props.setProperty("apiKeyLocation", "header");
props.setProperty("apiKeyParam", "apikey");

Der Treiber sendet bei jedem Request: apikey: Ihr-API-Schlüssel

Client-Zertifikat (mTLS)

Für APIs, die beim TLS-Handshake ein Client-Zertifikat verlangen:

props.setProperty("auth", "clientcert");
props.setProperty("keyStore", "/pfad/zu/client.p12");
props.setProperty("keyStorePassword", "geheim");
// optional:
props.setProperty("keyStoreType", "PKCS12");
props.setProperty("keyPassword", "geheim");

Ohne keyStoreType wird PKCS12 verwendet (üblich für .p12-/.pfx-Dateien). Weitere Typen wie JKS sind möglich, sofern die JVM sie unterstützt (KeyStore.getInstance(...)).

Kombiniert mit Bearer-Token:

props.setProperty("keyStore", "/pfad/zu/client.p12");
props.setProperty("keyStorePassword", "geheim");
props.setProperty("auth", "bearer");
props.setProperty("token", "mein-bearer-token");

Hinweis: OAuth2-Token-Beschaffung (Login-Flow) ist noch nicht im Treiber implementiert. Das Token muss extern beschafft und als Property übergeben werden.

Rate Limits

Manche APIs begrenzen die Anzahl der Requests pro Zeiteinheit. Der Treiber unterstützt proaktive Drosselung und automatische Wiederholung bei HTTP 429:

Property Beschreibung Standard
requestIntervalMs Mindestabstand zwischen HTTP-Request-Starts in Millisekunden (0 = aus) 0
retryOn429 Bei HTTP 429 automatisch erneut versuchen true
maxRetries Maximale Wiederholungen bei HTTP 429 5

Bei requestIntervalMs=1000 wartet der Treiber nicht pauschal 1 Sekunde nach jedem Aufruf. Er merkt sich den Startzeitpunkt des letzten Requests und wartet nur die verbleibende Zeit bis zum Intervall. Das ist besonders bei Paginierung effizient: war der letzte Request schnell und liegt er schon länger als 1 Sekunde zurück, folgt der nächste sofort.

Bei HTTP 429 wird der Request automatisch wiederholt. Die Wartezeit kommt aus dem Header Retry-After (Sekunden oder HTTP-Datum) oder — falls nicht gesetzt — aus requestIntervalMs (mindestens 1000 ms).

props.setProperty("requestIntervalMs", "1000");
props.setProperty("retryOn429", "true");

Siehe auch: Webmetic-Beispiel.

Spec-Datei

Jede anzubindende REST-API benötigt eine Spec-Datei im Format JSON oder YAML. Das Format wird an der Dateiendung erkannt: .json → JSON, .yaml / .yml → YAML. Vollständige Beispiele: spec-example.json, spec-example.yaml.

Struktur (JSON)

Beispieldatei: spec-example.json

Auszug (die Basis-URL steht in der JDBC-URL, nicht in der Spec):

{
  "entities": [
    {
      "name": "users",
      "path": "/users",
      "pagination": {
        "type": "offset",
        "limitParam": "limit",
        "offsetParam": "offset",
        "defaultLimit": 100
      },
      "columns": [
        { "name": "id", "type": "BIGINT", "primaryKey": true },
        { "name": "name", "type": "VARCHAR" },
        { "name": "email", "type": "VARCHAR" }
      ]
    }
  ]
}

Struktur (YAML)

Beispieldatei: spec-example.yaml

Gleicher Inhalt wie oben — Inhalt und Feldnamen sind identisch:

entities:
  - name: users
    path: /users
    pagination:
      type: offset
      limitParam: limit
      offsetParam: offset
      defaultLimit: 100
    columns:
      - name: id
        type: BIGINT
        primaryKey: true
      - name: name
        type: VARCHAR
      - name: email
        type: VARCHAR

Spec-Felder

Die jeweiligen Entities-Einträge definieren eine Tabelle.

Feld Beschreibung
baseUrl Basis-URL der API (optionaler Fallback, wenn die JDBC-URL keine HTTP-URL enthält)
entities Liste der JDBC-„Tabellen“
entities[].name Tabellenname
entities[].columns Spalten mit JDBC-Typ
entities[].path REST-Pfad
entities[].dataPath JSON-Pointer
entities[].filterParam Query-Parameter für serverseitige FILTER-Klausel (Inhalt wird unverändert übergeben)
entities[].selectParam Query-Parameter für Spaltenauswahl aus dem SELECT (z. B. OData $select)
entities[].orderByParam Query-Parameter für ORDERBY-Klausel
entities[].pagination Paginierung (siehe unten)
entities[].write false = alle Schreiboperationen deaktivieren (Standard: true)
entities[].insert Optional: Override für INSERT, oder false zum Deaktivieren
entities[].update Optional: Override für UPDATE, oder false zum Deaktivieren
entities[].delete Optional: Override für DELETE, oder false zum Deaktivieren

Es ist

  • name - der Tabellenname wie er in SQL verwendet wird
  • columns - Spalten mit Namen, Datentypen und optionale Primärschlüssel Kennzeichnung
  • path - der REST-Pfad für den Aufruf der REST-API
  • dataPath - JSON-Pointer zur Zeilen-Liste im Ergebnis einer Abfrage (siehe unten)

Alle weiteren Einträge sind optionale Ergänzungen.

Spalten-Typen

Unterstützte Typen in der Spec: BIGINT, INTEGER, BOOLEAN, DOUBLE, TIMESTAMP, DATE, JSON sowie die unten beschriebenen Zeichen- und Numerik-Typen.

Primärschlüssel werden durch ein "primaryKey": true an der Spalte gekennzeichnet.

SQL-ähnliche Längen- und Precision-Angaben

Für VARCHAR, CHAR, VARBINARY und DECIMAL kann die Länge bzw. Precision direkt im type-Feld in SQL-Notation angegeben werden. Die Angaben erscheinen in den JDBC-Metadaten (type_name, column_size, decimal_digits) und in system.column_list.

Typ Syntax Beispiel Metadaten
VARCHAR VARCHAR oder VARCHAR(n) "type": "VARCHAR(255)" column_size = n
CHAR CHAR oder CHAR(n) "type": "CHAR(10)" column_size = n
VARBINARY VARBINARY oder VARBINARY(n) "type": "VARBINARY(64)" column_size = n
DECIMAL DECIMAL, DECIMAL(p) oder DECIMAL(p,s) "type": "DECIMAL(10,2)" column_size = p, decimal_digits = s
NUMERIC Alias für DECIMAL "type": "NUMERIC(18)" wie DECIMAL

Hinweise:

  • Groß-/Kleinschreibung ist egal (varchar(255) = VARCHAR(255)).
  • DECIMAL ohne Klammern wird wie bisher als DOUBLE behandelt (Abwärtskompatibilität). Mit Klammern (DECIMAL(p) bzw. DECIMAL(p,s)) wird der JDBC-Typ DECIMAL verwendet.
  • Längen und Precision werden zur Laufzeit nicht erzwungen; sie dienen der Metadaten-Beschreibung für nachgelagerte Tools.
  • Ungültige Angaben (z. B. VARCHAR(0), DECIMAL(2,5), INTEGER(10)) führen beim Laden der Spec zu einem Fehler.

Beispiel:

"columns": [
  { "name": "code", "type": "CHAR(10)" },
  { "name": "email", "type": "VARCHAR(255)" },
  { "name": "payload", "type": "VARBINARY(256)" },
  { "name": "amount", "type": "DECIMAL(10,2)" }
]

Optional pro Spalte (für verschachteltes JSON):

Feld Bedeutung
jsonPath JSON Pointer für Lesen und Schreiben (RFC 6901, gleiche Syntax wie dataPath)
readPath Nur Lesen; Standard: jsonPath
writePath Nur Schreiben; Standard: jsonPath; null = nur lesbar
readOnly Spalte bei INSERT/UPDATE ignorieren
writeOnly Nicht aus API-Antwort befüllen
defaultWrite Standardwert bei INSERT, wenn Spalte fehlt
selectName API-Feldname für $select (Standard: Spaltenname bzw. erstes Segment von jsonPath)

Ohne jsonPath gilt der Spaltenname als Top-Level-JSON-Feld (bisheriges Verhalten).

jsonPath — verschachtelte JSON-Felder

Viele APIs (z. B. Microsoft Graph) liefern verschachtelte JSON-Strukturen. Mit jsonPath lassen sich SQL-Spalten darauf abbilden:

{
  "name": "start_dateTime",
  "type": "TIMESTAMP",
  "jsonPath": "/start/dateTime"
},
{
  "name": "start_timeZone",
  "type": "VARCHAR",
  "jsonPath": "/start/timeZone",
  "defaultWrite": "Europe/Berlin"
}
INSERT INTO calendar_events (subject, "start_dateTime", "end_dateTime")
VALUES ('Review', '2026-06-26T10:00:00', '2026-06-26T11:00:00');

Request-Body:

{
  "subject": "Review",
  "start": { "dateTime": "2026-06-26T10:00:00", "timeZone": "Europe/Berlin" },
  "end":   { "dateTime": "2026-06-26T11:00:00", "timeZone": "Europe/Berlin" }
}

Für komplexe Strukturen (z. B. Teilnehmerlisten) Typ JSON und JSON-Literal in SQL:

{ "name": "attendees", "type": "JSON", "jsonPath": "/attendees", "writeOnly": true }

Beim SELECT werden Werte von denselben Pfaden gelesen. Spaltennamen mit Unterstrich in SQL quoten ("start_dateTime").

dataPath – wo stehen die Zeilen im JSON?

Zum Parsen der JSON-Antwort muss per JSON-Pointer zum Array mit den Datensätzen navigiert werden. Zur Syntax siehe die RFC zu Json-Pointer.

API-Antwort dataPath
Flaches Array [{...},{...}] "" (oder Feld weglassen)
{ "data": [{...}] } /data
{ "items": [{...}] } /items

Der leere JSON-Pointer "" bezeichnet laut RFC 6901 das Wurzelelement — bei einer direkten Array-Antwort also das Array selbst. / ist nicht die Wurzel.

Flaches Array – Antwort ist direkt ein JSON-Array (Standard, dataPath weglassen oder ""):

[
  { "id": 1, "title": "Hello" },
  { "id": 2, "title": "World" }
]

Verschachtelt unter data (dataPath: "/data"):

{
  "data": [
    { "id": 1, "name": "Alice" },
    { "id": 2, "name": "Bob" }
  ]
}

Verschachtelt unter items (dataPath: "/items"):

{
  "items": [
    { "sku": "A-100", "qty": 5 },
    { "sku": "B-200", "qty": 12 }
  ]
}

Es werden nur flache Felder auf oberster Ebene jedes Array-Elements als Spalten gelesen — sofern kein jsonPath gesetzt ist. Verschachtelte Objekte und Arrays werden sonst als String serialisiert (siehe jsonPath oben).

API-Antwort:

[
  {
    "id": 1,
    "name": "Alice",
    "address": { "city": "Berlin", "zip": "10115" },
    "tags": ["vip", "beta"]
  }
]

Ergebnis als JDBC-Zeile (eine Spalte pro Top-Level-Feld):

id name address tags
1 Alice {"city":"Berlin","zip":"10115"} ["vip","beta"]

Die Felder city und zip innerhalb von address werden nur mit jsonPath zu eigenen Spalten (z. B. "jsonPath": "/address/city").

Pagination

type Bedeutung Parameter
none Ein Request, alle Zeilen
offset Limit/Offset limitParam, offsetParam, defaultLimit
page Seitennummer (1-basiert) limitParam, pageParam, defaultLimit
nextLink Opaque URL aus der Antwort limitParam, nextLinkPath, defaultLimit

defaultLimit ist die Seitengröße (Anzahl Zeilen pro Request). Bei offset und page holt der Treiber automatisch weitere Seiten, bis die API eine leere Liste liefert oder weniger Zeilen als defaultLimit zurückkommen. Bei nextLink folgt der Treiber der URL aus nextLinkPath (Standard: /@odata.nextLink), bis kein Link mehr geliefert wird.

Ohne Paginierung (none)

Die API liefert alle Datensätze in einem Request:

"pagination": { "type": "none" }
SELECT id, name FROM users;

→ ein Aufruf: GET /users

Offset-Paginierung

Spec, wenn die API limit und offset als Query-Parameter erwartet:

"pagination": {
  "type": "offset",
  "limitParam": "limit",
  "offsetParam": "offset",
  "defaultLimit": 2
}
SELECT id, name FROM users;

Beispiel: 5 Datensätze in der API, Seitengröße 2 — der Treiber führt nacheinander aus:

# HTTP-Request Antwort (Auszug)
1 GET /users?limit=2&offset=0 [{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]
2 GET /users?limit=2&offset=2 [{"id":3,"name":"Carol"},{"id":4,"name":"Dave"}]
3 GET /users?limit=2&offset=4 [{"id":5,"name":"Eve"}]

Nach Request 3 endet das Nachladen (nur 1 Zeile, weniger als defaultLimit). Das ResultSet enthält alle 5 Zeilen — für die Anwendung wirkt es wie ein einzelnes SELECT.

Seiten-Paginierung

Spec, wenn die API limit und page (ab Seite 1) erwartet:

"pagination": {
  "type": "page",
  "limitParam": "limit",
  "pageParam": "page",
  "defaultLimit": 2
}
SELECT id, name FROM users;

Gleiche 5 Datensätze, Seitengröße 2:

# HTTP-Request Antwort (Auszug)
1 GET /users?limit=2&page=1 [{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]
2 GET /users?limit=2&page=2 [{"id":3,"name":"Carol"},{"id":4,"name":"Dave"}]
3 GET /users?limit=2&page=3 [{"id":5,"name":"Eve"}]

nextLink-Paginierung (OData)

Für APIs wie Microsoft Graph, die @odata.nextLink in der JSON-Antwort liefern:

"dataPath": "/value",
"pagination": {
  "type": "nextLink",
  "limitParam": "$top",
  "nextLinkPath": "/@odata.nextLink",
  "defaultLimit": 100
}

Der erste Request nutzt $top; Folgeseiten werden über die komplette nextLink-URL abgerufen (ohne eigene Query-Parameter).

Beispiel JSONPlaceholder

Siehe JSONPlaceholder — dort u. a. Paginierung mit _limit/_page und die vollständige Spec.

Schreiben – Defaults und Overrides

Jede Entity braucht einen expliziten path in der Spec (REST-Endpunkt für GET {baseUrl}{path}). Der Treiber leitet ihn nicht aus dem SQL-Tabellennamen (name) ab — name und path können unterschiedlich sein (z. B. "name": "orders", "path": "/api/v2/order").

Derselbe path gilt auch für Schreiben. INSERT, UPDATE und DELETE sind standardmäßig eingeschaltet und der Treiber bildet sie so ab:

SQL Default HTTP
INSERT INTO … POST {path}
UPDATE … FILTER id=1 PUT {path}/{id}
DELETE … FILTER id=1 DELETE {path}/{id}

Wie der Treiber im Standard funktioniert: dass Schreiben ist erlaubt, die HTTP-Methode und das URL-Muster leiten sich aus dem Pfad ab ({path} für INSERT, {path}/{id} für UPDATE/DELETE).

{id} im Pfad wird durch den Wert aus FILTER param=wert ersetzt (z. B. FILTER id=1/posts/1).

Override bei abweichender API

Der Pfad und die Methode können zum Beispiel beim UPDATE überschrieben werden:

{
  "name": "posts",
  "path": "/posts",
  "update": { "path": "/v2/orders/{id}", "method": "PATCH" }
}

Schreiben deaktivieren

Das Schreiben kann so deaktiviert werden:

{
  "name": "users",
  "path": "/users",
  "write": false
}

Das geht auch einzeln: "insert": false, "update": false, "delete": false.

SQL-Syntax

Grundlegende SELECT-Abfrage

SELECT id, name, email
FROM users

Das entspricht: GET {baseUrl}/users.

FILTER-Klausel

Die FILTER-Klausel leitet Filter an die REST-API weiter — serverseitig, nicht lokal wie WHERE. Das Verhalten hängt von der Spec ab:

Standard: param=wert

Ohne filterParam in der Spec gilt die Syntax param=wert:

SELECT id, name FROM users FILTER id=2
;

SELECT id, title FROM posts FILTER userId=1
;

DELETE FROM posts FILTER id=1
;

Das erzeugt z.B. GET /users?id=2, GET /posts?userId=1, DELETE /posts/1.

Der Name links vom = ist der HTTP-Query-Parameter bzw. Platzhalter im DELETE-/UPDATE-Pfad ({id}). Er muss nicht mit einer SELECT-Spalte übereinstimmen.

API mit generischem Suchparameter (z. B. q):

SELECT id, name FROM users FILTER q=status = 'active'
;

GET /users?q=status+%3D+%27active%27

Mehrere Parameter mit AND

Ohne filterParam können mehrere Query-Parameter in einer FILTER-Klausel kombiniert werden:

SELECT company_id, company_name
FROM wem_company
FILTER company_id='12345'
;

GET /company?company_id=12345

AND ist case-insensitive. String-Werte in einfachen Anführungszeichen setzen. Siehe Webmetic-Beispiel.

Mit filterParam: nativer API-Ausdruck

Ist in der Spec filterParam gesetzt (z. B. "$filter" für OData/Microsoft Graph), wird der gesamte FILTER-Inhalt unverändert an diesen Query-Parameter übergeben — analog zu Salesforce (SOQL) oder Eloqua (OData):

"filterParam": "$filter"
SELECT id, "displayName" FROM users
  FILTER startswith(displayName,'A') AND accountEnabled eq true
;

GET /users?$filter=startswith(displayName,'A')+AND+accountEnabled+eq+true

Für UPDATE und DELETE gilt weiterhin FILTER param=wert zur Identifikation der Ressource im Pfad.

Hinweis: WHERE filtert lokal auf bereits geladene Zeilen. Für REST-APIs solltest du FILTER verwenden.

Bei ungültiger FILTER-Syntax meldet der Treiber den empfangenen FILTER-Text und einen konkreten Hinweis (z. B. fehlendes =, OR statt AND). Die HTTP-URL wird in diesem Fall noch nicht erzeugt — bei erfolgreichen Requests hilft stdoutlog=http.

ORDERBY-Klausel

Wenn orderByParam in der Spec gesetzt ist, kann man es in der Query verwenden:

SELECT id, name
FROM users
ORDERBY name ASC

Hinweis: Es heißt ORDERBY (datasqill-Dialekt), nicht ORDER BY.

Spaltenauswahl (selectParam)

Ist selectParam in der Spec gesetzt (z. B. "$select" für OData), sendet der Treiber nur die im SELECT referenzierten Spalten an die API. Die Feldnamen stammen aus der Spec:

  • Spaltenname, wenn kein jsonPath gesetzt ist
  • selectName, falls angegeben
  • sonst erstes Segment von jsonPath (z. B. /from/emailAddress/addressfrom)
"selectParam": "$select"
SELECT id, "displayName", mail FROM users
;

GET /users?$select=id,displayName,mail

Ohne selectParam werden alle in der Spec definierten Spalten angefordert (bisheriges Verhalten).

Spaltennamen und Anführungszeichen

Unquoted Identifier werden vom SQL-Parser in Kleinbuchstaben normalisiert (userIduserid). Spaltennamen in der Spec mit gemischter Schreibweise (z. B. userId aus JSON-APIs) müssen in INSERT und UPDATE in Anführungszeichen stehen:

INSERT INTO posts ("userId", title, body)
VALUES (1, 'Neuer Post', 'Inhalt')
;

UPDATE posts SET title = 'Neuer Titel' FILTER id=1
;

In SELECT gilt dasselbe für Spalten mit gemischter Schreibweise:

SELECT id, "userId", title FROM posts
;

FILTER-Parameter werden als Text an die API weitergegeben und sind von der Spaltennamen-Normalisierung nicht betroffen (FILTER userId=1 bleibt userId).

INSERT

INSERT schreibt neue Datensätze über die REST-API. Im Standard führt dies zu einem POST auf den Entity-path. Dieser kann bei Bedarf überschrieben werden durch:

"insert": { "path": "/v2/posts", "method": "POST" }
INSERT INTO posts ("userId", title, body)
VALUES (1, 'Neuer Post', 'Inhalt')
;

Entspricht: POST {baseUrl}/posts mit JSON-Body aus den Spalten/Werten.

UPDATE

UPDATE aktualisiert Datensätze über die REST-API. Die Angabe eines FILTER ist Pflicht. Im Standard wird ein HTTP PUT verwendet, etwa PUT auf {path}/{id}. Dies kann in der Spec mit PATCH überschrieben werden:

"update": { "method": "PATCH" }
UPDATE posts SET title = 'Neuer Titel', body = 'Neuer Inhalt' FILTER id=1
;

Entspricht: PUT {baseUrl}/posts/1 (bzw. PATCH bei Override).

DELETE

Mit DELETE können Datensätze in der REST-API gelöscht werden. Damit wird ein HTTP DELETE abgesetzt, der im Standard den Pfad aus der Spec verwendet ({path}/{id}). Auch dieser Pfad kann in der Spec überschrieben werden:

"delete": { "path": "/posts/{id}" }
DELETE FROM posts FILTER id=1
;

Der Wert aus id=1 ersetzt {id} im Pfad → DELETE {baseUrl}/posts/1.

Pfad-Parameter in der Spec

Enthält der Entity-path Platzhalter wie {userId}, werden diese aus dem FILTER oder INSERT gebunden:

"path": "/users/{userId}/events",
"update": { "path": "/users/{userId}/events/{id}", "method": "PATCH" }
SELECT id, subject FROM user_calendar_events FILTER userId='GUID' AND startswith(subject,'Team');

INSERT INTO user_calendar_events ("userId", subject, "start_dateTime", "end_dateTime")
VALUES ('GUID', 'Meeting', '2026-07-01T10:00:00', '2026-07-01T11:00:00');

UPDATE user_calendar_events SET subject = 'Neu' FILTER userId='GUID' AND id=EVENT-ID;

Pfad-Parameter (userId=…) stehen am Anfang des FILTER, getrennt durch AND vom OData-Teil ($filter) oder weiteren Pfad-Parametern (id=…).

System-Tabellen

Über das Schema system stehen virtuelle Tabellen zur Verfügung:

Tabelle Inhalt
table_list Alle Entities aus der Spec
column_list Spalten aller Entities
pk_list Primary Keys aus der Spec
SELECT table_name, table_type, remarks
FROM system.table_list
WHERE table_name = 'users'
;

SELECT column_name, type_name, column_size, decimal_digits
FROM system.column_list
WHERE table_name = 'users'
;

SELECT table_name, column_name, key_seq
FROM system.pk_list
WHERE table_name = 'users'
;

Fehlersuche

Zum Debuggen von Verbindungs- und Abfrageproblemen:

stdoutlog=cursor+http+rows
Level Ausgabe
cursor Geöffnete Tabelle und FILTER-Text
http HTTP-Methode und vollständige URL inkl. Query-Parameter
rows Anzahl empfangener Zeilen pro HTTP-Antwort und Gesamtzahl beim Schließen des Cursors
all alle Level

Typische Fehler:

Meldung Ursache / Lösung
FILTER could not be mapped to query parameters FILTER-Syntax prüfen: param=wert mit AND, Strings in '…'
HTTP 401 / HTTP 403 Auth prüfen (z. B. Webmetic: auth=apikey, Header Authorization ohne Bearer)
HTTP 429 requestIntervalMs setzen (Webmetic: 1000), siehe Rate Limits
Property wird nicht erkannt Property-Namen sind case-insensitive; Werte prüfen

Kompatible REST-APIs

Übersicht bekannter APIs, die sich per Spec-Datei anbinden lassen:

Beispiele

Schritt-für-Schritt-Anleitungen für konkrete REST-APIs:

  • JSONPlaceholder — öffentliche Test-API ohne Authentifizierung
  • GitHub — GitHub-API mit Bearer-Token
  • REST Countries — Länderdaten (nur Lesen, Paginierung, jsonPath)
  • OpenWeatherMap — Wettervorhersage mit API-Schlüssel und FILTER q=…
  • Microsoft Graph — Microsoft 365 (OAuth2, OData-FILTER, nextLink, $select)
  • Webmetic — B2B-Firmenstammdaten (wem_company), Paginierung und Rate Limiting (requestIntervalMs=1000)

Anmerkungen

Verschachteltes JSON

Felder wie "address": { "city": "Berlin" } werden nicht automatisch zu Spalte address.city. Nur Top-Level-Felder jedes Array-Elements sind Spalten.

Fehlerbehandlung

HTTP-Fehler (4xx, 5xx) führen zu SQLException mit Statuscode und Response-Body.