# Suchquery

Ein Suchquery besteht aus einer Liste von Ausdrücken, welche die Suche spezifizieren.

## Sonderzeichen

Sonderzeichen, sofern sie keinen speziellen Syntax bilden werden ignoriert und nicht in die Suche
eingeschlossen.

## Begriffe

Begriffe werden mit einen Leerzeichen abgetrennt im Query definiert. Begriffe bestehen nur aus
Zeichen der Zeichenklasse `\w`. Bindestriche dürfen zwischendrin vorkommen, aber nicht am Anfang.
Groß- und Kleinschreibung wird ignoriert.

Es muss nur ein Begriff aus der Liste von Begriffen passen. Passen mehrere fällt das Ranking besser
aus.

**Beispiele:**

- `Apfel` sucht nach allem, wo Apfel vorkommt
- `Apfel Banane` sucht nach allem, wo entweder Apfel oder Banane vorkommt. Wenn beides vorkommt, so
    erhält dies eine höhere Priorität

## Explizite Ausdrücke

Explizite Ausdrücke werden von doppelten Anführungsstrichen `"` umschlossen. Alle Zeichen darin
werden direkt gesucht und es wird nichts umformatiert. Anführungsstriche werden mit `\"` escaped.

**Beispiele:**

- `"Apfel, Banane"` sucht nach allem, wo der Substring "Apfel, Banane" in genau dieser Schreibung
    enthalten ist.

## Optionen

Es gibt verschiedene Optionen. Alle Optionen sind mit den Schlüssel, einen Doppelpunkt und einen
Wert versehen. Wenn Werte Leerzeichen enthalten, müssen diese mit doppelten Anführungsstrichen `"`
umschlossen werden.

### Attribute

Sucht direkt nach einen Attribut und vergleicht diesen.

> Attribute erzwingen den Typ `person`. Somit sind `type:person attr:age` und
> `attr:age` äquivalent.

Als Schlüssel für diese Operation gilt `attr`. Der Wert ist zweigeteilt. Er besteht aus den
Attributsschlüssel, einen Vergleichsoperator und einen Wert.

Folgende Vergleichsoperatoren werden unterstützt:

| Operator | Wertetyp | Beschreibung |
|-|-|-|
| `=` | alle | Das ganze Attribut muss den Wert entsprechen |
| `<=`, `<`, `>=`, `>` | `int`, `float` | Mathematischer Vergleichsoperator |
| `~=` | `string` | Attribut muss Wert enthalten |
| `^=` | `string` | Attribut muss mit Wert anfangen |
| `$=` | `string` | Attribut muss mit Wert aufhören |
| `@=` | `string` | Attribut muss einen Regulären Ausdruck genügen. Wenn dieser Anführungsstriche, Klammern oder Leerzeichen enthält, muss alles in Anführungsstriche gesetzt werden. |

Es kann auch der Operator und Wert weggelassen werden, dann wird nur die Existenz des Attributs
geprüft.

**Beispiele:**

- `attr:age>=5` Sucht nach allen Einträgen, dessen Alter gleich oder größer als 5 ist.
- `attr:opinion` Sucht nach allen Einträgen, die einen Wert für `opinion` haben
- `attr:hasConsent=true` Sucht nach allen Einträgen, die einen Consent hinterlegt haben.
- `attr:opinion@="do(es)?n't like m[ae]n"` Sucht nach allen Einträge mit einen Wert für `opinion`
    welcher einen bestimmten Regulären Ausdruck folgt.

Kann der Vergleichsoperator auf den Wert nicht angewandt werden, so schlägt dieser Teil immer fehl
und trifft auf nichts zu. Dasselbe tritt auch ein, wenn der Reguläre Ausdruck fehlerhaft ist.

### Felder

Sucht direkt nach einen Feld und vergleicht mit diesen.

Felder sind äquivalent so aufgebaut wie Attribute und arbeiten ähnlich, bieten aber die gleichen
Optionen an.

