major
#29800
Model patch of an existing database fails when a class gains a generalization and overrides its attributes in the same change
Adding a generalization to an existing class and, in the same model change, overriding attributes of the new generalization cannot be applied to an existing database (TL 8.0.0-alpha10): the automatic model patch at startup applies the overrides before the generalization.
Reproduction
- Start an application with a class m:B extending tl.model:TLObject and an interface m:A with a property x; create some data.
- Change the model: m:B gets the generalization m:A and overrides x` (<property name="x" override="true" …>`, e.g. to annotate a label or options).
- Start again with the existing database.
Expected: the patch adds the generalization and then the override. Actual: startup aborts with
Override failed. m:B#x is declared to be an override, but is not overriding anything (m:B extends tl.model:TLObject.)
A fresh database is set up correctly with the same model.
Seen in E-Home: ehome.physical:ParameterSetting became an ehome.operational:ParameterValue and overrode number, decimal and text ("Override failed. ehome.physical:ParameterSetting#number is declared to be an override, but is not overriding anything (ehome.physical:ParameterSetting extends tl.model:TLObject.)"). The local database had to be reset. The overrides were later removed, so the app no longer depends on this.
Expected fix: the model patch orders the changes so that new generalizations are applied before attribute overrides of the same class (or applies the patch of a class in two passes).
Lösung
Ursache. Die Elemente eines Modell-Patches werden vor dem Anwenden nach einer festen Priorität sortiert (Priority in com.top_logic.element.model.diff.apply). Dort stand das Anlegen überschreibender Attribute (CREATE_TYPE_PART_OVERRIDE) vor dem Hinzufügen von Generalisierungen (CREATE_OR_MOVE_GENERALISATION). Ein überschreibendes Property an einer bestehenden Klasse wird beim Anwenden sofort angelegt und dabei gegen die aktuellen Generalisierungen der Klasse geprüft (ModelResolver.createProperty), so dass die Prüfung fehlschlägt, wenn die Generalisierung erst durch ein späteres add-generalization hinzukommt. Referenzen und die Attribute neu angelegter Typen werden erst in complete() angelegt und sind nicht betroffen.
Änderungen.
- CREATE_OR_MOVE_GENERALISATION wird vor CREATE_TYPE_PART_OVERRIDE (direkt nach CREATE_TYPE_PART) angewendet. Die SQL-Migrationsprozessoren werden in Anwendungsreihenfolge erzeugt, so dass auch dort add-generalization vor dem Anlegen des Overrides steht.
- Ein bereits vorhandenes eigenes Attribut, das durch eine neue Generalisierung zu einer Überschreibung wird (bzw. durch Entfernen einer Generalisierung keine mehr ist): isOverride() ist aus der gespeicherten Definition (definition-Referenz) abgeleitet; das persistente Modell (auf dem der Start-Patch direkt angewendet wird) berechnet sie beim Ändern der Generalisierungen nicht neu. ApplyModelPatch berechnet beim Anwenden von add-generalization / remove-generalization die Definitionen der lokalen Attribute der Klasse und ihrer Spezialisierungen neu und erzeugt für jedes geänderte Attribut einen mark-override-Migrationsprozessor (MarkTLTypePartOverride). Der Prozessor erhält die Option copy-values: beim nachträglichen Markieren behält das Attribut seine eigenen Eigenschaften (mandatory usw.), beim Anlegen eines Overrides werden sie wie bisher von der Definition übernommen; ist definition das Attribut selbst, wird es als eigene Definition (keine Überschreibung) markiert.
- Dabei gefunden: Im transienten Modell (TLClassImpl) wurden die Definitionen beim Hinzufügen einer Generalisierung berechnet, bevor die Generalisierung in der Liste stand (ObservableList.beforeAdd), so dass ein Hinzufügen die Definitionen nie aktualisierte (das Entfernen schon). ObservableList hat nun einen afterAdd-Hook, TLClassImpl berechnet die Definitionen darin.
- Tests: TestModelPatch (transient, persistent direkt gepatcht sowie über die SQL-Migration) für beide Fälle in beide Richtungen, TestTLModelImpl für das transiente Modell.