# GraphQL Entwickler Dokumentation - Abfragen (Queries)

<span class="custom-button red-button">Gen. 24 Enterprise</span>

## 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 **Gen. 24 Enterprise von microtech büro+** ab Build 7076 erforderlich.

*    Für zugreifende **Benutzer** muss das **Kennzeichen: "Zugriff über GraphQL erlaubt"** gesetzt sein. (Registerkarte: INFORMATIONEN - BENUTZER - BENUTZER VERWALTEN - FIRMENVORGABEN - Kennzeichen: "Zugriff über GraphQL erlaubt"). Weitere Infos: [OAuth 2.0 API-Dokumentation](../18_graphql/oauthapidoku.md).

*    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](../04_moduluebergreifend/programmeinrichtung%20und%20konfiguration/serverkonfiguration/glserverkonfigurationhttp2.md)**.

### 1.3. Einrichtung und Verwendung des GraphQL-Servers

Seit der automatischen Bereitstellung erhält jeder Mandant seinen GraphQL-Endpunkt automatisch, sobald GraphQL für ihn aktiviert wird - ohne manuellen Automatisierungsdienst. Details dazu finden Sie unter **[GraphQL-Endpunkt - Einrichtung und Betrieb](../18_graphql/graphqlendpunkteinrichten.md)**.

Der nachfolgend beschriebene Weg über einen Automatisierungsdienst ist weiterhin nutzbar und wird vor allem für bestehende, bereits eingerichtete GraphQL-Server benötigt.

#### 1.3.1. Manuelle Einrichtung des GraphQL-Servers über einen Automatisierungsdienst

Sind die in Abschnitt 1.3 genannten Voraussetzungen erfüllt, kann ein Aufgaben-Dienst des Typs "GraphQL-Server" angelegt und gestartet werden.

![Automatisierungs-Dienst neu erfassen](https://assets.hilfe.microtech.de/bilder/18_graphql/graphqldoku_1.png)

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](https://assets.hilfe.microtech.de/bilder/18_graphql/graphqldoku_2.png)

Ö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](https://assets.hilfe.microtech.de/bilder/18_graphql/graphqldoku_3.png)

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

Rufen Sie die Endpunkt-URL in einem Webbrowser auf, um die interaktive GraphQL-Oberfläche (Apollo Sandbox) zu öffnen. Diese Umgebung ermöglicht es Ihnen, den gesamten Umfang der GraphQL-Schnittstelle mit allen verfügbaren Abfragen direkt im Browser zu testen.

![Beispielansicht der interaktiven GraphQL-Oberfläche](https://assets.hilfe.microtech.de/bilder/18_graphql/graphqldoku_4.png)

Die integrierte Web-Oberfläche ist nur eine von mehreren Möglichkeiten, mit dem GraphQL-Endpunkt zu interagieren. Sie können auch 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.

!!! tip "Tipp"

    Auf folgender Seite erhalten Sie praktische Tipps für Beispiel-Abfragen:

    * **[GraphQL Beispiel Anfragen](../18_graphql/graphqlbeispielqueries.md)**

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

* **GraphQL-Executor** *(in früheren Versionen der Doku auch als "Anwendungsserver" bezeichnet)*: Der Prozess, in 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 GraphQL-Executors implementiert und arbeitet direkt mit dem vorhandenen Anwendungsframework zusammen.

* **GraphQL-Relay**: Die Schnittstelle, die GraphQL-Anfragen von externen Clients entgegennimmt und an die GraphQL-Executor-Instanzen zur Verarbeitung weiterleitet. Die Antworten 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.

* **Executorseitig**: Operationen oder Funktionen, die im GraphQL-Executor 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:**

```graphql
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):**
```graphql
query {
  tblProducts {
    rowRead(exactMatch: { byNr: { kf1ArtNr: { string: "PROD-001" } } }) { # Liest einen Artikel
      fldArtNr
      fldSuchBeg
      fldArtikelArt
    }
  }
}
```

**Beispiel für `rowsRead` (alle Artikel):**
```graphql
query {
  tblProducts {
    rowsRead { # Liest alle Artikel-Datensätze
      fldArtNr
      fldSuchBeg
      fldArtikelArt
    }
  }
}
```

**Beispiel für `conRead` (paginierte Artikel):**

```graphql
# 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:**
```graphql
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:**

```graphql
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 (**A**mount **Co**mpositions) (z. B. `acoEPr`)
* **`ace...`**: Einzelner Eintrag einer Betragsgruppe (**A**mount **C**omposition **E**ntry)
* **`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`:**

```graphql
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`:**

```graphql
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`:**

```graphql
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

!!! warning "Beachten Sie"

    **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`:**

```graphql
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:**

```graphql
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):**

```graphql
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](https://hilfe.microtech.de/04_moduluebergreifend/eintr%20auf%20datei_ansicht/datei/info_einstellungen/db%20manager/selektionen%20und%20sortierungen/gldbmanagerselektionensortierungensuchen/).

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

```graphql
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`.

```graphql
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:

```graphql
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:

```graphql
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):