Felder können bei Interview und Person nur ausgewertet werden, wenn die Confidential Database
verfügbar ist. Wenn nicht, schlägt dies immer zwingend fehl. Es können nur bestimmte Felder
abgerufen werden:

- **Interview**: `time`, `location`, `interviewer`
- **Person**: `name`, `contact`
- **File**: `name`, `kind`

**Beispiele:**

- `field:name~=Ralf` Das Namensfeld enthält "Ralf"

### Typ

Sucht nur bestimmte Arten von Datensätzen ab.

Als Schlüssel gilt `type` und als Wert kann man einen von folgenden nutzen:

| Wert | Beispiel | Beschreibung |
|-|-|-|
| `interview` | `type:interview` | Sucht nur nach Interviews |
| `person` | `type:person` | Sucht nach einen Interviewten |
| `file` | `type:file` | Sucht nach einer Datei (hier werden nur die Metadaten berücksichtigt) |

### Rolle

Sucht nur noch Interviewten, welche eine Rolle innehaben.

Als Schlüssel gilt `role` und als Wert wird der Schlüssel für die Rolle genutzt:

**Beispiel:**

- `role:doctor` Sucht nach allen Doktoren
- `role:family` Sucht nach allen Familienmitgliedern

### Sortierung

Sortiert die Ergebnisse nach einen bestimmten Feld oder Score:

> Sortierungen dürfen nur in der obersten Ebene definiert werden! Andernfalls werden diese
> ignoriert.

Als Schlüssel gilt hier `sort`. Der Wert ist in mehrere Teile geteilt, welche mit einen Doppelpunkt
`:` abgetrennt sind.

Als erstes kommt hier die Sorte, dann abhängig von der Sorte noch zusätzliche Optionen und zuletzt
die Richtung.

| Sorte | Extra Feld | Richtung | Beispiel | Beschreibung |
|-|-|-|-|-|
| `score` | *keins* | `desc` | `sort:score:desc` | Sortiert anhand des resultierenden Scores. |
| `attr` | Schlüssel | `asc` | `sort:attr:age:asc` | Sortiert anhand eines Attributes. |
| `field` | Schlüssel | `asc` | `sort:field:name:asc` | Sortiert anhand eines Standardfelds. |
| `none` | *keins*  | *egal*  | `sort:none` | Sortiert das Ergebnis nicht und gibt sofort alles aus. Dies ist relevant, wenn es schnelle Ergebnisse geht und keine großen Datenmengen erwartet werden. |

Falls eine Richtung nicht angegeben wird, so wird die Standardmäßige Sortierung für die Sorte
genommen.

> Hinweis: `field` ist bei Interview und Person sind nur verfügbar, wenn die Confidential
> Database verfügbar ist. Andernfalls wird diese Sortierung immer ignoriert.

## Operatoren

Listen von Wörtern können mit Operatoren versehen werden, wodurch diese nur unter bestimmten
Bedingungen erfüllt werden.

### Negation

Ein Minus `-` direkt vor einen Ausdruck (es dürfen beliebig viele Leerzeichen zwischen Minus und
Ausdruck stehen) negiert diese Behauptung. Nur solange der interne Ausdruck nicht zutrifft, gilt
dies als erfolgreich.

Alternativ zu einen Minus darf auch ein Ausrufezeichen `!` verwendet werden.

Folgenden mehrere Negationszeichen aufeinander werden diese solange aufgelöst, bis nur noch eins
oder keins mehr übrig ist.

**Beispiele:**

- `-Apfel` sucht nach allem, was keinen Apfel enthält
- `Apfel - Banane` sucht nach allem was Apfel, aber keine Banane enthält.
- `-!--!Apfel` entspricht `-Apfel`

### AND, OR, XOR

Erlaubt komplexere Ausdrücke, indem man spezielle Operatoren einbaut.

