enhancement
major
minor
major
minor
The migration processor invert-link-table (InvertLinkTableProcessor) destroys every link it inverts when the database is MySQL. Found in Openium/Pietro #999 (8.0.0-alpha10).
Problem
Observed on MySQL 8.4: after invert-link-table, a link Meeting 1 -> GenericObject 2 reads GenericObject 2 -> GenericObject 2. In the application a file then lists itself as its meeting, and the meeting lists no file. The log says "Inverted 3 links" and reports no error.
Expected: GenericObject 2 -> Meeting 1, as on H2.
Reproduce (plain SQL, what the processor generates):
CREATE TABLE L (SOURCE_TYPE VARCHAR(20), SOURCE_ID BIGINT, DEST_TYPE VARCHAR(20), DEST_ID BIGINT);
INSERT INTO L VALUES ('Meeting',1,'GenericObject',2);
UPDATE L SET SOURCE_TYPE=DEST_TYPE, SOURCE_ID=DEST_ID, DEST_TYPE=SOURCE_TYPE, DEST_ID=SOURCE_ID;
SELECT * FROM L; -- MySQL 8.4: GenericObject 2 GenericObject 2
Cause
InvertLinkTableProcessor#doMigration swaps the columns in one single-table UPDATE. MySQL evaluates the assignments of a single-table UPDATE from left to right with the new values ("Single-table UPDATE assignments are generally evaluated from left to right", MySQL reference manual, UPDATE statement). H2, PostgreSQL and Oracle use the old values.
Lösung
- InvertLinkTableProcessor tauscht die Enden nicht mehr in einem einzigen UPDATE, sondern liest die betroffenen Zeilen (Branch, Identifier, Revision sowie Quell- und Zielspalten, optional auf die konfigurierte Referenz eingeschränkt) und schreibt sie per JDBC-Batch mit vertauschten Enden zurück, adressiert über den Primärschlüssel der Tabelle. Damit ist das Ergebnis auf jeder Datenbank gleich; die Logausgabe "Inverted N links" bleibt.
- Absicherung im SQL-Framework: DBHelper#updateAssignmentsSeeNewValues() meldet, ob der Dialekt in einem UPDATE bereits zugewiesene Spaltenwerte in späteren Zuweisungen sieht (MySQLHelper: ja). Der Statement-Builder (AbstractStatementBuilder) weist auf einem solchen Dialekt ein UPDATE zurück, dessen Wertausdruck eine zuvor in derselben Anweisung zugewiesene Spalte liest (IllegalArgumentException mit Spaltennamen) – statt stiller Datenkorruption ein Fehler. Die Spaltenreferenzen eines Ausdrucks sammelt der allgemeine Visitor ColumnReferenceCollector. Eine Spalte, die erst nach ihrem Lesen zugewiesen wird, bleibt erlaubt. Im Engine-Quelltext gibt es keine weitere Stelle, die davon betroffen wäre.
- Tests: TestInvertLinkTableProcessor (com.top_logic.element, läuft im Multi-DB-Lauf auch auf MySQL; auf MySQL 8.4 scheitert er ohne die Korrektur mit "after inversion: b -> b") und TestUpdateAssignmentOrder (com.top_logic.basic.db, ohne Datenbank gegen MySQLHelper/`H2Helper`).
Done when
- invert-link-table on MySQL leaves each link with source and destination exchanged.
- A test that runs the processor on MySQL fails on today's engine.