```graphql
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:

```graphql
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`**:
   * Verwendung von `allBetween` mit einem möglichst engen Bereich für die Schlüsselfelder bis einschließlich des ersten Schlüsselfeldes, das einen Bereich benötigt, um die initiale Datenmenge zu begrenzen.
   * Nutzung von `keyFilter` für die Feinabstimmung auf diesem Bereichsfeld und allen nachfolgenden Schlüsselfeldern.

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

3. **Alternative Sortierfolgen**:
   * Wahl einer Sortierfolge, die es ermöglicht, mit einem minimalen Bereich in `allBetween` alle relevanten Datensätze zu erfassen und gleichzeitig die Anzahl der per `keyFilter` zu filternden Datensätze zu minimieren.
   * 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 (**L**og **S**equence **N**umber) 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.

!!! warning "Beachten Sie"

    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:**

```graphql
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:**

```graphql
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`).

### 3.6 Erkennung von Tabellenstrukturänderungen (`structureTag`)

Während `modifyLSN` (Abschnitt 3.5) Änderungen an Datensätzen erkennt, ermöglicht das Feld `structureTag` die Erkennung von Änderungen an der Struktur einer Tabelle selbst. Wenn beispielsweise Felder hinzugefügt, entfernt oder umbenannt werden oder sich die Sortierfolgen ändern, ändert sich auch der `structureTag`-Wert der betroffenen Tabelle.

#### 3.6.1 Funktionsweise

Das Feld `structureTag` ist direkt im `Table`-Objekt (zurückgegeben von einem `tbl`...-Feld) verfügbar, auf der gleichen Ebene wie `rowRead`, `rowsRead` und `conRead`. Es gibt einen opaken Base64-kodierten String zurück, der den aktuellen Strukturstand der Tabelle repräsentiert. Dieser Wert ist nicht für die menschliche Interpretation bestimmt - er dient ausschließlich dem Vergleich auf Gleichheit.

*    Sind zwei `structureTag`-Werte identisch, hat sich die Tabellenstruktur nicht geändert.
*    Unterscheiden sich die Werte, wurde die Tabelle zwischenzeitlich umstrukturiert.

!!! danger "Beachten Sie"

    Es gibt keine definierte Reihenfolge zwischen verschiedenen `structureTag`-Werten. Man kann nur feststellen, ob sie gleich oder verschieden sind, nicht welcher "neuer" ist.

#### 3.6.2 Anwendungsfälle

*    **Schema-Caching**: Clients können den `structureTag` bei jedem Zugriff abfragen und mit dem gespeicherten Wert vergleichen. Bei      Abweichung müssen zwischengespeicherte Schema-Informationen (Feldlisten, Sortierfolgen, etc.) neu geladen werden.

