Der RestJdbc Treiber ermöglicht das Lesen und Schreiben von Daten aus bzw. in REST-APIs über JDBC mit SQL.
Es gelten folgende Konventionen:
jdbc:rest: verwendetjdbc:rest:https://…)baseUrl in der Spec als Fallback gesetzt werden, wenn die JDBC-URL keine HTTP-URL enthältpublic verwendet (Standard)users, orders)jsonPath)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:
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
@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: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.
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);
| 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
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 → basictoken gesetzt → bearerapiKey gesetzt → apikeykeyStore gesetzt → clientcertnoneDie 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.
props.setProperty("auth", "none");
Oder einfach keine Auth-Properties setzen.
props.setProperty("auth", "bearer");
props.setProperty("token", "eyJhbGciOiJIUzI1NiIs...");
Der Treiber sendet: Authorization: Bearer <token>
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.
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.
props.setProperty("auth", "basic");
props.setProperty("user", "api-user");
props.setProperty("password", "geheim");
Der Treiber sendet: Authorization: Basic <base64(user:password)>
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).
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.
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
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.
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.
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.
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" }
]
}
]
}
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
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
Alle weiteren Einträge sind optionale Ergänzungen.
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.
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:
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.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).
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").
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").
| 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.
none)Die API liefert alle Datensätze in einem Request:
"pagination": { "type": "none" }
SELECT id, name FROM users;
→ ein Aufruf: GET /users
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.
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"}] |
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).
Siehe JSONPlaceholder — dort u. a. Paginierung mit _limit/_page und die vollständige Spec.
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).
Der Pfad und die Methode können zum Beispiel beim UPDATE überschrieben werden:
{
"name": "posts",
"path": "/posts",
"update": { "path": "/v2/orders/{id}", "method": "PATCH" }
}
Das Schreiben kann so deaktiviert werden:
{
"name": "users",
"path": "/users",
"write": false
}
Das geht auch einzeln: "insert": false, "update": false, "delete": false.
SELECT id, name, email
FROM users
Das entspricht: GET {baseUrl}/users.
Die FILTER-Klausel leitet Filter an die REST-API weiter — serverseitig, nicht lokal wie WHERE. Das Verhalten hängt von der Spec ab:
param=wertOhne 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
ANDOhne 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.
filterParam: nativer API-AusdruckIst 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.
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.
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:
jsonPath gesetzt istselectName, falls angegebenjsonPath (z. B. /from/emailAddress/address → from)"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).
Unquoted Identifier werden vom SQL-Parser in Kleinbuchstaben normalisiert (userId → userid). 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 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 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).
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.
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=…).
Ü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'
;
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 |
Übersicht bekannter APIs, die sich per Spec-Datei anbinden lassen:
Schritt-für-Schritt-Anleitungen für konkrete REST-APIs:
jsonPath)FILTER q=…FILTER, nextLink, $select)wem_company), Paginierung und Rate Limiting (requestIntervalMs=1000)Felder wie "address": { "city": "Berlin" } werden nicht automatisch zu Spalte address.city. Nur Top-Level-Felder jedes Array-Elements sind Spalten.
HTTP-Fehler (4xx, 5xx) führen zu SQLException mit Statuscode und Response-Body.