| Operator | Beschreibung |
|-|-|
| `\|\|` oder `\|` | Der komplexe Ausdruck trifft nur zu, wenn eine von beiden Seiten zutrifft. |
| `&&` oder `&` | Der komplexe Ausdruck trifft nur zu, wenn beide Seiten zutreffen. |
| `^` | Der Ausdruck trifft nur zu, wenn entweder die linke Seite oder die rechte Seite zutrifft. Aber niemals beide gleichzeitig. |

Die Operatoren dürfen gemischt werden, hierbei gilt aber folgende Priorität: Zuerst wird `^`
ausgewertet, danach `&` und zum Schluss `|`. Falls eine andere Reihenfolge gewünscht ist, so sind
Klammern zu verwenden.

Der Unterschied zwischen den einfachen und doppelten Operator ist beim Scoring. Beim doppelten wird
nur der größte Score weitergegeben. Wobei beim einfachen die Summe genommen wird. Hierrüber lässt
sich also die Priorität in den Suchergebnissen festlegen.

**Beispiele:**

- `Apfel && Banane` Sucht nach allem, wo Apfel **und** Banane gleichzeitig vorkommen.
- `role:doctor || Doctor` Sucht nach allen mit der Rolle Doktor oder wo das Wort Doktor irgendwo
    vorkommt.
- `Apfel ^ Banane` Sucht nach allem, wo entweder Apfel oder Banane vorkommt, aber niemals beides
    gleichzeitig.

#### Unterschiede bei der Sortierreihenfolge

Man nehme an, wir haben folgende Texte, welche in der Datenbank existieren:

- `Apfel Kiwi Birne`
- `Apfel Kiwi`
- `Apfel Banane Birne Kirsche`
- `Apfel Kiwi Kirsche`

Diese werden je nach Query in einer unterschiedlichen Reihenfolge ausgegeben:

- `(apfel kiwi) | (birne | banane | kirsche)`:
    - `Apfel Banane Birne Kirsche`, Score: 4
    - `Apfel Kiwi Birne`, Score: 3
    - `Apfel Kiwi Kirsche`, Score: 3
    - `Apfel Kiwi`, Score: 2
- `(apfel kiwi) || (birne || banane || kirsche)`:
    - `Apfel Kiwi Birne`, Score: 2
    - `Apfel Kiwi`, Score: 2
    - `Apfel Kiwi Kirsche`, Score: 2
    - `Apfel Banane Birne Kirsche`, Score: 1

### Gruppen

Runde Klammern `(` und `)` können Listen Wörtern gruppieren und somit komplexere Ausdrücke
erstellen.

Falls es nicht genügend schließende Klammern gibt, so werden am Ende welche hinzugedacht. Gibt es zu
viele schließende Klammern, werden die ignoriert.

**Beispiele:**
- `Apfel && (Banane Kiwi)` Sucht nach allem, wo zwingend ein Apfel vorkommt und mindestens eins von
    Banane oder Kiwi

### Auflistung

Wenn Ausdrücke nur von Leerzeichen begrenzt sind, so werden diese zu Listen zusammengefasst. Diese
arbeiten nach speziellen Regeln:

1. Solange keine Optionen enthalten sind, so muss nur eins davon vorkommen. Dies entspräche der
    Logik, dass `Apfel Banane` und `Apfel | Banane` gleich arbeiten.
2. Wenn mehrere Ausdrücke in einer Auflistung zutreffen, so erhöht sich ihr Score. Bei einer
    einfachen Kette von `||` bleibt der Score erhalten.
3. Optionen werden in Auflistungen als zwingend angesehen. Die Auflistung kann somit nur noch
    erfolgreich passen, wenn diese Option erfolgreich sind. Die anderen Ausdrücke behalten ihre
    Logik bei.

    Somit entspräche `role:doctor Doctor House` `role:doctor && (Doctor House)`.
4. Bei der Abarbeitung werden die Optionen immer zuerst abgearbeitet. Erst dann werden sich die
    anderen Ausdrücke angeschaut. Hierbei werden auch Gruppen von Klammern beachtet.

    Somit entspräche `Doctor House role:doctor (attr:age)` 
    `role:doctor && (Doctor House (attr:age))`.

Hinweis: Jeder Query ist in einer Auflistung eingeschlossen und auch ein leerer Query ist eine
Auflistung. Eine leere Auflistung ist immer erfolgslos.

## Scorings

Es wird bei der Abarbeitung des Querys ein Score ermittelt. Hierbei gelten je nach Ausdruck folgende
Regeln:

1. **Begriffe:** Wenn passen, dann `1.0` andernfalls `0.0`
2. **Optionen:** Erzwingt das Fehlschlagen oder Erfolgreich sein von Auflistungen.
3. **Operator:**
    1. **Negation:** Wenn interne Ausdruck großer als 0, dann ist das Ergebnis `0.0`, andernfalls 
        `1.0`
    2. **AND, OR, XOR:** 
        - Mit `&`, `|` und `^`: Ergebnis ist Summe von linken und rechten Ausdruck, wenn Ausdruck
            erfolgreich. Andernfalls `0.0`.
        - Mit `&&` und `||`: Ergebnis ist Maximum vom linken und rechten, wenn Ausdruck
            erfolgreich. Andernfalls `0.0`.
    3. **Gruppen:** Der Score ist immer der Score aus der internen Auflistung
    4. **Auflistung:**
        - Wenn einer der Attribute fehlschlägt: `0.0`
        - Wenn leer: `0.0`
        - Ansonsten Summe der enthaltenden Scorings


## Queryoptimierung

Bevor der Query ausgewertet wird, wird sich die äußerste Auflistung angeschaut, ob bestimmte
Abfragen schon auf der Ebene der Datenbank gemacht werden können. Hierbei werden zuerst die Querys
nach folgenden Regeln umgebaut:

1. Wiederholte gleiche Operatoren, werden auf eine Hierarchie gebracht:
    - `apfel && (banane && kiwi)` entspricht `apfel && banane && kiwi`
2. Negation wird aufgelöst:
    - `-!-!-apfel` entspricht `-apfel`
    - `--apfel` entspricht `apfel`
3. Widersprüchliche Attribute werden aufgelöst:
    - `role:doctor role:family apfel` entspricht `()`
    - `type:interview` wird zu `()` wenn die Confidential Database nicht mehr verfügbar ist.
    - `type:interview attr:age` entspricht `()`
    - `type:person attr:age` entspricht `attr:age`
4. Verundete Gruppen nur aus Attributen werden zusammengefasst:
    - `(role:doctor) && (attr:age=5)` entspricht `(role:doctor attr:age=5)`
5. Wenn eine Auflistung nur eine Gruppe enthält, so wird der Inhalt der Gruppe in die Auflistung
    gezogen:
    - `(((apfel banane)))` entspricht `apfel banane`
    - `(((role:doctor)))` entspricht `role:doctor`
    - `role:doctor ((attr:age))` entspricht `role:doctor (attr:age)`
6. Vorauswertung von boolschen Ausdrücken:
    - `apfel && ()` entspricht `()`
    - `apfel && -()` entspricht `apfel`
    - `apfel || ()` entspricht `apfel`

    Oder hier nocheinmal als übersichtliche Tabelle. `op` steht hierfür für den Operator.

    | Operator | `apfel op ()` | `apfel op -()` |
    |-|-|-|
    | `&` | `()` | `apfel & -()` |
    | `&&` | `()` | `apfel` |
    | `\|` | `apfel` | `apfel \| -()` |
    | `\|\|` | `apfel` | `apfel` |
    | `^` | `apfel` | `-apfel` |

Der Query wird auf unterschiedliche Tabellen ausgeführt, also wird der Query je nach Tabelle noch
einmal extra umgebaut:

1. Wenn Confidential Database nicht verfügbar ist, wird folgendes optimiert:
    - `type:interview` zu `()` (immer)
    - `field:name` zu `()` (bei Person Tabelle)
2. Einschränkungen des Typs werden konkretisiert:
    - `type:interview` wird zu `()` bei einer Person Tabelle und zu `-()` bei einer Interview
        Tabelle
    - `attr:age` wird zu `()` bei einer Interview oder File Tabelle
    - `role:doctor` wird zu `()` bei einer Interview oder File Tabelle
3. Alle oben genannten Optimierungen werden nochmal geprüft
4. Sortieralternativen werden ausgewertet:
    - `(sort:field:name a | sort:attr:age b)` zu `sort:field:name (a | b)`
    - `(sort:field:name a || sort:attr:age b)` zu `sort:field:name (a || b)`
    - `(sort:field:name a & sort:attr:age b)` zu `sort:field:name sort:attr:age (a & b)`
    - `(sort:field:name a && sort:attr:age b)` zu `sort:field:name sort:attr:age (a && b)`
    - `(sort:field:name a ^ sort:attr:age b)` zu `a ^ b`
5. Unmögliche Sortierungen oder doppelte Sortierungen entfernt:
    - `sort:field:name:asc sort:field:name:desc a` zu `a`
    - `sort:field:name:asc sort:field:name:asc a` zu `sort:field:name:asc a`

Als nächstes werden sich die Optionen auf der oberesten Ebene angeschaut. Diese können die Auswahl
der Tabellen deutlich einschränken. Außerdem sind diese so strukturiert, dass sich diese ohne
weiteres problemlos auf der Datenbank ausführen lassen.

Danach werden sich die anderen Ausdrücke angeschaut. Für jeden wird separat nach folgenden Regeln
entschieden, ob sich dieser auf der Datenbank ausführen lässt:

- Enthält in irgend einer Tiefe eine Option: Nein
- Enthält in irgend einer Tiefe eine Auflistung mit mehr als einen Ausdruck: Nein
    > Hier könnte es Probleme mit dem Scoring geben.
- Enthält in irgend einer Tiefe den Operator `&` oder `|`: Nein
    > Auch hier gibt es ein Scoringproblem.

Alles was sich auf der Datenbank ausführen lässt, wird auch auf dieser ausgeführt. Danach geht der
Server die restlichen Ausdrücke durch, überprüft diese und berechnet einen Score.

Zum Schluss wird nach den Sortierungsregeln sortiert. Diese werden nur aus der obersten Ebene
genommen und auch in der Reihenfolge abgehandelt. Falls keine Sortierung angegeben ist, so wird
nach dem Score absteigend sortiert.

## Beispiele:

Eine Sammlung von komplexen Beispielen:

- `attr:age>=30 attr:age<=50 (field:name sort:field:name || sort:field:age:desc)`

    Sucht sich alle Person zwischen 30 und 50 heraus. Solange die Confidential Datenbank noch
    verfügbar ist, wird nach Name sortiert, andernfalls nach dem Alter absteigend.
- `(type:interview field:location~=London && field:interviewer~=Bob) || attr:location~=Londyn`

    Sucht nach allen Interviews, welche von Bob in London geführt wurden, oder allen Personen,
    dessen `location` Attribut fälschlicherweise `Londyn` enthält.
- `sort:none We need a pretty long example with many words in it`

    Sucht nach allen Datensätzen, welches eins der angegebenen Wörter enthält. Die Sortierung `none`
    sorgt dafür, dass einfach sofort ausgegeben und nicht gewartet werden soll.
- `"mail@example.com" "http://localhost.de"`

    Sucht nach Feldern, wo eine spezifische Emailadresse oder Link vorkommt. Da diese Sonderzeichen
    enthalten, müssen diese in doppelten Anführungsstrichen `"` gesetzt werden.