*    **Cache-Invalidierung**: Anwendungen, die Schema-Informationen wie Feldlisten oder Sortierfolgen lokal vorhalten, können den `structureTag` als Invalidierungskriterium verwenden.

!!! danger "Beachten Sie"

    "Achtung bei Strukturänderungen"
    Wenn sich der `structureTag` einer Tabelle ändert, sind alle zuvor gespeicherten `modifyLSN`-Werte (siehe Abschnitt 3.5) für diese Tabelle ungültig. Eine Tabellenrestrukturierung kann LSN-Werte neu vergeben, sodass gespeicherte LSN-Werte nicht mehr den erwarteten Datensätzen entsprechen. Clients, die `modifyLSN` für Konsistenzprüfungen oder Änderungsverfolgung verwenden, sollten daher bei einer `structureTag`-Änderung ihre gespeicherten LSN-Werte verwerfen und die betroffenen Daten vollständig neu laden.

#### 3.6.3 Beispiel

```graphql
query {
  tblProducts {
    structureTag # z. B. "Q8x2f0kH7EaRB6gAAQAAAA=="

    rowsRead {
      fldArtNr
      fldSuchBeg
    }
  }
}
```

Der `structureTag` kann in derselben Abfrage zusammen mit Datenzugriffen abgefragt werden. Da es sich um ein Feld des `Table`-Objekts handelt, löst es keine zusätzliche Datensatzabfrage aus.

## 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):**
```graphql
query {
  tblProducts {
    rowRead( # Direkte Angabe von kf1ArtNr impliziert exactMatch mit Standardsortierung byNr
      kf1ArtNr: { string: "1" }
    ) {
      fldArtNr
      fldSuchBeg
    }
  }
}
```
Diese Abfrage ist äquivalent zu:
```graphql
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`):**
```graphql
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:
```graphql
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`):**

```graphql
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`):**

```graphql
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.)*

