Auf der Suche nach dem kleinen Unterschied: Versionsvergleich für APEX-Anwendungen Sabine Heimsath its-people GmbH Frankfurt Schlüsselworte Oracle Application Express, APEX, Versionsverwaltung, Diff-Tool, Dateivergleich Einleitung Wer im professionellen Umfeld mit Apex arbeitet, kommt oft in die Situation, - dass andere Entwickler ebenfalls Änderungen in der Entwicklungsumgebung vornehmen - dass undokumentierte Änderungen in der Produktivumgebung vorgenommen wurden aber nicht in der Entwicklungsumgebung nachgezogen werden oder - dass beim Einchecken in die Versionsverwaltung ein Kommentar gepflegt werden soll, der die aktuellen Änderungen beschreibt. In diesen Fällen kann man versuchen, sich mit bordeigenen Mitteln zu behelfen. Hier bieten sich zunächst - die Application History (Applikationsansicht -> Utilities -> Change History), - die Page History (Seitenansicht -> Utilities -> History) oder - der Applikationsvergleich (Application Builder -> Cross Application Reports -> Application Comparison) an. Die Historien stoßen allerdings – auch wenn man alle verfügbaren Spalten einblendet – schnell an ihre Grenzen. Der Application Builder loggt Aktionen von Entwicklern einerseits sehr unspezifisch und andererseits auch dann, wenn eigentlich keine Änderung stattfand. So wird zum Beispiel das Springen von einem Tab zum nächsten in einer Report Definition auf jeden Fall als Änderung protokolliert, weil dabei gespeichert wird. Dadurch entstehen sehr viele irrelevante Einträge. Die relevanten Einträge sind hingegen meistens nicht detailliert genug. Es gibt zum Beispiel keine Information dazu, welches Attribut einer Page/eines Buttons/eines Items geändert wurde, und somit gibt es auch keine Möglichkeit, den alten und den neuen Wert zu vergleichen. Der Applikationsvergleich setzt voraus, dass beide Versionen im gleichen Workspace installiert sind. Dies bietet sich innerhalb einer Entwicklungsumgebung durchaus an. Aber auch hier sind die angebotenen Informationen eher dürftig, und es werden viele falsch positive Unterschiede angezeigt, wie man schnell feststellt, wenn man versucht, vermeintliche Unterschiede aus dem Report in der Entwickleransicht des jeweiligen Objektes nachzuvollziehen. Ein Lösungsansatz Um herauszufinden, was sich tatsächlich geändert hat, bietet sich das Apex-Anwendungs-Exportfile an. Es ist leicht lesbar, wenn man PL/SQL beherrscht, und beinhaltet sämtliche Informationen, die die Apex-Applikation beschreiben. Die Objekte der Datenbankebene sind im Normalfall nicht enthalten. Man kann sie explizit als Supporting Objects exportieren, aber da sie häufig ohnehin separat versioniert werden, werden sie hier nicht betrachtet. Um den Code interpretieren zu können, muss man ein wenig „Übersetzungsarbeit“ leisten. Die Optionen, die bei der Deklaration im Application Builder angeboten werden (in der jeweils eingestellten Sprache), werden im Export mit englischen Konstantennamen ausgegeben. Die Typen der Report-Spalten werden damit z. B. zu: Standard Report Column Display as Text (based on LOV, does not save state) Display as Text (saves state) Display as Text (escape special characters, does not save state) Date Picker (Classic) Date Picker Text Field Text Area Select List (static LOV) Select List (named LOV) Select List (query based LOV) … und weitere WITHOUT_MODIFICATION TEXT_FROM_LOV DISPLAY_AND_SAVE ESCAPE_SC DATE_POPUP DATE_PICKER TEXT TEXTAREA SELECT_LIST SELECT_LIST_FROM_LOV SELECT_LIST_FROM_QUERY Abb. 1: Beispiel für Konstantennamen: Reportspalten-Typen Die Apex-Exportfiles Es gibt verschiedene Arten von Export-Files. Bei allen handelt es sich um PL/SQL-Skripte, die es ermöglichen, die jeweiligen Objekte (oder eine komplette Applikation) in einer anderen Umgebung neu anzulegen. Hier wird zunächst der Export einer gesamten Applikation behandelt (im Bild rot umrandet). Der Export einzelner Seiten beinhaltet keine zusätzliche Information, die nicht auch im Applikationsexport enthalten ist. Abb. 2: Verschiedene Exportmöglichkeiten im Apex Builder Ein Exportfile beginnt mit Informationen zur Anwendung, zum Exportzeitpunkt und zum User. Dann folgt eine Statistik über die Bestandteile der Applikation, z. B. Anzahl der Pages, der Items, der Prozesse und weitere. Danach folgen Anweisungen zum Setzen von Umgebungsvariablen. In der Zielumgebung wird eine eventuell vorhandene Instanz der Applikation gelöscht. Dieser Teil kann in den meisten Fällen ignoriert werden. Interessant wird es etwa ab Zeile 150, denn hier wird damit begonnen, die Applikation neu aufzubauen. Die Struktur des Applikationsexports Im folgenden „Listing“ findet sich die Struktur des Exports einer kleinen Anwendung mit Beschreibung der einzelnen Aufrufe. Die Parameter werden weiter unten erläutert. Beim Analysieren der Datei sollte man wissen, dass eine Applikation häufig als ‚flow‘ referenziert wird, eine Seite als ‚page‘ oder ‚step‘ und eine Region auch als ‚plug‘ bezeichnet wird. wwv_flow_api.create_flow wwv_flow_api.create_user_interface wwv_flow_api.create_plugin_setting wwv_flow_api.create_icon_bar_item wwv_flow_api.create_tab Anlegen der Anwendung wwv_flow_api.create_page wwv_flow_api.create_page_plug wwv_flow_api.create_page_plug wwv_flow_api.create_page_button wwv_flow_api.create_page_button wwv_flow_api.create_page_branch wwv_flow_api.create_page_item wwv_flow_api.create_page_process Anlegen der ersten Seite Anlegen zweier Regionen wwv_flow_api.create_page wwv_flow_api.create_page_plug wwv_flow_api.create_flash_chart5 wwv_flow_api.create_flash_chart5_series wwv_flow_api.create_page wwv_flow_api.create_report_region wwv_flow_api.create_report_columns wwv_flow_api.create_report_columns wwv_flow_api.create_report_columns wwv_flow_api.create_page wwv_flow_api.create_page_plug wwv_flow_api.create_page_button wwv_flow_api.create_page_da_event wwv_flow_api.create_page_da_action wwv_flow_api.create_list wwv_flow_api.create_list_item wwv_flow_api.create_list_item wwv_flow_api.create_list_item Anlegen eines Elements im Menü rechts oben Anlegen des Standard-Tabs Anlegen zweier Buttons Anlegen eines Branches Anlegen eines Items Anlegen eines Page Processes Anlegen eines Flash-Diagramms … mit einer Datenreihe Anlagen einer Reportregion … mit den zugehörigen Reportspalten Anlegen eines Dynamic Action Events … mit der zugehörigen Action Anlegen einer Liste … mit den zugehörigen Items wwv_flow_api.create_menu wwv_flow_api.create_template wwv_flow_api.create_button_templates wwv_flow_api.create_plug_template Anlegen verschiedener Shared Components … unter anderem Templates … für die unterschiedlichsten Objekte (Pages, Lists, Reports u. a.) wwv_flow_api.create_theme wwv_flow_api.create_shortcut wwv_flow_api.create_authentication … und andere Objekte, die auch unter diesen Namen Application Builder wiederzufinden sind Abb. 3: Struktur einer Export-Datei Zunächst wird die Applikation mit der Prozedur wwv_flow_api.create_flow angelegt. Die Parameter findet man im Application Builder unter Edit Application Properties in den Reitern Definition, Security und Globalization. Die Parameternamen sind ziemlich sprechend gewählt, so dass man sie den Elementen aus der GUI leicht zu ordnen kann. Das Beispiel unten zeigt dies für die Einstellungen Logging, Feedback, Primärsprache, Quelle der Applikationssprache und die Versionsangabe zur Sicherstellung der Kompatibilität. Abb. 4: Zuordnung Apex-Builder-Elemente zu Programmzeilen Wenn die Applikation per create_flow angelegt wurde, können die Pages angelegt werden. Wie man sieht, heißen die Parameter fast genauso wie im Application Builder: Abb. 5: Definition einer Page Auf dieser Seite befindet sich unter anderem eine Reportregion. Im gekürzten Beispiel unten erkennt man zunächst die Definition des zugrunde liegenden SQL-Statements in der Variable s, dann folgt das Anlegen des Reports mit Applikations-ID (p_flow_id), der Page-ID (p_page_id), dem Regionsnamen (p_region_name), dem Template, das hier über eine ID referenziert wird, und der Display-Sequence, die den Platz der Region in der Rendering-Reihenfolge bestimmt. Abb. 6: Beispiel: Definition einer Reportregion (gekürzt) Jede Spalte des Reports wird mit einem eigenen Aufruf der Prozedur (create_report_columns) definiert: Abb. 7: Beispiel: Definition einer Reportspalte Neben den schon beschriebenen Parametern sieht man hier die Werte, die man im Application Builder in der Spaltendefinition zu sehen bekommt, z. B. die Spaltenüberschrift (p_column_heading), die Ausrichtung (p_column_alignment und p_heading_alignment), und Art der Darstellung (p_display_as), in diesem Fall WITHOUT_MODIFICATION was der ‚Standard Report Column‘ in der GUI entspricht. Das Häkchen für ‚Show‘ wird in diesem Fall zu p_hidden_column=‘N‘. Der Vergleich Hat man zwei Export-Files ein und derselben Applikation, die zu unterschiedlichen Zeitpunkten exportiert wurden, ist der Vergleich recht einfach: Abb. 8: Beispiel: Änderungen, die an einem Page Item vorgenommen wurden Hier sieht man, dass an dem Item mehrere Veränderungen vorgenommen wurden: Außer dem Default Wert wurden die Definition der LOV und Text und Wert für den NULL-Wert geändert. Ein weiterer Fall: Abb. 9: Änderungen, die an einer Page vorgenommen wurden Anscheinend wurde hier eine Änderung wieder rückgängig gemacht – oder der Nutzer hat zwischen den Tabs geblättert und dadurch das erneute Abspeichern der bestehenden Werte ausgelöst. Hat man zwei Export-Files einer Applikation vorliegen, zum Beispiel aus zwei verschiedenen Umgebungen, sieht ein einfacher Textvergleich mit einem Diff-Tool am Anfang etwa so aus: Abb. 10: Textvergleich ohne Konfiguration: Screenshot aus Beyond Compare Das Problem fällt sofort ins Auge: Allein durch den Export und den Import unter neuer ApplikationsID oder in einem anderen Workspace, verändern sich die IDs aller Objekte, so dass man fast nur rote Zeilen sieht. Mit einem Diff-Tool, das Reguläre Ausdrücke beherrscht, kann man diese Zeilen ausblenden, um sich auf die wichtigen Unterschiede konzentrieren zu können. In diesem Fall wurde Beyond Compare 3.0 verwendet. Konfiguration des Diff-Tools Um in Beyond Compare die Markierung der „irrelevanten“ Zeilen zu unterdrücken, sind unter Menüpunkt 'Session' -> Untermenüpunkt 'Session Settings' -> Reiter 'Importance' -> Button 'Edit Grammar' einige Einträge vorzunehmen. Im Reiter ‘Grammar’ sind standardmäßig schon einige Einträge für SQL-Dateien vorhanden (siehe unten). Die neu anzulegenden Einträge sind rot eingerahmt: Abb. 11: Beschreibung der nicht relevanten Zeilen mit regulären Ausdrücken Hierfür klickt man den ‘New…’ Button, und legt dann nacheinander die folgenden Einträge an. Die Bezeichnungen sind frei wählbar. Ein einheitliches Präfix vereinfacht natürlich die Selektion. Apex_ID_Line \d{15,}\s?\+\s?wwv_flow_api.g_id_offset Apex_Prompt_Line prompt .+ \d+ Apex_Upd_Line \s{0,5}.p_last_upd_yyyymmddhh24miss => '\d+' Diese Einträge dienen dazu, die Unterschiede zu definieren, die beim Vergleich als unwichtig eingestuft werden sollen. Nach dem Anlegen müssen im Reiter ‘Importance’ die Häkchen vor den neu angelegten Apex-Einträgen entfernt werden, damit Beyond Compare weiß, dass sie als unwichtig einzuordnen sind: Abb. 12: Deselektion der nicht relevanten Zeilen Problematisch wird es, wenn auf einer Seite sehr viele Pages dazugekommen sind (oder gelöscht wurden); denn dann ist es für das Tool schwierig, die Dateien korrekt auszurichten.1 Man kann Beyond Compare bei der Ausrichtung unterstützen, indem man bei der Definition der Grammatik bestimmte Zeilen gewichtet. Für Apex-Exporte bietet es sich zum Beispiel an, die PromptZeile vor jeder Seitendefinition sehr stark zu gewichten, da die Seitennummern während der Lebenszeit einer Anwendung im Normalfall keinen großen Änderungen unterworfen sind. Diese Gewichtung kann auch im Reiter ‚Grammar‘, im unteren Teil ‚Line weights‘ über ‚New...‘ oder ‚Edit...‘ vorgenommen werden: Abb. 13: Ankerpunkte zum Ausrichten festlegen Text matching: ^prompt ...PAGE \d+: Ganz wichtig: Nicht vergessen, im unteren Teil des Fensters ‚Session Settings‘ festzulegen, dass die Gültigkeit sich auf jede Session bezieht, damit man sich die Arbeit nicht mehrfach machen muss: Abb. 14: Text Compare – Session Settings dauerhaft verfügbar machen Die Konfiguration des Diff-Tools ist damit beendet. 1 Dieses Problem tritt nicht auf, wenn die Sourcen grundsätzlich mit dem ApexSplitter aufgeteilt werden. Wenn nun die ‘unwichtigen’ Einträge mit dem Button ausgeblendet werden, bekommen wir ein viel entspannteres Bild. Und plötzlich kann man die tatsächlichen Änderungen auf einen Blick erkennen, so wie im Beispiel unten: Abb. 15: Textvergleich ohne Konfiguration: Screenshot aus Beyond Compare Was sieht man? Beispiel 1: Hier wurde ein Interaktiver Report angepasst und als Primary abgespeichert. Vergleicht man den vorherigen Export mit dem Export nach der Änderung, kann man erkennen, dass die Spalte ADDRESS offensichtlich ausgeblendet wurde, da sie auf der rechten Seite fehlt (die Variable rc1 wird als Parameter p_report_columns übergeben) und dass eine Sortierung angewendet wurde (p_sort_column_1 und p_sort_direction_1): Abb. 16: Interactive Report: Ausgeblendete Spalte und Sortierung Nimmt man jetzt noch einen Filter hinzu, wird es richtig interessant, denn dann erscheint auf der rechten Seite ein neues Objekt, die Filterbedingung (worksheet_condition vom Typ FILTER): Abb. 17: Interactive Report: Filterbedingung Man erkennt den Spaltennamen, auf den gefiltert wird (p_column_name =>'STATE'), den Operator (p_operator =>'contains') und den Vergleichswert (p_expr =>'MO',). Außerdem wird sogar die daraus generierte SQL-Bedingung angegeben p_condition_sql =>'upper("STATE") like ''%''||upper(#APXWS_EXPR#)||''%''' und man sieht, wie Apex die benutzerfreundliche Darstellung realisiert. Der Ausdruck p_condition_display =>'#APXWS_COL_NAME# #APXWS_OP_NAME# ''MO'' wird durch Substitution in der GUI zu ', Beispiel 2: In diesem Beispiel wurden Änderungen an einer Select-Liste vorgenommen. Man sieht, dass das Item P6_CATEGORY wahrscheinlich verschoben wurde (kleinere Nummer in p_item_sequence), was aber erst im Zusammenhang mit den anderen Sequence-IDs verifiziert werden kann. Des Weiteren wurde ein NULL-Wert erlaubt (p_lov_display_null=> 'YES'), und der Anzeige- und Rückgabe-Wert für diesen definiert (p_lov_null_text und p_lov_null_value). Ein Bedingung (p_display_when_type=>'CURRENT_PAGE_EQUALS_CONDITION') sorgt dafür, dass das Item nur auf Seite 6 angezeigt wird (was bei einem Item auf Page 0 natürlich mehr Sinn ergeben würde). Im unteren Teil kann man sehen, dass zusätzlich noch Quick Picks angelegt wurden, und zwar zwei Stück jeweils mit Label und Value (p_quick_pick_label_* und p_quick_pick_value_*). Abb. 18: Diverse Änderungen an einem Page Item Hier wurde der Typ eines Items von ‚Radio Group‘ auf ‚Select List‘ geändert. Außerdem wurde keine zentral definierte LOV verwendet (erkennbar an der ID), sondern eine statisch. (p_lov=> 'STATIC2'): Abb. 19: Änderung der LOV-Definition an einem Page Item Probleme und Grenzen Nicht immer gelingt es Beyond Compare, die beiden Dateien passend auszurichten. Dann kann man dies manuell tun, indem man eine Zeile auf der linken Seite auswählt, [F7] drückt und dann mit der Maus die Zeile auf der rechten Seite anklickt, die mit der linken Zeile ausgerichtet werden soll. Die Apex-Export-Files sind gut strukturiert, wurden aber natürlich nicht mit dem primären Ziel erstellt, dass sie besonders gut zu vergleichen sein sollten. Daher kommt es manchmal zu Effekten wie dem, das längerer SQL- und PL/SQL-Text unterschiedlich umgebrochen wird, wie in diesem Beispiel Abb. 20:Ungleicher Umbruch eines langen Statements Als erfahrener Entwickler wird man das relativ leicht erkennen können, aber man kann auch Abhilfe schaffen, indem man nach Möglichkeit SQL in Views und PL/SQL in Packages auslagert, was auch andere Vorteile hat. Der Vergleich von Attributen – zum Beispiel von Items – ist relativ zuverlässig. Schwierig ist es festzustellen, ob sich eine Template-Zuordnung geändert hat, da das Template nicht über einen Namen, sondern über eine ID referenziert wird: p_plug_template=> 12319517529116625534+ wwv_flow_api.g_id_offset, Vergleichen wir zwei Versionen einer Applikation, die mit der gleichen Applikations-ID aus dem gleichen Workspace exportiert wurden, sind die IDs der Objekte gleich, eine Änderung fällt also auf. (Wenn die entsprechenden Zeilen eingeblendet sind.) Handelt es sich allerdings um zwei Versionen mit unterschiedlichen Applikations-IDs oder aus unterschiedlichen Workspaces, sind die IDs aller Objekte unterschiedlich. Somit sind „echte“ Änderungen einer Template-Zuordnung für uns nicht mehr erkennbar. Fazit Der Vergleich von Textdateien kann einem fast alle Unterschiede zwischen Applikationen zeigen; in manchen Fällen muss man aber trotzdem im Application Builder nachsehen, was diese Unterschiede bedeuten. Dafür werden einem sehr genau die Stellen gezeigt, an denen man suchen muss, was ein großer Vorteil gegenüber den bisherigen Möglichkeiten innerhalb von Apex ist. Beyond Compare bietet durch die Regulären Ausdrücke viele Möglichkeiten, sich den Vergleich genauso zu konfigurieren, wie man ihn braucht. Technisches Die Beispiele stammen aus Apex 4.2.2 und 4.2.3. Bei dem verwendeten Diff-Tool handelt es sich um Beyond Compare 3.3.8. Kontaktadresse: Sabine Heimsath its-people GmbH Lyoner Str. 44-48 D-60528 Frankfurt am Main Telefon: Fax: E-Mail Internet: +49 (0) 69-247 521 00 +49 (0) 69-247 521 021 [email protected] www. its-people.de