Zum Inhalt

GraphQL Entwickler Dokumentation - Abfragen (Queries)

Gen. 24 Enterprise

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.

Automatisierungs-Dienst neu erfassen

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.

Detail-Ansicht des Ereignisprotokolls

Öffnen Sie mit einem Doppelklick den Eintrag: "Automatisierungsaufgabe aktiviert [Im laufenden Automatisierungs-Service Betrieb]". In diesem Eintrag finden Sie eine entsprechende URL.

Abbildung des Ereignisprotokolls mit 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.

Beispielansicht der interaktiven GraphQL-Oberfläche

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" über fldArtNr).
  • 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 über kf...-Parameter (z. B. beziehen sich fldArtNr, 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, oder null, wenn kein entsprechender Datensatz gefunden wurde.
    • Es erfordert einen der Parameter exactMatch oder nearestMatch zur Spezifizierung des zu lesenden Datensatzes (siehe Kapitel 3).
    • Es ist ideal für das gezielte Abrufen eines bestimmten Datensatzes.
  • 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).
  • 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 einen node (das eigentliche Row-Objekt) enthält
    • pageInfo: 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 und after (siehe Kapitel 8).

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... (wie rowsRead) und con... (wie conRead) 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. gibt rowWgrNr ein einzelnes Row-Objekt zurück)
  • rows...: Liste von Datensätzen eines bestimmten Typs (z. B. gibt rowsRead eine Liste von Row-Objekten zurück)
  • con...: Connection-Objekt für Paginierung (z. B. gibt conRead 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 bei lnk...-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 Betragsgruppe
  • img...: 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, oder null, wenn kein entsprechender Datensatz gefunden wurde. Es ist nicht garantiert, dass nur ein einziger Datensatz den Kriterien entspricht.
  • nearestMatch: Sucht wie exactMatch. Wird kein exakt passender Datensatz gefunden, wird der nächstfolgende Datensatz in der Sortierreihenfolge zurückgegeben. Gibt den gefundenen Datensatz zurück, oder null, 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, oder null, 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 mit using: 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 einen by...-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 und toExclusive (standardmäßig false) 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ür conRead) 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 und nearestMatch 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 (ohne from/to) in einer allBetween-Abfrage fungiert sowohl als Start- und Endwert für dieses Schlüsselfeld. Ein separates from und to 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:

  1. Für ein früheres Schlüsselfeld (wie kf1...) einen Bereich definieren möchten, UND
  2. 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:

  1. 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.

  2. Definition eines allBetween-Bereichs (mit from/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.

  3. 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 auf allBetween ausgewählt wurden.
  • Die Verwendung von keyFilter ist effizienter als fastFilter oder slowFilter, 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
  1. Balance zwischen allBetween und keyFilter:
  2. 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.
  3. Nutzung von keyFilter für die Feinabstimmung auf diesem Bereichsfeld und allen nachfolgenden Schlüsselfeldern.

  4. Kombinierte Bedingungen:

  5. Einsatz logischer Operatoren (and, or, not) innerhalb des keyFilter für komplexe Filterbedingungen.
  6. Für optimale Performance ist die Kombination von keyFilter mit präzisen allBetween-Angaben empfehlenswert.

  7. Alternative Sortierfolgen:

  8. Wahl einer Sortierfolge, die es ermöglicht, mit einem minimalen Bereich in allBetween alle relevanten Datensätze zu erfassen und gleichzeitig die Anzahl der per keyFilter zu filternden Datensätze zu minimieren.
  9. Wenn es nur ein Schlüsselfeld mit einem Wertebereich gibt, sind optimale Sortierfolgen die, bei denen:

    1. Zuerst alle Felder, für die exakte Werte benötigt werden, in beliebiger Reihenfolge als Schlüsselfelder enthalten sind
    2. Das einzige Feld, für das ein Bereich benötigt wird, direkt nach diesen exakten Feldern kommt
    3. 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ür keyFilter, fastFilter oder slowFilter.

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 aktuellen fldModifyLSN 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
    }
  }
}
Diese Abfrage ist äquivalent zu:
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 oder conRead 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ür rowsRead/conRead kann als verkürzte Schreibweise für allBetween: { 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
    }
  }
}
Diese Abfrage ist äquivalent zu:
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 die fld...-Felder der Datensätze und werden als Parameter direkt innerhalb von rowRead, rowsRead oder conRead angegeben, parallel zu exactMatch, nearestMatch oder allBetween 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 durch kf...-Parameter) und wird innerhalb des by...-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. beim in-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: Ergibt true, wenn alle booleschen Ausdrücke darin true sind. Benötigt mindestens zwei Ausdrücke.
  • or: Ergibt true, wenn mindestens einer der booleschen Ausdrücke darin true 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: Ergibt true, wenn der Ausdruck null ist. ##### Syntax
        { isNull: AnyExpression } # z. B. { isNull: { field: fldAuftrNr } }
    
  • isNotNull: Ergibt true, wenn der Ausdruck nicht null 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 oder SlowBooleanFieldsEnum) dargestellt ist. Nur boolesche Felder können direkt in diesem Ausdruckskontext referenziert werden.
  • value: Gibt einen konstanten booleschen Wert an (true oder false). ##### 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 oder SlowFieldsEnum, 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 Typ Any 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 Interpretation
        { add: [SlowAnyExpression1, SlowAnyExpression2, ...] } # Ergibt Summe
        { sub: [SlowAnyExpression1, SlowAnyExpression2, ...] } # Ergibt Differenz (A - B - C ...)
        { mul: [SlowAnyExpression1, SlowAnyExpression2, ...] } # Ergibt Produkt
    
    Interpretiert als 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 Interpretation
          { div: [SlowAnyExpression1, SlowAnyExpression2] } # Ergibt Quotient
          { mod: [SlowAnyExpression1, SlowAnyExpression2] } # Ergibt Rest
      
      Interpretiert als 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)
      ]
    }