```graphql
# 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 GraphQL-Executor berechnet
* Können weitere Datenbankabfragen im Hintergrund auslösen
* Sind in der Darstellung nicht unmittelbar von regulären Datenbankfeldern zu unterscheiden

!!! warning "Beachten Sie"

    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:
  ```graphql
  # 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:
  ```graphql
  # 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

```graphql
    { 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

```graphql
{ 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

```graphql
    { 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

```graphql
    { 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
    ```graphql
        { isNull: AnyExpression } # z. B. { isNull: { field: fldAuftrNr } }
    ```
* **`isNotNull`**: Ergibt `true`, wenn der Ausdruck nicht `null` ist.
    ##### Syntax
    ```graphql
        { 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
    ```graphql
        { 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
    ```graphql
        { 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

```graphql
    { 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
    ```graphql
        { 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
        ```graphql
            { 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

```graphql
    # 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`. 

!!! success "Praktischer Nutzen"

    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

```graphql
    { 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.

```graphql
    {
      fnDay: [{ field: fldGspDat }]
    }
```

Dies würde den Tag des Datums im Feld `fldGspDat` als numerischen Wert zurückgeben.

###### Weitere Informationen zu Funktionen

Detaillierte Informationen zu den einzelnen Funktionen, einschließlich der Anzahl und Art der erforderlichen Parameter, erhalten Sie in unserer Online-Hilfe.

!!! info "Info"

    *    **[Übersicht aller Filter-Funktionen](https://hilfe.microtech.de/04_moduluebergreifend/weitere%20funktionen/filterdefinitionen_eingabe/glfilterdefinitionenuebersicht/)**

### 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)
```graphql
    { 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)

```graphql
    # 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 GraphQL-Executor 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`)

```graphql
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`)

```graphql
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.

!!! warning "Beachten Sie"

    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`)

```graphql
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:

```graphql
# 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:

```graphql
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):

```graphql
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:

   ```graphql
   # 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.

3. **Kontextabhängige Typsicherheit**: Der Typ der verwendeten Variable muss zum Kontext passen, in dem sie verwendet wird:

   ```graphql
   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

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

!!! warning "Beachten Sie"

    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:**

```graphql
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:**

```json
{
  "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:**

```graphql
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
    }
  }
}
```

<!--

!!! warning "Beachten Sie"

    Hiervon ausgenommen sind `tblDocuments.fldData` (und Dokumenteneingang), stattdessen ist `fnGetContentsAsBase64` / `fnSetContents` (extension, base64) zu verwenden. Siehe auch: [microtech Hilfe Praxisbeispiel - Offene Posten und Beleg eines Kunden (GraphQL) - Dokument per GUID abrufen](graphqlopbelegkunden.md#4-dokument-per-guid-abrufen)

-->

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

```graphql
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):**

```graphql
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).

!!! warning "Beachten Sie"

    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:**

```graphql
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:**

```graphql
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:**

```graphql
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 **A**mount **C**omposition **E**ntrie**s**) 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:**

```graphql
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

```graphql
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.

!!! warning "Beachten Sie"

    Die Verfügbarkeit von Instanzmethoden kann sich je nach Kontext unterscheiden.

#### 6.7.3 Beispiel in einer Query

```graphql
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 **L**i**nk**) 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**

```graphql
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:**

```graphql
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**

```graphql
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](https://relay.dev/graphql/connections.htm). 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:**

```graphql
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:**

```graphql
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
    }
  }
}
```

!!! warning "Beachten Sie"

    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.

!!! warning "Beachten Sie"

    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:

```graphql
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:**

```json
{
  "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:

```graphql
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:**

```json
{
  "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
        }
      ]
    }
  }
}
```

### 9.4 Stack Trace bei Fehlern: `@reportStackTrace`

Die `@reportStackTrace`-Direktive aktiviert die Ausgabe detaillierter Stack Traces bei Fehlern während der Ausführung einer GraphQL-Operation. Diese Direktive ist primär ein **Support-Tool** für die Fehleranalyse durch den microtech-Support.

#### Syntax und Verwendung

Die Direktive wird direkt an die Operation angehängt und benötigt keine Parameter:

```graphql
query @reportStackTrace {
  tblProducts {
    rowsRead {
      fldArtNr
      fldSuchBeg
    }
  }
}
```

#### Funktionsweise

Wenn die Direktive vorhanden ist:
- Gilt sie für die **gesamte Ausführung** der Operation, unabhängig davon, in welchem verschachtelten Feld der Fehler auftritt
- Bei auftretenden Exceptions wird der Stack Trace im `errors`-Array unter `extensions.stackTrace` als Liste von Strings hinzugefügt (zu den weiteren `extensions`-Feldern siehe Abschnitt 9.5)
- Die Direktive wirkt sich nur auf die Fehlerberichterstattung aus, nicht auf das Verhalten der Abfrage selbst

#### Beispiel-Response mit Stack Trace

```graphql
query @reportStackTrace {
  tblAddresses {
    rowRead(exactMatch: { byNr: { kf1AdrNr: { string: "10001" } } }) {
      fldAdrNr
      _raise(message: "Beispielfehler")
    }
  }
}
```

```json
{
  "data": {
    "tblAddresses": {
      "rowRead": {
        "fldAdrNr": "10001",
        "_raise": null
      }
    }
  },
  "errors": [
    {
      "message": "Beispielfehler",
      "locations": [
        {
          "line": 5,
          "column": 7
        }
      ],
      "path": [
        "tblAddresses",
        "rowRead",
        "_raise"
      ],
      "extensions": {
        "code": "USER_RAISED",
        "className": "EGraphQLUserException",
        "context": [
          "_raise",
          "rowRead(Nr, AdrNr: 10001)(AdrNr: 10001): Inaktiv 10001 Beispiel Elektronik GmbH in München",
          "tblAddresses"
        ],
        "stackTrace": [
          "02d81acd BpNTNx.exe   MbfGraphQL.Schema  1244 TMbfSchemaDocument.BeforeAfterInit$ActRec.$0$Body",
          "02d5bdfa BpNTNx.exe   GraphQL.Executer   1758 TGraphQLExecuter.ExecuteFieldSerialized",
          "02d5ca50 BpNTNx.exe   GraphQL.Executer   2112 TGraphQLExecuter.ExecuteSelectionSetSerialized",
          "..."
        ]
      }
    }
  ]
}
```

#### Anwendungsbereich

**Primärer Einsatzzweck:**
Diese Direktive wird typischerweise **auf Anfrage des microtech-Supports** eingesetzt. Wenn Sie einen Fehler an den Support melden, kann dieser Sie bitten, die Abfrage mit `@reportStackTrace` erneut auszuführen und die vollständige Fehlerantwort einschließlich Stack Trace zu übermitteln.

**Während der Entwicklung:**
- Entwickler können die Direktive nutzen, um detaillierte Informationen über interne Fehler direkt in der Response zu erhalten
- Der Stack Trace zeigt die genaue Aufrufkette innerhalb der microtech Software
- Hilfreich beim Nachvollziehen komplexer Fehlerszenarien

**Hinweis für Produktivumgebungen:**
Außerhalb der Software-Entwicklung lässt sich mit dem Stack Trace in der Regel nichts anfangen, da er interne Implementierungsdetails der microtech Software enthält.

#### Wichtige Hinweise

- Die Direktive ist **vollständig unabhängig** von anderen Direktiven wie `@skip`, `@include`, `@store` oder `@onNull`
- Es gibt aktuell **keine Berechtigungseinschränkungen** für die Verwendung dieser Direktive
- Ohne die Direktive werden Fehler im Standard-GraphQL-Format ohne Stack Trace zurückgegeben
- Der Stack Trace enthält keine sicherheitskritischen Informationen

### 9.5 Strukturierte Fehlerinformationen

Jede Fehlerantwort enthält im `extensions`-Objekt strukturierte Informationen zur Fehlerklassifikation. Bei Laufzeitfehlern umfasst dies den semantischen **`code`**, die interne Exception-Klasse (**`className`**), den Feldpfad (**`path`**) und die Auflösungskette mit Datensatzinformationen (**`context`**). Fehler, die vor der Ausführung auftreten (Syntaxfehler, Schemaverletzungen), enthalten nur den **`code`** — `GRAPHQL_PARSE_FAILED` oder `GRAPHQL_VALIDATION_FAILED`.

#### Fehlercodes

| Code | Bedeutung |
|------|-----------|
| `INVALID_ARGUMENT` | Ungültiger Eingabewert oder Parameter |
| `INVALID_STATE` | Operation im aktuellen Zustand nicht möglich |
| `CONCURRENCY_ERROR` | Sperrenkonflikt oder Deadlock |
| `NOT_FOUND` | Erwarteter Datensatz nicht gefunden |
| `AUTHORIZATION_ERROR` | Fehlende Berechtigung |
| `BUSINESS_LOGIC_ERROR` | Verletzung einer Geschäftsregel |
| `CONSTRAINT_VIOLATION` | Verletzung einer Datenbankeinschränkung |
| `USER_RAISED` | Durch `_raise` ausgelöst (Abschnitt 10.1.5) |
| `INTERNAL_ERROR` | Unerwarteter interner Fehler |

#### Beispiel: Fehlerkontext in verschachtelten Abfragen

Das `path`-Array gibt die Position des Fehlers im Ergebnis an (Feldnamen und Listenindizes gemäß GraphQL-Spezifikation). Das `context`-Array beschreibt denselben Weg mit fachlichem Kontext — welche Datensätze betroffen sind und welche Abfrageparameter verwendet wurden. Die Einträge in `context` sind von innen nach außen angeordnet, beginnend beim Fehlerort.

```graphql
query {
  tblAddresses {
    rowRead(exactMatch: { byNr: { kf1AdrNr: { string: "10001" } } }) {
      fldAdrNr
      lnkPostalAddresses {
        b: rowsRead(byAdrNrAnsNr: { usingAdrNr: {} }) {
          fldAnsNr
          lnkContactPeople {
            c: rowsRead(
              byAdrNrAnsNrAspNr: {
                usingAdrNrAnsNr: { kf3AspNr: { int: 0 } }
              }
            ) {
              fldAspNr
              _raise
            }
          }
        }
      }
    }
  }
}
```

```json
{
  "data": {
    "tblAddresses": {
      "rowRead": {
        "fldAdrNr": "10001",
        "lnkPostalAddresses": {
          "b": [
            {
              "fldAnsNr": 0,
              "lnkContactPeople": {
                "c": [
                  {
                    "fldAspNr": 0,
                    "_raise": null
                  }
                ]
              }
            }
          ]
        }
      }
    }
  },
  "errors": [
    {
      "message": "_raise called",
      "locations": [
        {
          "line": 15,
          "column": 15
        }
      ],
      "path": [
        "tblAddresses",
        "rowRead",
        "lnkPostalAddresses",
        "b",
        0,
        "lnkContactPeople",
        "c",
        0,
        "_raise"
      ],
      "extensions": {
        "code": "USER_RAISED",
        "className": "EGraphQLUserException",
        "context": [
          "_raise",
          "[0](AdrNr: 10001, AnsNr: 0, AspNr: 0): Erika Muster",
          "c: rowsRead(AdrNrAnsNrAspNr, usingAdrNrAnsNr, AspNr: 0)",
          "lnkContactPeople",
          "[0](AdrNr: 10001, AnsNr: 0): Inaktiv 10001 (BE-ELEKT) Beispiel Elektronik GmbH in München",
          "b: rowsRead(AdrNrAnsNr, usingAdrNr)",
          "lnkPostalAddresses",
          "rowRead(Nr, AdrNr: 10001)(AdrNr: 10001): Inaktiv 10001 Beispiel Elektronik GmbH in München",
          "tblAddresses"
        ]
      }
    }
  ]
}
```

Der `path`-Wert lässt sich direkt auf das `data`-Objekt anwenden — `b[0]` ist die erste Postanschrift, `c[0]` die erste Kontaktperson. Das `context`-Array ergänzt diese strukturelle Information mit fachlichem Inhalt: Schlüsselfelder und Bezeichnung des jeweiligen Datensatzes sowie die verwendeten Abfrageparameter.

## 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:**

```graphql
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`):**

```graphql
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:**

```graphql
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:**

```graphql
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.

!!! warning "Beachten Sie"

    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:**

```graphql
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

```graphql
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

```graphql
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

```graphql
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

```graphql
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

```graphql
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:**

```graphql
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:**

```graphql
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:**

```graphql
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:**
```graphql
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:**
```graphql
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:**
```graphql
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:**
```json
{
  "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 |

!!! warning "Beachten Sie"

    Alle Felder geben `null` zurück, wenn das gesuchte Element nicht existiert.

**Beispiel:**
```graphql
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:**
```graphql
query {
  __type(name: "PostalCodesTableQueryRead") {
    name
    kind
    extensions {
      name
      typeName
      description
      value
    }
    mapExtensions
    anyExtensionValue(name:"flags")
  }
}
```

**Antwort:**
```json
{
  "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:

```graphql
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:

*    **[GraphQL Entwickler Dokumentation - Mutationen (Mutations)](../18_graphql/graphqlmutations.md)**


Auf folgender Seite erhalten Sie praktische Tipps für Beispiel-Abfragen:

*    **[GraphQL Beispiel Queries](../18_graphql/graphqlbeispielqueries.md)**
