Seit einigen Jahren arbeite ich mit mehreren Entwicklern, Testern und Benutzern an einem umfangreichen Softwareprojekt. Bei der Verfolgung von Bugs und Problemen leistet uns der Mantis Bug Tracker gute Dienste. Mantis basiert ursprünglich auf MySQL als Datenbank-Backend, allerdings ist in jüngerer Zeit die Unterstützung weiterer Datenbanken hinzugekommen.
Der zu diesem Zweck eingesetzte MySQL-Server fristet ein Exotendasein, da alle anderen Datenbankanwendungen im Intranet auf PostgreSQL basieren. So entstand der Wunsch, mit dem Upgrade auf eine neuere Version des Bugtrackers auch auf PostgreSQL als Backend umzusteigen - natürlich sollten dabei keine Daten verloren gehen. Was sich sehr simpel anhört (SQL ist ja schließlich SQL) gestaltete sich jedoch gar nicht so einfach…
Bis alles auf dem PostgreSQL-Server lief, war viel Tüftelarbeit notwendig. Die beteiligten Computer (Server und Clients) liefen dabei alle unter Linux (Debian, openSUSE), wahrscheinlich funktionieren die Schritte aber grundsätzlich auch unter Windows (sed, grep und Konsorten machen das Leben allerdings um einiges leichter). Hier die einzelnen Schritte im Überblick:
Zunächst aktualisiere ich die vorhandene (auf MySQL basierende) Mantis-Installation auf die neueste Version, denn damit wird auch die Datenbankstruktur auf den neuesten Stand gebracht. Die dafür notwendigen Schritte sind auf der Mantis-Homepage dokumentiert und lassen sich wie folgt zusammenfassen:
Nun müssen die Daten aus der MySQL-Datenbank exportiert werden. Dafür würde man normalerweise das praktische Tool mysqldump verwenden. Das klappt hier jedoch nicht, da die Datenbankstrukturen nicht kompatibel sind. Vor allem bin ich über die folgenden Unterschiede gestolpert:
Ein guter Startpunkt, um sich mit dieser Thematik vertraut zu machen, ist zum Beispiel dieser Wikibooks-Artikel. Es spricht nichts dagegen, einen MySQL-Dump direkt zu editieren und alle Umwandlungen manuell durchzuführen - wenn man viel viel Zeit hat. Auf der Wikibooks-Seite findet sich allerdings auch der Link zu mysql2postgres, einem Tool, das die entsprechenden Änderungen automatisch vornimmt (Download-Link).
Es handelt sich um ein Ruby-Skript, für das ich die folgenden Komponenten (unter openSUSE) installieren musste:
Leider passte das Skript noch nicht so 100%ig für meine Zwecke, denn es wandelt lediglich tinyint(1)-Felder in boolean-Felder um. Bei meiner Mantis-Installation waren die Wahrheitswert-Felder jedoch vom Typ tinyint(4). Also musste ich das Skript ein wenig modifizieren, damit auch tinyint(4)-Felder zu boolean-Feldern umgewandelt werden.
Ich kenne mich (noch) überhaupt nicht mit Ruby aus, aber ziemlich am Anfang des Skripts gibt es einen Block (ab Zeile 90), der offensichtlich für die Übersetzung der Datentypen zuständig ist:
[...] def convert_type(type) case type when /bigint/ "bigint" when "bit(1)" "boolean" when "tinyint(1)" "boolean" when /tinyint/ "tinyint" when /int/ "integer" when /varchar/ "varchar" when /char/ "char" when /decimal/ "decimal" when /double/ "double precision" else type end. end [...]
Diesen Block konnte ich jedoch auch ohne Ruby-Kenntnisse um einen Abschnitt für tinyint(4) ergänzen:
Modifizierten Quelltext herunterladen: mysql2psql.modified
[...] when "tinyint(1)" "boolean" when "tinyint(4)" #<----- neu eingefügt tinyint(4) -> boolean "boolean" #<----- neu eingefügt tinyint(4) -> boolean when /tinyint/ "tinyint" [...]
Nun muss die Datei config.yml.sample nach config.yml kopiert und angepasst werden, indem man dort die Verbindungsdaten zur MySQL-Datenbank sowie einen Dateinamen für die Ausgabedatei (keine direkte Übertragung auf den PostgreSQL-Server!) einträgt. Außerdem muss die Tabelle mantis_bug_file_table vorerst ausgeschlossen werden, denn die Binärdaten müssen später gesondert behandelt werden. Einträge in eckigen Klammern [] müssen durch eigene Werte ersetzt werden.
mysql: hostname: [Adresse des MySQL-Servers] port: 3306 socket: username: [Benutzername] password: [Passwort] database: [Mantis-Datenbank] destination: # if file is given, output goes to file, else postgres file: mantis_table_data_raw.sql postgres: hostname: port: username: password: database: # if tables is given, only the listed tables will be converted. leave empty to convert all tables. #tables: #- table1 #- table2 #- table3 #- table4 # if exclude_tables is given, exclude the listed tables from the conversion. exclude_tables: - mantis_bug_file_table #- table6
Wenn nun das modifizierte Ruby-Skript ausgeführt wird, dann dauert es nicht lange, bis der PostgreSQL-kompatible Datenbank-Dump in die Datei mantis_table_data.sql gewandert ist.
Obwohl das Skript die Datenbankstruktur ziemlich gut erkennt und umwandelt, mochte ich mich beim Anlegen der Tabellen, Indizes, Sequenzen und Constraints lieber auf die Installationsroutinen von Mantis verlassen. Dadurch wird sichergestellt, dass zumindest die Struktur so ist wie sie sein sollte und nichts beim Konvertieren verloren geht oder falsch interpretiert wird. Zunächst kopiere ich jedoch die vom Skript erzeugte Datei mantis_table_data_raw.sql in eine Arbeitskopie mantis_table_data.sql, um in dieser Kopie meine Anpassungen vornehmen zu können.
cp mantis_table_data_raw.sql mantis_table_data.sql
Der Datenbank-Dump ist so strukturiert, dass an erster Stelle alle SQL-Anweisungen stehen, mit denen Tabellen, Sequenzen und Indizes angelegt werden. Erst danach kommen alle Anweisungen zum Füllen der Tabellen. Da ich nur Daten importieren möchte (die Datenbank Struktur soll Mantis anlegen - siehe unten), lösche ich aus der Datei mantis_table_data.sql alle Zeilen bis zur ersten COPY-Anweisung (diese fängt bei mir mit COPY „mantis_bug_history_table“ […] an).
Beim Einrichten der Datenbank fügt Mantis in einige Tabellen bereits erste Datensätze ein. Die entsprechenden Tabellen müssen vor dem Einfügen der neuen Daten geleert werden, weil es sonst zu Konflikten (doppelte ID's) kommt. Die folgenden Zeilen müssen deshalb an den ANFANG der Datei mantis_table_data.sql kopiert werden:
DELETE FROM mantis_category_table; DELETE FROM mantis_config_table; DELETE FROM mantis_plugin_table; DELETE FROM mantis_user_table;
Aber wozu gibt es denn den sed? Der Befehl
sed -i '1i\DELETE FROM mantis_category_table;\nDELETE FROM mantis_config_table;\nDELETE FROM mantis_plugin_table;\nDELETE FROM mantis_user_table;\n' mantis_table_data.sql
erledigt das direkt von der Kommandozeile aus.
Als nächstes suche ich aus der originalen Datei alle Sequenz-Zählerstände heraus und füge die SQL-Anweisungen an meine Arbeitskopie an. Natürlich nicht per Hand sondern mit dem Befehl
grep "SELECT pg_catalog.setval" mantis_table_data_raw.sql >> mantis_table_data.sql
So, nun zu dem Teil, der mir die meisten Probleme bereitet hat. Mantis speichert hoch geladene Dateien direkt als Binärdaten in der Datenbank. Diese ließen sich, zumindest in meinem Fall, nicht direkt mit dem mysql2postgres-Skript von blob nach bytea konvertieren, denn irgendwie kam immer etwas mit den Byte-Sequenzen durcheinander.
Erst nach langen Experimenten fand ich heraus, wie ich die Tabelle mittels mysqldump extrahieren muss und welche Veränderungen an der SQL-Syntax mit sed vorgenommen werden müssen. Nämlich:
Wenn man das weiß, ist der Rest schnell mit mysqldump und sed erledigt (Serveradresse, [Benutzername], [Passwort], [Datenbank] entsprechend ersetzen):
mysqldump -h[Serveradresse] -u[Benutzername] -p[Passwort] --database [Datenbank] --table mantis_bug_file_table --skip-extended-insert --complete-insert --hex-blob | sed -n -e "/INSERT /s/\`//g" -e "s/',0x\([0-9A-Fa-f]*\),/',E'\\\\x\1\',/g p" >> mantis_table_data.sql
… und schon befindet sich auch der Inhalt der Tabelle mantis_bug_file_table in der Dump-Datei. Da die hexadezimale Darstellung der Binärdaten sehr viel Platz verschwendet, kann die Datei nun schon ziemlich groß sein (in meinem Fall mehr als 40 MB).
Zu guter Letzt wird noch der Zähler für die Sequenz mantis_bug_file_table_id_seq gesetzt. Diese war in bisher nicht im Dump enthalten, da die Tabelle mantis_bug_file_table beim mysql2postgres-Export ausgelassen wurde ([Serveradresse], [Benutzername], [Passwort], [Datenbank] entsprechend ersetzen).
echo "SELECT pg_catalog.setval('mantis_bug_file_table_id_seq', $(expr $(mysql -h[Serveradresse] -u[Benutzername] -p[Passwort] [Datenbank] --skip-column-names --batch -e "SELECT MAX(id) FROM mantis_bug_file_table;") + 1), true);" >> mantis_table_data.sql
Wie bereits erläutert, überlasse ich das Anlegen der Datenbankstrukturen auf dem PostgreSQL-Server Mantis. Dafür muss die Datei config_inc.php so angepasst werden, dass nun die PostgreSQL-Datenbank anstatt der MySQL-Datenbank verwendet wird (dokumentiert auf der Mantis-Webseite). Die Datenbank und der Datenbankbenutzer sollten bereits vorhanden sein. Danach muss man lediglich http://[webserver]/admin/install.php aufrufen, die entsprechenden Verbindungsdaten eintragen und den Rest erledigt Mantis wie von Zauberhand.
Nun ist das Ziel fast erreicht. Alle relevanten Daten befinden sich in der Datei mantis_table_data.sql. Mit dem folgenden Befehl werden sie an den PostgreSQL-Server übertragen:
psql -h [Serveradresse] -d [Datenbank] -U [Benutzername] -W --log-file log1.log -f mantis_table_data.sql &>log2.log
Fehler werden, falls welche auftreten, in der Datei log2.log dokumentiert. Bei mir waren (seltsamerweise) einige Bug-Beschreibungen zu lang und passten nicht in den varchar(128)-Datentyp. Das konnte ich aber leicht im Dump anpassen, indem ich die entsprechenden Beschreibungen kürzte. Zu beachten ist, dass wenn in einer COPY-Anweisung ein Fehler auftritt die Daten der gesamten Tabelle nicht eingelesen werden.
Da man den Dump auch nicht einfach ein zweites mal einlesen sollte (doppelte ID's etc.), habe ich die Datenbank stets gelöscht, neu angelegt und den geänderten Dump neu eingelesen bis keine Fehler mehr gemeldet wurden.
Falls MySQL- und PostgreSQL-Server unterschiedliche Zeichenkodierungen verwenden, so werden unter Umständen Umlaute nicht korrekt dargestellt. In diesem Fall sollte man an den Anfang der Dump-Datei die Anweisung
SET client_encoding = '[Kodierung der MySQL-Datenbank]';
setzen. In meinem Fall lief der MySQL-Server mit „Latin1“ und die PostgreSQL-Datenbank mit „UTF8“. Also setzte ich die Anweisung SET client_encoding = 'latin1'; an den Anfang meiner Datei.
Danach war es vollbracht! Ich konnte ich mich normal bei Mantis einloggen, alle Daten waren verfügbar und ich konnte ganz normal weiter arbeiten.