Interpretiert als ((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 }] }
      ]
    }
Interpretiert als (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- oder or-Operatoren) ab, um die Klarheit zu erhöhen. and: [A, {and: [B, C]}] ist äquivalent zu and: [A, B, C].
  • Spezifische Vergleichsoperatoren verwenden: Wählen Sie spezifische Vergleichsoperatoren wie ne (ungleich) anstelle der Kombination von not mit eq. Verwenden Sie gt, lt, ge und le direkt, anstatt sie durch komplexere Logik nachzubilden.
  • Schnelle Ausdrücke bevorzugen: Verwenden Sie fastFilter oder keyFilter anstelle von slowFilter, 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 (in slowFilter), 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 und isNotNull 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, ob fldLagBestArt den Wert 1 oder 7 hat.
  • Die zweite Bedingung verwendet not und or, um sicherzustellen, dass fldAuftrNr weder null noch ein leerer String ist. Eine alternative Formulierung mit and, isNotNull und ne 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 von slowFilter gegenüber fastFilter wiegt in der Regel schwerer als der Lesbarkeitsgewinn durch in gegenüber or mit zwei eq-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, ob fldErledigtKz entweder null ist oder false ist. Die Prüfung auf false erfolgt durch direkte Negation (not) des booleschen Feldes fldErledigtKz.
  • Die zweite Bedingung stellt mit and sicher, dass fldArt weder null 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 nur null als Eingabe und gibt immer null zurück
  • BigInt: 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 kann
  • Any: Akzeptiert jedes grammatikalisch gültige GraphQL-Skalar, Liste oder Eingabeobjekt; gibt JSON zurück, das jede gültige Eingabe darstellen kann
  • Guid: Ein String, der eine GUID im Standardformat enthält, z. B.: {A63CBE46-D82C-4347-9AA7-7B6BDBC3FA72}
  • LocalDate: Ein lokaler Datums-String im YYYY-MM-DD Format
  • LocalTime: Ein lokaler Zeit-String im 24-Stunden-Format HH:mm[:ss[.SSS]]
  • LocalDateTime: Ein lokaler Datums-Zeit-String im YYYY-MM-DDTHH:mm[:ss[.SSS]] Format
  • RtfString: Ein String, der RTF enthält
  • Html: Ein String, der HTML enthält
  • Url: Ein String, der eine URL enthält
  • Char: Ein String, der genau ein Zeichen enthält
  • Variable: 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:

  1. 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 Skalartyp Variable 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.

  2. 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.

  1. 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

  1. 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 den row...-Mechanismus (siehe Abschnitt 6.3), der Details zum Steuersatz liefert.
  • aceByTaxCode(taxCode: Int!): AmountCompositionEntry - Gibt den einzelnen Eintrag aus aces zurück, der dem übergebenen taxCode entspricht. Gibt null 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 (wenn isNull den Wert true hat), werden die anderen Felder mit Standardwerten oder leeren Strings zurückgegeben, um Client-seitige Fehlerbehandlung zu vereinfachen.
  • Die Felder base64 und hexString 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:

  1. 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 und height abfragen, ohne die eigentlichen Bilddaten zu übertragen.

  2. Direkte Bildintegration: Für die direkte Einbettung von Bildern in HTML/CSS verwenden Sie base64 zusammen mit mimeType (z.B. in data: URLs), unabhängig vom tatsächlichen Speicherort des Bildes im Backend.

  3. Nachbearbeitung von Bildern: Für die clientseitige Bildbearbeitung oder -analyse können entweder base64 oder hexString verwendet werden, je nach den verwendeten Bibliotheken.

  4. 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:

  1. Selektives Abfragen: Fragen Sie nur die Felder ab, die Sie tatsächlich benötigen. Die Felder base64 und hexString enthalten die vollständigen Bilddaten und sollten nur angefordert werden, wenn tatsächlich das Bild selbst benötigt wird.

  2. 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.

  3. 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.

  4. 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 in Row-Objekten): Funktionen, die auf einem spezifischen Datensatz operieren und dessen Kontext nutzen.

  • Klassenmethoden (fn...-Felder in Table-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 von slowFilter und expr-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 von Row-Objekten der verlinkten Tabelle zurück (z. B. [InventoryEntryRowQueryRead] für Lagerbestandszeilen).
    • Erfordert Parameter zur Definition der Verlinkung und Sortierung (by... mit using...).
    • Akzeptiert optionale Parameter zur weiteren Einschränkung über Sortierfelder (kf...) und Filter (fastFilter, slowFilter, keyFilter) auf die verlinkten Datensätze.

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äfix by 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 im by...-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. bedeutet usingArtNr, dass fldArtNr des Ausgangsdatensatzes den Wert für kf1ArtNr 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 die rowsRead-Methode (innerhalb des lnk...-Objekts) angegeben und filtern die fld...-Felder der verlinkten Datensätze.
  • keyFilter: Wird innerhalb des using...-Parameters angegeben (parallel zu den optionalen kf...-Einschränkungen) und filtert die Schlüsselfelder der im by...-Parameter gewählten Sortierfolge der verlinkten Datensätze, auf die durch kf...-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 und PageInfo
  • Edge: Repräsentiert einen einzelnen Eintrag (Kante) in der Liste auf der aktuellen Seite; enthält einen cursor und den eigentlichen node
  • Node: Der eigentliche Datensatz (in diesem Schema das Row-Objekt mit seinen fld...-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 mittels by... und kf.... 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 und before 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ückgegebenen Edges pro Seite für die Vorwärtspaginierung. Wenn nicht angegeben, können alle passenden Datensätze zurückgegeben werden (potenziell sehr viele, daher dringend empfohlen first zu setzen!).
  • after: Optionaler String (ein cursor von einem vorherigen Aufruf, typischerweise pageInfo.endCursor). Gibt die Edges 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 von Edge-Objekten für die aktuelle Seite. Die Liste ist leer, wenn keine passenden Datensätze auf dieser Seite gefunden wurden.
  • pageInfo: Ein PageInfo-Objekt mit Metadaten zur Paginierung. Es wird empfohlen, dieses Feld nach edges abzufragen, da einige Informationen (wie startCursor, 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 den after-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 immer false zurück, da nur Vorwärtspaginierung mit after unterstützt wird.
  • hasNextPage: true, wenn es potenziell weitere Datensätze nach dem letzten Edge dieser Seite gibt (unter Berücksichtigung der Filter und allBetween-Bedingungen). false, wenn dies der letzte Datensatz der Ergebnismenge ist oder first nicht angegeben wurde und alle Ergebnisse auf dieser Seite sind.
  • startCursor: Cursor des ersten Edges auf dieser Seite, oder null, wenn edges leer ist.
  • endCursor: Cursor des letzten Edges auf dieser Seite, oder null, wenn edges leer ist. Dieser Wert wird typischerweise für den after-Parameter des nächsten Aufrufs verwendet, um die Folgeseite abzurufen.
  • edgeCount: Integer, der die Anzahl der tatsächlich zurückgegebenen Edges auf dieser Seite enthält (entspricht der Länge der edges-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 einem null-Wert ausgeführt werden soll. Mögliche Werte:
  • RETURN_NULL: Gibt null zurück (Standardverhalten)
  • RETURN_VALUE: Gibt einen alternativen Wert zurück
  • SKIP: Überspringt das Feld in der Ausgabe
  • RAISE_ERROR: Löst einen Fehler aus
  • returnValue: (Optional) Der Wert, der zurückgegeben werden soll, wenn das Feld null ist.
  • Wenn dieser Parameter angegeben wird, wird do automatisch auf RETURN_VALUE gesetzt.
  • Wenn skip als Wert angegeben wird, wird do automatisch auf SKIP gesetzt.
  • errorMessage: (Optional) Die Fehlermeldung für einen ausgelösten Fehler.
  • Wenn dieser Parameter angegeben wird, wird do automatisch auf RAISE_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 Feld null ist (Standardwert: true)
  • elseValue: (Optional) Wert, der gesetzt wird, wenn das Feld nicht null 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 oder expr 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 soll
  • expr: Ein Ausdruck, der ausgewertet und dessen Ergebnis in den Zieltyp konvertiert werden soll
  • Für _boolean muss dies ein Ausdruck vom Typ SlowBooleanExpression 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: