Zum Inhalt springen

Dynamic Query

Der Namespace /api/v1/dynamic/query stellt Endpunkte bereit, um flexible SQL-ähnliche Abfragen über JSON zu definieren. Die Engine übersetzt Request-Payloads in SQLAlchemy-Queries und unterstützt unter anderem:

  • SELECT
  • WHERE
  • JOIN
  • GROUP BY
  • ORDER BY
  • Subqueries in Filtern

Verfügbare Endpunkte:

MethodePfad
GET/api/v1/dynamic/query/tables
GET/api/v1/dynamic/query/attributes
POST/api/v1/dynamic/query
POST/api/v1/dynamic/query/bulk
POST/api/v1/dynamic/query/default-configuration

Alle Dynamic-Query-Endpunkte benötigen Authentifizierung. Standardmäßig ist ein effektiver Tenant-Kontext erforderlich. Tabellen und Attribute werden nach READ-Berechtigung gefiltert.


query_model und target_model müssen Keys sein, die GET /api/v1/dynamic/query/tables zurückgibt. Der aktuelle Registry-Aufbau verwendet Tabellennamen aus __tablename__; verlasse dich nicht darauf, dass Singular-, Plural- oder Klassenname-Varianten zusätzlich funktionieren.

Beispiele für tabellenbasierte Keys:

  • users
  • agents
  • agent_tools

Eine Dynamic-Query-Anfrage enthält dieses Schema:

FeldTypBeschreibung
query_modelStringPflichtfeld. Key aus /tables.
query_attributesListeOptional. Liste aus Strings oder [expression, alias]-Paaren.
query_filterObjektOptional. Filterbaum mit Operatoren.
order_byListe aus StringsOptional. -field sortiert absteigend.
group_byListe aus StringsOptional. Gruppierungsfelder.
joinsListeOptional. Join-Definitionen.
pageObjektPflichtfeld bei POST /dynamic/query und bei jedem Eintrag in POST /dynamic/query/bulk.

Join-Definitionen haben dieses Format:

{
"target_model": "agent_tools",
"on": "id=agent_id",
"isouter": false
}

on kann ein String oder null sein. isouter ist ein Boolean.

Terminal-Fenster
curl https://api.livoi.de/api/v1/dynamic/query \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_TOKEN' \
--data '{
"query_model": "users",
"query_attributes": ["id", "email"],
"page": { "number": 1, "size": 10 }
}'

Terminal-Fenster
curl 'https://api.livoi.de/api/v1/dynamic/query/tables?include_attributes=true' \
--header 'Authorization: Bearer YOUR_SECRET_TOKEN'

Beispielantwort:

{
"tables": {
"users": ["id", "email", "created_at"],
"agents": ["id", "name", "tenant_id"],
"agent_tools": ["id", "agent_id", "tool_id"]
}
}
Terminal-Fenster
curl 'https://api.livoi.de/api/v1/dynamic/query/attributes?query_model=users' \
--header 'Authorization: Bearer YOUR_SECRET_TOKEN'

Beispielantwort:

{
"attributes": [
"content_type",
"created_at",
"file_name",
"id",
"size",
"tags",
"title",
"updated_at",
"uri"
]
}

{
"query_model": "users",
"joins": [
{ "target_model": "agents", "on": "id=user_id", "isouter": true }
],
"query_attributes": [
"id",
"email",
"agents.name"
],
"page": { "number": 1, "size": 10 }
}

Filterfelder verwenden dieses Format:

field__operator

Felder ohne Operator sind ungültig. Statt permission_level muss zum Beispiel permission_level__eq verwendet werden.

Unterstützte Operatoren:

SuffixSQLWert
__eq=Einzelwert
__ne!=Einzelwert
__lt<Einzelwert
__lte<=Einzelwert
__gt>Einzelwert
__gte>=Einzelwert
__likeLIKEString
__ilikeILIKEString
__inINListe
__not_inNOT INListe
__isnullIS NULL / IS NOT NULLBoolean

in und not_in verlangen Listen. isnull erwartet einen booleschen Wert. Filter können mit and und or verschachtelt werden.

{
"query_model": "users",
"query_filter": {
"and": [
{ "permission_level__in": ["ADMIN", "OWNER"] },
{ "last_login__gte": "2024-01-01T00:00:00Z" },
{ "deleted_at__isnull": true }
]
},
"page": { "number": 1, "size": 10 }
}

{
"id__in": {
"subquery": {
"query_model": "agent_tools",
"query_attributes": ["agent_id"],
"query_filter": {
"tool_id__eq": "tool_123"
}
}
}
}

Subquery-Vergleiche sind für normale Operatoren scalar mit limit(1). Für in und not_in wird die Subquery als Mengenvergleich genutzt.


  1. target_model

    Der Ziel-Key muss aus /api/v1/dynamic/query/tables stammen.

  2. ON-Clause

    Das Format ist left_field=right_field, zum Beispiel id=agent_id.

  3. Join-Typ

    ”isouter”: true erzeugt einen LEFT OUTER JOIN.

{
"query_model": "agents",
"joins": [
{
"target_model": "agent_tools",
"on": "id=agent_id",
"isouter": true
}
],
"query_attributes": [
"id",
"name",
["ARRAY_AGG(agent_tools.tool_id)", "tool_ids"]
],
"group_by": ["id", "name"],
"page": { "number": 1, "size": 10 }
}

{
"query_model": "users",
"order_by": [
"permission_level",
"-last_login"
],
"page": { "number": 1, "size": 10 }
}
  • field: aufsteigend
  • -field: absteigend

{
"query_model": "agents",
"query_attributes": [
"tenant_id",
["COUNT(id)", "agent_count"]
],
"group_by": ["tenant_id"],
"page": { "number": 1, "size": 10 }
}

Terminal-Fenster
curl https://api.livoi.de/api/v1/dynamic/query/bulk \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_SECRET_TOKEN'

Beispiel-Body:

{
"queries": [
{
"query_model": "users",
"query_attributes": ["id", "email"],
"page": { "number": 1, "size": 10 }
},
{
"query_model": "agents",
"query_attributes": [
"tenant_id",
["COUNT(id)", "agent_count"]
],
"group_by": ["tenant_id"],
"page": { "number": 1, "size": 10 }
}
]
}

POST /api/v1/dynamic/query/default-configuration

Filter:

  • reference
  • function

BereichLimit
query_attributes500
Bulk-Queries100
Filter-Branches250
Filter-Depth25
Filter-Nodes100
Filter-Werte für in / not_in100
group_by100
joins50
order_by100
page.size1000

{
"status_code": "403",
"status_message": "Forbidden",
"errors": [
{
"info": "Missing permission for agents.read."
}
]
}