GraphQL Entwickler Dokumentation - Abfragen (Queries)
1. Einführung
1.1. Über diese Dokumentation
Diese Dokumentation führt Sie Schritt für Schritt in die Funktionalität der Schnittstelle ein und behandelt spezifische Aspekte des microtech GraphQL-Schemas, der Tabellenstruktur (tbl...
) und der Paginierung. Ziel ist es, Ihnen alle Vorteile von GraphQL verständlich und anhand von Beispielen zu vermitteln. Wir empfehlen, diese Dokumentation vollständig durchzuarbeiten, um die Besonderheiten der Schnittstelle und deren Einsatz in Verbindung mit der microtech Software gezielt zu erlernen.
1.2. Voraussetzungen
Zur Nutzung des Datenbankzugriffs per GraphQL ist eine installierte microtech BETA Version (Gen. 24 Enterprise) erforderlich. Vor der Nutzung der GraphQL-Schnittstelle muss sichergestellt werden, dass für den Datenserver HTTP/2 eingerichtet und aktiviert ist und ein entsprechendes Zertifikat in der "microtech Serverkonfiguration" (BpConfig.exe) hinterlegt ist. Weitere Informationen hierzu finden Sie in der microtech Hilfe: Serverkonfiguration - Register: HTTP/2.
1.3. Einrichtung und Verwendung des GraphQL-Servers
1.3.1. Einrichtung des GraphQL-Servers
Sind die in Abschnitt 1.3 genannten Voraussetzungen erfüllt, kann ein Aufgaben-Dienst des Typs "GraphQL-Server" angelegt und gestartet werden. Bei der Einrichtung haben Sie die Möglichkeit, die Option "Interaktive Web-Oberfläche aktivieren" auszuwählen.
Im Ereignisprotokoll des gestarteten Dienstes erhalten Sie die URL, über die auf den GraphQL-Endpunkt zugegriffen werden kann. Kopieren Sie hierzu die unter Registerkarte: DATEI - INFORMATIONEN - GLOBALE DATEN - Schaltfläche: EREIGNIS PROTOKOLL bereitgestellte URL.
Weiterhin lässt sich auf dem Register: AUTOMATISIERUNGS-DIENSTE über Registerkarte: START - DETAILS die Tabelle: EREIGNIS-PROTOKOLL einblenden.
Öffnen Sie mit einem Doppelklick den Eintrag: "Automatisierungsaufgabe aktiviert [Im laufenden Automatisierungs-Service Betrieb]". In diesem Eintrag finden Sie eine entsprechende URL.
Die im Ereignisprotokoll angezeigte URL ist der Endpunkt, an den GraphQL-Anfragen gesendet werden können. Die URL selbst enthält einen Zugriffsschlüssel und gewährleistet den Zugriff auf alle Datenbanktabellen des Servers, die durch die Berechtigungen im Aufgaben-Dienst freigegeben sind. Aus Sicherheitsgründen sollte diese URL nur mit autorisierten Personen geteilt werden, da sie die Berechtigung zum Zugriff auf den GraphQL-Server beinhaltet.
1.3.2. Verwendung der interaktiven GraphQL-Oberfläche
Wenn bei der Einrichtung des GraphQL-Servers die interaktive Web-Oberfläche aktiviert wurde, können Sie durch das Eingeben der Endpunkt-URL in einen Webbrowser eine graphische Benutzeroberfläche aufrufen. Diese interaktive Umgebung dient dazu, den gesamten Umfang der GraphQL-Schnittstelle mit allen für die microtech Software verfügbaren Abfragen zu testen.
Es ist wichtig zu beachten, dass diese integrierte Web-Oberfläche nur eine von mehreren Möglichkeiten ist, mit dem GraphQL-Endpunkt zu interagieren. Selbst wenn die eingebaute Oberfläche deaktiviert ist, können Sie weiterhin GraphQL-Anfragen an den Endpunkt senden und beliebige andere GraphQL-Entwicklungsumgebungen wie GraphiQL, Apollo Studio, Postman oder Insomnia verwenden.
Im Folgenden finden Sie praktische Beispiele, die als Inspiration und Anleitung für eigene Abfragen dienen können.
Tipp
Auf folgender Seite erhalten Sie praktische Tipps für Beispiel-Abfragen:
1.4. Architekturüberblick
Die GraphQL-Schnittstelle der microtech Software basiert auf einer mehrschichtigen Architektur, bei der verschiedene Komponenten zusammenarbeiten. Zum Verständnis der Dokumentation ist es wichtig, die folgenden Begriffe und Konzepte zu kennen:
1.4.1. Komponenten der Architektur
-
BpServer (Datenserver): Der Datenbankserver, benannt nach "Büro plus" (früherer Name der Software), auf dem die eigentlichen Daten gespeichert sind. Er basiert auf NexusDB, einer in Delphi entwickelten, komponentenorientierten Datenbank, die eine hohe Modularität und Anpassungsfähigkeit bietet. Der BpServer ist für die effiziente Speicherung, Indizierung und den Basisabruf der Daten verantwortlich.
-
Anwendungsserver: Der Server, auf dem die microtech Geschäftslogik ausgeführt wird. Er übernimmt komplexere Berechnungen, führt Regeln aus und bereitet Daten für die Präsentation vor. Der GraphQL-Ausführungscode ist als integraler Bestandteil des Anwendungsservers implementiert und arbeitet direkt mit dem vorhandenen Anwendungsframework zusammen.
-
GraphQL-Server: Die Schnittstelle, die GraphQL-Anfragen von externen Clients entgegennimmt und an die Anwendungsserver zur Verarbeitung weiterleitet. Die Antworten der Anwendungsserver werden dann zurück an den Client, der die Anfrage gestellt hat, übermittelt.
1.4.2. Wichtige Begriffe
-
Datenserverseitig: Operationen oder Funktionen, die direkt auf dem Datenserver ausgeführt werden können. Diese sind in der Regel effizienter, da sie näher an den eigentlichen Daten arbeiten und weniger Daten über Netzwerkschichten bewegen müssen. Ein Beispiel hierfür ist der
fastFilter
, der direkt in Datenbankabfragen übersetzt werden kann. -
Anwendungsserverseitig: Operationen oder Funktionen, die auf dem Anwendungsserver ausgeführt werden, nachdem Daten vom Datenserver geladen wurden. Diese können komplexere Logik enthalten, die über einfache Datenbankoperationen hinausgeht, sind aber potenziell weniger effizient. Ein Beispiel hierfür ist der
slowFilter
, der komplexere Berechnungen durchführen kann, die auf Datenbankebene nicht ohne weiteres möglich sind.
Diese Unterscheidung ist besonders beim Verständnis und der Optimierung von Filterausdrücken wichtig (siehe Kapitel 5), da sie direkten Einfluss auf die Performance und Funktionalität Ihrer GraphQL-Abfragen hat.
2. Grundlagen
2.0 Begriffliche Unterscheidung
In dieser Dokumentation werden verschiedene Arten von "Feldern" unterschieden:
- GraphQL-Felder sind Elemente in der GraphQL-Abfrage, die meist mit verschiedenen Präfixen versehen sind, um ihre Funktion zu kennzeichnen (z. B.
tbl...
,row...
,fld...
, etc.). - Datenfelder sind Felder in der Tabelle, auf die in GraphQL über Felder mit dem Präfix
fld...
zugegriffen wird (z. B. auf das Datenfeld "ArtNr" überfldArtNr
). - Schlüsselfelder sind Datenfelder, die als Teil einer Sortierfolge verwendet werden. Auf dasselbe Datenfeld kann je nach Kontext über verschiedene GraphQL-Parameter zugegriffen werden: direkt als Wert über ein
fld...
-Feld oder als Teil einer Sortierfolge überkf...
-Parameter (z. B. beziehen sichfldArtNr
,kf1ArtNr
,kf2ArtNr
alle auf dasselbe Datenfeld "ArtNr", aber in unterschiedlichen Kontexten). - Sortierfolgen sind definierte Reihenfolgen in der Tabelle, auf die in GraphQL über Parameter mit dem Präfix
by...
zugegriffen wird.
2.1 Zugriff auf Tabellen über tbl...
-Felder
Der Zugriff auf Datenbanktabellen erfolgt über GraphQL-Felder auf der obersten Ebene der query
, die mit dem Präfix tbl...
beginnen (z. B. tblProducts
, tblClient
). Diese tbl...
-Felder stellen die einzelnen Tabellen dar und bilden den Ausgangspunkt für alle Operationen, die mit dieser Tabelle durchgeführt werden können. Sie liefern einen Zugriffskontext zurück, über den dann die eigentlichen Daten und Funktionen der jeweiligen Tabelle angesprochen werden können.
Wichtig zu beachten ist, dass der Zugriff auf ein tbl...
-Feld selbst noch keine Datenbankoperation auslöst – erst die Verwendung der darauf aufbauenden Felder führt zu tatsächlichen Datenbankabfragen.
Beispiel:
query {
tblProducts { # Zugriff auf das ProductsTableQueryRead-Objekt
# ... hier folgen Felder zum Lesen von Daten (rowRead, rowsRead, conRead)
}
tblClient { # Zugriff auf das ClientTableQueryRead-Objekt
# ...
}
}
2.2 Lesen von Datensätzen aus Table
-Objekten
Innerhalb eines Table
-Objekts (zurückgegeben von einem tbl...
-Feld) gibt es GraphQL-Felder, um auf die eigentlichen Datensätze (Row
-Objekte) zuzugreifen:
-
Das
rowRead
-Feld liest einen einzelnen Datensatz.- Es gibt ein einzelnes
Row
-Objekt (z. B.ProductRowQueryRead
) zurück, odernull
, wenn kein entsprechender Datensatz gefunden wurde. - Es erfordert einen der Parameter
exactMatch
odernearestMatch
zur Spezifizierung des zu lesenden Datensatzes (siehe Kapitel 3). - Es ist ideal für das gezielte Abrufen eines bestimmten Datensatzes.
- Es gibt ein einzelnes
-
Das
rowsRead
-Feld liest eine Liste von Datensätzen.- Es gibt eine Liste von
Row
-Objekten (z. B.[ProductRowQueryRead]
) zurück. - Es kann ohne Parameter aufgerufen werden, um alle Datensätze der Tabelle in der Standardsortierung zu erhalten.
- Es kann mit dem Parameter
allBetween
aufgerufen werden, um einen bestimmten Bereich von Datensätzen abzurufen (siehe Kapitel 3).
- Es gibt eine Liste von
-
Das
conRead
-Feld liest eine Liste von Datensätzen mittels Relay Connection (Paginierung).- Es gibt ein
Connection
-Objekt zurück (z. B.ProductConnectionQuery
), das zwei Hauptkomponenten enthält: edges
: Eine Liste von Edge-Objekten, wobei jedes Edge unter anderem einennode
(das eigentlicheRow
-Objekt) enthältpageInfo
: Metadaten zur Paginierung- Es kann ohne Parameter aufgerufen werden (entspricht
allBetween
ohne Einschränkung). - Es kann mit dem Parameter
allBetween
aufgerufen werden, um den Bereich der Verbindung einzuschränken. - Es akzeptiert zusätzliche Paginierungsparameter wie
first
undafter
(siehe Kapitel 8).
- Es gibt ein
Wichtig zu beachten ist, dass alle drei Felder erst bei ihrer tatsächlichen Verwendung in einer GraphQL-Abfrage Datenbankoperationen auslösen.
Beispiel für rowRead
(ein spezifischer Artikel):
query {
tblProducts {
rowRead(exactMatch: { byNr: { kf1ArtNr: { string: "PROD-001" } } }) { # Liest einen Artikel
fldArtNr
fldSuchBeg
fldArtikelArt
}
}
}
Beispiel für rowsRead
(alle Artikel):
query {
tblProducts {
rowsRead { # Liest alle Artikel-Datensätze
fldArtNr
fldSuchBeg
fldArtikelArt
}
}
}
Beispiel für conRead
(paginierte Artikel):
# Eine einzelne Abfrage für alle Paginierungsschritte
query GetProductsPage($afterCursor: String = null) {
tblProducts {
conRead(
first: 10,
after: $afterCursor # Optional: null für erste Seite, endCursor von vorheriger Seite für Folgeseiten
) {
edges {
node {
fldArtNr
fldSuchBeg
fldArtikelArt
}
cursor # Positions-Marker für die Paginierung
}
pageInfo {
hasNextPage # Gibt es weitere Seiten?
endCursor # Cursor des letzten Artikels auf dieser Seite
}
}
}
}
# Variablen für die erste Seite:
# {
# "afterCursor": null # Kann wegen Default-Wert = null weggelassen werden
# }
# Variablen für die nächste Seite:
# {
# "afterCursor": "endCursor von vorheriger Seite"
# }
2.3 Einzeldatensatz-Tabellen (z. B. tblClient
)
Manche Tabellen enthalten konzeptionell nur einen einzigen Datensatz, wie beispielsweise die Mandanteninformationen (tblClient
). Diese Einzeldatensatz-Tabellen weisen folgende Besonderheiten auf:
- Mit dem Feld
rowRead
kann ohne explizite Suchparameter auf den Datensatz zugegriffen werden, da keine Suche erforderlich ist. - Die Listenzugriffsfelder mit den Präfixen
rows...
(wierowsRead
) undcon...
(wieconRead
) stehen bei diesen Tabellen nicht zur Verfügung, da es konzeptionell keine Liste von Datensätzen gibt.
Beispiel:
query {
tblClient {
rowRead { # Zugriff auf den einzigen Datensatz ohne Parameter
fldMandNr
fldMandTyp
}
}
}
2.4 Zugriff auf Datenfelder eines Datensatzes (Row
-Objekt)
Innerhalb eines Row
-Objekts (zurückgegeben z. B. von rowRead
, rowsRead
oder conRead.edges.node
) ermöglichen fld...
-Felder den Zugriff auf die Datenfelder des Datensatzes (z. B. fldArtNr
auf das Datenfeld ArtNr
).
Je nach Typ des Datenfeldes können zusätzlich GraphQL-Felder mit anderen Präfixen (z.B. aco...
für Betragsgruppen, img...
für Bilder, tbl...
für verschachtelte Tabellen, row...
für verknüpfte Datensätze) verfügbar sein, die strukturierten Zugriff auf den Inhalt des Datenfeldes bieten.
Details zu diesen Feldern und ihren Datentypen finden Sie in Kapitel 6.
2.5 Mehrere Tabellenzugriffe in einer Abfrage
Es ist möglich, in einer einzigen GraphQL-Abfrage auf mehrere Tabellen zuzugreifen, indem mehrere tbl...
-Felder aufgelistet werden. Obwohl sich dieses Handbuch in den Beispielen meist auf die Abfrage einer einzigen Tabelle konzentriert, können diese Abfragen beliebig kombiniert werden.
Beispiel:
query {
tblProducts {
rowsRead {
fldArtNr
fldSuchBeg
}
}
tblClient {
rowRead {
fldMandNr
}
}
}
2.6 Übersicht der verwendeten Präfixe
Im gesamten GraphQL-Schema werden konsistente Präfixe verwendet, um die Art und Funktion der verschiedenen Elemente kenntlich zu machen:
Datenzugriffsstruktur
tbl...
: Tabellen bzw. Tabellenobjekte (z. B.tblProducts
)row...
: Einzelner Datensatz eines bestimmten Typs (z. B. gibtrowWgrNr
ein einzelnesRow
-Objekt zurück)rows...
: Liste von Datensätzen eines bestimmten Typs (z. B. gibtrowsRead
eine Liste vonRow
-Objekten zurück)con...
:Connection
-Objekt für Paginierung (z. B. gibtconRead
ein Connection-Objekt zurück)
Datenfelder und Schlüssel
fld...
: Datenfelder innerhalb eines Datensatzes (z. B.fldArtNr
)by...
: Sortierfolgen (z. B.byNr
,bySuchBeg
)kf...
: Schlüsselfelder in Sortierfolgen (z. B.kf1ArtNr
)
Verlinkungen
lnk...
: Verlinkungen zu einer Tabelle mit verwandten Datensätzen (z. B.lnkInventory
)using...
: Angabe der Verlinkungsdatenfelder beilnk...
-Feldern (z. B.usingArtNr
)
Spezialisierte Datentypen
aco...
: Betragsgruppen (Amount Compositions) (z. B.acoEPr
)ace...
: Einzelner Eintrag einer Betragsgruppe (Amount Composition Entry)aces
: Liste von Einträgen einer Betragsgruppeimg...
: Bildfelder (z. B.imgBild
)
Filter und Funktionen
fn...
: Funktionen in Filterausdrücken (z. B.fnDay
,fnMonth
)fn...
(in Table/Row-Objekten): Geschäftslogik-Funktionen der Tabellenklassen (z. B.fnIsCustomer
)
System-Felder und Hilfsfunktionen
_...
: System-Felder für Typkonvertierung, bedingte Ausführung und Fehlerbehandlung (z. B._int
,_if
,_ifThenElse
,_raise
), verfügbar in allen Objektkontexten
3. Datensatzauswahl bei Abfragen in Table
-Objekten
Die primären Parameter für die Datensatzauswahl in Table
-Objekten sind exactMatch
und nearestMatch
für row...
-Felder (z. B. rowRead
) sowie allBetween
für rows...
- und con...
-Felder (z. B. rowsRead
und conRead
). Es ist wichtig zu beachten, dass bei jedem Aufruf nur jeweils einer dieser Parameter verwendet werden kann.
3.1 Parameter für rowRead
: exactMatch
, nearestMatch
, modifyLSN
und using
Das rowRead
-Feld dient dem gezielten Lesen eines einzelnen Datensatzes. Außer bei Einzeldatensatz-Tabellen muss eine Suchbedingung definiert werden, entweder:
- über genau einen der folgenden Parameter:
exactMatch
: Sucht nach einem Datensatz, der exakt den angegebenen Kriterien in der gewählten Sortierfolge entspricht. Gibt den ersten (in Sortierfolge) zutreffenden Datensatz zurück, odernull
, wenn kein entsprechender Datensatz gefunden wurde. Es ist nicht garantiert, dass nur ein einziger Datensatz den Kriterien entspricht.nearestMatch
: Sucht wieexactMatch
. Wird kein exakt passender Datensatz gefunden, wird der nächstfolgende Datensatz in der Sortierreihenfolge zurückgegeben. Gibt den gefundenen Datensatz zurück, odernull
, wenn die Tabelle leer ist. Bei Erreichen des Tabellendes wird auch rückwärts der nächste Datensatz gefunden.modifyLSN
: Sucht direkt nach einem Datensatz mit der angegebenen ModifyLSN. Gibt den Datensatz zurück, wenn er existiert, odernull
, wenn kein solcher Datensatz gefunden wurde.-
using
: Für Tabellen, die das Konzept eines "aktuellen" oder "Standard" Datensatzes unterstützen (z.B. der aktuell angemeldete Benutzer), kann mitusing: current
direkt auf diesen zugegriffen werden, ohne explizite Suchkriterien anzugeben. -
oder durch die verkürzte Schreibweise (siehe Kapitel 4.1), wodurch implizit
exactMatch
mit der Standardsortierung gewählt wird.
Innerhalb von exactMatch
und nearestMatch
muss die gewünschte Sortierfolge über genau einen by...
-Parameter spezifiziert werden. Es stehen verschiedene by...
-Parameter zur Verfügung, die jeweils unterschiedliche Sortierfolgen der Datenbanktabelle repräsentieren. Die verfügbaren Sortierfolgen für jede Tabelle können über das GraphQL-Schema eingesehen werden und werden in interaktiven GraphQL-Oberflächen kontextbezogen angezeigt.
Der modifyLSN
-Parameter kann auch in Kombination mit exactMatch
oder nearestMatch
verwendet werden, um zusätzlich zu prüfen, ob der gefundene Datensatz eine bestimmte Versionskennung hat (siehe Abschnitt 3.5).
Beispiel mit exactMatch
:
query {
tblProducts {
rowRead(
exactMatch: { # Parameter für rowRead
byNr: { # Auswahl der Sortierfolge
kf1ArtNr: { string: "PROD-001" } # Angabe des Schlüsselfeld-Wertes
}
}
) {
fldArtNr
fldSuchBeg
}
}
}
Beispiel mit nearestMatch
:
query {
tblProducts {
rowRead(
nearestMatch: { # Parameter für rowRead
byNr: { # Auswahl der Sortierfolge
kf1ArtNr: { string: "PROD-000" } # Suche nach diesem Wert oder dem nächsten
}
}
) {
fldArtNr
fldSuchBeg
}
}
}
Beispiel mit using: current
:
query {
tblUsers {
rowRead(using: current) { # Liest den Datensatz des aktuell angemeldeten Benutzers
fldAnmNa # Benutzername
}
}
}
Der using
-Parameter mit dem Wert current
ist besonders nützlich für:
- Abfrage der eigenen Benutzerdaten ohne Kenntnis der Benutzer-ID
- Zugriff auf Standard- oder Vorgabewerte, die für den aktuellen Kontext gelten
- Vereinfachung von Abfragen, bei denen der "aktuelle" Datensatz benötigt wird
Wichtige Hinweise zu using: current
:
- Nur verfügbar für Tabellen, die das Konzept eines "aktuellen" Datensatzes unterstützen
- Kann nicht mit anderen Suchparametern wie exactMatch
oder Schlüsselfeldern kombiniert werden
- Gibt null
zurück, wenn kein aktueller Datensatz verfügbar ist (z.B. bei fehlendem Kontext)
3.2 Parameter für rowsRead
und conRead
: allBetween
Die GraphQL-Felder rowsRead
und conRead
dienen dem Lesen mehrerer Datensätze. Der Bereich der zurückgegebenen Datensätze kann definiert werden:
- über den Parameter
allBetween
: allBetween
: Definiert einen Start- und Endpunkt für die Abfrage basierend auf den Werten der Schlüsselfelder (kf...
) in der gewählten Sortierfolge.- Innerhalb von
allBetween
muss die Sortierfolge über genau einenby...
-Parameter spezifiziert werden. - Standardmäßig liefert
allBetween
alle Datensätze, deren Schlüsselwerte im angegebenen Bereich liegen (inklusive der Grenzwerte selbst). -
Mit den optionalen Parametern
fromExclusive
undtoExclusive
(standardmäßigfalse
) kann gesteuert werden, ob die Grenzen des Bereichs ein- oder ausgeschlossen werden. Diese Parameter beziehen sich auf den gesamten ersten bzw. letzten Schlüssel des Bereichs. -
oder durch die verkürzte Schreibweise (siehe Kapitel 4.2), wodurch implizit
allBetween
mit der Standardsortierung gewählt wird. -
Wenn kein Parameter angegeben wird, werden alle Datensätze (für
rowsRead
) bzw. die gesamte Connection (fürconRead
) in der Standardsortierung zurückgegeben.
Die verfügbaren Sortierfolgen für jede Tabelle können über das GraphQL-Schema eingesehen werden und werden in interaktiven GraphQL-Oberflächen kontextbezogen angezeigt.
Beispiel mit rowsRead
und allBetween
:
query {
tblProducts {
rowsRead(
allBetween: { # Optionaler Parameter für rowsRead
byNr: { # Auswahl der Sortierfolge
kf1ArtNr: { # Angabe des Bereichs für das Schlüsselfeld
from: { string: "PROD-100" },
to: { string: "PROD-199" }
}
}
}
) {
fldArtNr
fldSuchBeg
}
}
}
Beispiel mit rowsRead
und allBetween
mit exklusiver oberer Grenze:
query {
tblProducts {
rowsRead(
allBetween: {
byNr: {
kf1ArtNr: {
from: { string: "PROD-100" },
to: { string: "PROD-200" }
}
toExclusive: true # Bereich endet vor "PROD-200"
}
}
) {
fldArtNr
fldSuchBeg
}
}
}
Beispiel mit rowsRead
ohne Parameter (alle Datensätze):
query {
tblProducts {
rowsRead { # Keine Parameter -> alle Datensätze (Standardsortierung)
fldArtNr
fldSuchBeg
}
}
}
Die Verwendung von allBetween
mit conRead
wird in Kapitel 8 (Paginierung) genauer erläutert.
3.3 Auswahl der Sortierfolge (by...
)
Wie in den Beispielen gezeigt, wird die Sortierfolge innerhalb der Parameter exactMatch
, nearestMatch
oder allBetween
über einen by...
-Parameter ausgewählt.
3.3.1 Grundlagen der Sortierfolge
Jede Datenbanktabelle verfügt über vordefinierte Sortierfolgen, welche durch by...
-Parameter repräsentiert werden (z. B. byNr
, bySuchBeg
, byArtNrLagNr
). Ihr Name setzt sich aus dem Indexnamen mit dem hinzugefügten Präfix by
zusammen und muss nicht immer die Namen aller Schlüsselfelder enthalten.
Sie können im Programm benutzerdefinierte Sortierfolgen anlegen. Weitere Informationen dazu finden Sie in der microtech Hilfe: Selektionen und Sortierungen.
Die verfügbaren Sortierfolgen können über das GraphQL-Schema eingesehen werden und werden in interaktiven GraphQL-Oberflächen kontextbezogen angezeigt.
3.3.2 Was ist eine Sortierfolge?
Eine Sortierfolge definiert die Reihenfolge der Datensätze basierend auf einem oder mehreren Schlüsselfeldern (kf...
). Bei mehreren Feldern wird primär nach kf1...
, sekundär (bei Gleichheit von kf1...
) nach kf2...
usw. sortiert.
3.3.3 Beschränkungen und Hinweise
- Es kann immer nur eine Sortierfolge pro Aufruf eines GraphQL-Feldes (
rowRead
,rowsRead
,conRead
) ausgewählt werden. - Die gewählte Sortierfolge beeinflusst, welcher Datensatz bei
exactMatch
undnearestMatch
als "erster" oder "nächster" betrachtet wird. - Bei der Verwendung von
allBetween
bestimmt die Sortierfolge nicht nur die Reihenfolge der zurückgegebenen Daten, sondern auch deren Umfang, da die Auswahl der Datensätze durch die Festlegung eines Start- und Endpunkts innerhalb dieser Sortierung eingeschränkt wird.
3.4 Angabe von Feldwerten in der Sortierfolge (kf...
)
Nachdem die Sortierfolge (by...
) gewählt wurde, können die Werte für die entsprechenden Schlüsselfelder (kf1...
, kf2...
, etc.) spezifiziert werden, um die Suche (exactMatch
, nearestMatch
) oder den Bereich (allBetween
) zu definieren.
3.4.1 Einzelne Feldwerte
Im einfachsten Fall geben Sie einen einzelnen Feldwert für das erste Schlüsselfeld (kf1...
) innerhalb der gewählten Sortierfolge an. Dies verfeinert die Datensatzauswahl und begrenzt die Resultate auf Datensätze, die den spezifizierten Kriterien entsprechen (bei exactMatch
/nearestMatch
), oder dient als Startpunkt (bei allBetween
, wenn nur ein Feld angegeben wird).
Typ des Schlüsselfeldwerts
Jedes Schlüsselfeld (kf...
) hat einen festgelegten Datentyp (String, Int, etc.). Beim Angeben eines Schlüsselfeldwerts muss daher auch der entsprechende GraphQL-Eingabetyp spezifiziert werden. Abhängig vom Schlüsselfeldtyp stehen verschiedene Typen wie int
, float
, string
, localdate
und weitere zur Verfügung. Die genauen Typ-Optionen können Sie dem Schema entnehmen.
Beispiel (exactMatch
mit einem Feldwert):
Im folgenden Beispiel wird die Sortierfolge byNr
verwendet. Dabei wird der Wert "1"
für das Schlüsselfeld kf1ArtNr
angegeben, und zwar als string
.
query {
tblProducts {
rowRead(
exactMatch: {
byNr: {
kf1ArtNr: { string: "1" } # Wert für das erste (und einzige) Schlüsselfeld
}
}
) {
fldArtNr
fldSuchBeg
fldArtikelArt
}
}
}
Mit dieser Abfrage würde nur der Artikel zurückgegeben, dessen kf1ArtNr
exakt dem Wert "1"
entspricht, oder null, wenn kein solcher Datensatz existiert.
3.4.2 Mehrere Feldwerte in der Sortierfolge
In vielen Fällen umfasst die Sortierfolge mehr als ein Feld (kf1...
, kf2...
etc.). In solchen Szenarien können Sie mehrere Feldwerte spezifizieren, um die Auswahl der zurückgegebenen Datensätze noch präziser zu gestalten.
Verschachtelte Angabe und Reihenfolge
Die Schlüsselfeldwerte werden in der Reihenfolge angegeben, in der sie in der Sortierfolge erscheinen (kf1...
, kf2...
, usw.). Die Angabe erfolgt verschachtelt, beginnend mit dem ersten Schlüsselfeld (kf1...
) der Sortierfolge. Es ist nicht erforderlich, alle Schlüsselfelder der Sortierfolge anzugeben. Wenn jedoch Schlüsselfelder spezifiziert werden, muss dies in der korrekten Reihenfolge der Sortierfolge geschehen, was durch die Verschachtelung erzwungen wird.
Beispiel (exactMatch
mit mehreren Feldwerten)
Im folgenden Beispiel wird die Sortierfolge byArtNrLagNr
für die Tabelle tblWarehouseSlotInventory
(LagerplatzBestand) verwendet. Es werden mehrere Felder spezifiziert: kf1ArtNr
, kf2LagNr
, kf3LpRegal
und kf4LpSaeule
.
query {
tblWarehouseSlotInventory {
rowRead(
exactMatch: {
byArtNrLagNr: { # Sortierfolge
kf1ArtNr: { string: "8" # Wert für das 1. Feld
kf2LagNr: { string: "1" # Wert für das 2. Feld
kf3LpRegal: { string: "4" # Wert für das 3. Feld
kf4LpSaeule: { string: "S1" } # Wert für das 4. Feld
}
}
}
}
}
) {
fldArtNr
fldLagNr
fldLpRegal
fldLpSaeule
fldLpEbene
}
}
}
Mit dieser Abfrage wird nur der erste Datensatz zurückgegeben, der exakt den angegebenen Kriterien für kf1ArtNr
, kf2LagNr
, kf3LpRegal
und kf4LpSaeule
entspricht.
3.4.3 Von/Bis-Bereich in allBetween
Innerhalb von allBetween
kann (muss aber nicht) für das zuletzt angegebene kfN...
-Feld ein from
/to
-Bereich definiert werden. Für alle davor angegebenen kf...
-Felder gilt der exakte Wert als Bereichsgrenze (implizites from
/to
mit demselben Wert).
Beispiel (rowsRead
mit allBetween
und Bereich):
Im folgenden Beispiel wird die Tabelle tblWarehouseSlotInventory
(LagerplatzBestand) und die Sortierfolge byArtNrLagNr
verwendet. Für das Feld kf5LpEbene
wird ein Von/Bis-Bereich mit den Werten "E"
bis "E99"
angegeben:
query {
tblWarehouseSlotInventory {
rowsRead(
allBetween: {
byArtNrLagNr: {
kf1ArtNr: { string: "8"
kf2LagNr: { string: "1"
kf3LpRegal: { string: "4"
kf4LpSaeule: { string: "S1"
kf5LpEbene: { # Bereich für das 5. (letzte angegebene) Feld
from: { string: "E" },
to: { string: "E99" }
}
}
}
}
}
}
}
) {
fldArtNr
fldLagNr
fldLpRegal
fldLpSaeule
fldLpEbene
}
}
}
Mit dieser Abfrage werden alle Datensätze zurückgegeben, deren Werte für kf1ArtNr
bis kf4LpSaeule
exakt den Angaben entsprechen und deren Wert für kf5LpEbene
im Bereich von "E"
bis "E99"
(einschließlich) liegt.
Wichtige Hinweise
- Der explizite Von/Bis-Bereich (
from
/to
) ist nur für das letzte in der Abfrage angegebene Schlüsselfeld der Sortierfolge verfügbar. - Die Angabe eines einzelnen Wertes für ein
kf...
-Schlüsselfeld (ohnefrom
/to
) in einerallBetween
-Abfrage fungiert sowohl als Start- und Endwert für dieses Schlüsselfeld. Ein separatesfrom
undto
ist in diesem Fall nicht möglich.
3.4.4 Verständnis der Sortierfolge-Beschränkungen in allBetween
Warum kann man from
/to
-Bereiche in allBetween
nur auf dem letzten Schlüsselfeld (kf...
) einer Sortierfolge verwenden?
Die allBetween
-Abfrage in GraphQL erlaubt die Festlegung eines expliziten Von/Bis-Bereichs nur für das zuletzt angegebene Schlüsselfeld (kfN...
) der Sortierfolge. Diese Einschränkung ergibt sich aus der Art und Weise, wie die Daten in der microtech Software sortiert und indiziert sind.
Beispiel: Tabelle mit Spalten A und B
Um dies zu veranschaulichen, betrachten wir ein einfaches Beispiel. Stellen Sie sich eine Tabelle vor, die zwei Spalten "A" und "B" hat. Jede Spalte enthält die Werte 1 bis 3. Die Sortierfolge sei byAB
mit den Schlüsselfeldern kf1A
und kf2B
. Die sortierten Daten sehen wie folgt aus:
A | B
-----
1 | 1
1 | 2
1 | 3
2 | 1
2 | 2
2 | 3
3 | 1
3 | 2
3 | 3
Nun stellen Sie sich vor, Sie möchten einen Von/Bis-Bereich für kf1A
zwischen 1 und 3 festlegen und gleichzeitig für kf2B
den spezifischen Wert 2 auswählen. Da die Daten nach der Sortierfolge (kf1A
dann kf2B
) sortiert sind, würde eine solche Abfrage alle Zeilen zwischen dem Startpunkt (kf1A
=1, kf2B
=2) und dem Endpunkt (kf1A
=3, kf2B
=2) zurückgeben.
Das Ergebnis wäre:
A | B
-----
1 | 1
1 | 2 <-- Startpunkt (kf1A=1, kf2B=2)
1 | 3
2 | 1
2 | 2
2 | 3
3 | 1
3 | 2 <-- Endpunkt (kf1A=3, kf2B=2)
3 | 3
Das Problem hierbei ist, dass das Ergebnis auch Zeilen mit Werten für Spalte B enthalten würde, die nicht gleich 2 sind (nämlich 1 und 3). Dies widerspricht dem Versuch, für kf2B
den spezifischen Wert 2 festzulegen, während für kf1A
ein Bereich gilt.
Schlussfolgerung
Daher erlaubt der allBetween
-Parameter nur die Angabe eines expliziten Von/Bis-Bereichs für das zuletzt angegebene Schlüsselfeld (kfN...
) der Sortierfolge. Für alle vorherigen Schlüsselfelder (kf1...
bis kf(N-1)...
) wird der angegebene Wert als exakte Bereichsgrenze verwendet. Dies stellt sicher, dass die zurückgegebenen Datensätze den beabsichtigten Kriterien entsprechen und keine unerwarteten Werte in den vorherigen Schlüsselfeldern der Sortierfolge enthalten.
3.4.5 Umgehung der Sortierfolge-Beschränkungen mittels keyFilter
Das Problem: Beschränkung von Bereichsangaben in allBetween
Wie im vorherigen Abschnitt erläutert, erlaubt allBetween
nur für das letzte angegebene Schlüsselfeld einen expliziten Von/Bis-Bereich. Dies wird jedoch problematisch, wenn Sie gleichzeitig:
- Für ein früheres Schlüsselfeld (wie
kf1...
) einen Bereich definieren möchten, UND - Für nachfolgende Schlüsselfelder (wie
kf2...
) spezifische Werte oder andere Bedingungen festlegen möchten.
Diese Einschränkung verhindert komplexere mehrdimensionale Filterbedingungen, die in vielen praktischen Anwendungsfällen benötigt werden.
Die Lösung: Verwendung von keyFilter
Der keyFilter
-Parameter bietet eine effiziente Möglichkeit, diese Beschränkung zu umgehen. Er kann innerhalb des by...
-Parameters (parallel zum kf1...
-Parameter) angegeben werden und ermöglicht Filterbedingungen auf die Schlüsselfelder der gewählten Sortierfolge.
Strategie zur Umgehung der Beschränkung:
-
Angabe von exakten Werten für alle Schlüsselfelder bis zu dem Feld, das einen Bereich benötigt. Diese exakten Werte können für beliebig viele Schlüsselfelder definiert werden.
-
Definition eines
allBetween
-Bereichs (mitfrom
/to
) für das erste Schlüsselfeld, das tatsächlich einen Wertebereich benötigt - dies muss das letzte in der Abfrage angegebene Schlüsselfeld sein. -
Einsatz eines
keyFilter
zur weiteren Filterung des Bereichsfelds selbst sowie zur Einschränkung von Wertebereichen oder -kombinationen für alle weiteren Schlüsselfelder, die nach dem Bereichsfeld folgen.
Wichtige Hinweise:
- Der
keyFilter
wird auf die Schlüsselfelder (kf...
) der aktuell ausgewählten Sortierfolge angewendet, nachdem die Datensätze basierend aufallBetween
ausgewählt wurden. - Die Verwendung von
keyFilter
ist effizienter alsfastFilter
oderslowFilter
, da er serverseitig bereits während des Lesens der Schlüssel der Sortierfolge ausgewertet werden kann, bevor die eigentlichen Datensätze geholt werden. Er ist jedoch auf die Schlüsselfelder der Sortierfolge beschränkt. - Nicht alle Schlüsselfelder sind im
keyFilter
verfügbar, insbesondere bei speziellen Sortiertypen (z. B. numerische Sortierung für String-Datenfelder), bei denen die Werte für die Sortierung transformiert wurden.
Praktische Beispiele
Beispiel 1: Bereich für kf1...
und spezifischer Wert für kf2...
Abruf von Artikeln mit einer Artikelart (kf1ArtikelArt
) zwischen 0 und 9, beschränkt auf Suchbegriffe, die mit "A" beginnen:
query {
tblProducts {
rowsRead(
allBetween: {
byArtSuchBeg: { # Sortierung nach Artikelart und Suchbegriff
kf1ArtikelArt: { # Bereich für Artikelart
from: { int: 0 },
to: { int: 9 }
}
# Hier kommt der keyFilter für kf2SuchBeg ins Spiel
keyFilter: {
# Alle Suchbegriffe, die mit "A" beginnen (>= "A" und < "B")
and: [
{ ge: [{field: kf2SuchBeg}, {value: "A"}] },
{ lt: [{field: kf2SuchBeg}, {value: "B"}] }
]
}
}
}
) {
fldArtNr
fldSuchBeg
fldArtikelArt
}
}
}
Beispiel 2: Komplexere Filterung mit nicht-sequentiellen Werten
Abfrage von Lagerplatzbeständen für Artikel "8" in Lager "1", aber nur für bestimmte, nicht aufeinanderfolgende Regale (Regale 1, 3 und 5):
query {
tblWarehouseSlotInventory {
rowsRead(
allBetween: {
byArtNrLagNr: {
kf1ArtNr: { string: "8" # Exakter Wert für Artikel
kf2LagNr: { string: "1" # Exakter Wert für Lager
# Hier kann kein einfacher from/to-Bereich verwendet werden,
# da die gewünschten Regale nicht sequentiell sind
keyFilter: {
# Nur Regale 1, 3 und 5
or: [
{ eq: [{field: kf3LpRegal}, {value: "1"}] },
{ eq: [{field: kf3LpRegal}, {value: "3"}] },
{ eq: [{field: kf3LpRegal}, {value: "5"}] }
]
}
}
}
}
}
) {
fldArtNr
fldLagNr
fldLpRegal
fldLpSaeule
fldLpEbene
fldMge
}
}
}
Beispiel 3: Kombinierte Bereiche mit logischen Operatoren
Abfrage von Artikeln mit Ausschluss bestimmter Artikelarten und Eingrenzung des letzten Umsatzdatums:
query {
tblProducts {
rowsRead(
allBetween: {
byArtLtzUmsatz: {
# Umfassender Bereich für kf1ArtikelArt
kf1ArtikelArt: {
from: { int: 0 },
to: { int: 9 }
}
# Filterung mit keyFilter für die genauen Bedingungen
keyFilter: {
and: [
# Artikelarten 3, 4, und 5 ausschließen (ArtikelArt < 3 ODER ArtikelArt > 5)
{ or: [
{ lt: [{field: kf1ArtikelArt}, {value: 3}] },
{ gt: [{field: kf1ArtikelArt}, {value: 5}] }
]},
# Letztes Umsatzdatum muss im ersten Quartal 2024 liegen
{ ge: [{field: kf2LtzUmsDat}, {value: "2024-01-01"}] },
{ le: [{field: kf2LtzUmsDat}, {value: "2024-03-31"}] }
]
}
}
}
) {
fldArtNr
fldArtikelArt
fldLtzUmsDat
fldSuchBeg
fldStdPreis
}
}
}
Optimierungstipps
- Balance zwischen
allBetween
undkeyFilter
: - Verwendung von
allBetween
mit einem möglichst engen Bereich für die Schlüsselfelder bis einschließlich des ersten Schlüsselfeldes, das einen Bereich benötigt, um die initiale Datenmenge zu begrenzen. -
Nutzung von
keyFilter
für die Feinabstimmung auf diesem Bereichsfeld und allen nachfolgenden Schlüsselfeldern. -
Kombinierte Bedingungen:
- Einsatz logischer Operatoren (
and
,or
,not
) innerhalb deskeyFilter
für komplexe Filterbedingungen. -
Für optimale Performance ist die Kombination von
keyFilter
mit präzisenallBetween
-Angaben empfehlenswert. -
Alternative Sortierfolgen:
- Wahl einer Sortierfolge, die es ermöglicht, mit einem minimalen Bereich in
allBetween
alle relevanten Datensätze zu erfassen und gleichzeitig die Anzahl der perkeyFilter
zu filternden Datensätze zu minimieren. -
Wenn es nur ein Schlüsselfeld mit einem Wertebereich gibt, sind optimale Sortierfolgen die, bei denen:
- Zuerst alle Felder, für die exakte Werte benötigt werden, in beliebiger Reihenfolge als Schlüsselfelder enthalten sind
- Das einzige Feld, für das ein Bereich benötigt wird, direkt nach diesen exakten Feldern kommt
- Eventuell weitere Schlüsselfelder folgen können, die für die Einschränkung nicht relevant sind
Diese Struktur ermöglicht die direkte Verwendung von
allBetween
ohne Notwendigkeit fürkeyFilter
,fastFilter
oderslowFilter
.
Zusammenfassung
Die Kombination von allBetween
mit keyFilter
bietet eine leistungsstarke und effiziente Möglichkeit, die inhärenten Beschränkungen der eindimensionalen Sortierung zu umgehen und komplexe Abfragen mit Bereichsfilterung auf mehreren Schlüsselfeldern zu realisieren.
Durch ein gutes Verständnis dieser Mechanismen können Sie präzise und dennoch performante GraphQL-Abfragen erstellen, die genau die Daten liefern, die Ihre Anwendung benötigt.
3.5 Konsistenzsicherung mit modifyLSN
-Parameter
Das Feld rowRead
auf Top-Level-Tabellen (tbl...
) unterstützt einen zusätzlichen optionalen Parameter modifyLSN
, der für Konsistenzsicherung und Optimistic Concurrency Control genutzt werden kann.
3.5.1 Grundlagen der Log Sequence Numbers (LSN)
Jeder Datensatz in einer Top-Level-Tabelle verfügt über die Systemfelder fldModifyLSN
und fldInsertLSN
:
fldModifyLSN
: Eine 64-Bit-Ganzzahl (BigInt), die bei jeder Änderung des Datensatzes aktualisiert wird.fldInsertLSN
: Eine 64-Bit-Ganzzahl, die den LSN-Wert zum Zeitpunkt des ersten Einfügens des Datensatzes speichert.
Die LSN (Log Sequence Number) ist eine auf dem Datenserver eindeutige, stetig ansteigende Zahl. Nach dem ersten Einfügen sind fldInsertLSN
und fldModifyLSN
identisch. Bei jeder Änderung wird fldModifyLSN
aktualisiert, während fldInsertLSN
konstant bleibt.
Wichtiger Hinweis: Bei Datenbankreorganisationen oder dem Packen einer Tabelle können LSN-Werte neu vergeben werden. Sie eignen sich daher nicht als gespeicherte Verlinkungen zu diesen Datensätzen.
Hinweis: Diese LSN-Felder existieren nur in Top-Level-Tabellen (tbl...
), nicht in untergeordneten Tabellen, die als Felder in anderen Tabellen enthalten sein können (tbl...
innerhalb eines Row
-Objekts).
3.5.2 Prüfung auf Datensatzänderungen
Wenn im rowRead
-Aufruf der modifyLSN
-Parameter angegeben wird, erfolgt nach dem Finden des Datensatzes (auf Basis der anderen Parameter wie exactMatch
oder nearestMatch
) ein Vergleich:
- Stimmt der
modifyLSN
-Parameterwert mit dem aktuellenfldModifyLSN
des Datensatzes überein, wird der Datensatz normal zurückgegeben. - Bei Abweichung wird
null
zurückgegeben, was signalisiert, dass der Datensatz seit dem letzten Abruf geändert wurde.
Beispiel mit Konsistenzprüfung:
query {
tblProducts {
rowRead(
exactMatch: {
byNr: {
kf1ArtNr: { string: "PROD-001" }
}
},
modifyLSN: "12345678901234" # Zu prüfende LSN als BigInt
) {
fldArtNr
fldSuchBeg
fldModifyLSN # Aktuelle LSN für zukünftige Vergleiche
}
}
}
3.5.3 Direkte Suche nach ModifyLSN
Der modifyLSN
-Parameter kann auch als alleiniges Suchkriterium verwendet werden, ohne weitere Parameter wie exactMatch
oder nearestMatch
:
- Wenn ein Datensatz mit der angegebenen ModifyLSN existiert, wird dieser zurückgegeben.
- Existiert kein solcher Datensatz, wird
null
zurückgegeben.
Beispiel mit direkter LSN-Suche:
query {
tblProducts {
rowRead(
modifyLSN: "12345678901234" # Suche nach Datensatz mit dieser LSN
) {
fldArtNr
fldSuchBeg
}
}
}
Hinweis: Der modifyLSN
-Parameter wird nur von rowRead
unterstützt, nicht jedoch von rowsRead
oder conRead
. Er funktioniert auch bei Tabellen, die konzeptuell nur einen Datensatz enthalten können (wie tblClient
).
4. Verkürzte Schreibweisen durch implizite Parameter
Verkürzte Schreibweisen können die Lesbarkeit und Erstellung von Abfragen bei Verwendung der Standardsortierung vereinfachen. Sie ersetzen die explizite Angabe von exactMatch
oder allBetween
mit der Standardsortierung durch eine kompaktere Syntax. Diese Kurzformen sind nur anwendbar, wenn die entsprechenden Parameter (exactMatch
, nearestMatch
oder allBetween
) nicht bereits explizit angegeben sind.
4.1 Verkürzte Schreibweise für exactMatch
in rowRead
Das direkte Angeben des ersten Schlüsselfelds (kf1...
) der Standardsortierung als Parameter für rowRead
kann als verkürzte Schreibweise für exactMatch: { by<StandardIndexName>: { kf1... } }
verwendet werden. Die Standardsortierung hängt von der spezifischen Tabelle ab.
Beispiel (Verkürzte Schreibweise für exactMatch
mit Standardsortierung):
query {
tblProducts {
rowRead( # Direkte Angabe von kf1ArtNr impliziert exactMatch mit Standardsortierung byNr
kf1ArtNr: { string: "1" }
) {
fldArtNr
fldSuchBeg
}
}
}
query {
tblProducts {
rowRead(
exactMatch: {
byNr: { # Explizite Angabe der Standardsortierung 'Nr'
kf1ArtNr: { string: "1" }
}
}
) {
fldArtNr
fldSuchBeg
}
}
}
4.2 Verkürzte Schreibweise für Standardsortierung in rowsRead
/conRead
- Aufruf ohne Parameter: Das Weglassen von Parametern bei
rowsRead
oderconRead
ist die primäre Methode, um alle Datensätze in der Standardsortierung abzurufen. - Kurzform für
allBetween
mit Standardsortierung: Das direkte Angeben des ersten Schlüsselfelds (kf1...
) der Standardsortierung als Parameter fürrowsRead
/conRead
kann als verkürzte Schreibweise fürallBetween: { by<StandardIndexName>: { kf1... } }
verwendet werden. Die Standardsortierung hängt von der spezifischen Tabelle ab.
Beispiel (Verkürzte Schreibweise für allBetween
mit Standardsortierung byNr
):
query {
tblProducts {
rowsRead(
kf1ArtNr: { # Direkte Angabe von kf1ArtNr impliziert allBetween mit Standardsortierung byNr
from: { string: "0" }
to: { string: "12000" }
}
) {
fldArtNr
fldSuchBeg
}
}
}
query {
tblProducts {
rowsRead(
allBetween: {
byNr: { # Explizite Angabe der Standardsortierung 'Nr'
kf1ArtNr: {
from: { string: "0" }
to: { string: "12000" }
}
}
}
) {
fldArtNr
fldSuchBeg
}
}
}
5. Filterausdrücke in GraphQL
5.1 Einleitung
Filter (fastFilter
, slowFilter
, keyFilter
) schränken die Menge der durch rowRead
, rowsRead
oder conRead
zurückgegebenen Datensätze weiter ein.
Warum sind Filter wichtig?
Filter ermöglichen die präzise Einschränkung der zurückgegebenen Datenmenge, um effizientere und zielgerichtete Abfragen durchzuführen.
Wo können Sie Filter anwenden?
Filter können an verschiedenen Stellen des GraphQL-Schemas angewendet werden:
-
fastFilter
/slowFilter
: Diese Filter wirken auf diefld...
-Felder der Datensätze und werden als Parameter direkt innerhalb vonrowRead
,rowsRead
oderconRead
angegeben, parallel zuexactMatch
,nearestMatch
oderallBetween
bzw. deren verkürzten Schreibweisen. Sie ermöglichen komplexe Filterbedingungen auf die Datensatzinhalte.fastFilter
: Werden datenserverseitig ausgewertet und direkt in Datenbankabfragen übersetzt, was sie besonders effizient macht.slowFilter
: Werden anwendungsserverseitig ausgewertet und können komplexere Logik enthalten (z. B. berechnete Felder, spezielle Funktionen), die auf Datenbankebene nicht ohne weiteres möglich ist.
-
keyFilter
: Dieser Filter wirkt auf die Schlüsselfelder der aktuell ausgewählten Sortierfolge (referenziert durchkf...
-Parameter) und wird innerhalb desby...
-Parameters angegeben.- Er wird datenserverseitig bereits beim Lesen der Schlüssel der Sortierfolge angewendet, bevor die eigentlichen Datensätze geladen werden, und ist daher besonders performant.
- Nicht alle Schlüsselfelder sind im
keyFilter
verfügbar, insbesondere bei speziellen Sortiertypen (z. B. numerische Sortierung für String-Datenfelder), bei denen die Werte für die Sortierung transformiert wurden.
Beispiel (fastFilter
/slowFilter
in rowsRead
):
query {
tblProducts {
rowsRead(
allBetween: { byNr: { kf1ArtNr: { from: {string: "A"}, to: {string: "Z"} } } } # Optional
fastFilter: { eq: [{field: fldArtikelArt}, {value: 1}] } # Filter auf fld...
# slowFilter: { ... } # Filter auf fld...
) {
fldArtNr
fldSuchBeg
fldArtikelArt
}
}
}
Beispiel (keyFilter
in rowsRead
):
query {
tblProducts {
rowsRead(
allBetween: {
bySuchBeg: {
kf1SuchBeg: { from: {string: "A"}, to: {string: "B" } }
toExclusive: true # beendet den Bereich *vor* dem ersten Datensatz der to entspricht
keyFilter: { ne: [{field: kf1SuchBeg}, {value: "ABC"}] } # Filter auf kf1SuchBeg <> "ABC"
}
}
# fastFilter/slowFilter hier möglich
) {
fldArtNr
fldSuchBeg
fldArtikelArt
}
}
}
5.2 Das Schema (Filterdefinitionen)
(Das Schema der Filterausdrücke selbst ist hier zur Referenz aufgeführt.)
# input objects with this directive need to have exactly one field specified
# see https://www.apollographql.com/blog/more-expressive-schemas-with-oneof
directive @oneOf on INPUT_OBJECT
# specifies an optional minimum and/or maximum length for a list argument or field
directive @length(min: Int, max: Int) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
# specifies a variant list argument or field must have the same type for all elements
directive @sametype on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
input FastBooleanExpression @oneOf {
and : [FastBooleanExpression!] @length(min: 2)
or : [FastBooleanExpression!] @length(min: 2)
not : FastBooleanExpression
eq : [FastAnyExpression!] @length(min: 2, max: 2)
gt : [FastAnyExpression!] @length(min: 2, max: 2)
lt : [FastAnyExpression!] @length(min: 2, max: 2)
ne : [FastAnyExpression!] @length(min: 2, max: 2)
ge : [FastAnyExpression!] @length(min: 2, max: 2)
le : [FastAnyExpression!] @length(min: 2, max: 2)
isNull : FastAnyExpression
isNotNull : FastAnyExpression
field : FastBooleanFieldsEnum # server side available boolean fields only
value : Boolean
}
input FastAnyExpression @oneOf {
field : FastFieldsEnum # all filterable server side available fields
value : Any
}
input SlowBooleanExpression @oneOf {
and : [SlowBooleanExpression!] @length(min: 2)
or : [SlowBooleanExpression!] @length(min: 2)
not : SlowBooleanExpression
eq : [SlowAnyExpression!] @length(min: 2, max: 2)
gt : [SlowAnyExpression!] @length(min: 2, max: 2)
lt : [SlowAnyExpression!] @length(min: 2, max: 2)
ne : [SlowAnyExpression!] @length(min: 2, max: 2)
ge : [SlowAnyExpression!] @length(min: 2, max: 2)
le : [SlowAnyExpression!] @length(min: 2, max: 2)
in : InListExpression
isNull : SlowAnyExpression
isNotNull : SlowAnyExpression
# fn... : [SlowAnyExpression] # Platzhalter für spezifische Funktionsaufrufe
field : SlowBooleanFieldsEnum # all boolean fields including client calculated ones
value : Boolean
}
input InListExpression {
left: SlowAnyExpression!
list: [Any!]! @sametype @length(min: 1)
}
input SlowAnyExpression @oneOf {
and : [SlowBooleanExpression!] @length(min: 2) # Logische Operatoren innerhalb von Any
or : [SlowBooleanExpression!] @length(min: 2)
not : SlowBooleanExpression
eq : [SlowAnyExpression!] @length(min: 2, max: 2) # Vergleichsoperatoren innerhalb von Any
gt : [SlowAnyExpression!] @length(min: 2, max: 2)
lt : [SlowAnyExpression!] @length(min: 2, max: 2)
ne : [SlowAnyExpression!] @length(min: 2, max: 2)
ge : [SlowAnyExpression!] @length(min: 2, max: 2)
le : [SlowAnyExpression!] @length(min: 2, max: 2)
in : InListExpression
isNull : SlowAnyExpression
isNotNull : SlowAnyExpression
neg : SlowAnyExpression # Arithmetische Operatoren
add : [SlowAnyExpression!] @length(min: 2)
sub : [SlowAnyExpression!] @length(min: 2)
mul : [SlowAnyExpression!] @length(min: 2)
div : [SlowAnyExpression!] @length(min: 2, max: 2)
mod : [SlowAnyExpression!] @length(min: 2, max: 2)
# fn... : [SlowAnyExpression] # Platzhalter für spezifische Funktionsaufrufe
field : SlowFieldsEnum # all filterable fields including calculated ones
value : Any
}
5.3 Verständnis von Filterausdrücken
5.3.1 Einleitung
Filterausdrücke ermöglichen komplexe Abfragen mit logischen, vergleichenden und arithmetischen Operationen. Sie sind in schnelle (fastFilter
, keyFilter
) und langsame (slowFilter
) Ausdrücke unterteilt, die jeweils unterschiedliche Einsatzbereiche und Performance-Charakteristiken bieten.
5.3.2 Unterschied zwischen schnellen und langsamen Filterausdrücken
-
Schnelle Filterausdrücke (
fastFilter
,keyFilter
) werden datenserverseitig ausgewertet und direkt in Datenbankabfragen übersetzt. Dies macht sie hocheffizient, besonders bei großen Datenmengen, beschränkt sie aber auf Ausdrücke, die die Datenbank nativ unterstützt. -
Langsame Filterausdrücke (
slowFilter
) werden anwendungsserverseitig ausgewertet, nachdem alle potenziellen Datensätze vom Datenserver geladen wurden. Dies ermöglicht komplexere Operationen und Berechnungen, die auf Datenbankebene nicht direkt möglich sind, kann aber bei großen Datenmengen weniger effizient sein.
5.3.3 Berechnete Felder in Filterausdrücken
Im GraphQL-Schema existieren neben direkten Datenbankfeldern auch berechnete Felder, die das gleiche fld...
-Präfix verwenden und folgende Eigenschaften haben:
- Werden erst bei ihrem ersten Zugriff im Anwendungsserver berechnet
- Können weitere Datenbankabfragen im Hintergrund auslösen
- Sind in der Darstellung nicht unmittelbar von regulären Datenbankfeldern zu unterscheiden
Wichtig: Berechnete Felder können ausschließlich im slowFilter
verwendet werden und erscheinen nicht in den Enums für fastFilter
oder keyFilter
. Wenn ein fld...
-Feld nicht im fastFilter
verfügbar ist, handelt es sich wahrscheinlich um ein berechnetes Feld.
Diese Einschränkung besteht, da berechnete Felder nicht direkt in der Datenbank gespeichert sind. Die Verwendung berechneter Felder in Filterausdrücken kann erhebliche Auswirkungen auf die Performance haben, besonders bei großen Datenmengen.
5.3.4 Typen von Ausdrücken
Das Schema definiert vier Haupttypen von Filterausdrücken:
Ausdruckstyp | Verwendung | Besonderheiten |
---|---|---|
FastBooleanExpression | fastFilter , keyFilter |
Für datenserverseitig verfügbare boolesche Felder. Optimiert für grundlegende Vergleiche und boolesche Operationen. |
SlowBooleanExpression | slowFilter |
Unterstützt zusätzlich berechnete Felder, in -Operator und Funktionen (fn... ). |
FastAnyExpression | Innerhalb von fastFilter , keyFilter |
Für datenserverseitig verfügbare Felder aller Typen. Ideal für einfache Abfragen mit Feldzugriffen oder konstanten Werten. |
SlowAnyExpression | Innerhalb von slowFilter |
Unterstützt zusätzlich arithmetische Operationen, berechnete Felder und Funktionen. Bietet mehr Flexibilität für komplexe Anforderungen. |
5.3.5 Struktur der Ausdrücke
Filterausdrücke werden basierend auf ihrer Operanden-Anzahl kategorisiert:
- Unäre Operatoren: Ein Operand (z.B.
not
,isNull
,isNotNull
,neg
) - Binäre Operatoren: Genau zwei Operanden (z.B.
eq
,gt
,lt
,div
,mod
) - Variadische Operatoren: Zwei oder mehr Operanden (z.B.
and
,or
,add
,sub
,mul
). Auch als Multi-Operanden-Operatoren bekannt.
Ausdrücke können verschachtelt und kombiniert werden, um komplexe logische Bedingungen zu erstellen. Sie können aus Feldern (fld...
oder kf...
), Werten oder weiteren (Unter-)Ausdrücken bestehen.
5.3.6 Strukturregeln für Filterausdrücke
Das Schema definiert Regeln für korrekte Filterausdrücke:
-
@oneOf
: In jedem Filterobjekt darf nur ein Operator verwendet werden:# FALSCH - löst einen Fehler aus: fastFilter: { eq: [{field: fldStatus}, {value: 1}], gt: [{field: fldMenge}, {value: 0}] # Verletzt @oneOf-Regel } # RICHTIG - logische Operatoren kombinieren: fastFilter: { and: [ { eq: [{field: fldStatus}, {value: 1}] }, { gt: [{field: fldMenge}, {value: 0}] } ] }
-
@length
: Definiert erforderliche Parameteranzahl: - Vergleichsoperatoren (
eq
,gt
, etc.): Genau zwei Werte -
Logische Operatoren (
and
,or
): Mindestens zwei Unterausdrücke -
@sametype
: In Listen (z.B. beimin
-Operator) müssen alle Werte denselben Typ haben:# FALSCH - löst einen Fehler aus: slowFilter: { in: { left: { field: fldArtikelArt }, list: [1, "2", 3] # Gemischte Typen verletzen @sametype-Regel } } # RICHTIG - konsistente Typen: slowFilter: { in: { left: { field: fldArtikelArt }, list: [1, 2, 3] # Alle Werte haben denselben Typ } }
Verstöße gegen diese Regeln werden spätestens bei der Ausführung erkannt und lösen Fehlermeldungen aus. Manche GraphQL-Clients können sie bereits bei der Schema-Validierung erkennen.
5.4 Boolesche Ausdrücke (FastBooleanExpression
, SlowBooleanExpression
)
Boolesche Ausdrücke ergeben einen Wahrheitswert (true
oder false
) und werden verwendet, um Bedingungen zu formulieren. Sie bilden die oberste Ebene der Filter (fastFilter
, slowFilter
, keyFilter
).
5.4.1 Logische Operatoren (and
, or
)
Diese variadischen Operatoren kombinieren mehrere boolesche (Unter-)Ausdrücke zu einer einzigen logischen Bedingung:
and
: Ergibttrue
, wenn alle booleschen Ausdrücke darintrue
sind. Benötigt mindestens zwei Ausdrücke.or
: Ergibttrue
, wenn mindestens einer der booleschen Ausdrücke darintrue
ist. Benötigt mindestens zwei Ausdrücke.
Syntax und Interpretation
{ and: [BooleanExpression1, BooleanExpression2, ...] }
{ or: [BooleanExpression1, BooleanExpression2, ...] }
Diese Ausdrücke werden von links nach rechts ausgewertet: BooleanExpression1 AND BooleanExpression2 AND ...
bzw. BooleanExpression1 OR BooleanExpression2 OR ...
. Die Auswertung erfolgt mit Short-Circuit-Evaluation: Bei AND wird der zweite Ausdruck nicht ausgewertet, wenn der erste bereits false ist. Bei OR wird der zweite Ausdruck nicht ausgewertet, wenn der erste bereits true ist.
5.4.2 NOT-Operator (not
)
Der unäre not
-Operator negiert einen booleschen (Unter-)Ausdruck:
Syntax
{ not: BooleanExpression }
5.4.3 Vergleichsoperatoren (eq
, gt
, lt
, ne
, ge
, le
)
Diese binären Operatoren vergleichen zwei Allgemeine Ausdrücke (FastAnyExpression
oder SlowAnyExpression
- je nach Kontext) und ergeben einen booleschen Wert:
Syntax und Interpretation
{ Operator: [AnyExpression1, AnyExpression2] }
Interpretiert als AnyExpression1 Operator AnyExpression2
. Benötigt genau zwei Ausdrücke.
Operatoren
eq
: Equal (Gleich).gt
: Greater than (Größer als).lt
: Less than (Kleiner als).ne
: Not equal (Ungleich).ge
: Greater or equal (Größer oder gleich).le
: Less or equal (Kleiner oder gleich).
5.4.4 IN-Operator (in
) (nur in SlowBooleanExpression
)
Der in
-Operator überprüft, ob der Wert des linken Ausdrucks (left
, ein SlowAnyExpression
) in einer bestimmten Liste von konstanten Werten (list
) gefunden wird.
Syntax und Interpretation
{ in: {
left: SlowAnyExpression, # z. B. { field: fldLagBestArt }
list: [AnyValue1, AnyValue2, ...] # z. B. [ 1 , 7 ] oder [ "A", "B" ]
}
}
Interpretiert als left IN (AnyValue1, AnyValue2, ...)
.
Die konstanten Werte in der Liste müssen alle denselben Typ haben (@sametype
) und es muss mindestens ein Wert angegeben werden (@length(min: 1)
).
5.4.5 NULL-Prüfoperatoren (isNull
, isNotNull
)
Diese unären Operatoren prüfen, ob ein Allgemeiner Ausdruck (FastAnyExpression
oder SlowAnyExpression
) den Wert null
hat:
isNull
: Ergibttrue
, wenn der Ausdrucknull
ist. ##### Syntax{ isNull: AnyExpression } # z. B. { isNull: { field: fldAuftrNr } }
isNotNull
: Ergibttrue
, wenn der Ausdruck nichtnull
ist. ##### Syntax{ isNotNull: AnyExpression }
5.4.6 Feld und Wert (field
, value
)
Innerhalb boolescher Ausdrücke können Sie auch direkt ein boolesches Feld (fld...
/ kf...
) oder einen konstanten booleschen Wert angeben:
field
: Bezieht sich auf ein bestimmtes boolesches Feld (z. B.fldIstAktiv
), das als Aufzählungswert (FastBooleanFieldsEnum
oderSlowBooleanFieldsEnum
) dargestellt ist. Nur boolesche Felder können direkt in diesem Ausdruckskontext referenziert werden.value
: Gibt einen konstanten booleschen Wert an (true
oderfalse
). ##### Syntax{ field: BooleanFieldEnum } { value: true } # oder { value: false }
5.5 Allgemeine Ausdrücke (FastAnyExpression
, SlowAnyExpression
)
Allgemeine Ausdrücke repräsentieren einen Wert beliebigen Typs (Zahl, String, Datum, Boolean, etc.) und werden innerhalb von Booleschen Ausdrücken (z. B. in Vergleichen, NULL-Prüfungen) oder als Parameter für Funktionen verwendet.
- Schnelle Allgemeine Ausdrücke (
FastAnyExpression
): Erlauben nur den Zugriff auf spezifische Felder (field
) und konstante Werte (value
). - Langsame Allgemeine Ausdrücke (
SlowAnyExpression
): Bieten zusätzlich arithmetische, logische und funktionale Operatoren.
5.5.1 Gemeinsame Elemente für FastAnyExpression
und SlowAnyExpression
5.5.1.1 Feld und Wert (field
, value
)
Innerhalb von Allgemeinen Ausdrücken (sowohl Schnell als auch Langsam) können Sie direkt ein Feld (fld...
/ kf...
) oder einen konstanten Wert angeben:
field
: Bezieht sich auf spezifische Felder, dargestellt als Aufzählungswerte (FastFieldsEnum
oderSlowFieldsEnum
, z. B.fldPreis
,kf1ArtNr
).value
: Gibt einen konstanten Wert des entsprechenden Typs an (z. B.{ value: 10.5 }
,{ value: "Text" }
,{ value: "2023-12-24" }
). Der TypAny
erlaubt verschiedene Skalare. ##### Syntax{ field: FieldEnum } { value: AnyValue }
5.5.2 Elemente nur für SlowAnyExpression
Die folgenden Operatoren sind nur in Langsamen Allgemeinen Ausdrücken (SlowAnyExpression
) verfügbar:
5.5.2.1 Negationsoperator (neg
)
Der unäre neg
-Operator negiert einen Allgemeinen Ausdruck (typischerweise numerisch), indem er seinen Wert mit -1 multipliziert.
Syntax
{ neg: SlowAnyExpression } # z. B. { neg: { field: fldPreis } }
5.5.2.2 Arithmetische Operatoren (add
, sub
, mul
, div
, mod
)
Diese Operatoren führen arithmetische Berechnungen auf (typischerweise numerischen) Allgemeinen Ausdrücken durch:
- Variadische Operatoren (
add
,sub
,mul
): Können zwei oder mehr Ausdrücke als Eingabe nehmen. ###### Syntax und InterpretationInterpretiert als{ add: [SlowAnyExpression1, SlowAnyExpression2, ...] } # Ergibt Summe { sub: [SlowAnyExpression1, SlowAnyExpression2, ...] } # Ergibt Differenz (A - B - C ...) { mul: [SlowAnyExpression1, SlowAnyExpression2, ...] } # Ergibt Produkt
Expr1 + Expr2 + ...
,Expr1 - Expr2 - ...
,Expr1 * Expr2 * ...
. - Binäre Operatoren (
div
,mod
): Benötigen genau zwei Ausdrücke.div
: Teilt den ersten Ausdruck durch den zweiten Ausdruck.mod
: Berechnet den Rest der Division (Modulo). ###### Syntax und InterpretationInterpretiert als{ div: [SlowAnyExpression1, SlowAnyExpression2] } # Ergibt Quotient { mod: [SlowAnyExpression1, SlowAnyExpression2] } # Ergibt Rest
SlowAnyExpression1 / SlowAnyExpression2
,SlowAnyExpression1 MOD SlowAnyExpression2
.
5.5.2.3 Logische und Vergleichsoperatoren innerhalb Allgemeiner Ausdrücke
Die logischen Operatoren (and
, or
, not
) und Vergleichsoperatoren (eq
, gt
, etc.) sowie in
, isNull
, isNotNull
können auch innerhalb eines Langsamen Allgemeinen Ausdrucks verwendet werden. Sie nehmen die entsprechenden (Unter-)Ausdrücke entgegen und produzieren einen booleschen Wert als Ergebnis des Allgemeinen Ausdrucks.
Syntax und Interpretation
# Beispiel: Boolesches Ergebnis innerhalb eines SlowAnyExpression
{ and: [
{ eq: [{field: fldStatus}, {value: 1}] },
{ gt: [{field: fldMenge}, {value: 0}] }
]
}
Die Syntax ist konsistent mit den Operatoren innerhalb der Booleschen Ausdrücke. Das Ergebnis dieses SlowAnyExpression
wäre true
oder false
.
5.5.2.4 Funktionen (fn...
)
Funktionen bieten eine erweiterte Möglichkeit, Berechnungen und Transformationen innerhalb von Langsamen Ausdrücken durchzuführen. Alle Funktionen beginnen mit dem Präfix fn
. Die Liste der verfügbaren Funktionen (fn...
) entspricht denen, die bereits in den Filterbedingungen innerhalb der microtech Software dokumentiert sind (z. B. Datumsfunktionen, Stringfunktionen etc.).
Syntax und Interpretation
{ fnFunktionsname: [SlowAnyExpression1, SlowAnyExpression2, ...] }
Funktionen nehmen eine Liste von Langsamen Allgemeinen Ausdrücken als Parameter. Die Anzahl und Art der Parameter hängen von der spezifischen Funktion ab.
Beispiel: fnDay
Funktion
Die fnDay
-Funktion extrahiert den Tag aus einem Datumsfeld.
{
fnDay: [{ field: fldGspDat }]
}
Dies würde den Tag des Datums im Feld fldGspDat
als numerischen Wert zurückgeben.
Weitere Informationen zu Funktionen
Für detaillierte Informationen zu den einzelnen Funktionen, einschließlich der Anzahl und Art der erforderlichen Parameter, verweisen wir auf die Übersicht aller Filter-Funktionen.
5.6 Komplexe Ausdrücke schreiben
Komplexe Filterbedingungen werden durch die Verschachtelung und Kombination von Booleschen und Allgemeinen Ausdrücken erstellt.
5.6.1 Verschachtelung
Betten Sie Ausdrücke ineinander ein, um mehrstufige logische und arithmetische Operationen zu erstellen. Boolesche Operatoren (and
, or
, not
) erwarten boolesche Unterausdrücke. Vergleichsoperatoren (eq
, gt
, etc.) und NULL-Prüfungen (isNull
, isNotNull
) erwarten allgemeine Unterausdrücke. Arithmetische Operatoren (add
, neg
, etc.) erwarten ebenfalls allgemeine Unterausdrücke.
Beispiel (Boolescher Ausdruck)
{ or: [
{ and: [ # Unterausdruck 1 (boolesch)
{ eq: [{field: fldStatus}, {value: 1}] }, # Vergleicht zwei allgemeine Ausdrücke
{ gt: [{field: fldDatum}, {value: "2023-01-01"}] } # Vergleicht zwei allgemeine Ausdrücke
]
},
{ not: { field: fldAktiv } } # Unterausdruck 2 (boolesch, negiert boolesches Feld)
]
}
((fldStatus == 1) AND (fldDatum > "2023-01-01")) OR (NOT fldAktiv)
.
5.6.2 Kombination
Nutzen Sie verschiedene Ausdrücke zusammen, um differenzierte Bedingungen zu erstellen.
Beispiel (nur in Langsamen Ausdrücken möglich)
# Boolescher Ausdruck (oberste Ebene)
{ eq: [ # Vergleicht Ergebnis von add mit Ergebnis von mul
# Allgemeiner Ausdruck 1 (Ergebnis ist numerisch)
{ add: [{ field: fldPreis1 }, { field: fldPreis2 }] },
# Allgemeiner Ausdruck 2 (Ergebnis ist numerisch)
{ mul: [{ field: fldMenge }, { value: 0.9 }] }
]
}
(fldPreis1 + fldPreis2) == (fldMenge * 0.9)
.
5.6.3 Überlegungen zum Schreiben komplexer Ausdrücke
* **Reihenfolge der Auswertung**: Die Reihenfolge der Auswertung erfolgt implizit durch die Verschachtelung (von innen nach außen). Bei variadischen Operatoren (`and`, `or`, `add`, `sub`, `mul`) erfolgt die Auswertung der Argumente von links nach rechts. Es gibt keine expliziten Klammern zur Steuerung der Reihenfolge; die Struktur definiert sie.
* **Einhaltung von Direktiven**: Halten Sie sich an die von den Direktiven wie `@oneOf`, `@length` und `@sametype` auferlegten Beschränkungen, um wohlgeformte Ausdrücke sicherzustellen.
* **Verständnis für variadische Operatoren**: Variadische Operatoren wie `and` und `or` erlauben zwei oder mehr Ausdrücke als Eingabe und werden von links nach rechts ausgewertet. Zum Beispiel wird `and: [Expr1, Expr2, Expr3]` als `(Expr1 AND Expr2) AND Expr3` ausgewertet. Dieses sequenzielle Auswerten ist beim Erstellen von Ausdrücken mit mehreren Operanden zu beachten.
5.7 Optimierung und praktische Überlegungen
Filterausdrücke können komplex werden und erfordern eine durchdachte Gestaltung und Optimierung. Dieser Abschnitt skizziert wichtige Überlegungen und bewährte Verfahren.
5.7.1 Optimierung verschachtelter Ausdrücke
Tief verschachtelte Ausdrücke können schwer lesbar und schwierig zu interpretieren sein. Berücksichtigen Sie Folgendes:
- Verschachtelte logische Operatoren abflachen: Flachen Sie logische Operatoren desselben Typs (z. B. mehrere verschachtelte
and
- oderor
-Operatoren) ab, um die Klarheit zu erhöhen.and: [A, {and: [B, C]}]
ist äquivalent zuand: [A, B, C]
. - Spezifische Vergleichsoperatoren verwenden: Wählen Sie spezifische Vergleichsoperatoren wie
ne
(ungleich) anstelle der Kombination vonnot
miteq
. Verwenden Siegt
,lt
,ge
undle
direkt, anstatt sie durch komplexere Logik nachzubilden. - Schnelle Ausdrücke bevorzugen: Verwenden Sie
fastFilter
oderkeyFilter
anstelle vonslowFilter
, wann immer die benötigte Funktionalität verfügbar ist. Dies trägt maßgeblich zur Verbesserung der Abfrageleistung bei, da die Filterung näher an der Datenquelle (Datenbank) erfolgen kann.slowFilter
erfordert eine Auswertung im Anwendungsserver nach dem Abrufen der potenziell größeren Datenmenge. - Implizite Klammern durch Verschachtelung: Die Struktur verschachtelter Ausdrücke impliziert die Reihenfolge der Auswertung. Das Verständnis dieser impliziten Reihenfolge hilft bei der Erstellung klarer Ausdrücke.
5.7.2 Umgang mit großen Listen in variadischen Operationen
Bei der Arbeit mit großen Listen in variadischen Operationen wie and
, or
, add
, sub
und mul
(in Langsamen Ausdrücken) können die folgenden Praktiken hilfreich sein:
- Leistungsüberlegungen: Umfangreiche Listen können die Abfrageleistung beeinträchtigen, insbesondere bei
slowFilter
. Achten Sie auf die Komplexität des Ausdrucks und testen Sie mit realistischen Datengrößen. - Lesbarkeit beibehalten: Teilen Sie große Listen gegebenenfalls in logische Gruppen auf oder verwenden Sie Alternativen wie den
in
-Operator (inslowFilter
), wenn anwendbar.
5.7.3 Überlegungen zur NULL-Handhabung
Das Verständnis und die korrekte Handhabung von NULL
-Werten sind wesentlich:
- Explizite NULL-Prüfungen: Verwenden Sie
isNull
undisNotNull
für spezifische NULL-Prüfungen. - Bei Vergleichen mit NULL-Werten ist Vorsicht geboten, da das Verhalten je nach Filter-Typ (fastFilter, slowFilter, keyFilter) unterschiedlich sein kann. Es wird empfohlen, für NULL-Prüfungen explizit die Operatoren isNull und isNotNull zu verwenden.
5.7.4 Tipps zum Testen von Ausdrücken
Da das Debuggen von Filterausdrücken direkt im GraphQL-Kontext eingeschränkt ist, können folgende Strategien helfen:
- Iterative Entwicklung: Beginnen Sie mit einfachen Ausdrücken und bauen Sie schrittweise Komplexität auf. Testen Sie in jedem Stadium, um die Korrektheit zu überprüfen.
- Repräsentative Daten verwenden: Testen Sie mit Daten, die tatsächlichen Anwendungsfällen ähneln, um die Performance und Korrektheit unter realistischen Bedingungen zu beurteilen.
- Komplexe Ausdrücke dokumentieren: Führen Sie klare Dokumentationen oder Kommentare für komplexe Ausdrücke, um zukünftiges Verständnis und Wartung zu erleichtern.
Durch Berücksichtigung dieser Punkte können Sie Filterausdrücke erstellen, die nicht nur funktional korrekt, sondern auch optimiert, klar und wartbar sind.
Hinweis: Die in diesem Kapitel beschriebenen Expression-Typen (SlowBooleanExpression
und SlowAnyExpression
) können auch in den System-Feldern mit _
-Präfix (siehe Kapitel 10) als expr
-Parameter verwendet werden. Dies ermöglicht die Nutzung derselben Ausdruckssyntax nicht nur für Filterung, sondern auch für Typkonvertierung, bedingte Ausführung und andere Operationen in verschiedenen Kontexten der GraphQL-Abfrage.
5.8 Beispiele
In diesem Kapitel werden konkrete Beispiele für die Verwendung von Filterausdrücken im GraphQL-Schema vorgestellt.
5.8.1 Kombination von logischen und Vergleichsoperatoren (fastFilter
)
Ziel
Filterung von Datensätzen, bei denen das Feld fldLagBestArt
entweder den Wert 1 oder 7 hat, UND das Feld fldAuftrNr
nicht null UND nicht leer ist.
Code (fastFilter
)
fastFilter: {
and: [
{ # Bedingung 1: fldLagBestArt ist 1 ODER 7
or: [
{ eq: [ { field: fldLagBestArt }, { value: 1 } ] },
{ eq: [ { field: fldLagBestArt }, { value: 7 } ] }
]
},
{ # Bedingung 2: fldAuftrNr ist NICHT (null ODER leer)
not: {
or: [
{ isNull: { field: fldAuftrNr } },
{ eq: [ { field: fldAuftrNr }, { value: "" } ] }
]
}
# Alternative für Bedingung 2: fldAuftrNr ist NICHT null UND NICHT leer
# and: [
# { isNotNull: { field: fldAuftrNr } },
# { ne: [ { field: fldAuftrNr }, { value: "" } ] }
# ]
}
]
}
Analyse
- Der äußere
and
-Operator verbindet zwei Hauptbedingungen. - Die erste Bedingung verwendet
or
, um zu prüfen, obfldLagBestArt
den Wert 1 oder 7 hat. - Die zweite Bedingung verwendet
not
undor
, um sicherzustellen, dassfldAuftrNr
wedernull
noch ein leerer String ist. Eine alternative Formulierung mitand
,isNotNull
undne
ist ebenfalls möglich.
5.8.2 Lesbarkeitsoptimierung mit dem in
-Operator (slowFilter
)
Ziel
Optimierung der Lesbarkeit des vorherigen Beispiels durch Verwendung des in
-Operators (nur in slowFilter
verfügbar), um die Überprüfung zu vereinfachen, ob das Feld fldLagBestArt
einen der Werte 1 oder 7 enthält.
Code (slowFilter
)
slowFilter: {
and: [
{ # Bedingung 1: fldLagBestArt IN (1, 7)
in: {
left: { field: fldLagBestArt },
list: [ 1 , 7 ] # Beachten Sie: Keine {value: ...} Hülle für Listenelemente
}
},
{ # Bedingung 2: fldAuftrNr ist NICHT null UND NICHT leer (wie oben)
and: [
{ isNotNull: { field: fldAuftrNr } },
{ ne: [ { field: fldAuftrNr }, { value: "" } ] }
]
}
]
}
Analyse
- Der
in
-Operator vereinfacht die erste Bedingung. - Wichtiger Hinweis: Diese Optimierung sollte nur dann verwendet werden, wenn der Ausdruck aus anderen Gründen bereits ein
slowFilter
sein muss (z. B. wegen berechneter Felder oder Funktionen). Der Performance-Nachteil vonslowFilter
gegenüberfastFilter
wiegt in der Regel schwerer als der Lesbarkeitsgewinn durchin
gegenüberor
mit zweieq
-Vergleichen.
5.8.3 Verschachtelung und direkte Negation eines Booleschen Feldes (fastFilter
)
Ziel
Filterung von Datensätzen, bei denen das Feld fldErledigtKz
entweder null
ist ODER false
ist, UND das Feld fldArt
nicht null
UND nicht gleich 0 ist.
Code (fastFilter
)
fastFilter: {
and: [
{ # Bedingung 1: fldErledigtKz ist null ODER NICHT fldErledigtKz (d.h. false)
or: [
{ isNull: { field: fldErledigtKz } },
{ not: { field: fldErledigtKz } } # Direkte Negation des booleschen Feldes
]
},
{ # Bedingung 2: fldArt ist NICHT null UND NICHT 0
and: [
{ isNotNull: { field: fldArt } },
{ ne: [ { field: fldArt }, { value: 0 } ] }
]
}
]
}
Analyse
- Der äußere
and
-Operator verbindet die beiden Hauptbedingungen. - Die erste Bedingung prüft mit
or
, obfldErledigtKz
entwedernull
ist oderfalse
ist. Die Prüfung auffalse
erfolgt durch direkte Negation (not
) des booleschen FeldesfldErledigtKz
. - Die zweite Bedingung stellt mit
and
sicher, dassfldArt
wedernull
noch 0 ist.
Dieses Beispiel zeigt die direkte Verwendung eines booleschen Feldes innerhalb von not
und die Kombination verschiedener Operatoren.
6. Arbeiten mit Datensätzen (Row
-Objekte)
Dieser Abschnitt beschreibt den Zugriff auf die Datenfelder eines Datensatzes (Row
-Objekts), nachdem dieser über rowRead
, rowsRead
oder conRead.edges.node
selektiert wurde.
6.1 Zusätzliche skalare Typen
Es gibt zusätzlich zu den Standard-GraphQL-Skalartypen (wie Int
, Float
, String
, Boolean
, ID
) auch spezielle benutzerdefinierte skalare Typen:
Void
: Akzeptiert nurnull
als Eingabe und gibt immernull
zurückBigInt
: Unterstützt positive oder negative Ganzzahlen mit bis zu 64 Ziffern (repräsentiert als JSON-String, wenn der Wert außerhalb des sicheren Bereichs von JSON-Number liegt)Variant
: Akzeptiert jedes grammatikalisch gültige GraphQL-Skalar, Liste oder Eingabeobjekt, das als Windows Variant-Typ dargestellt werden kann; gibt JSON zurück, das jede gültige Eingabe darstellen kannAny
: Akzeptiert jedes grammatikalisch gültige GraphQL-Skalar, Liste oder Eingabeobjekt; gibt JSON zurück, das jede gültige Eingabe darstellen kannGuid
: Ein String, der eine GUID im Standardformat enthält, z. B.:{A63CBE46-D82C-4347-9AA7-7B6BDBC3FA72}
LocalDate
: Ein lokaler Datums-String imYYYY-MM-DD
FormatLocalTime
: Ein lokaler Zeit-String im 24-Stunden-FormatHH:mm[:ss[.SSS]]
LocalDateTime
: Ein lokaler Datums-Zeit-String imYYYY-MM-DDTHH:mm[:ss[.SSS]]
FormatRtfString
: Ein String, der RTF enthältHtml
: Ein String, der HTML enthältUrl
: Ein String, der eine URL enthältChar
: Ein String, der genau ein Zeichen enthältVariable
: Ein spezieller Eingabetyp, der nur eine Referenz auf eine GraphQL-Variable erlaubt
6.1.1 Kontextabhängigkeit von Variant
und Any
Die Typen Variant
und Any
sind kontextabhängig - ihre Interpretation hängt vom Verwendungskontext ab:
Bei Eingabewerten wird der erwartete Datentyp vom Zielfeld bestimmt, auch wenn Any
als Typdeklaration verwendet wird:
# Filter auf ein Datumsfeld - obwohl der Typ "Any" ist, muss ein Datum angegeben werden
fastFilter: {
eq: [
{ field: fldGueltigAb }, # Datumsfeld
{ value: "2023-12-24" } # Datumswert im Format YYYY-MM-DD erforderlich
]
}
# Filter auf ein Zahlenfeld - hier muss eine Zahl angegeben werden
fastFilter: {
gt: [
{ field: fldPreis }, # Zahlenfeld
{ value: 99.95 } # Numerischer Wert erforderlich
]
}
Bei Ausgabewerten von fld...
-Feldern kann der as
-Parameter die Rückgabeformatierung steuern:
query {
tblProducts {
rowsRead {
# Dasselbe Feld in verschiedenen Formaten
preisText: fldPreis(as: TEXT) # "99,95 €" (für Anzeige formatiert)
preisValue: fldPreis(as: FLOAT) # 99.95 (numerischer Wert)
# Datumswerte in verschiedenen Formaten
datumStandard: fldGueltigAb # Standardformat (z.B. "2023-12-24")
datumFormatiert: fldGueltigAb(as: TEXT) # "24.12.2023" (lokalisiert)
}
}
}
Die Kontextabhängigkeit von Any
und Variant
bietet Flexibilität, erfordert aber Vorsicht. Die meisten GraphQL-Clients behandeln alle benutzerdefinierten Skalartypen, die sie nicht kennen, als "alles erlaubt" (was für Any
genau das gewünschte Verhalten ist). Allerdings bedeutet dies auch, dass ungültige Werte nicht bei der Client-seitigen Schema-Validierung, sondern erst bei der Server-seitigen Ausführung erkannt werden. Dies kann zu Laufzeitfehlern führen, wenn etwa ein String-Wert für ein numerisches Feld oder ein ungültiges Datumsformat verwendet wird. Die Validierung erfolgt immer im Kontext des Zielfelds.
6.1.2 Arbeiten mit dem Variable
-Typ
Der Variable
-Typ ist ein spezieller Eingabetyp, der ausschließlich eine Referenz auf eine GraphQL-Variable akzeptiert. Dieser Typ wird hauptsächlich in Direktiven wie @store
und @onNull
eingesetzt (siehe Kapitel 9 für Details zu Direktiven):
query ExampleWithVariables(
$myVar: Any = null
$storeVar: Any = null
) {
tblProducts {
rowsRead {
fldArtNr
# Verwendung des Variable-Typs in der @store Direktive
fldSuchBeg @store(in: $storeVar) # 'in' erwartet den Typ Variable
# Normale Ausgabe des Werts
storedValue: _any(value: $storeVar)
}
}
}
Wichtig zu wissen:
-
Client-Warnmeldungen: Viele GraphQL-Clients zeigen Warnungen oder Fehler an, wenn eine Variable an einen Parameter vom Typ
Variable
übergeben wird. Dies liegt daran, dass diese Clients den benutzerdefinierten SkalartypVariable
nicht kennen und meist erwarten, dass der Typ einer Variable exakt dem erwarteten Eingabetyp entspricht, wo die Variable verwendet wird. Diese Warnungen können ignoriert werden - die Abfrage wird trotzdem korrekt ausgeführt. -
Nur Variablenreferenzen erlaubt: An einen Parameter vom Typ
Variable
darf nur eine Variable, niemals ein fester Wert übergeben werden:
# FALSCH - löst einen Server-Fehler aus:
fldSuchBeg @store(in: "festwert") # Server lehnt dies bei der Schema-Validierung ab
# RICHTIG:
fldSuchBeg @store(in: $storeVar) # Eine Variable verwenden
Während GraphQL-Clients diesen Fehler oft nicht erkennen, wird der Server ihn bereits bei der Schema-Validierung feststellen.
- Kontextabhängige Typsicherheit: Der Typ der verwendeten Variable muss zum Kontext passen, in dem sie verwendet wird:
query (
$stringVar: String = "", # Für Text-Felder geeignet
$intVar: Int = 0, # Für Zahlen-Felder geeignet
$objectVar: Any = null # Für komplexe Rückgaben geeignet
) {
tblProducts {
rowRead(kf1ArtNr: { string: "PROD-001" }) {
# String-Feld in String-Variable speichern - kompatibel
fldArtNr @store(in: $stringVar)
# Numerisches Feld in Int-Variable speichern - kompatibel
fldLagMge @store(in: $intVar)
# Einzelnes Unterfeld eines Objekts in String-Variable - kompatibel
wgrEinzelfeld: rowWgrNr {
fldWgrNr @store(in: $stringVar) # Direktive auf einzelnes Unterfeld
}
# Mehrere Unterfelder als Objekt in Any-Variable speichern - kompatibel
wgrObjekt: rowWgrNr @store(in: $objectVar) {
fldWgrNr
fldBez
}
# FEHLER: Mehrere Unterfelder als Objekt in Int-Variable speichern
wgrFehler: rowWgrNr @store(in: $intVar) { # FEHLER: Objekt kann nicht in Int konvertiert werden
fldWgrNr
fldBez
}
}
}
}
Der Typ der Variable muss zum erwarteten Rückgabetyp passen:
- Für einzelne skalare Werte: passender Typ (String
, Int
, etc.)
- Für Objekte mit mehreren Feldern: Any
- Bei unklaren oder variierenden Rückgabetypen: Any
als sicherste Wahl
- Deklaration: Variablen müssen immer in der Operation deklariert werden. Verwenden Sie Standardwerte (z.B.
= null
), um Fehler zu vermeiden, wenn die Variable nicht extern übergeben wird.
6.1.3 Der spezielle skip
-Wert
Es gibt einen speziellen Wert skip
, der in jedem Any
-Typ verwendet werden kann. Bei der finalen Ausgabe als JSON hat dieser Wert eine besondere Bedeutung:
* Wenn ein Feld den Wert skip
zurückgibt, wird dieses Feld in der JSON-Ausgabe vollständig ausgelassen.
* Wenn skip
in einer Liste vorkommt, wird dieser Eintrag aus der Liste entfernt.
* Wenn skip
als Wert eines Objektschlüssels vorkommt, wird dieses Schlüssel-Wert-Paar aus dem Objekt entfernt.
* Eine Any
-Variable kann den Wert skip
enthalten und an Felder übergeben werden.
Wichtig: Dieses Verhalten wird erst bei der finalen Ausgabe nach JSON angewendet. Wenn ein Rückgabewert, der skip
enthält, zwischenzeitlich mit @store
in eine Variable gespeichert wird, bleibt der skip
-Wert innerhalb dieser Variable zunächst vollständig erhalten (sowohl direkt, als auch in Listen und Objekten). Erst bei der endgültigen JSON-Ausgabe werden die skip
-Werte entsprechend behandelt.
Diese Funktionalität ist besonders nützlich, um die Ausgabe dynamisch zu gestalten und unnötige Felder zu unterdrücken, ohne die Struktur der Abfrage ändern zu müssen.
Beispiel zur skip
-Verwendung:
query SkipExample($any: Any = skip) {
tblAddresses {
# Direktes Überspringen von Feldern
echoString: _any(value: "test") # Wird ausgegeben
echoSkip: _any(value: skip) # Wird übersprungen
# In Listen und Objekten
echoList: _any(value: [1, "a", skip, null]) # "skip" wird entfernt
echoObject: _any(value: {int:1, string:"a", skip:skip, null:null}) # Schlüssel "skip" wird entfernt
# Mit Variablen
echoSkipStored: _any(value: $any) # Übersprungen, wenn $any=skip
}
}
Resultierende JSON für obiges Beispiel:
{
"data": {
"tblAddresses": {
"echoString": "test",
"echoList": [1, "a", null],
"echoObject": {
"int": 1,
"string": "a",
"null": null
}
}
}
}
6.2 Datenfelder (fld...
)
Der Zugriff auf die eigentlichen Datenfelder eines Datensatzes erfolgt über GraphQL-Felder mit dem Präfix fld...
(z. B. fldArtNr
, fldPreis
) innerhalb des Row
-Objekts. Alle diese fld...
-Felder haben den Rückgabetyp Any
, was Flexibilität bei der Formatierung der Ausgabe über den optionalen as
-Parameter ermöglicht.
6.2.1 Verwendung des as
-Parameters
Der as
-Parameter, optional angegeben beim Aufruf eines fld...
-Feldes, nimmt einen Enum-Wert von einer Untermenge des Typs FieldAsEnum
an, der das gewünschte Ausgabeformat des Datenfeldes bestimmt. Wird as
nicht angegeben, wird ein datenfeldtypabhängiges Standardformat verwendet.
Beispiel:
query {
tblProducts {
rowsRead { # Zugriff auf die Liste der Rows
fldArtNr(as: STRING) # Zugriff auf fldArtNr, explizit als roher String formatiert
fldPreis(as: FLOAT) # Zugriff auf fldPreis, explizit als Fließkommazahl formatiert
}
}
}
6.2.2 Unterstützte Ausgabeformate (FieldAsEnum
)
Folgende Ausgabeformate können über den as
-Parameter gewählt werden. Die Verfügbarkeit hängt vom zugrundeliegenden Datentyp des Datenfeldes ab.
enum FieldAsEnum {
IS_ACCESS_ALLOWED # Ist der Zugriff auf dieses Feld erlaubt? (Boolean)
IS_NULL # Ist der Wert des Feldes NULL? (Boolean)
ALWAYS_NULL # Gibt immer null zurück (Void)
SKIP # Gibt immer skip zurück (Skip)
TEXT # Formatierte Textrepräsentation (String)
DISPLAY_TEXT # Für Anzeige formatierte Textrepräsentation (String)
BOOLEAN # Boolescher Wert (Boolean)
FLOAT # Fließkommazahl (Float)
LOCALDATE # Lokales Datum (LocalDate)
LOCALTIME # Lokale Zeit (LocalTime)
LOCALDATETIME # Lokale Datums-/Zeitangabe (LocalDateTime)
INT # 32-Bit Ganzzahl (Int)
BIGINT # Ganzzahl bis zu 64 Zeichen (BigInt)
CHAR # Einzelnes Zeichen (Char)
STRING # Roher String-Wert (String)
HEXSTRING # Binärdaten als Hex-String (String)
BASE64 # Binärdaten als Base64-String (String)
RTF_STRING # RTF-formatierter Text (RtfString)
HTML # HTML-formatierter Text (Html)
GUID # GUID-String (Guid)
URL # URL zum Abrufen von Binärdaten (Url)
}
IS_ACCESS_ALLOWED
Ein Boolean
, der angibt, ob der Zugriff auf dieses Feld (fld...
) für den aktuellen Benutzer und Kontext erlaubt ist; Zugriff kann aufgrund von Berechtigungen oder anderen Faktoren für bestimmte Datensätze oder Datenfelder eingeschränkt sein; wenn IS_ACCESS_ALLOWED
den Wert false
zurückliefert, werden alle anderen Ausgabeformate (außer potenziell DISPLAY_TEXT
, welches möglicherweise einen Hinweistext zur fehlenden Zugriffsberechtigung liefert) null
zurückliefern
IS_NULL
Ein Boolean
, der angibt, ob der Wert des Datenfeldes (fld...
) null
ist (true
) oder nicht (false
); falls der Zugriff nicht erlaubt ist (IS_ACCESS_ALLOWED == false
), kann der Rückgabewert ebenfalls null
sein; falls IS_NULL
wahr ist (true
), werden alle folgenden Ausgabeformate (außer IS_ACCESS_ALLOWED
und SKIP
) stets null
zurückgeben
ALWAYS_NULL
Liefert immer null
SKIP
Liefert immer skip
(siehe 6.1.3)
TEXT
Eine Textrepräsentation des Datenfeldes; die genaue Formatierung hängt vom zugrundeliegenden Datenfeldtyp, Parametern, dem aktuellen Benutzer usw. ab (z. B. Datumsformat, Dezimaltrennzeichen); ein Wert, der als TEXT
ausgelesen wurde, kann normalerweise in derselben Anmeldesitzung auch wieder über einen entsprechenden Eingabeparameter (oft text
genannt) geschrieben werden
DISPLAY_TEXT
Eine für die Anzeige im User Interface gedachte Textrepräsentation des Datenfeldes, ähnlich der Darstellung in der Tabellenansicht der microtech Software; dies kann zusätzliche Informationen wie Einheiten oder Währungssymbole enthalten; der zurückgegebene Wert ist möglicherweise nicht direkt zum Zurückschreiben geeignet
BOOLEAN
Ein boolescher Wert (true
/false
); nur für Datenfelder verfügbar, die logisch als Boolean interpretiert werden können
FLOAT
Eine Fließkommazahl im standardmäßigen JSON-Number-Format (IEEE 754 double precision); nur für numerische Datenfelder verfügbar
LOCALDATE
Ein lokaler Datums-String im YYYY-MM-DD
-Format; nur für Datums- oder Datums-/Zeit-Datenfelder verfügbar; die Zeitzone hängt vom Server und Mandanten ab
LOCALTIME
Ein lokaler Zeit-String im 24-Stunden-Format HH:mm[:ss[.SSS]]
; nur für Zeit- oder Datums-/Zeit-Datenfelder verfügbar; die Zeitzone hängt vom Server und Mandanten ab
LOCALDATETIME
Ein lokaler Datums-Zeit-String im YYYY-MM-DDTHH:mm[:ss[.SSS]]
-Format; nur für Datums-/Zeit-Datenfelder verfügbar; die Zeitzone hängt vom Server und Mandanten ab
INT
Ein 32-Bit-Signed-Integer im standardmäßigen JSON-Number-Format; nur für numerische Datenfelder verfügbar, deren Wertebereich passt
BIGINT
Ein Integer mit bis zu 64 Zeichen; wird als JSON-Number zurückgegeben, wenn der Wert im Bereich -2^53+1
bis 2^53-1
liegt, andernfalls als JSON-String; nur für numerische Datenfelder verfügbar
CHAR
Ein JSON-String mit genau einem Zeichen; nur für Datenfelder verfügbar, die einzelne Zeichen repräsentieren
STRING
Ein JSON-String; für String-Datenfelder ist dies der exakte, unveränderte Wert, der im Datenfeld gespeichert ist; für andere Datenfeldtypen ist STRING
eine Rohformatierung, die nicht von externen Faktoren wie Benutzereinstellungen abhängt (z. B. keine Tausendertrennzeichen bei Zahlen, festes Datumsformat)
HEXSTRING
Ein JSON-String, der die Binärdaten des Feldes als Folge von Hexadezimalziffern kodiert (z. B. 48656C6C6F
); jeweils zwei Zeichen entsprechen einem Byte
BASE64
Ein JSON-String, der die Binärdaten des Feldes als Base64-kodiert enthält
RTF_STRING
Ein JSON-String mit Text und Formatierung im Rich-Text-Format (RTF); nur für Datenfelder verfügbar, die RTF speichern
HTML
Ein JSON-String mit Text und Formatierung im HTML-Format; wird ggf. aus RTF oder anderen Formaten konvertiert; nur für Datenfelder verfügbar, die Text mit Formatierung speichern können
GUID
Ein JSON-String mit einer GUID im Standardformat (z. B. "{5A4F8D06-7021-4668-A321-B6AB5314D2A6}"
); nur für GUID-Datenfelder verfügbar
URL
Eine URL (als JSON-String), über die der Inhalt des Datenfeldes (typischerweise Binärdaten wie Bilder oder Dokumente) in einer separaten HTTP-Anfrage abgerufen werden kann; die Gültigkeit der URL ist nur während der aktuellen Anmeldesitzung und nur solange garantiert, bis die zugrundeliegenden Daten geändert werden
6.2.3 Kontextspezifische Bedeutung und Verfügbarkeit
Die Verfügbarkeit der as
-Formate hängt vom Typ des zugrundeliegenden Datenfeldes ab. Ein as: BOOLEAN
ist z. B. für ein reines Datums-Datenfeld nicht sinnvoll, während as: URL
nur für Blob-Datenfelder verfügbar ist. Die genauen Optionen für ein spezifisches fld...
-Feld sind dem Schema zu entnehmen oder werden in interaktiven GraphQL-Oberflächen kontextbezogen angezeigt. Das Schema definiert spezifische Enums für jeden Datenfeldtyp (z. B. GetBooleanFieldAsEnum
, GetRtfBlobFieldAsEnum
), die nur die jeweils gültigen FieldAsEnum
-Werte enthalten.
Beispiel für mögliche Einschränkungen (Illustrativ):
enum GetBooleanFieldAsEnum {
IS_ACCESS_ALLOWED
IS_NULL
SKIP
TEXT
DISPLAY_TEXT
BOOLEAN
INT
STRING
}
enum GetRtfBlobFieldAsEnum {
IS_ACCESS_ALLOWED
IS_NULL
SKIP
TEXT
DISPLAY_TEXT
RTF_STRING
HTML
}
6.2.4 Systemfelder für Versionierung in Top-Level-Tabellen
Jede Top-Level-Tabelle (tbl...
auf oberster Ebene der Abfrage) enthält spezielle Systemfelder, die für die Versionierung von Datensätzen verwendet werden:
fldModifyLSN
: Repräsentiert den aktuellen Versionsstand des Datensatzes als 64-Bit-Ganzzahl. Wird bei jeder Änderung aktualisiert.fldInsertLSN
: Speichert den Versionsstand zum Zeitpunkt des Einfügens des Datensatzes. Bleibt über die Lebensdauer des Datensatzes konstant (außer bei Datenbankreorganisationen).
Wichtig: Diese LSN-Felder sind nur in Top-Level-Tabellen verfügbar und nicht in untergeordneten Tabellen, die als Felder innerhalb anderer Datensätze abgerufen werden (also nicht in Tabellen, auf die über tbl...
-Felder innerhalb eines Row
-Objekts zugegriffen wird).
Diese Felder sind besonders nützlich für:
- Optimistic Concurrency Control: Sicherstellung, dass ein Datensatz zwischen Abruf und Änderung nicht von anderen Benutzern modifiziert wurde.
- Änderungsverfolgung: Identifikation geänderter Datensätze seit einem bestimmten Zeitpunkt.
- Datensynchronisation: Effiziente Synchronisation zwischen Systemen durch Übertragung nur der neueren Datensätze.
Weitere Informationen zur Bedeutung und Verwendung der LSN-Felder finden Sie in Abschnitt 3.5.
Beispiel zum Abrufen der LSN-Werte:
query {
tblProducts {
rowsRead {
fldArtNr
fldModifyLSN # Aktuelle Änderungs-LSN
fldInsertLSN # Ursprüngliche Einfüge-LSN
}
}
}
6.3 Datenfelder, die auf andere Datensätze verweisen (row...
)
Einige Datenfelder in einem Datensatz (zugänglich über GraphQL-Felder mit dem Präfix fld...
, z. B. fldWgrNr
in Artikeln) enthalten einen Wert (oft eine Nummer oder einen anderen Schlüssel), der auf einen spezifischen einzelnen Datensatz in einer anderen (oder derselben) Tabelle verweist (z. B. im Fall von fldWgrNr
auf einen Datensatz in der Warengruppen-Tabelle). Solche Referenz-Datenfelder bieten oft ein zusätzliches GraphQL-Feld im Row
-Objekt, um direkt auf diesen verknüpften Datensatz zuzugreifen.
Der Name dieses zusätzlichen GraphQL-Feldes setzt sich aus dem Präfix row...
und dem Namen des ursprünglichen Datenfeldes (ohne fld
-Präfix) zusammen (z. B. wird für das Datenfeld "WgrNr" das GraphQL-Feld rowWgrNr
bereitgestellt).
Diese row...
-Felder haben keine eigenen Parameter und geben direkt das Row
-Objekt der Zieltabelle (z. B. ProductGroupRowQueryRead
) oder null
zurück, falls das ursprüngliche Datenfeld (fld...
) null
ist oder der Wert nicht auf einen gültigen Datensatz in der Zieltabelle verweist. Innerhalb des zurückgegebenen Row
-Objekts können dann dessen fld...
-Felder abgefragt werden.
Beispiel:
query {
tblProducts {
rowsRead { # Zugriff auf ProductRow-Objekte
fldArtNr
fldWgrNr # Enthält die Nummer der Warengruppe
rowWgrNr { # Zugriff auf das verknüpfte ProductGroupRow-Objekt
# Felder der Warengruppe:
fldWgrNr
fldBez
}
fldEinh # Enthält die Einheit
rowEinh { # Zugriff auf das verknüpfte UnitRow-Objekt
# Felder der Einheit:
fldKuBez
fldBez
fldMgeFak
fldEPreisFak
}
}
}
}
6.4 Datenfelder, die Datentabellen enthalten (tbl...
)
Einige Datensätze enthalten in ihren Datenfeldern untergeordnete Tabellen (z. B. enthält ein Mandanten-Datensatz im Datenfeld "BnkVb" eine Tabelle mit Bankverbindungen, auf die über das GraphQL-Feld fldBnkVb
zugegriffen werden kann). Für den strukturierten Zugriff auf diese verschachtelten Tabellen existiert ein zusätzliches GraphQL-Feld im übergeordneten Row
-Objekt.
Der Name dieses GraphQL-Feldes setzt sich aus dem Präfix tbl...
und dem Namen des ursprünglichen Datenfeldes (ohne fld
-Präfix) zusammen (z. B. wird für das Datenfeld "BnkVb" das GraphQL-Feld tblBnkVb
bereitgestellt).
Diese tbl...
-Felder geben ein Table
-Objekt der untergeordneten Tabelle zurück (z. B. BankAccountsTableQueryRead
), ähnlich den tbl...
-Feldern auf der obersten Ebene der Abfrage. Innerhalb dieses Table
-Objekts stehen die Felder rowRead
und rowsRead
zur Verfügung, um auf die Datensätze der enthaltenen Tabelle zuzugreifen. Diese akzeptieren dieselben Parameter für Sortierung (by...
/kf...
), Einschränkung (exactMatch
, nearestMatch
, allBetween
) und Filterung (fastFilter
/slowFilter
/keyFilter
), wie sie in den Kapiteln 3 bis 5 für die Top-Level-Zugriffsfelder beschrieben wurden. Das Feld conRead
für Paginierung ist für diese enthaltenen Tabellen jedoch nicht verfügbar.
Der Rückgabewert von rowsRead
ist eine Liste von Row
-Objekten der enthaltenen Tabelle (oder eine leere Liste), und der von rowRead
ist ein einzelnes Row
-Objekt oder null
.
Beispiel:
query {
tblClient {
rowRead { # Zugriff auf ClientRow
fldMandNr
# Zugriff auf die enthaltene BankAccounts-Tabelle:
tblBnkVb { # Gibt ein BankAccountsTableQueryRead-Objekt zurück
# Zugriff auf die Datensätze innerhalb der enthaltenen Tabelle:
rowsRead( # Lese alle Bankkonten dieses Mandanten
# Optional: Sortierung, Filter etc. anwendbar
# allBetween: { byNr: { ... } }
# fastFilter: { ... }
) {
# Felder der enthaltenen BankAccounts-Tabelle:
fldNr
fldZahlArt
fldBnkVb
fldBLZ
}
}
}
}
}
6.5 Betragsgruppen (aco...
)
Betragsgruppen sind Datenfelder in der Datenbank, die Beträge repräsentieren (z. B. "EPr" für Einzelpreis). Sie bieten eine detaillierte Aufschlüsselung eines Betrags in seine Netto-, Brutto- und Steueranteile. Zusätzlich ermöglichen sie bei Bedarf eine weitere Aufgliederung nach den beteiligten Steuerschlüsseln.
Der Zugriff auf diese Detailinformationen erfolgt über GraphQL-Felder mit dem Präfix aco...
, die Teil des Row
-Objekts sind. Der Name dieser aco...
-Felder setzt sich aus dem Präfix aco
und dem Namen des ursprünglichen Betragsdatenfeldes (ohne fld
-Präfix) zusammen (z. B. wird für das Datenfeld "EPr" das GraphQL-Feld acoEPr
bereitgestellt).
6.5.1 Speicherung und Berechnung
Betragsgruppen können entweder als Brutto- oder als Nettowert gespeichert sein. Die aco...
-Struktur berechnet beim Zugriff automatisch den jeweils anderen Wert (Netto oder Brutto) sowie den Gesamtsteuerbetrag. Das GraphQL-Feld isCalculatedAndStoredAsGross
innerhalb der aco...
-Struktur gibt Auskunft darüber, welcher Wert ursprünglich gespeichert wurde (true
für Brutto, false
für Netto). Dies ist relevant für das Verständnis potenzieller Rundungsdifferenzen.
6.5.2 Struktur und verfügbare Felder (aco...
)
Ein aco...
-Feld (z. B. acoEPr
) gibt ein Objekt vom Typ AmountCompositionQuery
zurück, das strukturierte Informationen enthält. Je nach Konfiguration und Art des zugrundeliegenden Datenfeldes können unterschiedliche Felder innerhalb dieses Objekts verfügbar sein:
Immer verfügbar (Basis-Informationen):
totalGrossAmount
:Float
- Gesamt-Bruttobetrag.totalNetAmount
:Float
- Gesamt-Nettobetrag.totalTaxAmount
:Float
- Gesamt-Steuerbetrag.isCalculatedAndStoredAsGross
:Boolean
- Gibt an, ob der Betrag als Brutto (true
) oder Netto (false
) gespeichert wurde. Dies beeinflusst, wo Rundungsdifferenzen auftreten können.
Zusätzlich verfügbar, wenn das Datenfeld Einzelwerte pro Steuersatz unterstützt:
aces
:[AmountCompositionEntry!]
- (für Amount Composition Entries) Eine Liste mit Einträgen für jeden beteiligten Steuersatz. Jeder Eintrag (AmountCompositionEntry
) enthält:taxCode
:Int
- Der Steuerschlüssel (z. B. 19, 7).grossAmount
:Float
- Bruttobetrag für diesen Steuersatz.netAmount
:Float
- Nettobetrag für diesen Steuersatz.taxAmount
:Float
- Steuerbetrag für diesen Steuersatz.rowValueAddedTaxType
:ValueAddedTaxTypeRowQueryRead
- Zugriff auf den verknüpften Umsatzsteuerdatensatz über denrow...
-Mechanismus (siehe Abschnitt 6.3), der Details zum Steuersatz liefert.
aceByTaxCode(taxCode: Int!)
:AmountCompositionEntry
- Gibt den einzelnen Eintrag ausaces
zurück, der dem übergebenentaxCode
entspricht. Gibtnull
zurück, wenn kein Eintrag für diesen Steuercode existiert oder wenn das Datenfeld keine Einzelwerte unterstützt.
Zusätzlich verfügbar, wenn das Datenfeld einen Soll/Haben-Wert darstellt (z. B. Salden):
totalBalance
:Float
- Gesamt-Saldo (immer Brutto). Das Vorzeichen gibt Soll/Haben an: positiv für Haben, negativ für Soll.isDebit
:Boolean
- Gibt an, ob es sich um einen Soll-Betrag (true
) oder Haben-Betrag (false
) handelt.
(Hinweis: Ein Datenfeld kann auch beide zusätzlichen Gruppen von Informationen unterstützen, also sowohl aces
/aceByTaxCode
als auch totalBalance
/isDebit
bereitstellen.)
Beispiel:
query {
tblTransactionItems {
rowsRead {
fldBez
fldEPr(as: TEXT) # Ursprüngliches Betragsfeld (Einzelpreis)
# Zugriff auf die Betragsgruppen-Details für fldEPr
acoEPr {
# Basis-Felder (immer da)
totalGrossAmount
totalNetAmount
totalTaxAmount
isCalculatedAndStoredAsGross
# Felder für Einzelwerte (nur wenn fldEPr Einzelwerte unterstützt)
aces {
taxCode
grossAmount
netAmount
taxAmount
# Zugriff auf verknüpfte Steuerdaten via row...
rowValueAddedTaxType {
fldStSchl
fldStArt
fldBez
}
}
# Gezielter Zugriff auf einen Eintrag per Steuercode (nur wenn Einzelwerte unterstützt)
aceByTaxCode(taxCode: 19) {
taxCode
grossAmount
netAmount
taxAmount
}
# Felder für Soll/Haben (fldEPr ist kein Soll/Haben-Feld)
# totalBalance
# isDebit
}
}
}
}
6.6 Bildfelder (img...
)
Bildfelder sind Datenfelder, die Bilder und zugehörige Metadaten repräsentieren (z.B. Artikelbilder, Adressfotos). Sie bieten eine strukturierte Möglichkeit, auf Bilddaten und deren Eigenschaften wie Größe, Abmessungen und Formatinformationen zuzugreifen.
Der Zugriff auf diese Bildinformationen erfolgt über GraphQL-Felder mit dem Präfix img...
, die Teil des Row
-Objekts sind. Der Name dieser img...
-Felder setzt sich aus dem Präfix img
und dem Namen des ursprünglichen Bilddatenfeldes (ohne fld
-Präfix) zusammen (z. B. wird für das Datenfeld "Bild" das GraphQL-Feld imgBild
bereitgestellt).
6.6.1 Struktur und verfügbare Felder (img...
)
Ein img...
-Feld (z.B. imgBild
) gibt ein Objekt vom Typ ImageQuery
zurück, das die folgenden strukturierten Informationen enthält:
Basisfelder des ImageQuery-Objekts:
isNull
:Boolean
- Gibt an, ob das Bildfeld einen Null-Wert enthält.true
bedeutet, dass kein Bild vorhanden ist.base64
:String!
- Eine Base64-kodierte Repräsentation des Bildes, die direkt in HTML-Elementen wie<img src="data:image/jpeg;base64,...">
verwendet werden kann.hexString
:String!
- Eine hexadezimale Stringrepräsentation der Bilddaten, bei der jedes Byte als zwei Hexadezimalziffern dargestellt wird.reference
:String!
- Eine Referenz zum Bild, die je nach Speicherort des Bildes unterschiedliche Formate haben kann:- Leerer String (
""
): Das Bild ist direkt in der Datenbank gespeichert (Binärdaten im Datensatz). - Serverlokaler Dateipfad (z.B.
"C:\\Temp\\image.png"
): Das Bild ist als Link auf eine Datei im Dateisystem des Servers gespeichert. bpref://
-URL (z.B."bpref://1@Bld:Nr/BILDNAME"
): Das Bild ist in der Bilder-Datentabelle gespeichert oder in der Bilder-Tabelle existiert ein Datensatz, der auf eine lokale Datei verweist.mimeType
:String!
- Der MIME-Typ des Bildes (z.B. "image/jpeg", "image/png"), der das Bildformat angibt.size
:Int!
- Die Größe des Bildes in Bytes.height
:Int!
- Die Höhe des Bildes in Pixeln.width
:Int!
- Die Breite des Bildes in Pixeln.
Wichtig zu beachten:
- Auch wenn das Bild
null
ist (wennisNull
den Werttrue
hat), werden die anderen Felder mit Standardwerten oder leeren Strings zurückgegeben, um Client-seitige Fehlerbehandlung zu vereinfachen. - Die Felder
base64
undhexString
enthalten die vollständigen Bilddaten, was bei großen Bildern zu erheblichen Datenübertragungsmengen führen kann. Es wird empfohlen, diese Felder selektiv und nur bei Bedarf abzufragen.
6.6.2 Typische Anwendungsfälle
Die img...
-Struktur bietet verschiedene Möglichkeiten, auf Bilddaten zuzugreifen, je nach Anwendungsfall:
-
Bildmetadaten ohne Bilddaten abfragen: Für Listen oder Übersichten, bei denen nur Informationen wie Größe oder Abmessungen benötigt werden, können Sie nur
isNull
,size
,width
undheight
abfragen, ohne die eigentlichen Bilddaten zu übertragen. -
Direkte Bildintegration: Für die direkte Einbettung von Bildern in HTML/CSS verwenden Sie
base64
zusammen mitmimeType
(z.B. indata:
URLs), unabhängig vom tatsächlichen Speicherort des Bildes im Backend. -
Nachbearbeitung von Bildern: Für die clientseitige Bildbearbeitung oder -analyse können entweder
base64
oderhexString
verwendet werden, je nach den verwendeten Bibliotheken. -
Analyse des Speicherorts: Die Interpretation des
reference
-Felds kann für Diagnose- oder Verwaltungszwecke nützlich sein, um zu verstehen, wie und wo Bilder im Backend gespeichert sind.
6.6.3 Beispiel
query {
tblProducts {
rowsRead(
kf1ArtNr: { string: "PROD-001" }
) {
fldArtNr
fldSuchBeg
# Zugriff auf das Produktbild
imgBild {
# Prüfung, ob ein Bild vorhanden ist
isNull
# Metadaten des Bildes
mimeType
size
width
height
# Referenz zum Speicherort des Bildes
reference # Zeigt an, wo das Bild gespeichert ist (DB, Dateisystem, Bilder-Tabelle)
# Vollständige Bilddaten (nur bei Bedarf abfragen)
# base64
# hexString
}
}
}
}
6.6.4 Optimierung der Bildabfragen
Beim Umgang mit Bildern in GraphQL sollten folgende Best Practices beachtet werden:
-
Selektives Abfragen: Fragen Sie nur die Felder ab, die Sie tatsächlich benötigen. Die Felder
base64
undhexString
enthalten die vollständigen Bilddaten und sollten nur angefordert werden, wenn tatsächlich das Bild selbst benötigt wird. -
Prüfung vor Verarbeitung: Verwenden Sie das
isNull
-Feld, um zu prüfen, ob überhaupt ein Bild vorhanden ist, bevor Sie versuchen, es zu verarbeiten oder anzuzeigen. -
Verständnis des Speicherorts: Obwohl das
reference
-Feld nicht für den direkten Zugriff auf Bilddaten verwendet wird, kann es nützlich sein, um den Speichermechanismus im Backend zu verstehen. Dies kann bei der Entwicklung komplexerer Anwendungen oder bei der Fehlerbehebung hilfreich sein. -
Datenmenge reduzieren: Bei Listenansichten mit vielen Datensätzen ist es oft sinnvoller, zunächst nur die Metadaten der Bilder zu laden und die eigentlichen Bilddaten erst auf Anforderung (z.B. beim Klicken oder Scrollen) in einer separaten Abfrage nachzuladen.
6.7 Tabellen- und Datensatzfunktionen (fn...
)
Zusätzlich zu den Datenfeldern (fld...
) und anderen strukturierten Zugriffen stehen in GraphQL auch Funktionen der zugrundeliegenden Tabellenklassen zur Verfügung. Diese werden über fn...
-Felder bereitgestellt und bieten direkten Zugriff auf die Geschäftslogik der microtech Software.
6.7.1 Arten von Funktionen
Es werden zwei Arten von fn...
-Feldern unterschieden:
-
Instanzmethoden (
fn...
-Felder inRow
-Objekten): Funktionen, die auf einem spezifischen Datensatz operieren und dessen Kontext nutzen. -
Klassenmethoden (
fn...
-Felder inTable
-Objekten): Funktionen auf Tabellenebene, die unabhängig von einem einzelnen Datensatz arbeiten.
6.7.2 Verfügbarkeit
Welche fn...
-Felder für eine Tabelle oder einen Datensatz verfügbar sind, ist tabellenspezifisch und hängt von der implementierten Geschäftslogik ab. Die verfügbaren Funktionen können über das GraphQL-Schema eingesehen werden und werden in interaktiven GraphQL-Oberflächen kontextbezogen angezeigt.
Wichtig: Die Verfügbarkeit von Instanzmethoden kann sich je nach Kontext unterscheiden.
6.7.3 Beispiel in einer Query
query {
tblAddresses {
# Klassenmethode auf Tabellenebene
fnIsCustomer(adrNr: "12345") # Prüft, ob Adresse 12345 ein Kunde ist
rowRead(kf1AdrNr: { string: "12345" }) {
fldAdrNr
# Instanzmethoden auf Datensatzebene
fnIsCustomer # Boolean: Ist dieser Datensatz ein Kunde?
fnIsVendor # Boolean: Ist dieser Datensatz ein Lieferant?
fnIsIndividual # Boolean: Ist dieser Datensatz eine Einzelperson?
}
}
}
6.7.4 Verwendungshinweise
- Parameter:
- Klassenmethoden arbeiten unabhängig vom Kontext eines einzelnen Datensatzes und erhalten benötigte Daten über Parameter
-
Instanzmethoden nutzen den Kontext des aktuellen Datensatzes, können aber zusätzliche Parameter erfordern
-
Rückgabetypen: Die Rückgabetypen variieren je nach Funktion:
- Einfache Typen:
Boolean
,String
,Int
,Float
- Komplexe Typen: Objekte mit strukturierten Daten
-
Listen: Arrays von Werten oder Objekten
-
Performance: Funktionsaufrufe können komplexe Geschäftslogik ausführen und weitere Datenbankabfragen im Hintergrund auslösen. Bei der Verwendung in
rowsRead
wird die Funktion für jeden Datensatz ausgeführt, was bei großen Datenmengen zu berücksichtigen ist.
6.7.5 Unterschied zu anderen Feldern
Die fn...
-Funktionen unterscheiden sich von anderen Feldern:
- Von berechneten
fld...
-Feldern: fn...
-Felder sind explizit als Funktionen gekennzeichnet- Sie repräsentieren aktive Operationen statt passive Datenzugriffe
-
Sie können Parameter akzeptieren
-
Von Filterausdrucks-Funktionen (
fn...
in Filtern): - Die Filterausdrucks-Funktionen (wie
fnDay
,fnMonth
) sind nur innerhalb vonslowFilter
undexpr
-Parametern verfügbar - Table/Row-Funktionen sind direkte GraphQL-Felder der Objekte und nicht Teil von Ausdrücken
Diese Funktionalität erweitert die GraphQL-Schnittstelle um direkten Zugriff auf die Geschäftslogik der microtech Software und ermöglicht es, komplexe Operationen durchzuführen, ohne zusätzliche API-Aufrufe oder clientseitige Implementierungen zu benötigen.
7. Verlinkte Datentabellen (lnk...
)
Viele Datensätze stehen in einer Beziehung zu Datensätzen in anderen Tabellen (z. B. ein Artikel zu seinen Lagerbeständen, eine Adresse zu ihren Offenen Posten). Diese Verlinkungen werden im GraphQL-Schema durch lnk...
-Felder innerhalb eines Row
-Objekts repräsentiert, die ähnlich wie tbl...
-Felder die verlinkte Tabelle als solche darstellen.
Der Name eines solchen Feldes setzt sich aus dem Präfix lnk
(für Link) und dem Namen der verlinkten Datentabelle zusammen (z. B. lnkInventory
für Lagerbestände, lnkOpenItems
für offene Posten).
Diese lnk...
-Felder geben ein spezifisches Link
-Objekt zurück (z. B. ProductToInventoryLinkQuery
), das ähnlich wie ein Table
-Objekt GraphQL-Felder zum Abrufen der verlinkten Datensätze bereitstellt.
7.1 Abrufen verlinkter Datensätze (rowsRead
)
Innerhalb des Link
-Objekts (das von einem lnk...
-Feld zurückgegeben wird) gibt es das GraphQL-Feld rowsRead
, um die Liste der verlinkten Datensätze abzurufen.
rowsRead
: Gibt eine Liste vonRow
-Objekten der verlinkten Tabelle zurück (z. B.[InventoryEntryRowQueryRead]
für Lagerbestandszeilen).- Erfordert Parameter zur Definition der Verlinkung und Sortierung (
by...
mitusing...
). - Akzeptiert optionale Parameter zur weiteren Einschränkung über Sortierfelder (
kf...
) und Filter (fastFilter
,slowFilter
,keyFilter
) auf die verlinkten Datensätze.
- Erfordert Parameter zur Definition der Verlinkung und Sortierung (
Grundlegendes Beispiel: Abrufen verlinkter Datensätze
query {
tblProducts {
rowRead(kf1ArtNr: { string: "PROD-001" }) { # Ein bestimmter Artikel
fldArtNr
fldSuchBeg
# Zugriff auf verlinkte Lagerbestände für diesen Artikel:
lnkInventory { # Gibt ProductToInventoryLinkQuery zurück
# Abrufen der verlinkten Bestände:
rowsRead (
# Einfachste Verlinkungsform - nur erforderliche Parameter:
byArtNrLagNrArtBDat: { # Sortierung der Lagerbestände
usingArtNr: {} # Verlinkung über die Artikelnummer
}
) {
# Felder der verlinkten Lagerbestände:
fldLagNr
fldMge
fldBuchDat
}
}
}
}
}
Dieses Beispiel zeigt die grundlegende Struktur:
1. Zunächst wird ein Artikel als Ausgangsdatensatz ausgewählt
2. Dann wird über lnkInventory
auf verlinkte Lagerbestände zugegriffen
3. Mit rowsRead
und den minimal erforderlichen Parametern werden die verlinkten Datensätze abgerufen
7.2 Sortierung der verlinkten Daten (by...
)
Der erste zwingend erforderliche Parameter für das rowsRead
-Feld innerhalb eines lnk...
-Objekts ist ein by...
-Parameter. Dieser definiert die gewünschte Sortierfolge der verlinkten Tabelle und legt fest, welche Schlüsselfelder dieser Tabelle für die Verlinkung mit dem Ausgangsdatensatz verwendet werden.
- Es gibt einen oder mehrere mögliche
by...
-Parameter, deren Namen sich aus dem Präfixby
und dem Namen einer Sortierfolge der verlinkten Tabelle zusammensetzt (z. B.byArtNrLagNrArtBDat
). - Nur Sortierfolgen der verlinkten Tabelle, deren erste(s) Schlüsselfeld(er) (
kf1...
) einen Bezug zu Feldern im Ausgangsdatensatz haben, stehen hier zur Verfügung. - Genau ein
by...
-Parameter muss angegeben werden.
7.3 Auswahl der Verlinkungsfelder (using...
)
Innerhalb des gewählten by...
-Parameters muss genau ein using...
-Parameter angegeben werden.
- Dieser legt fest, welche Datenfelder des Ausgangsdatensatzes verwendet werden, um die Werte für die ersten Schlüsselfelder (
kf...
) der imby...
-Parameter gewählten Sortierfolge der verlinkten Tabelle zu liefern. Die Verlinkung erfolgt also über die Werte dieser Datenfelder. - Der Name des
using...
-Parameters legt eindeutig fest, welche Datenfelder des Ausgangsdatensatzes verwendet werden (z. B. bedeutetusingArtNr
, dassfldArtNr
des Ausgangsdatensatzes den Wert fürkf1ArtNr
der verlinkten Tabelle liefert).
7.4 Weitere Einschränkungen über Sortierungsfelder (kf...
)
Innerhalb des using...
-Parameters können optional weitere Einschränkungen für die nachfolgenden Schlüsselfelder der im by...
-Parameter gewählten Sortierfolge der verlinkten Tabelle vorgenommen werden. Dies geschieht analog zur Angabe von Schlüsselfeldwerten in Kapitel 3.4, indem die kf...
-Parameter verschachtelt angegeben werden. Hier kann auch ein from
/to
-Bereich für das letzte angegebene kfN...
-Feld definiert werden.
7.5 Filter (fastFilter
, slowFilter
, keyFilter
)
Zusätzlich können Filter auf die verlinkten Datensätze angewendet werden:
fastFilter
/slowFilter
: Werden als Parameter direkt für dierowsRead
-Methode (innerhalb deslnk...
-Objekts) angegeben und filtern diefld...
-Felder der verlinkten Datensätze.keyFilter
: Wird innerhalb desusing...
-Parameters angegeben (parallel zu den optionalenkf...
-Einschränkungen) und filtert die Schlüsselfelder der imby...
-Parameter gewählten Sortierfolge der verlinkten Datensätze, auf die durchkf...
-Parameter zugegriffen wird.
Beispiel:
query {
tblProducts { # Artikel abfragen
rowRead(kf1ArtNr: {string: "ART4711"}) { # Einen bestimmten Ausgangsdatensatz
fldArtNr
# Zugriff auf verlinkte Lagerbestände für diesen Artikel:
lnkInventory { # Gibt ProductToInventoryLinkQuery zurück
# Abrufen der verlinkten Bestände:
rowsRead (
# 1. Sortierung der verlinkten Tabelle & Verlinkungsfeld wählen:
byArtNrLagNrArtBDat: { # Sortierung der Lagerbestände
# 2. Verlinkung definieren: kf1ArtNr der Lagerbestände soll fldArtNr des Artikels verwenden
usingArtNr: {
# 3. Optionale Einschränkung auf weitere kf-Felder:
kf2LagNr: { string: "1" } # Nur Lagerbestände aus Lager 1
# Optional: keyFilter auf kf-Felder:
# keyFilter: { gt: [{field: kf4BuchDat}, {value: "2024-01-01"}] } # Beispiel
}
}
# Optional: Filter auf fld-Felder:
fastFilter: { gt: [{field: fldMge}, {value: 0.0}] } # Nur Lagerbestände > 0
# slowFilter: { ... }
) {
# Felder der verlinkten Lagerbestände:
fldBuchDat
fldBelegNr
fldMge
}
}
}
}
}
7.6 Verschachtelte Verlinkungen
Eine besonders mächtige Eigenschaft des GraphQL-Schemas ist die Möglichkeit, lnk...
-Verlinkungen beliebig tief zu verschachteln. Dies bedeutet, dass Sie nicht nur von einem Ausgangsdatensatz zu verlinkten Datensätzen navigieren können, sondern von diesen verlinkten Datensätzen wiederum zu deren verlinkten Datensätzen und so weiter.
Diese Verschachtelungsfähigkeit bietet mehrere Vorteile:
- Komplexe Geschäftsprozesse abbilden: Eine einzelne Abfrage kann einer vollständigen Geschäftsprozess-Kette folgen (z.B. Artikel → Lagerbestand → Beleg → Kunde)
- Reduzierung der Anzahl benötigter API-Aufrufe: Statt mehrerer sequentieller Abfragen kann eine einzige verschachtelte Abfrage verwendet werden
- Präzise Datenauswahl: Auf jeder Ebene können Sie genau die benötigten Felder spezifizieren
Beispiel für tiefere Verschachtelung von Verlinkungen
query {
tblProducts {
rowRead(kf1ArtNr: { string: "PROD-001" }) {
fldArtNr
fldSuchBeg
# EBENE 1: Verlinkung zu Lagerbeständen
lnkInventory {
rowsRead(
byArtNrLagNrArtBDat: {
usingArtNr: {} # Verlinkung über die Artikelnummer
}
) {
fldLagNr
fldMge
fldBuchDat
fldBelegNr # Verlinkungsfeld zum Beleg
# EBENE 2: Verlinkung zum zugehörigen Beleg/Vorgang
lnkTransactions {
rowsRead(
byBelegNr: {
usingBelegNr: {} # Verlinkung über die Belegnummer
}
) {
fldBelegNr
fldArt # Vorgangsart
fldDat # Belegdatum
fldBez # Vorgangsbezeichnung
# EBENE 3: Weitere Verlinkungen möglich...
# z.B. zu Vorgangspositionen, Adressen, etc.
}
}
}
}
}
}
}
Diese Abfrage demonstriert drei Ebenen von Verlinkungen:
1. Von einem Artikel zu seinen Lagerbeständen via lnkInventory
2. Von jedem Lagerbestand zu den zugehörigen Belegen/Vorgängen via lnkTransactions
3. Eine Andeutung für weitere mögliche Verlinkungen (die je nach Bedarf fortgesetzt werden könnten)
Auf jeder Ebene können sämtliche bereits vorgestellten Techniken angewendet werden: Filter zur Einschränkung der Datenmenge, präzise Feldauswahl, und weitere Verlinkungsparameter.
In der Praxis ermöglicht diese Verschachtelung die Beantwortung komplexer Geschäftsfragen wie: "Zeige mir alle Belege samt Kundendaten, die mit dem Lagerbestand eines bestimmten Artikels zusammenhängen" in einer einzigen Abfrage.
8. Paginierung durch Relay Cursor Connection Spezifikation (conRead
)
8.1 Einleitung
Für potenziell große Listen von Datensätzen, die über tbl...
-Felder auf oberster Ebene abgerufen werden, bietet das GraphQL-Schema Paginierung mittels der Relay Cursor Connection Specification. Dies wird über das conRead
-Feld der Table
-Objekte realisiert und ist der empfohlene Weg, um große Ergebnislisten effizient seitenweise abzurufen.
8.2 Verständnis der Relay Connection Spezifikation
Die Spezifikation definiert ein Standardmuster für die Abfrage von Listen und deren Paginierung. Die Schlüsselkonzepte sind:
- Connection: Ein Objekt, das eine "Seite" der Liste repräsentiert; enthält eine Liste von
Edges
undPageInfo
- Edge: Repräsentiert einen einzelnen Eintrag (Kante) in der Liste auf der aktuellen Seite; enthält einen
cursor
und den eigentlichennode
- Node: Der eigentliche Datensatz (in diesem Schema das
Row
-Objekt mit seinenfld...
-Feldern) - Cursor: Ein eindeutiger, opaker String, der die Position eines
Edge
in der gesamten Ergebnismenge markiert; wird verwendet, um die nächste oder vorherige Seite anzufordern - PageInfo: Enthält Metadaten zur Paginierung, wie z. B. ob eine nächste Seite existiert (hasNextPage) und die Positions-Marker (Cursor) des ersten und letzten Eintrags auf der aktuellen Seite
8.3 Implementierung in der microtech Software (conRead
)
Das conRead
-Feld eines Table
-Objekts auf oberster Ebene (zurückgegeben von einem tbl...
-Feld, z. B. tblProducts.conRead
) gibt ein Connection
-Objekt zurück (z. B. ProductConnectionQuery
).
- Es akzeptiert dieselben Parameter zur Datenauswahl und Sortierung wie
rowsRead
:allBetween
(optional): Zur Einschränkung des Bereichs mittelsby...
undkf...
. Wenn nicht angegeben, wird die gesamte Tabelle betrachtet.- Filter (
fastFilter
,slowFilter
,keyFilter
): Zur weiteren Filterung der Ergebnismenge.
- Zusätzlich akzeptiert es die Relay-spezifischen Paginierungsparameter:
first
: Maximale Anzahl der Einträge pro Seite (Vorwärtspaginierung).after
: Cursor, nach dem die Seite beginnen soll (Vorwärtspaginierung).- (Hinweis:
last
undbefore
für Rückwärtspaginierung werden derzeit nicht unterstützt).
Beispiel:
query GetProductsPage($pageSize: Int = 10, $afterCursor: String) {
tblProducts {
conRead( # Zugriff über conRead für Paginierung
# Optionale Datenauswahl und Filter (wie bei rowsRead):
allBetween: {
byNr: { kf1ArtNr: { from: {string: "A"}, to: {string: "Z"} } }
}
# Optional: fastFilter, slowFilter, keyFilter
# Relay Paginierungsparameter:
first: $pageSize, # Max. Anzahl Artikel pro Seite (aus Variable)
after: $afterCursor # Optional: Cursor für die nächste Seite (aus Variable)
) {
# Die Kanten (Verbindungen) zu den eigentlichen Daten
edges {
cursor # Eindeutiger Cursor für diesen Artikel
node { # Das ProductRowQueryRead-Objekt (der eigentliche Datensatz)
fldArtNr
fldSuchBeg
}
}
# Informationen zur Paginierung
pageInfo {
hasNextPage # Gibt es eine weitere Seite? (Boolean)
endCursor # Cursor des letzten Artikels auf dieser Seite (String)
startCursor # Cursor des ersten Artikels auf dieser Seite (String)
edgeCount # Anzahl Artikel auf dieser Seite (Int) - Kann abgefragt werden
}
}
}
}
# Beispielhafte Variablen für die erste Seite:
# {
# "pageSize": 10,
# "afterCursor": null
# }
# Beispielhafte Variablen für die zweite Seite (endCursor aus pageInfo der ersten Seite verwenden):
# {
# "pageSize": 10,
# "afterCursor": "cursorDesLetztenElementsVonSeite1"
# }
8.4 Zusätzliche Parameter für conRead
: first
, after
first
: Optionale Ganzzahl > 0. Limitiert die maximale Anzahl der zurückgegebenenEdges
pro Seite für die Vorwärtspaginierung. Wenn nicht angegeben, können alle passenden Datensätze zurückgegeben werden (potenziell sehr viele, daher dringend empfohlenfirst
zu setzen!).after
: Optionaler String (eincursor
von einem vorherigen Aufruf, typischerweisepageInfo.endCursor
). Gibt dieEdges
zurück, die in der gewählten Sortierfolge nach dem Datensatz liegen, der durch den angegebenen Cursor repräsentiert wird. Der Datensatz selbst muss nicht mehr existieren; der Cursor dient als Referenzpunkt in der Sortierung.
8.5 Felder des Connection
-Objekts
edges
: Eine Liste vonEdge
-Objekten für die aktuelle Seite. Die Liste ist leer, wenn keine passenden Datensätze auf dieser Seite gefunden wurden.pageInfo
: EinPageInfo
-Objekt mit Metadaten zur Paginierung. Es wird empfohlen, dieses Feld nachedges
abzufragen, da einige Informationen (wiestartCursor
,endCursor
) dann effizienter ermittelt werden können.
8.6 Felder des Edge
-Objektes
cursor
: Ein eindeutiger, opaker String, der die Position dieses Edges in der gesamten Ergebnismenge repräsentiert. Er sollte als unveränderlicher Wert behandelt und für denafter
-Parameter des nächsten Aufrufs verwendet werden.node
: Der eigentliche Datensatz (Row
-Objekt) für diesen Edge.
8.7 Felder des PageInfo
-Objektes
hasPreviousPage
: Gibt immerfalse
zurück, da nur Vorwärtspaginierung mitafter
unterstützt wird.hasNextPage
:true
, wenn es potenziell weitere Datensätze nach dem letzten Edge dieser Seite gibt (unter Berücksichtigung der Filter undallBetween
-Bedingungen).false
, wenn dies der letzte Datensatz der Ergebnismenge ist oderfirst
nicht angegeben wurde und alle Ergebnisse auf dieser Seite sind.startCursor
: Cursor des ersten Edges auf dieser Seite, odernull
, wennedges
leer ist.endCursor
: Cursor des letzten Edges auf dieser Seite, odernull
, wennedges
leer ist. Dieser Wert wird typischerweise für denafter
-Parameter des nächsten Aufrufs verwendet, um die Folgeseite abzurufen.edgeCount
: Integer, der die Anzahl der tatsächlich zurückgegebenenEdges
auf dieser Seite enthält (entspricht der Länge deredges
-Liste).
Die Felder des PageInfo
-Objektes sind auch verfügbar, wenn nur auf dieses zugegriffen wird und edges
nicht abgefragt werden (nützlich, um z. B. nur zu prüfen, ob es überhaupt weitere Ergebnisse gibt, ohne die Daten selbst zu laden).
8.8 Vorteile und Anwendungsbereiche
Die Relay Connection Spezifikation via conRead
bietet:
- Effiziente Paginierung großer Datenmengen, vermeidet das Laden aller Daten auf einmal.
- Verbesserte Performance und geringere Server- sowie Clientlast.
- Ein konsistentes und standardisiertes Muster für Listenabfragen, das von vielen GraphQL-Clients unterstützt wird.
- Stabile Paginierung auch bei sich ändernden Daten, da Cursor als Referenzpunkte dienen.
9. Direktiven im GraphQL-Schema
GraphQL-Direktiven bieten zusätzliche Kontrolle über das Verhalten und die Verarbeitung von Abfragen. Neben den Standard-GraphQL-Direktiven unterstützt das microtech GraphQL-Schema weitere spezielle Direktiven.
9.1 Standard-Direktiven: @skip
und @include
Die Standard-GraphQL-Direktiven @skip
und @include
werden verwendet, um Felder basierend auf Bedingungen ein- oder auszuschließen. Diese werden vor der Ausführung des dekorierten Feldes ausgewertet und verhindern evtl. die Ausführung.
Beispiel:
query (
$includePrice: Boolean!
$skipDescription: Boolean!
) {
tblProducts {
rowsRead {
fldArtNr
fldStdPreis @include(if: $includePrice) # Feld nur einschließen, wenn $includePrice true ist
fldSuchBeg @skip(if: $skipDescription) # Feld überspringen, wenn $skipDescription true ist
}
}
}
Wichtig: In Kombination mit der @store
-Direktive (siehe unten) können @skip
und @include
auch auf Variablen reagieren, die innerhalb der Abfrage dynamisch geändert werden. Dies ermöglicht komplexe Kontrollflüsse innerhalb einer GraphQL-Abfrage.
9.2 Wertespeicherung: @store
Die @store
-Direktive ermöglicht es, den Rückgabewert eines Feldes in einer Variable zu speichern, die später in der Abfrage wiederverwendet werden kann.
Syntax und Parameter:
in
: (erforderlich) Die Variable, in der der Wert gespeichert werden soll. Die Variable muss in der Abfrage-Deklaration bereits definiert sein.
Wichtig: Die verwendeten Variablen müssen in der Operation vorab deklariert werden und sollten einen Defaultwert (z. B. null
) haben, um Fehler zu vermeiden, wenn die Variable nicht von außen übergeben wurde. Da GraphQL-IDEs den speziellen Skalartyp Variable
möglicherweise nicht korrekt erkennen, können sie fälschlicherweise Fehler anzeigen (z. B. wenn eine Variable vom Typ Any
an einen Parameter vom Typ Variable
übergeben wird). Die Abfrage wird trotz dieser IDE-Warnungen korrekt ausgeführt.
Beispiel:
query (
$storedSuchBeg: Any = null
$showGes: Boolean = false
) {
tblProducts {
rowsRead(
kf1ArtNr: {
from: {string: "2"}
to: {string: "3"}
}
) {
# Suchbegriff in Variable $storedSuchBeg speichern
fldSuchBeg @store(in: $storedSuchBeg)
# Gesperrt Kennzeichen in Variable $showGes speichern
fldGspKz @store(in: $showGes)
# Details bedingt anzeigen (basierend auf dynamisch aktualisierter Variable $showGes)
fldGspInfo(as:DISPLAY_TEXT) @include(if: $showGes)
# Wert der Variable $storedSuchBeg ausgeben
storedSuchBeg: _any(value: $storedSuchBeg)
}
}
}
Resultierende JSON für obiges Beispiel:
{
"data": {
"tblProducts": {
"rowsRead": [
{
"fldSuchBeg": "AA",
"fldGspKz": null,
"storedSuchBeg": "AA"
},
{
"fldSuchBeg": "AB",
"fldGspKz": true,
"fldGspInfo": "Ausgelaufen",
"storedSuchBeg": "AB"
}
]
}
}
}
9.3 Null-Behandlung: @onNull
Die @onNull
-Direktive definiert, wie mit null
-Werten im Rückgabewert eines GraphQL-Feldes (wie fld...
, row...
, etc.) umgegangen werden soll. Die Direktive wird ausgewertet, nachdem das GraphQL-Feld vollständig ausgeführt wurde.
Parameter:
do
: (Optional) Legt die Aktion fest, die bei einemnull
-Wert ausgeführt werden soll. Mögliche Werte:RETURN_NULL
: Gibtnull
zurück (Standardverhalten)RETURN_VALUE
: Gibt einen alternativen Wert zurückSKIP
: Überspringt das Feld in der AusgabeRAISE_ERROR
: Löst einen Fehler ausreturnValue
: (Optional) Der Wert, der zurückgegeben werden soll, wenn das Feldnull
ist.- Wenn dieser Parameter angegeben wird, wird
do
automatisch aufRETURN_VALUE
gesetzt. - Wenn
skip
als Wert angegeben wird, wirddo
automatisch aufSKIP
gesetzt. errorMessage
: (Optional) Die Fehlermeldung für einen ausgelösten Fehler.- Wenn dieser Parameter angegeben wird, wird
do
automatisch aufRAISE_ERROR
gesetzt. set
: (Optional) Konfiguration zum Setzen einer Variable:var
: Variable, die gesetzt werden soll (muss vorher in der Abfrage deklariert sein)nullValue
: (Optional) Wert, der gesetzt wird, wenn das Feldnull
ist (Standardwert:true
)elseValue
: (Optional) Wert, der gesetzt wird, wenn das Feld nichtnull
ist (Standardwert:false
)
Hinweis: Der do
-Parameter muss nicht explizit angegeben werden, wenn einer der anderen Parameter vorhanden ist. Die Direktive wählt automatisch die passende Aktion basierend auf den vorhandenen Parametern.
Tipp: Die Standardwerte von nullValue
(true) und elseValue
(false) machen es besonders einfach, @onNull
mit dem set
-Parameter für boolesche Variablen zu verwenden, die später mit @include
oder @skip
in der Abfrage eingesetzt werden.
Beispiele:
query (
$foundStatus: String = null
$hideWarehouses: Boolean = null
) {
tblProducts {
rowsRead(
kf1ArtNr: {
from: {string: "1"}
to: {string: "2"}
}
) {
# Bei null: Alternativwert zurückgeben (do wird automatisch auf RETURN_VALUE gesetzt)
fldSuchBeg @onNull(returnValue: "Nicht gefunden")
# Bei null: Feld überspringen (do wird automatisch auf SKIP gesetzt)
fldStdPreis @onNull(returnValue: skip)
# Bei null: Fehler auslösen (do wird automatisch auf RAISE_ERROR gesetzt)
fldArtNr @onNull(errorMessage: "Artikelnummer darf nicht null sein")
# Bei null: String-Variable setzen (funktioniert mit jedem do-Wert)
rowLagNr @onNull(
returnValue: skip
set: {
var: $foundStatus
nullValue: "Kein Lager definiert"
elseValue: "Hat ein Standardlager"
}
) {
fldLagNr
}
foundStatus: _any(value: $foundStatus)
# Bei null: Boolean-Variable setzen (nutzt die Standardwerte true/false) und skip ausgeben
# Wenn fldLagMge null ist, wird $hideWarehouses auf false gesetzt (nicht verstecken)
fldLagMgeSkipNull: fldLagMge @onNull(
returnValue: skip
set: {
var: $hideWarehouses
}
)
# Lagerbestandsinformationen bedingt anzeigen, wenn Gesamtmenge vorhanden ist
lnkWarehouses @skip(if: $hideWarehouses) {
rowsRead(
byArtNrLagNr: { usingArtNr: {} }
) {
fldLagNr
fldMge
}
}
# Explizite Angabe des do-Parameters
fldLagMge @onNull(do: RETURN_VALUE, returnValue: 0)
}
}
}
Resultierende JSON für obiges Beispiel:
{
"data": {
"tblProducts": {
"rowsRead": [
{
"fldSuchBeg": "A",
"fldArtNr": "1",
"rowLagNr": {
"fldLagNr": "1"
},
"foundStatus": "Hat ein Standardlager",
"fldLagMgeSkipNull": 15,
"lnkWarehouses": {
"rowsRead": [
{
"fldLagNr": "1",
"fldMge": 10
},
{
"fldLagNr": "2",
"fldMge": 5
}
]
},
"fldLagMge": 15
},
{
"fldSuchBeg": "AA",
"fldArtNr": "2",
"foundStatus": "Kein Lager definiert",
"fldLagMge": 0
}
]
}
}
}
10. Erweiterte GraphQL-Kernfunktionalitäten
Neben den in den vorherigen Kapiteln beschriebenen Funktionen bietet das microtech GraphQL-Schema zwei weitere Kernfunktionalitäten:
System-Felder (_...
) sind Hilfsfunktionen für Typkonvertierung, bedingte Ausführung und Berechnungen. Diese Felder sind in allen Objektkontexten verfügbar – auf Operationsebene, in Tabellen, innerhalb von Datensätzen, und allen anderen Objekten. Sie ermöglichen die Durchführung von Berechnungen, Transformationen und die dynamische Steuerung der Abfragelogik direkt innerhalb der GraphQL-Abfrage.
Erweiterte Schema-Metadaten erweitern die Standard-GraphQL-Introspection um zusätzliche Metadaten über die Datenbankstruktur und Konfiguration. Diese Erweiterungen ermöglichen programmatischen Zugriff auf microtech-spezifische Schema-Informationen.
10.1 System-Felder
10.1.1 Übersicht der System-Felder
System-Felder sind in vier Kategorien unterteilt:
Typisierte Konvertierung und Ausdrucksauswertung:
Konvertierung von Werten und Auswertung von Ausdrücken mit definiertem Zieltyp:
* _int
* _float
* _string
* _boolean
* _bigInt
* _any
* _guid
* _localDate
* _localTime
* _localDateTime
Kontrollfluss:
* _if
- Bedingte Objektrückgabe
* _ifThenElse
- Bedingte Wertrückgabe
Fehlerbehandlung:
* _raise
- Explizites Auslösen von Exceptions
Metadaten-Abfrage:
* _type
- Gibt den __Type
des aktuellen Kontexts zurück
Die folgende Tabelle zeigt alle verfügbaren System-Felder im Detail:
GraphQL-Feld | Rückgabetyp | Beschreibung |
---|---|---|
_int |
Int |
Konvertiert einen Wert in eine 32-Bit-Ganzzahl |
_float |
Float |
Konvertiert einen Wert in eine Fließkommazahl |
_string |
String |
Konvertiert einen Wert in einen String |
_boolean |
Boolean |
Konvertiert einen Wert in einen booleschen Wert oder wertet einen booleschen Ausdruck aus |
_bigInt |
BigInt |
Konvertiert einen Wert in eine Ganzzahl mit bis zu 64 Stellen |
_any |
Any |
Gibt den Wert unverändert zurück oder wertet einen Ausdruck aus |
_guid |
Guid |
Konvertiert einen Wert in eine GUID |
_localDate |
LocalDate |
Konvertiert einen Wert in ein lokales Datum |
_localTime |
LocalTime |
Konvertiert einen Wert in eine lokale Zeit |
_localDateTime |
LocalDateTime |
Konvertiert einen Wert in ein lokales Datum mit Zeit |
_if |
Kontext-Objekt oder null |
Gibt das aktuelle Objekt oder null zurück, abhängig von einer Bedingung |
_ifThenElse |
Any |
Gibt einen von zwei Werten zurück, abhängig von einer Bedingung |
_raise |
Void |
Löst eine Exception mit optionaler Nachricht aus |
_type |
__Type |
Gibt die Typ-Informationen des aktuellen Objektkontexts zurück |
10.1.2 Verwendungszweck und Zusammenspiel mit Variablen
System-Felder ermöglichen es, mit dynamischen Werten zu arbeiten, die während der Abfrageausführung in Variablen gespeichert wurden (siehe Kapitel 9.2 zur @store
-Direktive). Dies erlaubt:
- Dynamische Typkonvertierung: Variablen können je nach Kontext in den benötigten Typ konvertiert werden
- Berechnungen zur Laufzeit: Komplexe Ausdrücke mit Zugriff auf Datenfeldwerte
- Bedingte Abfragelogik: Teile der Abfrage können basierend auf Laufzeitwerten ein- oder ausgeblendet werden
Performance-Hinweis: Die Ausdrucksauswertung erfolgt anwendungsserverseitig. Obwohl dies Rechenzeit kostet, ist es in der Regel effizienter als mehrere Client-Server-Anfragen für bedingte Logik.
10.1.3 Typkonvertierung und Ausdrucksauswertung
Alle Typkonvertierungsfelder (_int
, _float
, _string
, etc.) folgen einem einheitlichen Muster:
- Sie akzeptieren entweder
value
oderexpr
als Parameter (nie beide gleichzeitig) - Bei Verwendung im Kontext eines
Row
-Objekts haben Ausdrücke automatisch Zugriff auf die Datenfelder dieses Datensatzes - Sie konvertieren das Ergebnis in den durch den Feldnamen spezifizierten Typ
10.1.3.1 Gemeinsame Parameter
value
: Ein beliebiger Wert (meist eine Variable), der in den Zieltyp konvertiert werden sollexpr
: Ein Ausdruck, der ausgewertet und dessen Ergebnis in den Zieltyp konvertiert werden soll- Für
_boolean
muss dies ein Ausdruck vom TypSlowBooleanExpression
sein (siehe Kapitel 5.4) - Für alle anderen Felder muss dies ein Ausdruck vom Typ
SlowAnyExpression
sein (siehe Kapitel 5.5)
10.1.3.2 Beispiele
Wertausgabe via _any
direkt auf Operationsebene:
query (
$variable: Any = 42
) {
# Direkte Verwendung auf Operationsebene
variableWert: _any(value: $variable)
# Weiterhin normaler Zugriff auf Tabellen
tblProducts {
rowsRead {
fldArtNr
}
}
}
Berechneter Ausdruck mit Datenfeldnutzung (expr
):
query {
tblProducts {
rowsRead {
fldArtNr
fldSuchBeg
fldLagMge
fldStdPreis
# Berechnung eines abgeleiteten Wertes aus Standardpreis und Lagermenge
gesamtwert: _float(expr: {
mul: [
{ field: fldLagMge },
{ field: fldStdPreis }
]
})
# Prüfen, ob Lagerbestand vorhanden ist
hatBestand: _boolean(expr: {
gt: [
{ field: fldLagMge },
{ value: 0 }
]
})
}
}
}
10.1.3.3 Unterschied zur direkten Feldabfrage
Während Datenfelder direkt über fld...
-Felder abgefragt werden können, bieten System-Felder zusätzliche Möglichkeiten:
- Komplexe Berechnungen: System-Felder mit
expr
erlauben Berechnungen mit mehreren Datenfeldern und konstanten Werten - Typkonvertierung: System-Felder erzwingen einen bestimmten Rückgabetyp, unabhängig vom Quelldatentyp
- Bedingte Logik: In Kombination mit
_if
und_ifThenElse
ermöglichen sie dynamische Abfragen
10.1.3.4 Fehlerbehandlung bei Typkonvertierung
Null-Behandlung: Alle System-Felder folgen dem Prinzip "null in → null out". Wenn der value
-Parameter oder das Ergebnis eines expr
-Ausdrucks null
ist, gibt das System-Feld ebenfalls null
zurück.
Konvertierungsfehler: Ungültige Typkonvertierungen (z.B. String "abc" zu Int) lösen Laufzeitfehler aus, die nach Standard-GraphQL-Regeln behandelt werden.
10.1.4 Bedingte Ausführung mit _if
und _ifThenElse
10.1.4.1 Das _if
-Feld
Das _if
-Feld prüft eine Bedingung und gibt basierend auf dem Ergebnis das aktuelle Objekt oder null
zurück.
Parameter:
* value
: (Optional) Ein boolescher Wert (meist eine Variable)
* expr
: (Optional) Ein SlowBooleanExpression
-Ausdruck
Hinweis: Es muss genau ein Parameter, entweder value
oder expr
, angegeben werden.
Rückgabe:
* Wenn die Bedingung true
ist: das aktuelle Objekt (der Kontext, auf dem _if
aufgerufen wurde)
* Wenn die Bedingung false
oder null
ist: null
Beispiel:
query {
tblProducts {
rowsRead {
fldArtNr
fldSuchBeg
fldLagMge
# Nur wenn Lagermenge > 0, Details anzeigen
details: _if(expr: {
gt: [{ field: fldLagMge }, { value: 0 }]
}) {
fldStdPreis
fldLagMge
}
}
}
}
In diesem Beispiel wird das details
-Objekt nur für Artikel mit fldLagMge > 0
zurückgegeben, andernfalls ist es null
.
10.1.4.2 Das _ifThenElse
-Feld (If-Then-Else)
Das _ifThenElse
-Feld ermöglicht eine bedingte Auswahl zwischen zwei Werten, ähnlich dem ternären Operator in vielen Programmiersprachen.
Parameter:
* value
: (Optional) Ein boolescher Wert (meist eine Variable)
* expr
: (Optional) Ein SlowBooleanExpression
-Ausdruck
* then
: (Optional) Der Wert, der zurückgegeben wird, wenn die Bedingung true
ist (standardmäßig null
)
* else
: (Optional) Der Wert, der zurückgegeben wird, wenn die Bedingung false
oder null
ist (standardmäßig null
)
Hinweis: Es muss genau ein Parameter für die Bedingung, entweder value
oder expr
, angegeben werden.
Beispiel:
query {
tblProducts {
rowsRead {
fldArtNr
fldSuchBeg
fldLagMge
# Statustext je nach Lagermenge
lagerStatus: _ifThenElse(
expr: { gt: [{ field: fldLagMge }, { value: 0 }] },
then: "Verfügbar",
else: "Nicht auf Lager"
)
}
}
}
In diesem Beispiel wird für jeden Artikel abhängig von der Lagermenge ein entsprechender Statustext zurückgegeben.
Wichtiger Hinweis: Das _ifThenElse
-Feld kann nicht innerhalb eines Ausdrucks (expr
) verwendet werden.
10.1.5 Fehlerbehandlung mit _raise
Das _raise
-Feld ermöglicht das explizite Auslösen einer Exception mit einer optionalen Fehlermeldung.
Parameter:
* message
: (Optional) Ein String mit der Fehlermeldung
Rückgabetyp: Void
(der Aufruf kehrt nie normal zurück)
Anwendungsfälle: * Validierung von Eingabedaten * Abbruch der Abfrage bei unerwünschten Zuständen * Erzwingen von Fehlern für Testzwecke
Beispiel:
query {
tblProducts {
rowsRead {
fldArtNr
fldLagMge
# Fehler auslösen, wenn Lagermenge negativ ist
_if(expr: { lt: [{ field: fldLagMge }, { value: 0 }] }) {
_raise(message: "Negative Lagermenge gefunden!")
}
}
}
}
In diesem Beispiel wird ein Fehler mit der Nachricht "Negative Lagermenge gefunden!" ausgelöst, wenn ein Artikel mit einer negativen Lagermenge gefunden wird.
10.1.6 Realisierung komplexer Logik (durch Kombination mit Direktiven)
System-Felder können mit den in Kapitel 9 beschriebenen Direktiven kombiniert werden, insbesondere mit @store
(Kapitel 9.2) und @onNull
(Kapitel 9.3), um komplexe Logik zu realisieren.
10.1.6.1 Beispiel: Bedingte Anzeige mit dynamischen Variablen
query ($hatBestand: Boolean = false) {
tblProducts {
rowsRead {
fldArtNr
fldSuchBeg
# Prüfen, ob Artikel einen Lagerbestand hat
_boolean(expr: {
gt: [{ field: fldLagMge }, { value: 0 }]
}) @store(in: $hatBestand)
# Details nur anzeigen, wenn Lagerbestand vorhanden
details: _if(value: $hatBestand) {
fldLagMge
fldStdPreis
}
}
}
}
In diesem Beispiel wird die Variable $hatBestand
dynamisch aufgrund des Lagerbestands jedes Artikels aktualisiert und zur Steuerung der Anzeige weiterer Details verwendet.
10.1.6.2 Beispiel: Komplexe Bedingungslogik
query {
tblProducts {
rowsRead {
fldArtNr
fldGspKz
fldLagMge
# Bedingter Wert basierend auf Artikel-Status
lieferStatus: _ifThenElse(
expr: {
and: [
# Artikel ist nicht gesperrt
{ not: { field: fldGspKz } },
# Lagermenge > 0
{ gt: [{ field: fldLagMge }, { value: 0 }] }
]
},
then: "Lieferbar",
else: "Nicht lieferbar"
)
}
}
}
Dieses Beispiel zeigt, wie komplexe Bedingungslogik mit verschachtelten Ausdrücken und mehreren Datenfeldern realisiert werden kann.
10.1.7 Praktische Einsatzbeispiele
10.1.7.1 Formatierung und Anreicherung von Daten
query {
tblProducts {
rowsRead {
fldArtNr
fldSuchBeg
# Artikelnummer und Suchbegriff kombinieren
artikelText: _string(expr: {
add: [
{ field: fldArtNr },
{ value: " - " },
{ field: fldSuchBeg }
]
})
}
}
}
10.1.7.2 Dynamische Filterung innerhalb eines Objekts
query {
tblProducts {
rowsRead {
fldArtNr
fldSuchBeg
fldGspKz
# Nur nicht gesperrte Artikel mit Warengruppe anzeigen
warengruppe: _if(expr: {
not: { field: fldGspKz }
}) {
rowWgrNr {
fldWgrNr
fldBez
}
}
}
}
}
10.1.7.3 Bedingte Darstellung mit Datumsverarbeitungen
query {
tblTransactions {
rowsRead {
fldBelegNr
fldDat
# Datumsverarbeitung: Aktuelles Datum
aktuellesDatum: _localDate(expr: { fnGetAktDate: [] })
# Datum ein Jahr zurück (12 Monate)
jahresVergleich: _localDate(expr: {
fnIncDate: [
{ fnGetAktDate: [] },
{ value: 0 },
{ value: -12 }
]
})
# Prüfen ob Beleg älter als ein Jahr ist
istAlt: _boolean(expr: {
lt: [
{ field: fldDat },
{ fnIncDate: [
{ fnGetAktDate: [] },
{ value: 0 },
{ value: -12 }
]}
]
})
}
}
}
Mit dieser umfassenden Palette an System-Feldern bietet die microtech GraphQL-Schnittstelle äußerst flexible Möglichkeiten, komplexe Datenabfragen und -manipulationen direkt in der Abfrage zu definieren und so die Effizienz und Lesbarkeit Ihrer Anwendungen zu verbessern.
10.1.8 Metadaten-Abfrage mit _type
Das _type
-Feld ermöglicht den direkten Zugriff auf die Typ-Informationen des aktuellen Objektkontexts. Es gibt ein __Type
-Objekt zurück, das alle Schema-Informationen einschließlich der microtech-spezifischen Extensions enthält.
10.1.8.1 Funktionsweise
_type
benötigt keine Parameter und gibt immer den __Type
des Objekts zurück, auf dem es aufgerufen wird. Dies funktioniert in allen Kontexten – auf Operationsebene, in Tabellen, in Datensätzen und allen anderen Objekttypen.
10.1.8.2 Anwendungsfälle
- Schema-Exploration: Erkunden der verfügbaren Felder und deren Typen im aktuellen Kontext
- Dynamische Metadaten-Abfrage: Zugriff auf microtech-spezifische Extensions ohne den Typ-Namen zu kennen
- Debugging: Überprüfung der tatsächlichen Typ-Struktur zur Laufzeit
- Generische Abfragen: Erstellen von Abfragen, die mit verschiedenen Objekttypen funktionieren
10.1.8.3 Beispiele
Grundlegende Verwendung:
query {
# Typ-Informationen der Query-Operation
_type {
name # "Query"
kind # "OBJECT"
}
tblProducts {
# Typ-Informationen der Tabelle
_type {
name # z.B. "ProductsTableQueryRead"
mapExtensions # Tabellen-spezifische Metadaten
}
rowsRead {
# Typ-Informationen des Row-Objekts
_type {
name # z.B. "ProductRowQueryRead"
fields(excludeSystem: true) {
name
typeName
}
}
}
}
}
Zugriff auf erweiterte Metadaten:
query {
tblProducts {
_type {
# Direkt auf Extensions zugreifen
mapExtensions # Alle Extensions als Map
anyExtensionValue(name: "flags") # Spezifische Extension
# Strukturierte Extension-Informationen
extensions {
name
value
}
}
}
}
Kombination mit anderen System-Feldern:
query ($showDebugInfo: Boolean = false) {
tblProducts {
rowsRead {
fldArtNr
# Bedingte Debug-Informationen
debugInfo: _if(value: $showDebugInfo) {
_type {
name
fields {
name
typeName
isDeprecated
}
}
}
}
}
}
Das _type
-Feld ist besonders nützlich für die Entwicklung generischer Tools und für das Debugging komplexer GraphQL-Abfragen.
10.2 Erweiterte Schema-Metadaten
Das microtech GraphQL-Schema erweitert die Standard-Introspection um zusätzliche Metadaten. Diese Erweiterungen ermöglichen programmatischen Zugriff auf erweiterte Schema-Informationen wie Datenbankstruktur, Tabellenbeziehungen und Konfigurationsdetails.
10.2.1 Erweiterte Introspection-Objekte
Alle Standard-GraphQL-Introspection-Objekte (__Schema
, __Type
, __Field
, __InputValue
, __EnumValue
, __Directive
) wurden um zusätzliche Extension-Felder erweitert.
Hinweis zur Verwendung in Entwicklungsumgebungen: Die meisten GraphQL-IDEs und interaktiven Oberflächen verwenden ihre eigene Definition der Introspection-Typen basierend auf der Standard-GraphQL-Spezifikation. Diese Tools erkennen die microtech-spezifischen Erweiterungen nicht und zeigen möglicherweise Fehler oder Warnungen an, wenn Sie Extension-Felder wie mapExtensions
, anyExtensionValue
oder typeName
verwenden.
Diese Warnungen können ignoriert werden – die Abfragen werden trotzdem korrekt ausgeführt. Es handelt sich um ein reines Anzeigeproblem der Entwicklungsumgebung, nicht um einen tatsächlichen Fehler. Dieses Verhalten ist vergleichbar mit der in Kapitel 6.1.2 beschriebenen Situation beim Variable
-Typ.
10.2.2 Extension-Zugriff
10.2.2.1 Das mapExtensions
-Feld (empfohlen)
Das mapExtensions
-Feld (Rückgabetyp: Any
) stellt alle Extension-Werte in einem Map-Format zur Verfügung und ist der effizienteste Weg für den Zugriff auf Extension-Daten:
Syntax:
mapExtensions # Gibt alle Extensions als Key-Value-Map zurück (Any)
Dieses Format sollte bevorzugt verwendet werden, wenn nur die Extension-Werte benötigt werden.
10.2.2.2 Das extensions
-Feld
Das extensions
-Feld gibt eine Liste von Extension-Objekten mit strukturierten Metadaten zurück:
Syntax:
extensions {
name # String: Name der Extension
typeName # String: GraphQL-Typ der Extension
description # String: Beschreibung der Extension
value # Any: Der eigentliche Wert der Extension
}
Verwenden Sie dieses Format nur, wenn Sie zusätzlich zu den Werten auch Typ-Informationen und Beschreibungen benötigen.
10.2.2.3 Das anyExtensionValue
-Feld
Das anyExtensionValue(name: String!)
-Feld (Rückgabetyp: Any
) ermöglicht den direkten Zugriff auf den Wert einer spezifischen Extension:
Syntax:
anyExtensionValue(name: "extensionName") # String! parameter ist erforderlich
10.2.3 Zusätzliche microtech-spezifische Felder
10.2.3.1 Das typeName
-Feld
Die Introspection-Objekte __Field
und __InputValue
wurden um ein typeName
-Feld erweitert, das eine String-Repräsentation des GraphQL-Typs liefert. Im Gegensatz zur Standard-Introspection wird die Typ-Information direkt als String bereitgestellt, einschließlich Non-Null- und List-Modifikatoren. Das typeName
-Feld kann Werte wie "Int"
, "Int!"
, "[Type]"
, "[Type!]"
, "[Type]!"
oder "[Type!]!"
enthalten.
Beispiel aus dem Schema:
{
"name": "aceByTaxCode",
"description": "",
"args": [
{
"name": "taxCode",
"description": "",
"typeName": "Int"
}
],
"typeName": "AmountCompositionEntryQuery",
"isDeprecated": false
},
{
"name": "aces",
"description": "",
"args": [],
"typeName": "[AmountCompositionEntryQuery]",
"isDeprecated": false
}
Das typeName
-Feld zeigt direkt "[AmountCompositionEntryQuery]"
für Listen und "Int"
für einfache Typen, anstatt die bis zu 4-fach verschachtelte ofType
-Struktur der Standard-Introspection zu verwenden.
10.2.3.2 Der excludeSystem
-Parameter
Das fields
-Feld von __Type
unterstützt einen zusätzlichen excludeSystem
-Parameter:
Syntax:
fields(includeDeprecated: true, excludeSystem: true)
Wenn excludeSystem: true
gesetzt ist, werden die System-Felder (_...
) aus der Rückgabe ausgeschlossen.
10.2.3.3 Einzelzugriffs-Felder
Alle Introspection-Objekte wurden um direkte Zugriffsmethoden für ihre Listen-Felder erweitert. Diese Felder ermöglichen den effizienten Zugriff auf einzelne Elemente ohne das Abrufen kompletter Listen.
Verfügbare Einzelzugriffs-Felder:
Introspection-Objekt | Feld | Alternative zu |
---|---|---|
__Schema |
type(name: String!) |
types -Liste |
__Schema |
directive(name: String!) |
directives -Liste |
__Type |
field(name: String!) |
fields -Liste |
__Type |
inputField(name: String!) |
inputFields -Liste |
__Type |
enumValue(name: String!) |
enumValues -Liste |
__Field , __Directive |
arg(name: String!) |
args -Liste |
Wichtig: Alle Felder geben null
zurück, wenn das gesuchte Element nicht existiert.
Beispiel:
query {
__schema {
# Direkter Zugriff auf einen Typ
type(name: "SetBooleanFieldInput") {
# Direkter Zugriff auf ein Input-Feld
inputField(name: "text") {
name
typeName
}
}
# Direkter Zugriff auf eine Direktive
directive(name: "onNull") {
name
# Direkter Zugriff auf ein Argument der Direktive
arg(name: "do") {
name
typeName
}
}
}
tblClient {
rowRead {
_type {
# Direkter Zugriff auf ein Feld
field(name: "fldID") {
name
# Direkter Zugriff auf ein Argument des Feldes
arg(name: "as") {
name
type {
# Direkter Zugriff auf einen Enum-Wert
enumValue(name: "TEXT") {
name
}
}
}
}
}
}
}
}
Diese Erweiterungen reduzieren den Datenübertragungsaufwand erheblich, wenn nur spezifische Schema-Elemente benötigt werden.
10.2.4 Praktische Beispiele
10.2.4.1 Tabellen-Informationen abfragen
Abfrage:
query {
__type(name: "PostalCodesTableQueryRead") {
name
kind
extensions {
name
typeName
description
value
}
mapExtensions
anyExtensionValue(name:"flags")
}
}
Antwort:
{
"data": {
"__type": {
"name": "PostalCodesTableQueryRead",
"kind": "OBJECT",
"extensions": [
{
"name": "type",
"typeName": "DBStrukturKzEnum",
"description": "",
"value": "dbsPLZ"
},
{
"name": "dbAlias",
"typeName": "DbAliasEnum",
"description": "",
"value": "dbaGlobal"
},
{
"name": "shortName",
"typeName": "String!",
"description": "",
"value": "Plz"
},
{
"name": "name",
"typeName": "String!",
"description": "",
"value": "Postleitzahlen"
},
{
"name": "shortDescription",
"typeName": "String!",
"description": "",
"value": "Postleitzahlen"
},
{
"name": "description",
"typeName": "String!",
"description": "",
"value": "Postleitzahlen"
},
{
"name": "flags",
"typeName": "[DBSFlagEnum!]!",
"description": "",
"value": [
"dbsfSetVgbIndex",
"dbsfSelFelder",
"dbsfSelSortierungen",
"dbsfNeedRecreate",
"dbsfFilterDef",
"dbsfAllowIndexSuche",
"dbsfCreated",
"dbsfInternalAutoIncStruktur",
"dbsfTableExists",
"dbsfHatBereichLoeschenCommand",
"dbsfVerzMehrfachSuche",
"dbsfCommandDesigner",
"dbsfUnicodeUnterstuetzung",
"dbsfAllowClientCaching",
"dbsfInternalClientCaching",
"dbsfInternalLz4RecordCompression",
"dbsfInternalKeylessIndices",
"dbsfInfoGraphQLRecordType",
"dbsfInfoGraphQLRootTable"
]
}
],
"mapExtensions": {
"type": "dbsPLZ",
"dbAlias": "dbaGlobal",
"shortName": "Plz",
"name": "Postleitzahlen",
"shortDescription": "Postleitzahlen",
"description": "Postleitzahlen",
"flags": [
"dbsfSetVgbIndex",
"dbsfSelFelder",
"dbsfSelSortierungen",
"dbsfNeedRecreate",
"dbsfFilterDef",
"dbsfAllowIndexSuche",
"dbsfCreated",
"dbsfInternalAutoIncStruktur",
"dbsfTableExists",
"dbsfHatBereichLoeschenCommand",
"dbsfVerzMehrfachSuche",
"dbsfCommandDesigner",
"dbsfUnicodeUnterstuetzung",
"dbsfAllowClientCaching",
"dbsfInternalClientCaching",
"dbsfInternalLz4RecordCompression",
"dbsfInternalKeylessIndices",
"dbsfInfoGraphQLRecordType",
"dbsfInfoGraphQLRootTable"
]
},
"anyExtensionValue": [
"dbsfSetVgbIndex",
"dbsfSelFelder",
"dbsfSelSortierungen",
"dbsfNeedRecreate",
"dbsfFilterDef",
"dbsfAllowIndexSuche",
"dbsfCreated",
"dbsfInternalAutoIncStruktur",
"dbsfTableExists",
"dbsfHatBereichLoeschenCommand",
"dbsfVerzMehrfachSuche",
"dbsfCommandDesigner",
"dbsfUnicodeUnterstuetzung",
"dbsfAllowClientCaching",
"dbsfInternalClientCaching",
"dbsfInternalLz4RecordCompression",
"dbsfInternalKeylessIndices",
"dbsfInfoGraphQLRecordType",
"dbsfInfoGraphQLRootTable"
]
}
}
}
10.2.4.2 Vollständige Schema-Introspection
Für umfassende Schema-Analyse kann die folgende Abfrage verwendet werden:
query IntrospectionQuery {
__schema {
queryType @onNull(returnValue:skip) { name }
mutationType @onNull(returnValue:skip) { name }
subscriptionType @onNull(returnValue:skip) { name }
types { ...FullType }
directives { ...Directive }
}
}
fragment FullType on __Type {
kind
name @onNull(returnValue:skip)
description @onNull(returnValue:skip)
fields(includeDeprecated: true excludeSystem: true) @onNull(returnValue:skip) { ...Field }
inputFields @onNull(returnValue:skip) { ...InputValue }
interfaces @onNull(returnValue:skip) { ...TypeRef }
enumValues(includeDeprecated: true) @onNull(returnValue:skip) { ...EnumValue }
possibleTypes @onNull(returnValue:skip) { ...TypeRef }
extensions: mapExtensions @onNull(returnValue:skip)
}
fragment InputValue on __InputValue {
name
description
typeName
defaultValue @onNull(returnValue:skip)
extensions: mapExtensions @onNull(returnValue:skip)
}
fragment TypeRef on __Type {
kind
name @onNull(returnValue:skip)
ofType @onNull(returnValue:skip) {
kind
name @onNull(returnValue:skip)
ofType @onNull(returnValue:skip) {
kind
name @onNull(returnValue:skip)
ofType @onNull(returnValue:skip) {
kind
name @onNull(returnValue:skip)
}
}
}
}
fragment Directive on __Directive {
name
description
locations
args { ...InputValue }
extensions: mapExtensions @onNull(returnValue:skip)
}
fragment Field on __Field {
name
description
args @onNull(returnValue:skip) { ...InputValue }
typeName
isDeprecated
deprecationReason @onNull(returnValue:skip)
extensions: mapExtensions @onNull(returnValue:skip)
}
fragment EnumValue on __EnumValue {
name
description @onNull(returnValue:skip)
isDeprecated
deprecationReason @onNull(returnValue:skip)
extensions: mapExtensions @onNull(returnValue:skip)
}
Diese Abfrage nutzt die @onNull
-Direktive (siehe Kapitel 9.3) um null
-Werte zu handhaben und die Ausgabe zu bereinigen.
10.2.5 Anwendungsfälle
Die erweiterten Metadaten ermöglichen:
- Automatische Tool-Generierung: Basierend auf Schema-Metadaten
- Schema-Exploration: Programmatischer Zugriff auf erweiterte Schema-Informationen
- Entwicklungsunterstützung: Besseres Verständnis der zugrundeliegenden Strukturen
10.2.6 Bewährte Verfahren
10.2.6.1 Effiziente Abfragen
- Bevorzugen Sie
mapExtensions
: Für den Zugriff auf Extension-Werte - Verwenden Sie
anyExtensionValue
: Für einzelne, bekannte Extensions - Nutzen Sie
excludeSystem: true
: Um System-Felder bei der Schema-Analyse auszuschließen - Kombinieren Sie mit
@onNull
: Für saubere JSON-Ausgaben
10.2.6.2 Tool-Entwicklung
- Schema-Caching: Die erweiterten Metadaten ändern sich selten und können aggressiv gecacht werden
- Robuste Fehlerbehandlung: Tools sollten auch funktionieren, wenn spezifische Extensions fehlen
- Typname-Nutzung: Verwenden Sie
typeName
für einfachere Typ-Verarbeitung
Hinweis: Die verfügbaren Extension-Namen und -Werte können sich je nach microtech Software-Version und Konfiguration unterscheiden. Tools sollten flexibel auf verfügbare Extensions reagieren.
Mutationen mit GraphQL
Im nächsten Kapitel stellen wir Ihnen das Mutationen-Konzept vor:
Auf folgender Seite erhalten Sie praktische Tipps für Beispiel-Abfragen: