Gibt es an einem Objekt ein Attribut mit TL-Script Auswertung (z.B. eine Speichervorschrift), in der die Berechnung über einen Filter ausgeführt wird, kann die Auswertung dieses Ausdrucks zu einem "null"-Fehler führen.
Caused by: java.lang.IllegalArgumentException: Literals with value 'null' are not allowed.
Das kann einerseits zu einem Problem führen, wenn man mit anderen Attributen rechnet, die null sein können. Andererseits aber auch, wenn man z.B. ein Formular eines Objektes Editieren möchte.
Im zweiten Fall gibt es dann keine konkrete Instanz und dementsprechend ist das andere Attribut, mit dem man rechnen möchte null. Dadurch ist dann das Formular leer, man kann es nicht editieren und es tritt der Fehler oben auf.
Beispiel
Speichervorschrift an einem Attribut mit folgendem TL-Script Code:
obj -> {
attr1 = $obj.get(`my.module:Object1#attr1`);
all(`my.module:Object2`)
.filter(o2 ->
$attr1 >= $o2.get(`my.module:Object2#a`)
&&
$attr1 <= $o2.get(`my.module:Object2#b`)
).singleElement()
.get(`my.module:Object2#attr2`);
}
Der Fehler entsteht durch die Auswertung im Filter und kann durch 2 Fälle ausgelöst werden:
- obj == null, dann ist auch attr1 == null
- obj != null, attr1 ist nicht mandatory und im aktuellen obj nicht belegt.
Der 1. Fall tritt z.B. auf, wenn man ein Formular für den Typ von obj bearbeiten möchte, denn hier gibt es kein konkretes obj.
Der Auslöser des Fehlers, kann in der Script Konsole Nachgestellt werden:
- null >= 2 -> null: Ein Vergleich mit null ergibt immer null, unabhängig von der Art des Vergleichs und des anderen Werts.
- null && null -> false: Die Verkettung (AND) von null mit beliebigem 2. Wert ergibt immer false.
Der Ausdruck oben, hat bis mindestens 8.0.0-alpha5 zu null ausgewertet, da der Ausdruck im Filter dann false war.\\ Nach Migration zu 8.0.0-alpha8 tritt aber stattdessen der weiter oben beschriebene Fehler auf, da die Auswertung des Ausdrucks in .filter() nun direkt auf der Datenbank stattfindet.
Ursache
Seit #29088 werden Größenvergleiche (<`, `<=, >`, `>=) in einem filter() über all() an die Datenbank delegiert (CompiledCompareOp). Eine äußere Variable (im Beispiel $attr1) wird erst bei der Auswertung als Literal in die Datenbank-Abfrage eingesetzt. Ist ihr Wert null, entsteht ein null-Literal, das die Knowledge-Base ablehnt. Gleichheitsvergleiche (CompiledEquals) behandelten diesen Fall bereits, Größenvergleiche nicht.
Zusätzlich lieferte die Datenbank-Auswertung bei nicht belegten Attributen unter einer Negation ein anderes Ergebnis als die Auswertung im Speicher: In SQL ist NOT (NULL >= 150) unbestimmt, das Objekt fällt aus dem Ergebnis. In TL-Script ist !($o.a >= 150) bei leerem a` dagegen `true, das Objekt gehört zum Ergebnis. Betroffen waren z.B. !($o.a >= 150), !($o.a > 150 && $o.b > 0), !(($o.a >= 150) == false) und !($v && $o.a > 150).
Lösung
Die TL-Script-Semantik von null bleibt unverändert: Ein Vergleich mit null ergibt null, das in einem booleschen Kontext (&&, !`, Filter) als `false gilt, die Negation von null ist true, == behandelt null als gewöhnlichen Wert. Die Datenbank-Auswertung eines Filters liefert jetzt in allen Fällen dasselbe Ergebnis wie die Auswertung im Speicher:
- Ein Teilausdruck des Filters (CompiledValue) kann außer seinem Datenbank-Ausdruck auch eine zweiwertige Bedingung (nie SQL-unbestimmt), einen Null-Test und seinen Wert als Operand eines Vergleichs erzeugen. Logische Operatoren und Vergleiche (CompiledPredicate) sind nie null.
- Eine Negation wird aus der zweiwertigen Bedingung gebildet; ein Größenvergleich wird dort mit Nicht-Null-Prüfungen seiner Operanden abgesichert.
- Ein Größenvergleich mit einer äußeren Variable mit Wert null wird zu false, eine boolesche Variable mit Wert null ebenfalls; für andere Typen wird der Teilausdruck im Speicher ausgewertet.
- Gleichheitsvergleiche prüfen einen null-Operanden über den Null-Test; ein als Wert verwendetes Vergleichsergebnis (($o.a >= 150) == false) wird im Speicher ausgewertet.
Test
- test.com.top_logic.model.search.expr.TestSearchExpression.testKBNullSemantics(): vergleicht für äußere Variablen (null und belegt, boolesch null/`true`/`false`), negierte Vergleiche über nicht belegte Spalten und als Wert verwendete Vergleichsergebnisse das Ergebnis der Datenbank-Auswertung mit dem der Auswertung im Speicher.