169 22 10MB
German Pages 289 [292] Year 2003
Perl Anwendungen und fortgeschrittene Techniken von Helmut Seidel unter Mitwirkung von Prof. Dr. Jürgen Schröter Fachhochschule Landshut
Oldenbourg Verlag München Wien
Titelabbildung: Peter Kornherr Ebenfalls bei Oldenbourg erschien von den Autoren Schröter und Seidel der einführende Band „Perl - Grundlagen und effektive Strategien", ISBN 3-486-258893, der die wichtigsten Sprachelemente von Perl (Skalare, Arrays, Hashes, Referenzen, Formate, Reguläre Ausdrücke) erläutert und sehr intensiv auf die Objektorientierung und komplexe Datenstrukturen in Perl eingeht.
Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über abrufbar.
© 2004 Oldenbourg Wissenschaftsverlag GmbH Rosenheimer Straße 145, D-81671 München Telefon: (089) 45051-0 www.oldenbourg-verlag.de Das Werk einschließlich aller Abbildungen ist urheberrechtlich geschützt. Jede Verwertung außerhalb der Grenzen des Urheberrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Das gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und die Einspeicherung und Bearbeitung in elektronischen Systemen. Lektorat: Christian Kornherr Herstellung: Rainer Hartl Umschlagkonzeption: Kraxenberger Kommunikationshaus, München Gedruckt auf säure- und chlorfreiem Papier Druck: R. Oldenbourg Graphische Betriebe Druckerei GmbH ISBN 3-486-25902-4
Inhaltsverzeichnis 1
Vorwort
1
1.1
Ein Wort zu freier Software
2
2
Dank
5
3
Perl-Pakete
7
3.1
Was sind Perl-Pakete?
9
3.2
Deklaration von Paketen
10
3.3 3.3.1 3.3.2 3.3.3
Einbinden von Paketen mit use und r e q u i r e Die Anweisung use Die Anweisung r e q u i r e Wo Perl nach Paketen sucht
11 11 12 13
3.4
Zugriff auf Bezeichner anderer Pakete
14
3.5
Pakete initialisieren und terminieren
17
3.6
Symbole aus anderen Symboltabellen importieren
20
3.7
Autoloading
23
4
M o d u l e in Perl
27
4.1
Eigene Perl-Module erzeugen
27
4.2 4.2.1 4.2.2
Arbeiten mit Perl-Modulen Arbeiten mit Perl-Modulen in der Standard-Distribution Arbeiten mit Perl-Modulen in der ActiveState-Distribution
28 28 32
5
Perl mit C - M o d u l e n erweitern
35
5.1
Ein C-Modul entwickeln
36
6
Objektorientierte Programmierung
41
6.1 6.1.1 6.1.2 6.1.3
Grundlagen der Objektorientierten Programmierung Objekte, Eigenschaften und Methoden Kapselung von Daten Klassen, Objekte und Instanzen
42 42 44 45
VI 6.1.4
Inhaltsverzeichnis
6.1.5
Instanzmethoden und -Eigenschaften, Klassenmethoden und -Eigenschaften Vererbung
45 47
6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.2.8
Objektorientierte Programmierung mit Perl Klassen mit Perl erstellen Anlegen und Aufrufen von Eigenschafts-Methoden Methoden, die den Verhaltensweisen eines Objektes entsprechen Klassen- und Instanzmethoden, Klassen- und Instanzvariablen Vererbung Überschreiben von Methoden Aufrufen von überschriebenen Methoden der Oberklasse Und die Polymorphie?
49 49 52 57 62 63 67 68 69
7
Datenbank-Programmierung mit P e r l / D B I
71
7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5
Grundlagen des Datenbank-Designs Relationale Datenstrukturen Grundregeln des Datenbankdesigns Normalformlehre Beziehungsstrukturen Aufnahmestruktur
72 72 74 75 77 80
7.2 7.2.1 7.2.2 7.2.3 7.2.4 7.2.5 7.2.6 7.2.7 7.2.8
Die Abfragesprache SQL Der SQL-Standard Abfragen mit SQL erstellen Einfache Abfragen mit dem Befehl SELECT Abfrageeinschränkung mit WHERE Die Vergleichs-Operatoren von SQL Die Verknüpfung von Relationen (JOIN) Funktionen Datenbanken mit SQL-Befehlen pflegen
81 81 82 83 86 88 90 92 93
7.3 7.3.1
Die Programmierung der Datenbank-Schnittstelle Perl/DBI Das Konzept von Perl/DBI
96 96
7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.4.6 7.4.7 7.4.8 7.4.9 7.4.10 7.4.11 7.4.12
Arbeiten mit DBI/DBD Vorhandene Datenbank-Treiber abfragen Verbindung zu einer Datenbank aufbauen Die Datenbankverbindung schließen Ausführen einfacher SELECT-Abfragen Datensätze aus der Abfrage lesen Datensätze des Resultsets einzeln übernehmen Metadaten zum Resultset ermitteln Den ersten Datensatz abfragen Einzelne Spalten abfragen Das Resultset als Ganzes übernehmen Abfragen optimieren Datenbanken mit SQL-Statements pflegen
98 98 99 104 105 106 107 114 119 120 123 126 129
Inhaltsverzeichnis
VII
7.4.13
Metadaten zur Datenbank ermitteln
132
8
C G I - P r o g r a m m i e r u n g mit P e r l
135
8.1 8.1.1 8.1.2 8.1.3 8.1.4
Einrichten und Konfigurieren des Apache-Webservers Den Apache-Webserver auf einem Linux-System installieren Den Apache-Webserver auf einem Windows-System installieren Test Ihrer Apache-Installation Den Apache-Webserver für die Ausführung von Perl/CGI-Skripten konfigurieren
136 136 137 137
8.2
Was ist CGI?
139
8.3 8.3.1 8.3.2 8.3.3 8.3.4 8.3.5 8.3.6 8.3.7
Ein kurzer HTML-Exkurs Das HTML-Skelett Kommentare Text Das textliche Erscheinungsbild Abschnittsunterteilungen, Absätze und Zeilenumbrüche Überschriften Querlinien
140 140 141 141 141 142 143 143
8.4
Hyperlinks
143
8.5
Inline-Grafiken
144
8.6 8.6.1
Listen Tabellen
145 145
8.6.2
Umlaute und Sonderzeichen
146
8.7
Ein einfaches CGI-Beispiel zur Einführung
147
8.8 8.8.1
Ein etwas umfangreicheres Beispiel Die CGI-Umgebungsvariablen
151 152
8.9 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 8.9.6
Eingabe-Formulare mit Perl und CGI erstellen Das -Tag von HTML Das -Tag Mehrzellige Textfelder Das -Tag Formulardaten auslesen Ein SQL-Monitor
154 155 156 160 161 163 163
8.10
Perl/CGI-Anwendungen mit Hilfe von Templates modularisieren
171
8.11 8.11.1 8.11.2 8.11.3 8.11.4 8.11.5
Perl/CGI und Grafik Das Modul GD Grafiken mit Perl/CGI laden und anzeigen Dynamische Grafiken mit Perl/CGI erstellen Speichern von Grafiken Diagramme mit dem Modul GD: : Graph erstellen
182 183 183 187 192 194
138
VIII
Inhaltsverzeichnis
9
Grafische Benutzerschnittstellen mit P e r l / T k
197
9.1
Das Perl/Tk-Toolkit
197
9.2
Ein einfaches Beispiel
198
9.3 9.3.1 9.3.2 9.3.3 9.3.4
Grundlegende Widget-Eigenschaften von Perl/Tk Grundlegende Widget-Optionen Das Erscheinungsbild von Widgets festlegen Callbackfunktionen mit der -command-Methode Bitmaps und Bilder in Widgets anzeigen
202 202 203 206 206
9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.4.6
Die Standard-Widgets von Perl/Tk Das Label-Widget Das Button-Widget Checkbutton- und Radiobutton-Widgets Das Listbox-Widget Das Scrollbar-Widget Das Entry-Widget
208 209 211 212 216 219 221
9.5 9.5.1 9.5.2 9.5.3 9.5.4
Einige 'Nicht-Standard'-Steuerelemente von Perl/Tk Das NoteBook-Widget von Perl/Tk Das BrowseEntry-Widget von Perl/Tk Das HList-Widget von Perl/Tk Tabellarische Daten mit dem TixGrid-Widget anzeigen
223 223 226 230 236
9.6 9.6.1 9.6.2 9.6.3 9.6.4
Geometrie-Management mit Perl/Tk Der Geometrie-Manager Pack Widget-Positionierung mit Hilfe von Frames Benutzerschnittstellen optimieren Ein Beispiel mit dem Grid-Geometrie-Manager
240 241 242 244 249
9.7
Menüs mit Perl/Tk erstellen
253
9.8
Ein Editor mit Perl/Tk
256
9.9
Perl/CGI und Perl/Tk
266
10
Weitergabe der fertigen Anwendung
267
11
Nachwort
269
12
Anhang
271
12.1 12.1.1
Skripten, die bei der Arbeit verwendet wurden rmtab.pl: Ersetzen von Tabulator-Zeichen durch Leerzeichen
271 271
12.1.2
rmcode.pl: Ersetzen von CODE-Tags durch LaTeX-Tags
272
13
Literaturverzeichnis
275
Index
277
1
Vorwort
Als Professor Schröter die Idee zu diesem Buch-Projekt hatte, war ich sofort Feuer und Flamme. Viele Gedanken über das, was ich schreiben würde, gingen mir durch den Kopf. Langsam machte sich Ernüchterung breit. Jedes der Themen, die in diesem Buch behandelt werden sollten, würde spielend ein eigenes Buch dieses Umfanges füllen. Also, wie den Stoff begrenzen? Wie ein Buch schreiben, das nicht schon andere Autoren vor mir geschrieben haben? Diese Fragen haben mich eine ganze Weile verfolgt. Verschiedene Ansätze führten in die Irre und wurden wieder verworfen. Schließlich besann ich mich auf das, was ich bin: Ein Perl-Programmierer, der täglich viele Stunden damit verbringt, Anwendungen der verschiedensten Art in Perl zu schreiben. Warum sollte ich also nicht mein Buch schreiben? Ein Buch, in dem ich die Techniken und Vorgehensweisen zusammenfasse, die ich bei meiner Arbeit anwende. Sicherlich, dachte ich, kann man über jedes der hier angesprochenen Themen weit mehr sagen; aber, so stellte ich mir die Frage, was von all dem Stoff benötige ich für meine tägliche Arbeit tatsächlich? Die Idee begann Form zu gewinnen. Ich wollte ein Buch schreiben, das ich mir neben meinen Computer legen und in dem ich die Punkte nachlesen kann, die ich immer wieder vergesse. Es sollte daher auch Themen einschließen, die nicht unmittelbar zu Perl gehören, ohne die es allerdings nicht möglich ist, umfangreichere Programme zu erstellen. Zu diesen Themen gehört beispielsweise eine Einführung in das Design von Datenbanken und in die Grundlagen der Abfragesprache SQL genauso wie eine kurze Einführung in die Seitenbeschreibungssprache HTML oder die Einrichtung des Apache Webservers. Den breitesten Raum in diesem Buch nimmt natürlich die Programmiersprache Perl selbst ein. Wir zeigen Ihnen, wie Sie Ihre Anwendungen mit Packages modularisieren und mit Modulen eigene Funktionsbibliotheken anlegen können. Auch Techniken, von denen so mancher C-Programmierer nur träumen mag, wie das Ausführen von Code zur Kompilierzeit oder das Nachladen von Code zur Laufzeit, stellen wir Ihnen vor. Falls Sie in einem Projekt an die Grenzen der Möglichkeiten von Perl stoßen, was unserer Meinung nach fast gar nicht möglich ist, zeigen wir Ihnen, wie einfach es ist, Module in C zu erstellen und diese mit Perl-Code zu verbinden. Der Begriff der Objektorientierten Programmierung ist in aller Munde; wir stellen ausführlich dar, was sich hinter diesem Begriff verbirgt, und wie Sie diese Techniken in Ihren Projekten einsetzen können. Anwendungen, die nicht in der einen oder anderen Weise mit Datenbanken kommunizieren, sind heute fast nicht mehr denkbar. Daher verfügt auch Perl über eine hervorragende Datenbankschnittstelle, die wir Ihnen umfassend vorstellen.
1 Vorwort
2
Perl ist mit dem Internet groß geworden, daher können wir den Bereich der CGIProgrammierung nicht beiseite lassen. Wir erläutern, wie Sie Ihre Testumgebung einrichten, stellen die Grundlagen der Seitenbeschreibungssprache HTML dar und tauchen dann tief in die CGI-Programmierung ein. Wir erklären Ihnen, wie Sie Datenbanken aus Webseiten heraus ansprechen, wie Sie Ihren Code mit Hilfe von Templates modularisieren und vieles mehr. Vor allem möchten wir Ihnen aber auch zeigen, dass der Einsatz von Perl und CGI auch dann lohnt, wenn Sie keine Web-Anwendungen schreiben möchten. Das Perl/Tk-Toolkit zum Erstellen von grafischen Benutzerschnittstellen ist eine der mächtigsten Erweiterungen zu Perl. In unserem Buch stellen wir Ihnen dieses Toolkit in einem ausführlichen Querschnitt vor und zeigen auch einige Steuerelemente in Ihrer Anwendung, die in der Literatur mitunter vergessen werden, die aber in komplexen Programmen von größtem Nutzen sind. Mit den beiden Bänden dieses Buches erhalten Sie einen umfassenden Einblick in die Softwareentwicklung mit Perl, von den Grundlagen bis zur fertigen Anwendung. Dabei setzen wir im vorliegenden zweiten Band voraus, dass Sie die Grundlagen der PeriProgrammierung beherrschen, und auf Ihrem Rechner eine Perl-Installation vorhanden ist. Ich würde mir wünschen, dass der Funke der Begeisterung, den mir meine tägliche Arbeit mit Perl beschert, auch auf Sie überspringt und Sie feststellen, dass Perl tatsächlich eine der mächtigsten Programmiersprachen ist, die derzeit auf dem Markt zu finden sind und, dass sie längst dem Stadium einer Skriptsprache entwachsen ist. Perl kann sich heute durchaus mit System-Sprachen wie C, C + + und Java messen und lässt diese in einigen Bereichen recht "alt aussehen". Viel Spaß bei der Lektüre dieses Buches!
1.1
Ein Wort zu freier Software
Als ich vor nunmehr sechs Jahren damit begann, mich mit dem damals noch recht jungen Betriebssystem LINUX zu beschäftigen, warfen mir die meisten Menschen vor, ich sei verrückt. Doch mich faszinierte die Idee der freien Software, deren Quellcodes nicht in Panzerschränken versteckt sind, sondern bei der jedermann zum Programm gleich die Quellcodes mitbekommt. Und das zumeist umsonst 1 ! 1999 beschloss ich, die Hauptaktivität meiner Firma in Richtung LINUX zu verlegen. Bis heute habe ich diesen Entschluss nicht eine Sekunde bereut! Die Linuxer sind ein hilfreiches Völkchen, und im Laufe der Jahre bin ich mit vielen Menschen rund um den Globus in Kontakt gekommen, die mir (und ich hoffentlich auch Ihnen) bei so manchem Problem mit Tipps und teilweise seitenlangen "Howto"-Mails zur Seite standen. Dieses Buch ist ausschließlich mit freier Software entstanden! Hier ein kurzer unvollständiger Überblick über die Software, die ich bei der Erstellung dieses Buches eingesetzt habe: Es ist ein weit verbreiteter Irrtum, dass freie Software immer kostenlos sei! Der Begriff frei bezieht sich allerdings auf die Tatsache, dass jeder das Recht hat, den Quellcode dieser Software einzusehen und nach Belieben zu verändern. 1
1.1 Ein Wort zu freier Software
3
Betriebssysteme. Auf meinen Computern setze ich derzeit zwei verschiedene LINUXDistributionen ein: Im Firmenbereich, wo es vor allem auf Sicherheit ankommt, sowie auf den Servern setze ich Debian-LINUX ein, das kostenlos unter der Web-Adresse: h t t p : //www. debian. org bezogen werden kann. Dort finden Sie auch Adressen von Firmen, die CD-Sätze dieser Distribution vertreiben. Debian ist keine Anfänger-Distributior und die Installation ist nicht ganz einfach. Auf meinen Desktop-Systemen verwende ich derzeit RedHat-LINUX in der aktuellen Version 9.0. Auch diese Distribution kann über die Web-Seite des Herstellers kostenlos (in einer drei-CD-Version) heruntergeladen werden ( h t t p : //www.redhat .de). Daneben ist RedHat-LINUX im Handel in verschiedenen Paketierungen zu erwerben. Textverarbeitungsprogramme und Editoren. Der größte Teil dieses Buches wurde mit den Editoren (in der Reihenfolge der Beliebtheit) Nedit, Emacs21 und Vim erstellt. Alle Editoren verfügen über Syntaxhighlighting (also das farbige Hervorheben bestimmter Schlüsselworte) für Perl und LaTeX und sind Bestandteil der genannten LINUXDistributionen. Bei der Korrektur leistete das umfangreiche Office-Paket OpenOffice wertvolle Dienste. OpenOffice kann in der deutschen Version kostenlos unter der folgenden InternetAdresse bezogen werden: h t t p : / / d e . o p e n o f f i c e . o r g . Der Text wurde mit LaTeX gesetzt, einem leistungsfähigen, aber nicht ganz einfach zu bedienenden Textsatz-System. LaTeX ist Bestandteil aller LINUX-Distributionen. Webserver und Webbrowser. Als Webserver verwende ich Apache, der weiter unten beschrieben wird. Als Browser Mozilla, der ehemalige Netscape-Browser. Auch diese beiden Pakete finden Sie auf allen gängigen LINUX-Distributionen. Datenbank-Systeme. Als Datenbank-Systeme nutze ich PostgreSQL und MySQL, die ebenfalls in jeder LINUX-Distribution vorhanden sind. Grafik. Alle Grafiken und Bildschirmhardcopies haben ich mit dem Grafikprogramm The Gimp und dem Zeichenprogramm Xfig erstellt. Raten Sie mal, wo Sie diese Anwendungen finden. Wenn Sie nichts mit LINUX zu tun haben möchten, brauchen Sie nicht traurig zu sein! Fast alle genannten Anwendungen sind mittlerweile auch für Windows-Systeme erhältlich und auch dort kostenlos. Lediglich Xfig ist meines Wissens bisher nicht portiert worden. Die Installation von PostgreSQL unter Windows ist nicht ganz einfach, so dass Sie vielleicht besser auf MySQL ausweichen.
2
Dank
An einem solchen Buchprojekt haben viele Menschen mitgewirkt. Diese Mitwirkung kann man in zwei Kategorien einteilen: freiwillig und unfreiwillig. Zunächst möchte ich mich bei den freiwilligen Helfern bedanken: An erster Stelle gilt mein Dank dem Mitautor und Initiator dieses Buches, Herrn Professor Dr. Jürgen Schröter, der mir nicht nur stets mit Rat und Hilfe zur Seite stand, sondern der mir auch die Möglichkeit gab, mein Wissen an der Fachhochschule Landshut an den Mann und die Frau zu bringen. Ebenfalls in die Kategorie der freiwilligen Helfer gehören "unsere" Lektoren Frau Irmela Wedler und Herr Christian Kornherr vom Oldenbourg Verlag, die unser Projekt stets engagiert betreuten. Auch Ihnen gilt mein Dank. Bedanken möchte ich mich auch bei Herrn Peter Kornherr für den gelungenen Umschlagentwurf und dafür, dass er unsere Hündin Lilly (die allerdings in die andere Kategorie gehört) auf den Umschlag dieses Buches gezaubert hat. Folgende Personen haben Ihre Hilfe zu diesem Buch eher unfreiwillig beigesteuert: Mein größter Dank gehört meiner Frau Susanne, die am Entstehungsprozess dieses Buches in vielfältiger Weise beteiligt war: Sie hat mich und meine Launen ertragen und hat das Buch Seite für Seite gelesen, korrigiert und so manchen komplizierten Schachtelsatz in verständliches Deutsch übersetzt. Und das, obwohl sie Computer eigentlich gar nicht mag! Danke! Mein Dank gebührt auch meinem Partner und Mitgeschäftsführer der idomeo GmbH Herrn Franz Held, der einige wichtige Projekte zu Gunsten des Buches verschob. Und schließlich möchte ich meiner Tochter Emily danken, die auf viel Zeit mit mir verzichten musste.
Landshut 2003 Helmut Seidel
3
Perl-Pakete
Jeder, der schon einmal ein Perl-Skript von mehr als 1000 Zeilen erstellt hat, kennt die Probleme, die damit verbunden sind, ein solches Programm zu pflegen. Schon nach kurzer Zeit ist das Rad Ihrer Wheel-Maus heiß gelaufen, und Ihnen stehen die Tränen in den Augen, vom Hin- und Herscrollen in Ihrer Datei. Auch ein anderes Szenario kennt fast jeder Programmierer. Sie haben für eine bestimmte Aufgabenstellung einen komplizierten Algorithmus entwickelt und in Ihre Anwendung integriert. Nach geraumer Zeit erstellen Sie eine weitere Anwendung, die diesen Algorithmus erneut verwenden soll. Hierfür bietet sich mit Ihrem bisherigen Wissensstand folgende Lösung an: Zunächst suchen Sie die Anwendung, in der Sie den Algorithmus formuliert haben. Glücklich können sich diejenigen schätzen, die ein Programmiertagebuch führen, indem alle Tätigkeiten und Lösungen verzeichnet sind. Sie können nun die Stelle suchen, an der Ihr Algorithmus ausformuliert ist und ihn mit Hilfe der Copy & Paste Funktionalität Ihres Editors in Ihr aktuelles Projekt einfügen. Nun müssen Sie die Namen aller globalen Variablen des Code-Blockes an das aktuelle Projekt anpassen. Ich hoffe, die Vorstellung dieses Vorganges treibt nicht nur mir den Angstschweiß auf die Stirn. Es grenzt schon an ein Wunder, wenn der so entstandene Code ohne Probleme funktionierte. Aber stellen Sie sich mal vor, nach einer weiteren längeren Zeitspanne, sagen wir mal von einem Jahr, ändern sich die Anforderungen an Ihren Algorithmus, weil sich beispielsweise die gesetzlichen Vorschriften geändert haben (etwa in einem Warenwirtschaftssystem). Sie müssen also den Code beider Anwendungen entsprechend anpassen. Und wieder schaffen Sie sich eine unüberschaubare Anzahl von möglichen Fehlerquellen, wieder endloses Testen von zwei Anwendungen ... Und was, wenn der Algorithmus so allgemein ist, dass Sie ihn nicht nur in zwei, sondern vielleicht in fünf oder gar zehn Skripten untergebracht haben? Wir brechen dies Horror-Szenarium an dieser Stelle ab ... Was, so mögen Sie sich vielleicht fragen, haben die beiden vorgestellten Szenarien gemeinsam? Die Antwort ist: in beiden Fällen können Sie sich das Leben erheblich vereinfachen, wenn Sie Ihre Anwendung modularisieren. Modularisieren bedeutet, dass eine Anwendung in überschaubare und inhaltlich zusammengehörige Einheiten zerlegt und in einzelnen Dateien hinterlegt wird. In den meisten Programmiersprachen werden diese Einheiten Module oder Bibliotheken genannt. Das Modularisieren von Anwendungen bietet Ihnen zwei nicht zu unterschätzende Vorteile: 1. Da Ihre Anwendung nun aus kleinen überschaubaren Einheiten besteht, ist sie leicht und bequem zu warten. Natürlich sollten Sie darauf achten, keine "SuperModule" mit vielen tausend Code-Zeilen zu schreiben. Vielmehr sollten Ihre Mo-
8
3 Perl-Pakete dule eine überschaubare Größe besitzen. Als Faustregel verwendet der Autor eine Referenzgröße von ungefähr 100 Zeilen als Maximum für ein Modul 1 . 2. Code, welchen Sie in Modulen abgelegt haben, können Sie auf besonders einfache Art und Weise wiederverwenden, wenn Sie einige grundlegende Regeln beachten. Wir werden in diesem und dem folgenden Kapitel genau darstellen, wie Sie Ihre eigenen Funktions-Bibliotheken anlegen, bzw. wie Sie die Bibliotheken (Module) anderer Entwickler in Ihre Anwendungen einbinden.
In diesem Kapitel werden wir uns mit den Packages in Perl beschäftigen. Das Verständnis dieses Themas ist grundlegend für die Perl-Konzepte der Module sowie der Objektorientierten Programmierung. Sie sollten daher das vorliegende Kapitel in jedem Fall durcharbeiten. Im Einzelnen werden wir folgende Themen behandeln: • Zunächst möchten wir Ihnen die Frage beantworten, was Pakete sind und den wichtigen Begriff des Namensraumes einführen. • Anschließend beantworten wir die Frage, wie Pakete deklariert werden und vor allem, was Sie dabei beachten sollten. • Wie Sie auf Variablen, Subroutinen etc. aus anderen Paketen zugreifen, wird das Thema des folgenden Abschnitts sein. • Wir werden dann erläutern, wie Sie Pakete in Ihre Anwendungen einbinden. • Besonders bei größeren Projekten kann es notwendig sein, Pakete bzw. Ihre Anwendung zu initialisieren. Hierunter verstehen wir, Code auszuführen, wenn die Anwendung oder das Paket kompiliert wird. Ebenso kann es notwendig sein, Code auszuführen, nachdem die Arbeit mit der Anwendung abgeschlossen wurde. Beides wird von Perl in hervorragender Weise unterstützt. Wie dies geschieht, wird das Thema des anschließenden Absatzes sein. • Für einen schnellen und bequemen Zugriff auf Variablen und Routinen eines anderen Paketes kann es nützlich sein, die Symbole des betreffenden Paketes in den aktuellen Namensraum zu importieren. Auch dieses möchten wir weiter unten erläutern. • Den Abschluss dieses Kapitels bildet eine Erklärung des ausgesprochen mächtigen Mechanismus des Autoloadings, mit dem es beispielsweise möglich ist, Code zur Laufzeit der Anwendung nachzuladen. 'Wenn Sie in einem Modul eine grafische Benutzerschnittstelle implementieren, werden Sie den Wert von 100 Zeilen sicher um ein Vielfaches überschreiten. Sie sollten sich daher angewöhnen, den Code für solche Benutzerschnittstellen vom restlichen Programmcode zu trennen und stets in einem eigenen Modul zu definieren.
3.1 Was sind Perl-Pakete?
3.1
9
Was sind Perl-Pakete?
Bisher haben Sie gelernt, dass Variablen, die ohne die Modifier my oder l o c a l deklariert wurden, globale Variablen und damit in der gesamten Anwendung sichtbar sind. Genau genommen war das nur die halbe Wahrheit. Tatsächlich gibt es in Perl solche globalen Variablen überhaupt nicht. Vielmehr beschränkt sich in Perl die Sichtbarkeit einer Variablen auf das umschließende Package. Ein Package ist also nichts anderes als ein eigener Namensraum, der die Sichtbarkeit von Variablen, Arrays, Hashes, Subroutinen, Datei- und Verzeichnishandies2 auf das umschließende Package einschränkt. Wird Programmcode nicht explizit einem Paket zugeordnet, übernimmt dies Perl für uns und ordnet den entsprechenden Code dem Package main zu. Folglich existiert in Perl-Anwendungen kein Code, der nicht einem Paket zugeordnet ist. Dies bedeutet, dass eigentlich auch keine wirklich globalen Variablen sondern lediglich modulglobale Variablen existieren. Alle Bezeichner eines Namensraumes werden in einer eigenen Symboltabelle abgelegt. Eine Symboltabelle wird dabei von einer Hash-Struktur gebildet, auf die, wie wir weiter unten sehen werden, mit den üblichen Hash-Methoden zugegriffen werden kann. Der Gültigkeitsbereich eines Paketes erstreckt sich dabei stets bis zur nächsten PaketDeklaration oder, falls keine folgt, auf den umschließenden Block, in dem das aktuelle Paket deklariert wurde. Falls Sie gar keine Pakete explizit deklariert haben oder die Paket-Deklaration unmittelbar an den Anfang einer Quellcode-Datei gestellt haben, ist dies die gesamte Datei. Was ist aber an diesem Umstand so bemerkenswert? Die Antwort auf diese Frage ist eigentlich ganz einfach: Nur so ist es überhaupt möglich, Anwendungen zu modularisieren und Code zu Bibliotheken zusammenzufassen. Wäre es anders, müssten Sie bei jeder neuen Variablen, die Sie verwenden, prüfen, ob Sie nicht schon irgendwo in Ihrem Anwendungs-Dschungel eine Variable dieses Namens verwendet haben. Stellen Sie sich nur einmal vor, Sie haben eine Variable $x als Zählvariable einer f or-Schleife verwendet, nun rufen Sie innerhalb des Schleifenkörpers eine Funktion aus einem Modul auf, die ebenfalls eine Variable dieses Namens verwendet und dieser einen festen Wert zuweist. Ihre f or-Schleife würde mit Sicherheit nicht so reagieren, wie Sie es erwarteten. Doch zum Glück war dies nur der Ausschnitt aus einem Albtraum 3 . Da in Perl allerdings der Gültigkeitsbereich von Bezeichnern stets eingeschränkt ist, können Sie problemlos Module und Bibliotheken erstellen, ohne Angst haben zu müssen, mehrmals den gleichen Variablennamen verwendet zu haben. Erst durch dieses Konzept wird auch die Programmierung in Teams möglich.
2
Wir werden im weiteren Verlauf zusammenfassend von Bezeichnern sprechen. I n meinen Seminaren ist es übrigens eine beliebte Fehlerquelle, in einer Anwendung mehrmals dieselben Variablennamen (einmal im Hauptprogramm, einmal in einer Unterroutine etc.) zu verwenden. "Er nimmt mir d a was nicht ..." ist in diesem Falle der Anfang einer gängigen Frage! Prinzipiell sollten Sie, um Ihrer Gesundheit willen, stets das P r a g m a strict verwenden und auf (modul-) globale Variablen ganz verzichten! 3
10
3.2
3 Perl-Pakete
Deklaration von Paketen
Um ein Paket zu erstellen, verwenden Sie die package-Anweisung von Perl, welche die folgende allgemeine Syntax besitzt. package Paketname;
Also beispielsweise: package Kontoverwaltung;
In der Regel sollten Sie in einer Datei jeweils nur eine einzige package-Anweisung als erste Anweisung der Datei 4 aufnehmen. Damit weisen Sie den gesamten Code einer Datei diesem Paket zu. Eine solche Datei sollte zudem stets die Dateinamenserweiterung .pm besitzen. Die Endung .pm steht übrigens für Perl Module. Nur Dateien, die diese Endung besitzen, können, wie wir im nächsten Abschnitt sehen werden, mit Hilfe der Anweisung use in eine Quellcode-Datei eingebunden werden. Gemäß der Philosophie von Perl: "Der Programmierer wird schon wissen, was er tut" können innerhalb einer Datei auch mehrere Pakete deklariert werden. Zudem kann beliebig zwischen diesen Paketen gewechselt werden. Beachten Sie hierzu das folgende Beispiel.
package GiroKonto; $inhaber = "Micky Maus"; $kontostand = 5000;
# Package GiroKonto deklarieren # Einige Package-Variablen einlegen
package SparKonto; $inhaber = "Daisy Duck"; $kontostand = 3500;
# Ein weiteres Package deklarieren # auch hier ein paar Variablen
# Zurueck zum Girokonto package GiroKonto; # Kontodaten GiroKonto ausgeben print "$inhaber hat $kontostand Euro auf seinem Konto.\n"; # Zurueck zum Sparkonto package SparKonto; \# Kontodaten SparKonto ausgeben print "$inhaber hat $kontostand Euro auf seinem Konto.\n";
Es ist also tatsächlich möglich, in einer Quellcode-Datei mehrfach Variablen gleichen Namens zu deklarieren, sofern Sie in unterschiedlichen Packages angelegt wurden. Dies funktioniert, da die Variablen jeweils in der Symboltabelle des aktuellen Packages abgelegt wurden. Allerdings sei an dieser Stelle nochmals darauf hingewiesen, dass es sich bei dem oben dargestellten Beispiel um einen ausgesprochen s c h l e c h t e n Stil handelt. 4 Sofern das von Ihnen angelegte Paket allerdings der Startpunkt einer größeren Anwendung ist, also das Paket main ersetzt, sollte die package-Anweisung erst nach der SheBang-Zeile stehen.
3.3 Einbinden von Paketen mit use und r e q u i r e
11
Genauso wie es möglich ist, mehrere Pakete in einer Datei zu deklarieren, ist es auch möglich, ein Paket über mehrere Dateien zu verteilen, indem mehrfach die gleiche package-Deklaration verwendet wird. Aber auch dies ist ein schlechter Stil und sollte in jedem Falle vermieden werden.
3.3
Einbinden von Paketen mit use und require
Nachdem Sie nun ein Paket mit wichtigen Routinen für Ihre Anwendung geschrieben haben, möchten Sie dieses natürlich auch nutzen. Um dies tun zu können, müssen Sie das betreffende Paket zunächst in Ihre Anwendung einbinden. Einbinden bedeutet, dass der Code eines Paketes 5 in die aktuelle Quellcode-Datei eingelesen wird 6 . Dieser Einlesevorgang kann entweder über die Anweisung use oder mit Hilfe der Anweisung require geschehen.
3.3.1
Die Anweisung use
Der sicherste Weg, ein Paket in Ihre Anwendung einzubinden, ist das Verwenden der Anweisung use. Die allgemeine Syntax der use-Anweisung ist use Paketname;
Um das Paket Kontoverwaltung.pm in Ihre Anwendung einzubinden, verwenden Sie die Anweisung use KontoVerwaltung;
Mit Hilfe von use können allerdings nur Pakete eingebunden werden, die in Dateien gespeichert sind, deren Dateinamenserweiterung .pm ist. Damit die Datei korrekt geladen werden kann, ist es zudem notwendig, dass beim Laden ein boolscher Erfolgswert zurückgegeben wird. Die letzte ausführbare Anweisung im globalen Gültigkeitsbereich des Paketes sollte daher wie folgt lauten. return 1;
Weniger gut lesbar, aber auch möglich ist folgende Variante. 1; Damit kann der allgemeine Aufbau einer Paket-Datei wie folgt dargestellt werden. # Deklaration des Paketes package Paketname; # evtl. Einbinden von weiteren Modulen/Paketen use Paketname; 5 Wir sollten eigentlich von einem Modul sprechen. Da wir aber diesen Begriff erst im nächsten Kapitel erläutern, sprechen wir hier noch etwas unhandlich von einem Paket. 6 C / C + + - P r o g r a m m i e r e r n wird dies bekannt vorkommen. Der Vorgang erinnert an das Einbinden von (header)-Dateien mit Hilfe der Präprozessor-Anweisung #include.
12
3 Perl-Pakete
# Deklaration Modulglobaler Variablen $var = wert; # Einen wahren Rueckgabewert erzeugen return 1; # Deklaration der Unterroutinen (Methoden) des Paketes sub unterroutinel { }
sub u n t e r r o u t i n e 2 { >
Der use-Befehl wird bereits zum Zeitpunkt des Kompilierens7 der Quellcode-Datei ausgeführt. Kann Perl also das gewünschte Paket nicht korrekt einbinden, wird die Anwendung gar nicht erst gestartet. So können Sie sicher sein, dass Sie nicht zur Laufzeit Ihrer Anwendung eine unangenehme Überraschung erleben, weil ein einzubindendes Paket nicht gefunden wurde.
3.3.2
Die Anweisung require
Wie oben beschrieben, können mit Hilfe des use-Befehls nur Dateien eingebunden werden, welche die Dateinamenserweiterung . pm besitzen; zudem ist es nicht möglich, Pakete dynamisch zur Laufzeit zu laden. Beides wird mit Hilfe der Anweisung r e q u i r e möglich. Um beispielsweise eine Datei mit Namen Kontoverwaltung.pl in Ihre Anwendung einzubinden, verwenden Sie die folgende Anweisung. require KontoVerwaltung;
Sofern Sie keine Dateinamenserweiterung angeben, nimmt r e q u i r e als Endung .pm an. Auch für die Anwendung von r e q u i r e ist es notwendig, dass die zu ladende Datei einen wahren boolschen Wert zurückgibt. Die Anweisung r e q u i r e bietet zudem eine zweite Syntax-Variante an. r e q u i r e "Kontoverwaltung.pm"; Diese Variante unterscheidet sich von der ersten in einigen Punkten. So ist es nun möglich, Dateien einzubinden, deren Datei-Endung nicht .pm lautet, also beispielsweise Kontoverwaltung.pl. Ein weiterer wichtiger Unterschied ist, dass diese Variante das einzubindende Paket nur im aktuellen Verzeichnis findet. Liegt das Paket an einem 7 Genau genommen ist der Zeitpunkt des Einbindens bereits die Parsing-Phase, eigentlichen Kompiliervorgang durchlaufen wird.
die noch vor dem
3.3 Einbinden von Paketen mit use und r e q u i r e
13
anderen Ort, so muss der Pfad explizit, wie im folgenden Beispiel gezeigt, angegeben werden. require
"/home/micky/kontoPackages/Kontoverwaltung.pm";
Die Anweisung require wird erst zur Laufzeit der Anwendung ausgeführt. Es ist also möglich, dynamisch Code in Ihre Applikation zu laden. Dies ist vor allem dann sinnvoll, wenn bestimmte Programmfunktionen nur sehr selten aufgerufen werden, wie beispielsweise eine Setup-Routine oder Ähnliches. Beim Aufruf prüft require zunächst, ob das gewünschte Paket bereits geladen wurde. Ist dies der Fall, wird der Aufruf ignoriert. Ist dies nicht der Fall, versucht die Anwendung das angegebene Paket zu laden. Der Entwickler selbst ist dafür verantwortlich, sicherzustellen, dass das angeforderte Paket auch existiert. Ist dies nicht so, kommt es zu einem Programmabsturz, das heißt, Perl beendet mit einem Fehler die Ausführung Ihrer Anwendung. Wenn es also nicht notwendig ist, Pakete dynamisch zu laden oder Pakete einzubinden, deren Dateinamenserweiterung nicht auf .pm endet 8 , sollten Sie in jedem Fall die useAnweisung verwenden.
3.3.3
Wo Perl nach Paketen sucht
Nicht immer möchten Sie die einzubindenden Dateien im aktuellen Programmverzeichnis ablegen. Schon nach kurzer Zeit hätten Sie bei häufig verwendeten Paketen ähnliche Probleme wie bei der (nun obsoleten) Verwendung der Copy & Paste-Methode. Es stellt sich also die Frage, wo Perl Pakete sucht, die Sie mit use oder require einbinden möchten. Falls der Perl-Interpreter die angegebenen Dateien nicht im aktuellen Anwendungsverzeichnis findet, sucht Perl in allen im Array ®INC eingetragenen Verzeichnissen nach dem geforderten Paket. Sofern Sie ein eigenes Bibliotheksverzeichnis anlegen möchten, müssen Sie dieses Array um Ihre Pfadangaben erweitern. Hierzu gibt es im Wesentlichen drei Möglichkeiten: 1. Der einfachste Weg ist der, den Kommandozeilenschalter - I 9 zu verwenden. perl -I/home/micky/perlPackages -1/h.ome/micky/kontoPackages meinProgreumn.pl
Wie gezeigt, geben Sie also beim Aufruf Ihrer Anwendung vor dem Namen des auszuführenden Programmes die Pfade zu Ihren Paket-Bibliotheken an. Rufen Sie die Anwendung des Öfteren auf, ist es sinnvoll, ein kurzes Shellskript oder unter Windows eine Batch-Datei zu schreiben, die den Aufruf für Sie erledigt. 8 9
Und von Ihnen auch nicht umbenannt werden kann. Für include = engl, einschließen
14
3 Perl-Pakete 2. Wenn Sie sehr viele Bibliotheken verwenden, wird es mit der Zeit müßig, bei jedem Aufruf erneut den Rattenschwanz von Include-Pfaden anzugeben. Sofern Sie stets die gleichen Bibliotheks-Pfade verwenden, können Sie diese auch dauerhaft in der Umgebungsvariablen PERL5LIB hinterlegen. Diese Variable enthält eine durch Doppelpunkte getrennte Pfadliste. Dabei ist zu beachten, dass Unterverzeichnisse zu den angegebenen Pfaden automatisch mit eingeschlossen werden. Um diese Variable auf einem UNIX/LINUX-System zu verändern, tragen Sie die Ergänzungen in eine der Dateien .profile oder . bashrc des jeweiligen Users oder in die systemglobale Datei profile im Verzeichnis /etc in der folgenden Art ein. PERL5LIB="/home/micky/kontoPackages: /home/micky/perlPackages:$PERL5LIB" export PERL5LIB
Nutzer von Windows-Systemen müssen die entsprechenden Eintragungen in der Datei autoexec.bat vornehmen, die im Rootverzeichnis des Laufwerkes C:\zu finden sind. Bevor Sie die Umgebungsvariable PERL5LIB dauerhaft verändern, sollten Sie aber wirklich sicher sein, was Sie tun! Im Zweifelsfalle werfen Sie einen Blick in das Handbuch Ihres Betriebssystems. 3.
Schließlich können Sie auch das Array @INC ergänzen, bevor Sie require oder use aufrufen. # OINC Einpassen unshift(@INC, "/home/micky/perlPackages"); unshift(@INC, "/home/micky/kontoPackages"); # Pakete einbinden require "Kontoverwaltung.pm"; require "InputTools.pm";
Um use zu verwenden, müssen Sie das Array, wie im Abschnitt "Initialisieren und Zerstören von Paketen", verändern, bevor die Anwendung kompiliert wird.
3.4
Zugriff auf Bezeichner anderer Pakete
Sie sind nun in der Lage, Packages zu erstellen und in Ihre Anwendung einzubinden. Was allerdings noch fehlt, um mit Paketen sinnvoll arbeiten zu können, ist eine Möglichkeit, auf die Bezeichner anderer Pakete Zugriff zu erlangen. Das Geheimnis, um dies zu erreichen, ist der sogenannte voll qualifizierte Name, der aus Typenzeichen und dem Namen des Paketes, gefolgt von einem zweifachen Doppelpunkt sowie dem Namen des Bezeichners besteht. Also in allgemeiner Form:
3.4 Zugriff auf Bezeichner anderer Pakete
15
[Typenzeichen]Paketname::bezeichner
Also beispielsweise zum Zugriff auf die Variable $kontostand aus dem Paket Konto. $Konto::kontostand;
Analog geschieht auch der Zugriff auf Array- oder Hash-Variablen. OKonto::buchungsdaten[0] ; '/,Konto:: kontoInhaberNamen{"Maus"};
Beachten Sie hierbei, dass das Typenzeichen stets vor dem Paketnamen steht und nicht vor dem Variablen-Bezeichner! Um eine Subroutine in einem anderen Paket aufzurufen, verwenden Sie die folgende Syntax. Paketname::subroutine(argument, argument[,...])
Also beispielsweise. Konto::einzahlen($aBetrag);
Lassen Sie uns dieses Vorgehen anhand eines Beispieles illustrieren. Wir gehen davon aus, dass Sie ein Paket Konto erstellt haben, in dem die Variablen kontoinhaber und kontostand sowie die beiden Subroutinen einzahlen und auszahlen deklariert sind. Beide Subroutinen erwarten als Argument einen Betrag. Zunächst erstellen wir das Paket Konto wie folgt. package Konto; # Datei: Konto.pm # Globale Variablen deklarieren $kontoinhaber = ""; $kontostand = 0.0; # ein 'wahrer' Wert muss zurueckgegeben werden return 1; sub einzahlen^ # Einzahlungsbetrag uebernehmen my $betrag = $_ [0]; # Wenn Einzahlung groesser als 0, buchen if ($betrag > 0.0) { $kontostand += $betrag; } eise {
16
3 Perl-Pakete print "Sie muessen einen Buchungsbetrag angeben!\n";
>
>
sub auszahlend # Auszahlungsbetrag uebernehmen my $betrag = $_[0]; # Wenn Auszahlung moeglich, Betrag abbuchen if ($betrag < $kontostand) { $kontostand -= $betrag; )• eise { print "Sie haben nicht genug Geld auf Ihrem Konto\n"; print "Auszahlung nicht moeglich!\n";
>
>
Nun schreiben wir eine kurze Anwendung, mit der wir einige Variablenwerte setzen und die Subroutinen aufrufen. #!/usr/bin/perl -w # Paket einbinden use Konto; # Kontoinhaber setzen print "Geben Sie den Namen des Kontoinhabers ein: "; my $name = ; chomp($name); $Konto::kontoinhaber = $name; # Eine Einzahlung auf das Konto vornehmen print "Geben Sie einen Einzahlungsbetrag ein: "; my $eBetrag = ; chomp($eBetrag); Konto::einzahlen($eBetrag); # Eine Auszahlung vornehmen print "Geben Sie den Auszahlungsbetrag ein: "; my $aBetrag = ; chomp($aBetrag); Konto::auszahlen($aBetrag); # Daten ausgeben print "\n\n$Konto:Kontoinhaber hat $Konto::kontostand " . "Euro auf seinem Konto\n";
17
3.5 Pakete initialisieren und terminieren
Wenn Sie die Skripte abtippen und starten, wird die in Abbildung 3.1 dargestellte Ausgabe erzeugt. Sie sollten dabei aber unbedingt beachten, dass beide Skripten im selben Verzeichnis stehen, sofern Sie nicht die Umgebungsvariable OINC verändern möchten. V
-
B *
ßatei gearbeiten Ansicht Terminal gehe zu {Jlfe [helmut@nb-seidel helmut]$ cd perl_buch/packages/examples/ [helmut # Kontrollausgabe - in der fertigen Anwendung loeschen print "\pfad im Hauptprogramm: $pfad\n;
Wie Sie sehen, können in einer BEGIN-Routine nicht nur Perls Spezialvariablen verändert werden, sondern auch "ganz normale" Variablen. Interessant ist dabei, dass Perl sich
20
3 Perl-Pakete
die in der Initialisierungs-Routine gesetzten Variablenwerte auch im Hauptprogramm merkt! Gerade bei großen Anwendungen, die möglicherweise noch an mehrere Kunden mit unterschiedlichen Betriebssystemen ausgeliefert werden, ist dieses Verhalten Gold wert. Bei komplexen Anwendungen kann es auch notwendig sein, nach Beenden der Applikation Code auszuführen, um beispielsweise Programmzustände auf der Festplatte zu sichern. Beim "planmäßigen" Ende der Applikation ist dies auch kein Problem: Sie schreiben einfach eine Unterroutine, die aufgerufen wird, bevor die Anwendung ihre Arbeit beendet. Doch leider leben wir nicht in dieser heilen Welt, wo Anwendungen stets zur rechten Zeit unter idealen Umständen enden. Vielmehr kommt es immer wieder zum abrupten Abbruch von Programmen, weil bestimmte Voraussetzungen für die Applikation plötzlich nicht mehr erfüllt sind. 11 In diesem Falle kann die Routine zum Beenden der Anwendung natürlich nicht mehr aufgerufen werden. Für derartige Fälle bietet Perl den END-Block an. Die Anweisungen innerhalb dieses Blockes werden immer ausgeführt, bevor die Anwendung die Kontrolle an das Betriebssystem zurück reicht, auch dann, wenn die Applikation mit einem Fehler beendet wurde. Betrachten Sie hierzu das folgende Beispiel. # E i n unsinniger Befehl, u m die Anwendungsausfuehrung zu b e e n d e n dies ist ein vollkommen unsinniger Befehl; sub END { # Die folgenden Anweisungen werden n o c h ausgefuehrt, # obwohl die Anwendung mit einem Fehler beendet w i r d print "\nDie Anwendung hat einen Fehler v e r u r s a c h t e n " ; print "Die Anwendung w i r d beendet!\n";
> Wie Sie sehen, werden die Anweisungen innerhalb des END-Blockes noch ausgeführt, obwohl die Anwendung einen Fehler erzeugt hat. Bedenken Sie aber, bevor Sie Ihre Anwendungen nun mit END-Blöcken spicken, dass dies stets nur der letzte Ausweg sein sollte und Sie nicht davon befreit, sinnvolle Fehlerabfang-Routinen zu schreiben!
3.6
Symbole aus anderen Symboltabellen importieren
Mitunter kann es recht lästig werden, immer dann, wenn man ein neues Konto anlegen möchte, den folgenden Rattenschwanz eintippen zu müssen. Kontoverwaltung::neu('Maus');
Viel angenehmer wäre es, könnte man einfach 11
Also weniger poetisch ausgedrückt: weil ein Fehler aufgetreten ist!
3.6 Symbole aus anderen Symboltabellen importieren
Abb. 3.3: Die Ausgabe des Beispielskriptes
21
end.pl
neu('Maus');
verwenden. Und genau dies kann erreicht werden, indem man zum use-Befehl in einer optionalen Liste angibt, welche Symbole in den aktuellen Namensraum importiert werden sollen. use Kontoverwaltung('neu', 'suchen', 'loeschen'); neu('Maus); # statt Kontoverwaltung::neu('Maus');
Damit man allerdings bei use eine Liste der zu importierenden Namen angeben kann, muss das Paket, das diese Liste liefert, in die Lage versetzt werden, die Namen zu exportieren. Zusätzlich muss das Paket wissen,welche Symbole zu exportieren sind, wenn beim use-Befehl keine Liste angegeben wurde. Um diese Aufgabe zu erledigen, stellt Perl das Standard-Modul Exporter zur Verfügung. Mit Hilfe dieses Moduls kann das Paket Kontoverwaltung auf folgende Weise realisiert werden. package Kontoverwaltung; use Exporter; # Modul Exporter einbinden ©ISA = ('Exporter'); # Von Exporter erben # Siehe hierzu Abschnitt zur Objektorientierten Programmierung # Liste der Symbole, die exportiert werden koennen
22
3 Perl-Pakete
@Export_Ok = ('neu', 'suchen', 'loeschen'); # Implementierung der Funktionen sub neu { >
sub suchen { >
sub loeschen { >
Das Array $name, "vorname" => $vorname, "gebdatum" => $gebdatum, "anschrift" => $anschrift, "beruf" => $beruf, "einkommen" => Seinkommen, "bargeld" => $bargeld,
52
6 Objektorientierte Programmierung
>;
>
# Instanz auf den Namen der Klasse taufen bless $r_kontoinhaber, $klasse; # Instanz zurueckgeben return $r_kontoinhaber;
# einen wahren Wert zurueckgeben return 1;
Nachdem Sie die Klassendatei angelegt und abgespeichert haben, können wir ein neues Objekt unserer Klasse anlegen. Wir verwenden zum Aufruf von Methoden einer Klasse den Pfeiloperator ->: #!/usr/bin/perl -w # Datei: klassel.pl # Einbinden der Klasse Kontoinhaber use Kontoinhaber; # neue Kontoinhaber-Instanz erzeugen und mit Werten vorbelegen Seigenschaften = ('Duck', 'Daisy', '12.03.1902', 'Erpelweg 4, 12345 Entenhausen', 'Klatschreporterin', '1234.56', '55.66'); my $ki = Kontoinhaber->new(@eigenschaften);
6.2.2
Anlegen und Aufrufen von Eigenschafts-Methoden
Wie weiter oben im Abschnitt Kapselung von Daten besprochen, sollte der Zugriff auf die Eigenschaften eines Objektes nicht direkt, sondern über speziell hierfür vorgesehene Methoden erfolgen. Methoden werden, wie wir gesehen haben, mit Hilfe von Funktionen realisiert. Wir verwenden dabei zum Lesen und Setzen dieselbe Methode, der wir den gleichen Namen geben, wie der entsprechenden Eigenschaft. Dabei testen wir zunächst, ob ein Parameter übergeben wurde. Ist dies der Fall, wird die Eigenschaft gesetzt. Anschließend gibt die aufgerufene Methode den Wert der in Frage stehenden Eigenschaft an den Aufrufer zurück. Damit erhält unsere Klasse Kontoinhaber folgendes Aussehen. package Kontoinhaber; # Konstruktor erstellen sub new {
6.2 Objektorientierte Programmierung mit Perl # Klassennamen uebernehmen my $klasse = shift; # Argumente uebernehmen my ($name, $vorname, $gebdatum, $anschrift, $beruf, $einkommen, $bargeld) = # Anlegen eines anonymen Hashes zur Aufnahme der # Eigenschaftswerte und uebernehmen der Werte my $r_kontoinhaber = { "name" => $name, "vorname" => $vorname, "gebdatum" => $gebdatum, "anschrift" => $anschrift, "beruf" => $beruf, "einkommen" => $einkommen, "bargeld" => $bargeld,
>; # Instanz auf den Namen der Klasse taufen bless $r_kontoinhaber, $klasse; # Instanz zurueckgeben return $r_kontoinhaber;
} sub name { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'name'} = shift if # Eigenschaftswert zurueck geben return $r_kontoinhaber->{'name'};
sub vorname { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'vorname'} = shift if # Eigenschaftswert zurueckgeben return $r_kontoinhaber->{' vorname' }•;
sub gebdatum { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'gebdatum'} = shift if Q_; # Eigenschaftswert zurueckgeben return $r_kontoinhaber->{'gebdatum'};
53
54
6 Objektorientierte Programmierung
sub anschrift { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert übergeben wurde, speichern $r_kontoinhaber->{'anschrift'} = shift if # Eigenschaftswert zurueckgeben return $r_kontoinhaber->{'anschrift'};
>
sub beruf { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'beruf'} = shift if # Eigenschaftswert zurueckgeben return $r_kontoinhaber->-['beruf'};
>
sub einkommen { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'einkommen'} = shift if @_; # Eigenschaftswert zurueck geben return $r_kontoinhaber->{'einkommen'};
} sub bargeld { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'bargeld'} = shift if @_; # Eigenschaftswert zurueckgeben return $r_kontoinhaber->{'bargeld'};
} # einen wahren Wert zurueckgeben return 1;
Beachten Sie, dass wir im oben stehenden Skript der Übersichtlichkeit halber auf alle Plausibilitätskontrollen verzichtet haben. Im wirklichen Programmieralltag sollten Sie hierauf allerdings auf keinen Fall verzichten. So sollten Sie beispielsweise ein unsinniges Geburtsdatum mit einer Meldung an den Aufrufer quittieren und den falschen Wert zurückweisen. Wir stellen Ihnen im folgenden Beispiel die Methode zum Abfragen und Setzen der Eigenschaft gebdatum vor, die das übergebene Datum darauf überprüft, ob
6.2 Objektorientierte Programmierung mit Perl
55
die Jahreszahl vierstellig ist. Ist dies nicht der Fall, wird eine Fehlermeldung ausgegeben, und das ursprüngliche D a t u m wird zurückgegeben.
sub gebdatum { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern if (®_) { my $dat = shift; # Datum zerlegen my ($tag, $mon, $jahr) = split(/\./, $dat); # Testen ob Jahreszahl 4-stellig if (length($jahr) < 4) { # Meldung ausgeben print "2-stellige Jahreszahlen ($jahr) nicht erlaubt!\n"; print "weiter "; # Return-Taste abwarten ; # weitere Plausibilitaetskontrollen } eise { $r_kontoinhaber->{'gebdatum'} = $dat
>
>
# Eigenschaftswert zurueckgeben return $r_kontoinhaber->{'gebdatum'};
Sie können nun auf die Eigenschaften zugreifen, diese auslesen und beliebig verändern. Das folgende Skript erstellt eine Instanz der Klasse Kontoinhaber und greift anschließend sowohl lesend als auch schreibend auf einzelne Eigenschaften der Instanz zu. Hierfür verwenden wir wiederum die Pfeilnotation.
#!/usr/bin/perl -w # Datei: klassel.pl # Einbinden der Klasse Kontoinhaber use Kontoinhaber; # neue Kontoinhaber-Instanz erzeugen und mit Werten vorbelegen Oeigenschaften = ('Duck', 'Daisy', '12.03.1902', 'Erpelweg 4, 12345 Entenhausen', 'Klatschreporterin',
56
6 Objektorientierte Programmierung
'1234.56', '55.66'); my $ki = Kontoinhaber->new(@eigenschaften); # Eigenschaftswerte ausgeben print $ki->name .", " . $ki->vorname . ", " . $ki->gebdatum . "\n"; # Eigenschaftswerte aendern $ki->gebdatum('13.01.1964'); # Eigenschaftswerte ausgeben print $ki->name .", " . $ki->vorname . ", " . $ki->gebdatum . "\n";
Natürlich können Sie auch, wie dies beispielsweise in VisualBasic üblich ist, zwei Funktionen für den Zugriff auf die Eigenschaften schreiben. Eine zum Lesen der Eigenschaft, getEigenschaft, und eine zweite, setEigenschaf t, zum Setzen der Eigenschaft. Es ist eine Frage der persönlichen Vorliebe. Für den Zugriff auf die Eigenschaft name müssen Sie in diesem Falle folgende beide Methoden erstellen.
# Eigenschaft zurueckgeben sub getName { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Eigenschaftswert zurueck geben return $r_kontoinhaber->{'name'};
} # Eigenschaft setzen sub setName { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'name'} = shift if 0_; # Eigenschaftswert zurueckgeben return $r_kontoinhaber->{'name'};
>
Mit den restlichen Eigenschaften verfahren Sie analog. Wie gesagt, es ist eine Frage der persönlichen Vorliebe, welche der vorgestellten Varianten Sie bevorzugen. Allerdings sollten Sie, wenn Sie sich für eine Variante entschieden haben, diese auch konsequent beibehalten! Beim Zugriff auf die Eigenschaften Ihrer Instanz müssen Sie den lesenden und den schreibenden Zugriff unterscheiden.
6.2 Objektorientierte Programmierung mit Perl
57
#!/usr/bin/perl -w # Datei: klassel.pl # Einbinden der Klasse Kontoinhaber use Kontoinhaberl; # neue Kontoinhaber-Instanz erzeugen und mit Werten vorbelegen Qeigenschaften = ('Duck', 'Daisy', '12.03.1902', 'Erpelweg 4, 12345 Entenhausen', 'Klatschreporterin', '1234.56', '55.66'); my $ki = Kontoinhaberl->new(@eigenschaften); # Eigenschaftswerte ausgeben print $ki->getName . "\n";
# Ausgabe: Duck
# Eigenschaftswerte aendern $ki->setName('Klaustrophobia'); # Eigenschaftswerte ausgeben print $ki->getName . "\n";
# Ausgabe: Klaustrophobia
Im Gegensatz zu anderen Programmiersprachen ist Perl nicht dem "AbsicherungsWahn" verfallen. In Perl bedeutet die Kapselung der Eigenschaften nicht die totale Abschottung der Klasse. Es hindert Sie also niemand daran, direkt auf Eigenschaften einer Klasse zuzugreifen und diese zu verändern. Perl geht vielmehr davon aus, dass Sie genügend Reife besitzen, um dies nicht zu tun. Besonders gefährlich wird ein solcher direkter Zugriff durch den Umstand, dass Klassen die Implementierung vor dem Aufrufer verbergen. Wurde die Realisierung einer Klasse verändert, so kann es sein, dass direkte Aufrufe ins Leere zielen. Aufrufer, die über die hierfür vorgesehenen Methoden auf die Eigenschaften einer Klasse zugreifen, merken von diesen Änderungen nichts.
6.2.3
Methoden, die den Verhaltensweisen eines Objektes entsprechen
Bisher haben wir lediglich Zugriffsmethoden für die Eigenschaften der Klasse erstellt, die diese direkt modifizieren oder abfragen. Klassen besitzen aber, wie wir weiter oben gesehen haben, auch Methoden, die den Verhaltensweisen von realen Objekten entsprechen. Um dies zu illustrieren, wenden wir uns der Klasse Konto zu, die über die beiden Methoden einzahlen und auszahlen verfügt. Auch diese Methoden werden mit Hilfe von Funktionen realisiert und unterscheiden sich vom Prinzip her kaum von den bereits vorgestellten Methoden zur Manipulation von Eigenschaftswerten. Erstellen wir zunächst den allgemeinen Rahmen der Konto-Klasse mit ihren Eigenschaftsmethoden.
6 Objektorientierte Programmierung
58 package Konto;
# Konstruktor erstellen sub new { # Klassennamen uebernehmen my $klasse = shift; # Argumente uebernehmen my ($kontoNr, $kontoinhaber, $kontostand, ) = 0_; # Anlegen eines anonymen Hashes zur Aufnahme der # Eigenschaftswerte und Uebernehmen der Werte my $r_konto = { "kontoNr" => $kontoNr, "kontoinhaber" => $kontoinhaber, "kontostand" => $kontostand,
>; # Instanz auf den Namen der Klasse taufen bless $r_konto, $klasse; # Instanz zurueckgeben return $r_konto;
> sub kontoNr { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Falls ein Wert ubergeben wurde, speichern $r_konto->-['kontoNr'} = shift if {'kontoNr'};
> sub kontoinhaber { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Falls ein Wert ubergeben wurde, speichern $r_konto->{'kontoinhaber'} = shift if # Eigenschaftswert zurueckgeben return $r_konto->{'kontoinhaber'};
} sub kontostand { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Falls ein Wert ubergeben wurde, speichern $r_konto->{'kontostand'} = shift if ®_; # Eigenschaftswert zurueckgeben return $r_konto->{'kontostand'};
6.2 Objektorientierte Programmierung mit Perl
59
> # einen wahren Wert zurueckgeben return 1;
Die Methode auszahlen soll die folgenden Aufgaben erfüllen: • Testen, ob beim Aufruf ein Parameter übergeben wurde und, ob es sich dabei um eine Zahl größer 0 handelt. • Ist dies der Fall, ist zu testen, ob der Auszahlungsbetrag kleiner oder gleich dem Kontostand ist. • Nur, wenn beide Bedingungen erfüllt sind, soll eine Auszahlung stattfinden. War dies nicht der Fall, so ist eine Fehlermeldung auszugeben, die den Aufrufer darüber informiert, was er falsch gemacht hat. • Die Methode gibt bei Erfolg den neuen Kontostand und bei einem Misserfolg -1 zurück. Der Fehlerwert -1 ist wichtig, damit der Aufrufer erkennt, ob wirklich ein Fehler vorlag oder nur alles Geld abgehoben wurde. Wir können diese Methode in der folgenden Weise implementieren.
#
...
sub auszahlen { # Referenz auf Objekt uebernehmen my $r_konto = shift; # testen, ob ein Argument uebergeben wurde if (C_) { # Argument uebernehmen my $ks = shift; # testen, ob eine Zahl > 0 uebergeben wurde if ($ks > 0) { # testen, ob ausreichend Geld auf dem Konto ist if ($ks {'kontostand'}) { # neuen Kontostand setzen $r_konto->{'kontostand'} - = $ks; # kontostand zurueckgeben return $r_konto->{'kontostand'}; } eise { print "Nicht genuegend Geld auf dem Konto vorhanden!\n";
} } eise { print "Keine Zahl oder 0 uebergeben!\n";
60
6 Objektorientierte Programmierung
} > else { print "Kein Argument uebergeben!\n";
> }
# -1 als Fehlerwert zurueckliefern return -1;
# einen wahren Wert zurueckgeben return 1;
Fügen Sie den oben angegebenen Code in Ihre Konto-Klasse vor der r e t u r n - A n w e i s u n g ein. U m zu testen, verwenden Sie folgenden Code.
#!/usr/bin/perl # Datei: kontol.pl # Einbinden der Klasse Konto use Konto; # neue Konto-Instanz erzeugen und mit Werten vorbelegen Seigenschaften = (100, 'Daisy Duck', 123.45); my $ko = Konto->new(Seigenschaften); # Eigenschaftswerte ausgeben print $ko->kontoNr . ", " . $ k o - K o n t o i n h a b e r . ", " . $ko->kontostand . "\n"; # Funktion auszahlen ohne Argument aufrufen - Fehler: print $ko->auszahlen() . "\n"; # Fehler: -1 # Funktion auszahlen mit einem falschen Argument aufrufen print $ko->auszahlen('heinz') . "\n"; # Funktion auszahlen mit einem zu grossen Wert aufrufen print $ko->auszahlen('567,89') . "\n"; # jetzt aber! print $ko->auszahlen(56.78) . "\n";
Sie sollten stets darauf achten, dass Sie, u m Methoden zu testen, alle Fehlermöglichkeiten berücksichtigen! Ansonsten können Sie sicher sein, dass Ihr Kunde als erstes die von Ihnen nicht getestete Fehlfunktion produziert, die in diesem Falle natürlich prompt zum Systemabsturz und lauten Telefonaten führt.
6.2 Objektorientierte Programmierung mit Perl
61
Die Methode einzahlen ist ein wenig einfacher zu realisieren, da wir hier nicht prüfen müssen, ob ausreichend Geld auf dem Konto vorhanden ist. Fügen Sie den nachstehenden Code hinter der Methode auszahlen in die soeben erstellte Klasse ein.
#
...
sub einzahlen { # Referenz auf Objekt uebernehmen my $r_konto = shift; # testen, ob ein Argument uebergeben wurde if («_) { # Argument uebernehmen my $ks = shift; # testen, ob eine Zahl > 0 uebergeben wurde if ($ks > 0) { # neuen Kontostand setzen $r_konto->{'kontostand'} += $ks; # kontostand zurueckgeben return $r_konto->{'kontostand'}; } eise { print "Keine Zahl oder 0 uebergeben!\n";
>
} eise { print "Kein Argument uebergeben!\n";
> # -1 als Fehlerwert zurueckliefern return -1;
} # einen wahren Wert zurueckgeben return 1;
Und schließlich das Skript zum Testen der Klasse. Fügen Sie auch hier den untenstehenden Code an das Ende Ihres Skriptes kontol.pl an.
print "Funktion einzahlen testen:\n"; # Funktion einzahlen ohne Argument aufrufen - Fehler: print $ko->einzahlen() . "\n"; # Fehler: -1 # Funktion einzahlen mit einem falschen Argument aufrufen print $ko->einzahlen('bertram') . "\n"; # aber j etzt! print $ko->einzahlen('98,76') . "\n";
62
6 Qbjektorientierte Programmierung
# und zum Test nochmal alles ausgeben print $ko->kontoNr . ", " . $ko->kontoinhaber . $ko->kontostand . "\n";
6.2.4
" .
Klassen- und Instanzmethoden, Klassen- und Instanzvariablen
Vielleicht haben Sie schon bemerkt, dass wir in unseren Beispielen bereits Klassen- und Instanzmethoden verwendet haben. In der Konto-Klasse ist der Konstruktor new eine Klassenmethode, die keine besondere Instanz der Klasse benötigt. Wir haben ja weiter oben dargestellt, dass diese zum Zeitpunkt des Aufrufes eines Konstruktors noch gar nicht existiert. Die Methoden einzahlen und auszahlen, wie auch die Methoden zum Setzen und Abfragen der Eigenschaften, sind Instanzmethoden, da sie, um sinnvoll eingesetzt werden zu können, eine konkrete Instanz der Konto-Klasse benötigen. Wie Sie sehen, unterscheiden sich in Perl Instanz- und Klassenmethoden nicht durch bestimmte Schlüsselwörter voneinander,wie das in vielen anderen Programmiersprachen der Fall ist. In Java oder C + + werden beispielsweise Klassenmethoden durch den Modifizierer s t a t i c gekennzeichnet. Beim Aufruf von Methoden sieht man allerdings deutlich den Unterschied, ob es sich um eine Klassenmethode
my $ko = Konto->new(@eigenschaften);
oder um eine Instanzmethode handelt
$ko->einzahlen('98,76');
Der Pfeiloperator (->) übergibt dabei an die aufgerufene Methode als ersten Parameter das Element, das auf seiner linken Seite steht. Im ersten Aufruf also den Namen der Klasse /Konto, im zweiten Aufruf eine Referenz auf die Instanz $ko. Aufgrund des Blessing-Mechanismus ist es Perl somit möglich, die Aufrufe zuzuordnen. In unseren Beispiel-Skripten haben wir dieses Argument stets aus der Parameterliste herausgeshiftet und zwischengespeichert. Der Zugriff auf das Objekt erfolgt stets mit Hilfe dieses Argumentes.
6.2 Objektorientierte Programmierung mit Perl
6.2.5
63
Vererbung
Wie bereits besprochen, ist die Vererbung eines der wichtigsten Konzepte der OOP schlechthin. Und es wird Sie nicht verwundern, dass auch Perl dieses Konzept in hervorragender Weise unterstützt. Perl realisiert die Vererbung mit Hilfe des Spezial-Arrays OISA. Der Begriff ISA sollte dabei als is a, zu deutsch ist ein gelesen werden. Dieses Array, das als Package-globale Variable deklariert sein muss, enthält eine Liste der Klassen, von denen die aktuelle Klasse erbt. Beachten Sie dabei, dass, sofern Sie das Pragma s t r i c t verwenden, es nur mit Hilfe des Pragmas v a r s möglich ist, Package-globale Variablen zu deklarieren. Um die Klasse GiroKonto zu erstellen, die von der Klasse Konto erbt, geben Sie den folgenden Code an. package GiroKonto; use Konto; # Modul Konto einbinden QISA = qw(Konto}; # von Klasse Konto erben
# Implementierung der Klasse
# Wahrer Rueckgabewert return 1;
Sofern Sie das Pragma strict verwenden package GiroKonto; use Konto; # Modul Konto einbinden use strict; use vars 'QISA'; OISA = qw(Konto}; # von Klasse Konto erben
# Implementierung der Klasse
64
6 Objektorientierte Programmierung
# Wahrer Rueckgabewert return 1;
Beachten Sie, dass es notwendig ist, das Modul der Oberklasse, in unserem Falle Konto .pm, mit Hilfe einer use oder einer require- Anweisung einzubinden, damit Perl auf die Methoden der Oberklasse zugreifen kann. Sie können mit dem folgenden Skript ein Objekt der Klasse GiroKonto anlegen und initialisieren. # Datei: GiroKonto.pl # Einbinden der Klasse Konto use GiroKonto; # neue Konto-Instanz erzeugen und mit Werten vorbelegen Seigenschaften = (100, 'Daisy Duck', 123.45); my $gko = GiroKonto->new(Seigenschaften); # Eigenschaftswerte ausgeben print $gko->kontoNr . ", " . $gko-Kontoinhaber . ", " . $gko->kontostand . "\n";
Die Klasse GiroKonto besitzt nun die gesamte Funktionalität und alle Eigenschaften der Klasse Konto, ohne dass Sie auch nur eine einzige Zeile Code hierfür gebraucht hätten. Wenn Sie die Oberklasse ergänzen oder ändern, spiegelt sich dies automatisch in allen abgeleiteten Klassen wider. Rufen Sie nun über eine Instanz der Klasse GiroKonto die Methode auszahlen auf, sucht Perl, wenn es die entsprechende Methode nicht findet, automatisch in allen im Array OISA aufgelisteten Oberklassen nach der Methode. Die Suche erstreckt sich dabei auch auf die in der Oberklasse im Array OISA angegebenen Klassen. Dies bedeutet, dass Sie nicht alle Oberklassen der Klassen-Hierarchie angeben müssen, sondern nur die jeweils direkten. Im Sinne der OOP tun Sie sich übrigens keinen Gefallen, wenn Sie versuchen, es besonders genau zu machen und jeweils die gesamte Vererbungshierarchie anzugeben! Stellen Sie sich vor, an irgend einer Stelle der Hierarchie fügen Sie eine Klasse ein, Sie wären gezwungen die OISA-Arrays in der gesamten Hierarchie zu ändern. Ein weiterer Punkt ist an dieser Stelle bemerkenswert: Perl unterstützt, ähnlich wie in C + + , die Mehrfachvererbung und nicht nur wie in Java die Einfachvererbung. Lassen Sie uns beide Begriffe kurz erläutern. In Java kann eine Klasse direkt nur jeweils von einer Oberklasse erben, das bedeutet, die Klasse GiroKonto kann nur von der Klasse Konto erben, nicht aber gleichzeitig von der
6.2 Objektorientierte Programmierung mit Perl
65
Klasse einer anderen Vererbungshierarchie, wie beispielsweise der Klasse Kontoinhaber. Dieser Umstand wird als Einfachvererbung bezeichnet. Der Vorteil liegt darin, dass eine potentielle Fehlerquelle ausgeschlossen ist. Andererseits müssen Sie in Fällen, in denen eine Mehrfachvererbung sinnvoll ist, erhebliche "Klimmzüge" veranstalten, um diesem Umstand Rechnung zu tragen. C + + und Perl hingegen verfolgen einen im Sinne der OOP flexibleren, allerdings auch gefährlicheren Ansatz. In beiden Sprachen ist es möglich, eine Klasse von Oberklassen verschiedener Vererbungs-Hierarchien erben zu lassen. Unsere Klasse GiroKonto könnte seine Funktionalität durchaus von den Klassen Konto und Kontoinhaber erben. Diese Möglichkeit wird in der Sprache der OOP Mehrfachvererbung genannt. Sie sollten diese Technik vor allem am Beginn Ihrer Objektorientierten Programmier-Tätigkeit tunlichst vermeiden, aber wenn Sie wissen was Sie tun, ist die Mehrfachvererbung ein mächtiger Mechanismus, der Ihnen viel Arbeit ersparen kann. Im folgenden Beispiel erstellen wir die Klasse SparKonto, die von der Klasse Konto abgeleitet wird. Wir werden in der abgeleiteten Klasse die Eigenschaft guthabenzinsSatz sowie die Methode jahreszinsBerechnen zufügen. Wir initialisieren die Klasse diesmal, indem wir zunächst einen leeren anonymen Hash anlegen, die neue Klasse taufen und anschließend die Eigenschaften durch Aufruf der entsprechenden Eigenschaftsmethoden initialisieren. Dabei werden die Aufrufe zur Initialisierung der Eigenschaften kontoNr, kontoinhaber und kontostand direkt an die Oberklasse weitergeleitet, die Initialisierung der Eigenschaft guthabenZinsSatz erfolgt mit Hilfe der entsprechenden Methode der neu angelegten Klasse. package SparKonto; use Konto; # von Klasse Konto erben OISA = qw(Konto); # Konstruktor der Klasse Konto ueberschreiben sub new -f # Klassennamen uebernehmen my $klasse = shift; # Argumente uebernehmen my ($kontoNr, $kontoinhaber, $kontostand, $guthabenzinsSatz ) = ®_; # Instanz initialisieren my $r_konto = {>; # Instanz auf den Namen der Klasse taufen bless $r_konto, $klasse; # Instanz durch. Aufruf der Eigenschaftsmethoden initialisieren # geerbte Eigenschaften: $r_konto->kont oNr($kontoNr);
66
6 Objektorientierte Programmierung $r_kont o->konto inhaber($kont o inhaber); $r_konto->kontostand($kontostand); # in der Unterklasse neu definierte Eigenschaften $r_konto->guthabenzinsSatz(IguthabenzinsSatz); # Aufrufen des Konstruktors der Oberklasse
>
# Instanz zurueckgeben return $r_konto;
# Methode zum Setzen und Abfragen des Guthabenzinssatzes sub guthabenzinsSatz { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Falls ein Wert ubergeben wurde, speichern $r_konto->{'guthabenzinsSatz'} = shift if Q_;
>
# Eigenschaftswert zurueck geben return $r_konto->{'guthabenzinsSatz'};
# Methode zum Berechnen des Jahreszinses sub jahreszinsBerechnen { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Guthabenzinssatz lesen my $zins = $r_konto->{'guthabenzinsSatz'}; # Jahreszins berechnen my $jahreszins = $r_konto->{'kontostand'} / 100 * $zins; # Jahreszins zurueckgeben return $jahreszins;
} # Weihren Rueckgabewert erzeugen return 1;
Speichern Sie den oben abgedruckten Programm-Code im gleichen Verzeichnis, in d e m sich bereits die Klasse Konto befindet unter dem Namen Sparkonto. pm. Sie können die Funktion der Klasse mit Hilfe des folgenden Skriptes testen.
#!/usr/bin/perl # Datei: SparKonto.pl
6.2 Objektorientierte Programmierung mit Perl
67
# Einbinden der Klasse Konto use SparKonto; # neue Konto-Instanz erzeugen und mit Werten vorbelegen Qeigenschaften = (100, 'Daisy Duck', 1234.56, 2.5); my $sko = SparKonto->new(Qeigenschaften); # Eigenschaftswerte ausgeben print $sko->kontoNr . ", " . $sko->kontoinhaber . ", " . $sko->kontostand . ", " . $sko->guthabenZinsSatz . "\n"; # Jahreszins ausgeben print "Auf Ihre Einlagen erhalten Sie einen Jahreszins von: "; print $sko->jahresZinsBerechnen . "\n";
6.2.6
Überschreiben von Methoden
Wie oben dargestellt, ist es mitunter notwendig, Methoden, die in der Oberklasse definiert sind, in der Unterklasse neu zu definieren. Man nennt diesen Vorgang Uberschreiben. In Perl überschreiben wir Methoden einfach dadurch, dass wir diese in einer Unterklasse neu anlegen. Perl beginnt mit seiner Suche nach der auszuführenden Methode stets innerhalb der aktuellen Klasse. Ist Perl bei seiner Suche erfolgreich, versucht es nicht mehr weiter oben in der Hierarchie weitere Methoden gleichen Namens zu finden, sondern führt die gefundene Methode gleich aus. Im folgenden Beispiel überschreiben wir die Methode kontostand in der Klasse SparKonto.
package SparKonto;
# Methode Kontostand ueberschreiben sub kontostand { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Kontostand uebernehmen my $ks = shift if Q_; # Die folgende Anweisung dient dazu, das Ueberschreiben # zu demonstrieren print "Ich fuehre die ueberschriebene Methode" . " der Klasse SparKonto aus!\n"; # und wie gehabt ... # Falls ein Wert ubergeben wurde, speichern
68
6 Objektorientierte Programmierung $r_konto->{'kontostand'} = shift if 0_; # Eigenschaftswert zurueckgeben return $r_konto->{'kontostand'};
Fügen Sie den oben abgedruckten Code in die Klasse Sparkonto ein, und führen Sie die Anwendung Sparkonto.pl erneut aus. An Hand der ausgegebenen Meldung stellen wir fest, dass tatsächlich die in der Klasse Spar Konto neu definierte Methode und nicht die in der Oberklasse definierte ausgeführt wird.
6.2.7
Aufrufen von überschriebenen Methoden der Oberklasse
Mitunter sind Methoden, die solche aus der Oberklasse überschreiben, lediglich Erweiterungen der dort bereits festgelegten Funktionalität. In diesem Fall ist es sinnvoll, in der Methode der Unterklasse diese zusätzliche Funktionalität zu implementieren und anschließend die entsprechende Methode der Oberklasse aufzurufen. Zu diesem Zweck bietet Perl das Pseudopaket SUPER an, mit dessen Hilfe es möglich ist, eine Methode der Oberklasse aufzurufen. Der Vorteil der Verwendung dieses Pseudopaketes liegt darin, dass der Name der aufzurufenden Klasse nicht "hart" codiert werden muss. Dieser Vorteil kommt vor allem dann zu tragen, wenn Sie die Klassenhierarchie verändert haben und beispielsweise die ursprüngliche Oberklasse durch eine andere mit neuem Namen ersetzt wurde. Um diesen Vorgang zu illustrieren, überschreiben wir in unserer Klasse Sparkonto die Methode kontoinhaber der Oberklasse Konto. package SparKonto;
# Methode Kontoinhaber ueberschreiben sub kontoinhaber { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Namen des Kontoinhabers uebernehmen $ki = shift if ; # Methode in der Superklasse aufrufen my $ki = $r_konto->SUPER::kontoinhaber($ki);
6.2 Objektorientierte Programmierung mit Perl
69
# Eigenschaftswert zurueckgeben return $ki;
>
Der dargestellte Aufruf der Oberklasse mit Hilfe des Pseudopaketes SUPER ist allerdings nur dann sinnvoll, wenn von der Möglichkeit der Mehrfachvererbung nicht Gebrauch gemacht wurde, da sonst Perl nicht weiß, in welcher Oberklasse nach der entsprechenden Methode gesucht werden soll. Erbt die aktuelle Klasse von mehr als einer Klasse, muss die gewünschte Oberklasse für den Zugriff explizit, wie im folgenden Beispiel, angegeben werden.
package SparKonto;
# Methode Kontoinhaber ueberschreiben sub kontoinhaber { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Namen des Kontoinhabers uebernehmen $ki = shift if ; # Methode in der Superklasse aufrufen my $ki = $r_konto->Konto::kontoinhaber($ki); # Eigenschaftswert zurueckgeben return $ki;
6.2.8
Und die Polymorphie?
Wir haben nun die Geheimnisse der Objektorientierten Programmierung gelüftet, und Sie sind in der Lage, selbst Objektorientierte Anwendungen mit Perl zu erstellen. Sie haben wahrscheinlich gemerkt, dass Sie mit Hilfe des Objektorientierten Paradigmas eine Menge Code einsparen können. Bei genauer Planung schreiben Sie wiederverwertbaren und modularen Code. Sie erhöhen die Sicherheit Ihrer Anwendung und minimieren den Aufwand zur Programmpflege. Arbeiten Sie in Teams, ist der Objektorientierte Ansatz geradezu ideal. Aber Achtung! Wie gesagt, die OOP ist nichts für Leute, die sich mit zwei Litern Kaffee, drei Schachteln Zigaretten und einer Schnellpizza an den Computer setzen und wild drauf los programmieren. OOP braucht sorgfältige Planung, damit sie sinnvoll eingesetzt werden kann!
70
6 Objektorientierte Programmierung
Zum Abschluss dieses Kapitels möchten wir Ihnen noch den Begriff Polymorphie erklären, der stets im Zusammenhang mit der OOP auftaucht. Polymorphie bedeutet nichts anderes, als dass verschiedene Klassen über Methoden mit demselben Namen verfügen können. So kann beispielsweise sowohl eine Klasse Hund als auch eine Klasse L u f t b a l l o n über die Methode p l a t z verfügen 11 . Das Objekt weiß dabei stets aufgrund seiner Klassenzugehörigkeit, welche der Methoden p l a t z auszuführen ist. Bedeutung erhält dieser Mechanismus vor allem innerhalb einer Vererbungshierarchie, wenn die Methode Nahrungsauf na Time an mehreren Stellen überschrieben wurde. Hier ist es von grundlegender Bedeutung, dass beispielsweise der Mensch in einer Klassenhierarchie von Tieren die ihm zugeschriebene Methode und nicht die weiter oben in der Hierarchie bei den Insekten definierte verwendet. Aber das wurde ja alles bereits erklärt. Nehmen Sie also das Wort Polymorphie als das, was es ist: Ein hübsches Wort, das Sie immer dann anbringen können, wenn Sie Ihren Intelligenzquotienten unter Beweis stellen müssen.
11 Das Beispiel stammt übrigens aus dem lesenswerten Buch zur Programmiersprache C + + [Kloeppel97].
7
Datenbank-Programmierung mit
Eine Anwendung, die nicht über eine Datenbank-Anbindung verfügt, ist heute kaum mehr vorstellbar. So ist ein Maßstab, ob eine Programmiersprache eine "gute" Programmiersprache ist, sicherlich die Frage, wie leicht oder kompliziert es mit den Mitteln der betreffenden Sprache ist, auf Datenbanksysteme zugreifen zu können. Als DatenbankEntwickler sollten Sie zudem einige weitere Fragen stellen, bevor Sie mit der Arbeit beginnen: • Wie hoch ist der Abstraktionsgrad, den die Datenbankschnittstelle der von mir gewählten Programmiersprache bietet? Ist es also ohne großen Aufwand möglich, das zugrundeliegende Datenbank-Management-System zu wechseln, wenn beispielsweise die Datenbank umfangreicher wird als in der Planungsphase angenommen. • Nicht zuletzt von Bedeutung ist die Frage nach der Geschwindigkeit der Datenzugriffskomponenten, besonders dann, wenn es sich beim geplanten Projekt um eine Web-Anwendung handelt oder der zu bearbeitende Datenbestand sehr groß ist. Denn, was nützt die komfortabelste Datenbank-Schnittstelle in Ihrer Programmiersprache, wenn die von Ihnen erstellte Anwendung im wahrsten Sinne des Wortes "absäuft"? Selbstverständlich müssen Sie immer Kompromisse eingehen. So werden Sie zumeist eher auf Komfort als auf Geschwindigkeit verzichten können. Perl zählt, wenn Sie die oben aufgeführten Punkte betrachten, sicherlich zu den besten verfügbaren Programmiersprachen (wir vermeiden hier bewusst den Superlativ!). Perl bietet mit DBI/DBD eine komfortable Datenbankschnittstelle mit einem sehr hohen Abstraktionsgrad. Bei einem Wechsel des Datenbank-Management-Systems müssen Sie in der Regel nur eine Zeile Programmcode ändern 1 . Zudem ist Perl/DBI schnell2! Einen weiteren Vorteil bietet die Plattformunabhängigkeit von Perl. Ihre Datenbank-Anwendung ist ohne jede Änderung auf einem LINUX-, einem Windows- oder Mac-System lauffähig. Das Kapitel zur Datenbank-Programmierung gliedert sich in drei Teile: • Der erste Teil befasst sich mit den Grundlagen des Designs von relationalen Datenbank-Systemen. Perl bietet Ihnen, wie bereits angedeutet, ein leistungsfä1 Und wenn Sie Ihre Anwendung gut geplant haben und die Verbindungsdaten aus einer Initialisierungsdatei einlesen, nicht einmal das. 2 Sogar sehr schnell - in einem Test des Autors war die Perl-Lösung ungefähr zwanzig mal schneller als eine vergleichbare Lösung, die mit VisualBasic und A D O erstellt wurde. Allerdings war die zweite Anwendung nicht sehr gut optimiert.
72
7 Datenbank-Programmierung mit Perl/DBI higes und vor allem schnelles Datenbank-Interface. Dieses kann seine Leistungsfähigkeit aber nur dann voll zur Geltung bringen, wenn die zugrundeliegende Datenbank gewissen Mindestanforderungen bezüglich der Gestaltung und Datenorganisation genügt. Sind diese nicht erfüllt, kommt jeder Versuch, mit Perl auf Ihre Datenbank zuzugreifen ungefähr dem Versuch gleich, mit Ihrem neuen Ferrari auf Feldwegen fahren zu wollen. Wir werden uns in diesem Abschnitt zudem damit befassen, wie Sie den Datenbank-Server MySQL auf Ihrem System einrichten und mit den notwendigen Rechten versehen. Zudem erläutern wir, welche Perl-Module Sie für das Durcharbeiten dieses Kapitels benötigen. • Perl/DBI nutzt zum Zugriff auf Datenbanken ausschließlich die Abfragesprache SQL3. Daher sind zumindest grundlegende Kenntnisse in dieser Abfragesprache nötig, um mit der Datenbank-Schnittstelle von Perl auf Ihre Datenbanken zugreifen zu können. Dieses Wissen vermitteln wir Ihnen im zweiten Abschnitt dieses Kapitels. • Der dritte Teil befasst sich ausführlich mit der Programmierung der Datenbankschnittstelle Perl/DBI. Wir werden Ihnen die Arbeit mit der Schnittstelle anhand von Beispielen vorstellen und dabei auch einige Tipps und Tricks einführen. Schließlich möchten wir Sie auf mögliche Fallstricke bei der Arbeit mit Perl/DBI hinweisen.
7.1
Grundlagen des Datenbank-Designs
Bevor wir uns mit der Abfragesprache SQL und der Programmierung von Perl/DBI beschäftigen können, sollten Sie die Grundlagen des Datenbankdesigns kennen. In der jahrelangen Praxis des Autors hat sich gezeigt, dass die meisten Probleme von DatenbankAnwendungen bereits in der Design-Phase angelegt sind. Eine nachlässig oder schlecht konzipierte Datenbank wird in den seltensten Fällen ihre Aufgaben zufriedenstellend erfüllen und spätestens dann, wenn es notwendig ist, die Datenstruktur anzupassen, gibt so manche Datenbank-Anwendung salopp gesagt "ihren Geist" auf. Zudem sollten Sie stets bedenken, dass eine schlecht entworfene Datenbankstruktur aus jedem Ferrari (sprich: schnelles Datenbank-Management-System) einen "lahmen Gaul" macht! Im folgenden Abschnitt werden wir uns daher zunächst kurz mit dem Aufbau von relationalen Datenbanken und anschließend mit ihrem Design befassen.
7.1.1
Relationale Datenstrukturen
In relationalen Datenbanken werden die Daten in einzelnen Tabellen organisiert und verwaltet. Über Tabellen-Grenzen hinweg werden die Daten der Tabellen verknüpft. Dabei ist der Aufbau einer solchen Datenbank nicht so einfach, wie es auf den ersten Blick scheinen mag, da relationale Datenbanken in der Praxis aus einer Vielzahl solcher Tabellen bestehen können - oft weit mehr als einhundert Tabellen, so dass komplexe Strukturen entstehen. Ausgesprochen als sickel.
7.1 Grundlagen des Datenbank-Designs
73
Bevor wir uns mit dem Design von Datenbanken beschäftigen können, sollten wir einige wichtige Begriffe erklären. Tabellen werden in der relationalen Terminologie Relation genannt, woher das relationale System auch seinen Namen hat 4 . Eine Relation besteht dabei aus einem Kopf, in dem die Spalten definiert sind und einem Rumpf, der aus den Dateneinträgen der Datenbank besteht. Eine Spalte einer Datenbank-Relation wird Attribut genannt. Einen einzelnen Datensatz einer Datenbank, also eine Zeile einer Relation, nennt man Tupel. Bei dem Entwurf der Relationen sind prinzipiell folgende Ziele zu berücksichtigen: • In einer Datentabelle dürfen keine doppelten Tupel enthalten sein. Das bedeutet, jeder Datensatz muss genau eine Tatsache bzw. ein Objekt bezeichnen. In der relationalen Terminologie werden solche Objekte als Entitäten bezeichnet. • Die einzige Ordnung der Datensätze einer Relation ist die Reihenfolge ihrer Anlage. Andere Ordnungen, wie beispielsweise eine alphabetische Sortierung, werden erst bei der Ausgabe der Daten erzeugt, aber niemals in der Datenrelation selbst abgelegt. Der Versuch, eine solche Ordnung innerhalb einer Datenbank zu erzeugen, würde zum einen zu extremen Bearbeitungszeiten bei der Anlage neuer Tupel führen, zum anderen wegen der permanenten Neusortierung innerhalb der Relation auf Dauer zu Inkonsistenzen und Funktionsstörungen. • Auch die Attribute sollten nicht geordnet sein. Bei der Anlage einer Relation ergibt sich eine solche Ordnung meist von selbst aus dem logischen Zusammenhang heraus. Bei der späteren Pflege der Datentabelle werden neue Attribute einfach an das Ende der Relation angehängt. Eine Ordnung der Attribute wird in der Regel erst bei der Abfrage der Daten angelegt. • Alle Attribute einer Tabelle dürfen in jedem Datensatz nur genau einen Wert enthalten. Man nennt dies atomar. • Kein Attribut sollte in einer Tabelle mehrfach vorkommen. Auch auf die gesamte Datenbank bezogen, sollte es keine solchen mehrfach Vorkommen von Attributen geben. Dieses mehrfache Auftreten von Attributen wird in der Fachsprache Redundanz oder deutsch Weitschweifigkeit genannt. Wir werden diese Begriffe im nächsten Abschnitt noch weiter untersuchen. Befolgt eine Relation alle angesprochenen Regeln, wird sie als normalisiert bezeichnet. Wir werden weiter unten besprechen, auf welche Weise diese Normalisierung erreicht wird. 4 In der Literatur findet man auch oft die Erklärung, das relationale System beziehe seinen Namen von dem englischen Begriff relation, was ins Deutsche übersetzt soviel wie Beziehung bedeutet. Das Datenbank-System beziehe seinen Namen nach dieser Lesart aus dem Umstand, dass relationale Datenbanken aus einer Vielzahl von verknüpften, also über Beziehungen miteinander verbundenen Tabellen bestehen. Auch wenn diese Definition nicht ganz zutreffend ist, erklärt sie dennoch das Wesen relationaler Datenbanken recht gut. Wir haben daher nichts dagegen einzuwenden, wenn Sie sich diese Definition merken.
74
7 Datenbank-Programmierung mit Perl/DBI
7.1.2
Grundregeln des Datenbankdesigns
Nachdem wir im vorhergehenden Teil die grundlegende Begrifflichkeit des relationalen Datenmodells erörtert haben, wenden wir uns im folgenden den grundlegenden Modellen und Vorgehensweisen des Datenbankdesigns zu. Es sei in diesem Zusammenhang darauf hingewiesen, dass es für das Funktionieren einer relationalen Datenbank von grundlegender Bedeutung ist, diese Regeln genau zu beachten. Datenbanken, die diesen Prinzipien nicht folgen, werden bestenfalls leidlich arbeiten; im produktiven Einsatz hingegen sind sie nicht verwendbar. Keine Redundanzen. Für jedes Datenelement wird genau eine Spalte vorgesehen, es darf nicht in mehreren Spalten vorkommen, wie etwa im Feld Vor- und Familienname und im Feld Vorname. Doppelt vorhandene Datenelemente bringen keinen weiteren Informationsgewinn, führen aber zu einer Verschwendung von Speicherplatz und Arbeitszeit. Zudem ist durch eine solche Redundanz die Konsistenz der Datenbank erheblich gefährdet. Stellen Sie sich vor, im Feld Vor- und Familienname findet sich der Eintrag "Helmut Seidel", im Feld Vorname hingegen der Eintrag "Helmuth". Welcher der beiden Angaben möchten Sie nun glauben? Besteht eine Datenbank aus mehreren Relationen, müssen diese auch untereinander frei von Überschneidungen sein. Entitäten - keine Prozessdaten. In einer Relation werden nur sogenannte Entitäten (Tatsachen) aufgenommen. Entitäten sind z.B. eine Person, eine Maschine, eine Bestellung, etc. 5 Jede Entität wird durch ihre Eigenschaften bzw. Attribute eindeutig beschrieben. Welches dieser Attribute für die Datenverarbeitung bedeutsam ist, hängt dabei vom Verarbeitungszweck ab. Werte, die durch Rechenoperationen mit Attributen ermittelt werden können, sind keine Entitäten sondern Prozessdaten und werden nicht in die Relation aufgenommen, da sie wiederum eine Form von Redundanz darstellen. Durch die Verwendung von Prozessdaten kann es in Datenbanken zu erheblichen Problemen kommen. Hierzu ein Beispiel: In einer Datenbank sind die Felder Geburtsdatum und Alter aufgenommen. Beim Attribut Alter handelt es sich allerdings um ein Prozessdatum, da es sich aus dem Geburtsdatum sowie dem Tagesdatum errechnen lässt. Die resultierende Datenbank müsste nun täglich gepflegt werden, damit alle Datensätze das aktuelle Alter enthalten 6 . Stellen Sie sich den damit verbundenen Aufwand bei ca. 10.000 Datensätzen oder mehr bitte einmal vor! Eindeutigkeit. Zu beachten ist ferner, dass die Beschreibung einer Entität stets eindeutig sein muss. So darf beispielsweise die Beschreibung einer Person nur auf eine ganz bestimmte Person zutreffen. Um diese Eindeutigkeit zu erreichen, erweitert man den Datensatz in der Regel um ein Identifikationsmerkmal, etwa eine Artikel- oder Lieferantennummer. Solch ein Identifikationsmerkmal nennt man in der Datenbankterminologie Schlüssel, Schlüsselbegriff oder Schlüsselattribut. In neuerer Zeit hat sich auch der Begriff Primärschlüssel für ein solches Attribut eingebürgert, wir werden zumeist diesen Begriff verwenden. 5
Objektorientiert gedacht, ließe sich eine Entität auch als Instanz eines Objektes beschreiben. Zwar haben Menschen in der Regel nur ein einziges Mal im Jahr Geburtstag, aber dummerweise hat fast jeder Mensch (in einer Datenbank) an einem anderen Tag Geburtstag! 6
7.1 Grundlagen des Datenbank-Designs
7.1.3
75
Normalformlehre
Die Normalformlehre beschreibt, wie Relationen aufgebaut werden sollten, um Redundanzen und Zugriffsprobleme zu vermeiden. Bereits bei seiner Vorstellung des relationalen Datenmodells 1970 führte E. F. Codd die ersten drei Normalformen ein. Diese haben bis heute unverändert ihre Bedeutung erhalten, wurden allerdings in der Folgezeit um weitere Normalformen erweitert, die aber nicht die entscheidende Wichtigkeit der ersten drei besitzen. Um diese zu erklären, werden wir eine kleine CD-Datenbank (CD_DATA) mit folgenden Attributen erstellen.
Attributs-Name cd_titel kuenst1er_name cd_ersch_jahr song_name song_laenge rl_name rl_adresse rl_plz rl_ort
Bedeutung Name der CD Name des Künstlers Erscheinungsjahr der CD Name eines Liedes Länge eines Liedes Name des Record-Labels Straße und Hausnummer des Record-Labels Postleitzahl des Record-Labels Ort des Record-Labels
Die erste Normalform. Eine Relation ist in der ersten Normalform, wenn alle zugrundeliegenden Gebiete nur atomare Werte enthalten. Ein Gebiet bezeichnet dabei die Menge der möglichen Einträge in einem Datensatz. Das Gebiet des Feldes r l _ p l z bezeichnet also die Menge aller möglichen Postleitzahlen. Dies bedeutet, kein Attribut darf mehrere Werte in einem Datensatz besitzen. Nach der ersten Normalform müssen daher die Felder song_name und song_laenge aus der Relation herausgenommen werden. Es ergibt sich nun folgende Datenstruktur. cd_relation cd_id cd_titel kuenstler_name cd_ersch_jahr rl_name rl_adresse rl_plz rl_ort song_relation song_id song_name
76
7 Datenbank-Programmierung mit Perl/DBI
song_laenge Beachten Sie, dass wir mit den zusätzlichen Attributen cd_id und song_id in jeder Relation ein Primärschlüssel-Feld angelegt haben, welches die Tupel später eindeutig bestimmen wird. Die zweite Normalform. Jedes Element des Datensatzes, das kein Schlüsselattribut ist, muss voll vom Schlüssel abhängen. Besteht ein Schlüssel aus mehreren Teilschlüsseln, so ist das Element aus der Relation herauszunehmen, das nur von einem Teilschlüssel abhängt. In unserem Beispiel kann ein Künstler mehrere CDs eingespielt haben. Damit ist aber das Attribut kuenstler_name nicht vom Schlüsselbegriff cd_id abhängig. Es ist daher sinnvoll, das Feld kuenstler_name ebenfalls aus der c d _ r e l a t i o n herauszunehmen. cd_relation cd_id cd_titel cd_ersch_jahr rl_name rl_adresse rl_plz rl_ort song_relation song_id song_name song_laenge kuenstler_relation kuenstler_id kuenst1er_name
Die dritte Normalform. Alle Elemente dürfen nur vom Schlüsselattribut, nicht aber von anderen Elementen des Datensatzes abhängen. Zwischen weiteren Elementen darf es also keine Abhängigkeiten geben. Zu einem Warengruppenschlüssel gehört beispielsweise eine bestimmte Bezeichnung. Diese ist aber nicht direkt vom Schlüsselattribut sondern vom Warengruppenschlüssel abhängig. Daher ist die Warengruppenbezeichnung in einer gesonderten Tabelle zu erfassen. Der Warengruppenschlüssel stellt in diesem Fall einen Fremd- oder Sekundärschlüssel dar. Wird in einem Satz der Schlüssel von zwei oder mehreren Elementen gebildet, spricht man von einem zusammengesetzten Schlüssel. In unserem Beispiel müssten nach der dritten Normalform die Angaben zum Record-Label in einer eigenen Relation untergebracht werden. cd_relation cd_id cd_titel
7.1 Grundlagen des Datenbank-Designs
77
cd_ersch_jahr song_relation song_id song_name song_laenge kuenstler_relation kuenstler_id kuenst1er_name record_label_relation rl_id rl_name rl_adresse rl_plz rl_ort
Zweckmäßigkeitsüberlegungen. Manche Datenelemente zeichnen sich dadurch aus, dass sich eine Änderung in ihren Werten auf die gesamte Tabelle auswirkt. Wird beispielsweise der Umsatzsteuersatz verändert, hat dies Auswirkungen auf sämtliche Datensätze einer Artikeltabelle. Hier ist es sinnvoll, die Steuersätze in einer Umsatzsteuertabelle zu speichern. Diese Tabelle enthält einen Steuerschlüssel, dem jeweils ein bestimmter Steuersatz zugeordnet ist. Alle Artikel-Datensätze greifen nun über diesen Steuerschlüssel auf den Steuersatz zu. Kommt eine zukünftige Regierung plötzlich auf die Idee, die Umsatzsteuer zu senken 7 , braucht dieser neue Steuersatz lediglich in der Umsatzsteuer-Relation geändert werden. Alle Artikel der Artikel-Relation spiegeln sofort diese Änderung. Dies ist nicht nur deutlich einfacher, als alle Artikel-Datensätze zu ändern, sondern bewahrt Sie auch vor peinlichen Fehlern, wenn Sie versehentlich vergessen haben, einige Datensätze zu ändern. Denormalisierung. Die record_label-Relation enthält die Attribute Postleitzahl und Ort ( r l _ p l z und r l _ o r t ) . Zwischen diesen Attributen besteht eine Abhängigkeit, was einen Verstoß gegen die dritte Normalform darstellt. Da aber voraussichtlich jede Plattengesellschaft eine eigene PLZ hat, würde die Aufteilung in zwei getrennte Relationen einen Mehraufwand bei der Datenbankpflege bedeuten und keinerlei Gewinn bringen. Es ist daher in diesem speziellen Fall sinnvoll, auf die Normalisierung zu verzichten. Diesen Vorgang nennt man Denormalisierung.
7.1.4
Beziehungsstrukturen
Zum Abschluss der Datenbankdesign-Phase müssen die bisher erstellten Relationen über die Schlüsselattribute miteinander verbunden werden. Dies geschieht, indem in die Re7
Ich hoffe dem Leser ist klar, dass dies nur ein frommer Wunsch des Autors ist!
78
7 Datenbank-Programmierung mit Perl/DBI
lation, die nicht den Primärschlüssel besitzt, ein Verweis auf diesen eingefügt wird. Derartige Schlüssel werden als Fremd- oder Sekundär-Schlüssel bezeichnet. Die Tabelle, in der das verbindende Element den Primärschlüssel bildet, nennt man Master-Tabelle-, die, in der das verbindende Element einen sogenannten Fremdschlüssel bildet, nennt man Detail- Tabelle. Nach dem Einfügen der Fremdschlüssel in unsere Datenbank ergibt sich folgendes Bild. cd_relation cd_id cd_titel cd_ersch_jahr kuenstler_id rl_id song_relation song_id cd_id song_name song_laenge kuenstler_relation kuenstler_id kuenstler_name record_label_relation rl_id rl_name rl_adresse rl_plz rl_ort
Im nächsten Schritt gilt es, die Art der Beziehung zwischen den einzelnen Relationen festzulegen. Hierzu bedient man sich am Besten einer sogenannten Beziehungsgrafik, in der die Verknüpfungen der einzelnen Relationen untereinander durch Linien dargestellt werden. Im Einzelnen lassen sich drei Typen von Beziehungsstrukturen unterscheiden, die im Folgenden beschrieben werden. Die 1 : 1 - Beziehung. Diese Form der Beziehung wird nur relativ selten verwendet. Bei der 1 : 1 Beziehung entspricht jeder Tupel in Relation A null oder einem Tupel in Relation B. Als Beispiel kann eine Personaldatenbank dienen, bei der aus Gründen der Datensicherheit die Angaben zur Person von denen zum Einkommen der betreffenden Person getrennt werden. Manchmal kann es sinnvoll sein, eine 1 : 1 - Beziehung zu verwenden, wenn nicht jedem Datensatz in Relation A ein Datensatz in Relation B entspricht, da hierdurch sowohl
79
7.1 Grundlagen des Datenbank-Designs
einkommen
personal pfirsnnal-nr name
Abb.
7.1: Die 1:1-
1 • 1
pfirsnnal-nr nettogehalt
Beziehung
Speicherplatz als auch Verarbeitungszeit eingespart wird. In unserer Beispieldatenbank findet sich keine 1 : 1 Beziehung. Die 1 : n - Beziehung. Diese Beziehungsform ist bei relationalen Datenbanksystemen die am häufigsten anzutreffende. Bei dieser Beziehung sind jedem Datensatz in Tabelle A null, ein oder auch mehrere Tupel in Tabelle B zugeordnet. So sind in unserer Beispieldatenbank jeder CD mehrere Songs zugeordnet.
Abb.
7.2: Die 1 : n - Beziehung
Die Tabelle, in welcher der Verbindungsschlüssel der Primärschlüssel ist, heißt Mastertabelle, die Tabelle, in welcher er der Fremdschlüssel ist, heißt Detailtabelle. Die n : m - Beziehung. Dies ist die am schwierigsten darzustellende Beziehungsform. Dabei sind jeder Zeile in Relation A null, ein oder mehrere Datensätze in Relation B zugeordnet, andererseits sind jedem Datensatz in Relation B wiederum null, ein oder mehrere Datensätze in Relation A zugeordnet. In unserem Beispiel findet sich diese Beziehungsform zwischen der c d _ r e l a t i o n und der k u e n s t l e r _ r e l a t i o n . So kann auf der einen Seite ein Künstler mehrere CDs aufgenommen haben, auf der anderen Seite können aber auch auf einer CD mehrere Künstler mitwirken. Diese Relationsform lässt sich in den meisten Datenbank-Management-Systemen nicht direkt sondern nur über Zwischentabellen darstellen, indem diese Beziehung in zwei 1 : n - Beziehungen aufgelöst wird. Wenn Sie über die vorgestellte CD-Datenbank noch einmal in Ruhe nachdenken, werden Sie feststellen, dass zwischen der c d _ r e l a t i o n und der s o n g _ r e l a t i o n ebenfalls eine n:m-Beziehung besteht. Tatsächlich ist es nicht nur üblich, dass sich auf einer CD mehrere Songs (früher nannte man sie Lieder) finden, sondern im Zeitalter von sogenannten Samplern und Greatest Hits-CDs kann es durchaus vorkommen, dass sich ein Lied auf mehreren CDs in Ihrer Sammlung befindet. In unserer Beispiel-Datenbank ignorieren
80
7 Datenbank-Programmierung mit Perl/DBI
cd_relation cdjd
ccLkuenstler 1 :n
cd_titel
Abb.
7.3:
kuenstlerjd cdjd
n : 1
kuenstler_rel. kuenstler id kuenstler_name
Die n : m - Beziehung
wir diesen Umstand der Übersichtlichkeit halber. Wenn Sie die vorgestellte Datenbank verwenden möchten, um Ihre CD-Sammlung zu verwalten, sollten Sie diesem Umstand Rechnung tragen; Sie erleben ansonsten garantiert eines Tages eine unangenehme Überraschung!
7.1.5
Aufnahmestruktur
Nachdem nun die Datenstruktur festgelegt ist, müssen noch die Datentypen der einzelnen Attribute ergänzt werden. Die wichtigsten Datentypen, die in allen Datenbanksystemen nach SQL-Standard enthalten sind, lauten
Datentyp INT FLOAT DOUBLE
DATE TIME
Beschreibung Ganzzahlen mit oder ohne Vorzeichen Fließkommazahlen einfacher Genauigkeit Fließkommazahlen doppelter Genauigkeit. Dies ist der Standarddatentyp, den Sie für Fließkommawerte verwenden sollten. Sie sollten DOUBLE auch für Geldbeträge verwenden, obwohl es in einigen Datenbank-Management-Systemen auch Datentypen wie MONEY oder CURRENCY gibt. Diese stellen allerdings keine Standard-Typen dar und sollten daher nur mit Vorsicht verwendet werden. Ein Standard-Datums-Wert Ein Standard-Zeit-Wert
Ebenso legen Sie in diesem Schritt zu jedem Feld, falls möglich, den zulässigen Wertebereich fest. Zu jedem Schlüsselfeld notieren Sie, ob es sich hierbei um einen Primäroder um einen Sekundärschlüssel handelt. Bevor Sie nun die so gewonnene Datenstruktur in eine Datenbank umsetzen, sollten sie diese noch ausführlich überdenken und, nach Möglichkeit, mit den zukünftigen Anwendern genauestens erörtern. Bedenken Sie, dass Fehler in der Planungsphase relativ leicht zu beseitigen sind, in der fertiggestellten Datenbank kann dies jedoch einen erheblichen Arbeitsaufwand bedeuten.
7.2 Die Abfragesprache SQL
7.2
81
Die Abfragesprache SQL
Im letzten Abschnitt haben wir uns mit dem Design von relationalen Datenbanksystemen befasst. Dabei konnten wir erkennen, dass der Entwurf von relationalen Systemen keine triviale Aufgabe ist. Schon bei einfachen Datenbankanwendungen kommt man schnell auf eine stattliche Anzahl von Relationen. In unserem Beispiel einer CDDatenbank haben wir dabei ganz auf sogenannte Service-Tabellen verzichtet, in denen mögliche Werte für häufig verwendete Attribute enthalten sind. Nachdem der Datenbankentwurf vollendet und mit den zukünftigen Anwendern gründlich diskutiert ist, besteht der nächste Schritt darin, den Datenbankentwurf zu implementieren, also ihn umzusetzen. Die hierfür notwendigen SQL-Befehle werden weiter unten behandelt. Bevor Sie eine Datenbank implementieren, sollten Sie allerdings das Handbuch des von Ihnen verwendeten Datenbank-Management-Systems zu Rate ziehen, da sich hier mitunter Datenbank spezifische Unterschiede ergeben 8 . Nun ist der Zeitpunkt gekommen, die neu erstellte Datenbank mit Leben, das heißt mit Daten zu füllen. Dabei stellt sich schnell die Frage, wie es möglich ist, die einmal eingegebenen Daten wieder sichtbar zu machen. Spätestens hier kommt die Abfragesprache SQL (sprich: Sickel) ins Spiel. Wie im weiteren Verlauf ersichtlich wird, dient SQL nicht nur zur Datenbankabfrage; gerade bei Client-Server-Systemen ist es üblich, die gesamte Datenbankerstellung über SQL zu realisieren. Noch ein Punkt soll an dieser Stelle erwähnt werden: Nicht alle gebräuchlichen Datenbank-Management-Systeme basieren auf SQL. Gerade im industriellen Bereich kommen Datenbanksysteme zum Einsatz, die grundlegend anders organisiert sind. Als Beispiel möchte ich das BerkeleyDB-Datenbanksystem erwähnen. Dieses System kann kostenlos von folgender Internetadresse bezogen werden: http://www.sleepycat.com.
7.2.1
Der SQL-Standard
Die Datenbankzugriffssprache SQL (Structured Query Language9, dt. ungefähr: Strukturierte Abfrage Sprache) wurde mehrfach normiert. Die erste Normierung fand im Jahre 1986 statt und wird als SQL-1-Norm bezeichnet. 1989 wurde diese Norm ergänzt und als Norm SQL-1+-^Soxm bezeichnet. Nach einer erheblichen Erweiterung des Funktionsumfanges entstand im Jahre 1992 die SQL-2-Norm und nur ein Jahr später wurde, nach erneuter erheblicher Funktionssteigerung, von der ISO (International Standardisation Organisation) der 5QL-3-Standard bekanntgegeben. Dieser umfasst vor allem die sogenannten objektrelationalen und postrelationalen Erweiterungen für Datenbanksysteme. Bei dem letzten der SQL-Standards handelt es sich um ein ausgesprochen umfangreiches Werk mit ca 1500 Seiten! Leider ist der SQLStandard in den einzelnen Datenbank-Management-Systemen höchst unterschiedlich umgesetzt. Microsoft Access beispielsweise setzt lediglich den SQL-1+ Standard von 1989 mit einigen Access-spezifischen Erweiterungen um. Den gleichen Standard ver8 F ü r die freien Datenbanksysteme MySQL und PostgreSQL können Sie die Handbücher, teilweise in mehreren Sprachen, aus dem Internet herunterladen. MySQL: http://www.mysql.org und PostgreSQL: http://www.postgresql.org 9 In der Literatur findet man auch des Öfteren die Umsetzung Standard Query Language für den Begriff SQL; Sie haben also die freie Wahl!
82
7 Datenbank-Programmierung mit Perl/DBI
wendet der Marktführer Oracle. Der SQL-2 Standard wurde bisher nur selten realisiert, ganz zu schweigen vom SQL-3-Standard. Das einzige Datenbank-Management-System, das den Standard von 1992 und einige Teile des 1993-Standards umsetzt, ist das freie System PostgreSQL für UNIX-Systeme, das kostenlos von folgender Webadresse bezogen werden kann: h t t p : / / w w w . p o s t g r e s g l . o r g - Eine Version für MS- Windows NT/2000/XP liegt zwar theoretisch vor, ist aber ausgesprochen schwierig einzurichten, da die Anwendung auf dem Zielrechner kompiliert werden muss. Leichter ist es, bei den genannten Betriebssystemen die frei verfügbare UNIX-Umgebung für Windows-Systeme Cygwin zu installieren, die neben einer Unzahl von nützlichen Anwendungen wie Perl und Bash auch einen PostgreSQL-Server und Client enthält 1 0 . Wenn Sie es leichter, aber nicht ganz so umfangreich haben möchten, raten wir Ihnen, das freie DatenbankSystem MySQL zu verwenden. MySQL liegt in Versionen für UNIX, MacOs und für alle Windows-Versionen vor. Besonders angenehm für Windows-Nutzer ist, dass es ein Setup-Programm gibt, mit dem die Einrichtung zu einem Kinderspiel wird 1 1 . LINUXAnwender haben in der Regel Glück, da beide Datenbank-Management-Systeme in den meisten LINUX-Distributionen enthalten sind. Die Ausführungen der folgenden Abschnitte beziehen sich auf das Datenbank-System MySQL. Da, wo sich Abweichungen zu einem PostgreSQL-System ergeben, werden wir dies gesondert vermerken 12 . Ein weiteres Problem bei der Umsetzung der Standards ist, dass fast jeder Hersteller mehr oder weniger deutlich von den SQL-Standards abweicht, indem produkt-spezifische Erweiterungen integriert werden. Diese Erweiterungen können zwar von einem gewissen Nutzen sein, aber auch zu großen Problemen führen, wenn eine Datenbank von einem System auf ein anderes portiert werden soll. Also beispielsweise dann, wenn eine AccessDatenbank im Laufe der Jahre so stark gewachsen ist, dass nun auf ein Client-ServerSystem, wie beispielsweise MySQL, ausgewichen werden soll. Wurden Access-spezifische Erweiterungen des SQL-Standards verwendet, ist eine Übertragung der Abfragen mitunter nur nach aufwändigen Anpassungen möglich 13 . Da aber SQL-Abfragen besonders dann, wenn sie sich über mehrere Relationen erstrecken, ausgesprochen kompliziert werden können, rate ich dringend davon ab, solche Erweiterungen zu verwenden.
7.2.2
Abfragen mit SQL erstellen
Nachdem wir nun den SQL-Standard kurz erläutert haben, möchten wir Ihnen im Folgenden eine Einführung in die Arbeit mit SQL-Befehlen geben. Dabei beschränken wir uns weitgehend auf die SQL-l+-Norm, also die SQL-Befehle, die in den meisten Datenbank-Management-Systemen umgesetzt sind. 10
Sie können Cygwin kostenlos unter folgender Internetadresse beziehen: h t t p : / / s o u r c e s . r e d h a t . com/cygwin. 11 MySQL kann unter der folgenden Internetadresse bezogen werden: http://www.mysql.org. Beachten Sie , dass der Einsatz von MySQL im kommerziellen Bereich nicht immer kostenlos ist! Sollten Sie mit Ihrer Anwendung unter diese Kategorie fallen, kann für einige Euro eine kommerzielle Lizenz erworben werden. 12 Der vorgestellte Stoff besitzt prinzipiell für alle derzeit verfügbaren Datenbank-ManagementSysteme Gültigkeit, da wir ausschließlich SQL-Features besprechen werden, die zum 1989-Standard gehören. 13 Wenn Sie mit Perl/DBI arbeiten möchten (wovon wir ausgehen, da Sie ansonsten diese Zeilen gar nicht lesen würden), sind Sie auf Standard-SQL angewiesen, da Perl/DBI keine produkt-spezifischen Erweiterungen unterstützt!
7.2 Die Abfragesprache SQL
83
Um die in den folgenden Abschnitten vorgestellten Beispiele nachvollziehen zu können, muss auf Ihrem System der Datenbank-Monitor gestartet sein. Auf einem LINUXSystem gehen Sie hierzu wie folgt vor. 1. Öffnen Sie ein Terminal-Fenster. 2. Geben Sie am Eingabe-Prompt den Befehl mysql an, wenn Sie mit einer MySQLDatenbank arbeiten, oder p s q l DATENBANKNAME, sofern Sie mit einer PostgreSQLDatenbank arbeiten. Falls Sie mit einem Windows-System arbeiten, müssen Sie ebenfalls zunächst ein TerminalFenster öffnen, das auf einem Windows-System MS-DOS Eingabeaufforderung oder ähnlich heißt. Falls dies nicht schon geschehen ist, starten Sie nun den MySQL-Server, mit Hilfe des folgenden Befehls, C:\mysql\bin\mysqld sofern Sie MySQL im Standard-Pfad installiert haben. Sie brauchen nun ein zweites Terminal-Fenster, da Sie das erste nicht schließen dürfen, ohne dass der MySQL-Server-Prozess beendet wird. In diesem Fenster können Sie nun den MySQL-Monitor starten. c: \mysql\bin\mysql. Wenn Sie den MySQL- oder PostgreSQL-Monitor gestartet haben, können Sie die in den folgenden Kapiteln angegebenen Beispiele wie dargestellt verwenden. Dabei können Sie beliebig viele Zeilenumbrüche eingeben. Jedes SQL-Statement wird mit einem Semikolon (;) und einem Druck auf die Eingabe-Taste abgeschlossen und ausgeführt. Bevor Sie allerdings Abfragen in einer Datenbank ausführen können, müssen Sie diese öffnen. Dies geschieht bei MySQL mit Hilfe der folgenden Befehlszeile. use CD_DATA;
7.2.3
Einfache Abfragen mit dem Befehl SELECT
Der SQL-Befehl SELECT wählt Daten aus einer Datenbank aus. Im einfachsten Fall besteht eine SELECT-Abfrage aus vier Teilen: • Dem Schlüsselwort SELECT14, • der Angabe der auszugebenden Spalten der Relation, • Dem Schlüsselwort FROM • und dem Namen einer Relation oder Abfrage, auf die sich die Abfrage beziehen soll. 14 SQL ist im Gegensatz zu Perl nicht case-sensitive, unterscheidet also nicht zwischen Groß- und Kleinschreibung. Wir schreiben in diesem Buch der Klarheit halber alle SQL-Statements groß.
84
7 Datenbank-Programmierung mit Perl/DBI
Möchte man beispielsweise alle Spalten der Relation cd_relation, die wir weiter oben als Beispiel erstellt haben, ausgeben, ist der folgende SQL-Befehl an die Datenbank zu übermitteln. SELECT * FROM cd.relation;
Dabei sind folgende Regeln zu beachten: • SQL ist eine formatfreie Sprache. Das bedeutet, anders als in vielen Programmiersprachen wie z. B. Perl und C, wird in SQL nicht zwischen der Groß- und Kleinschreibung unterschieden. Der obige Befehl ließe sich daher auch in der folgenden Art notieren. select * from cd_relation;
Es ist aber ratsam, bei der Angabe von Relations- und Attribut-Namen (wie cd_relation) die Groß- und Kleinschreibung zu beachten, da es hier bei einigen Datenbank-Management-Systemen (wie dBASE 15 ) zu Problemen kommen kann. Zudem hat es sich in den letzten Jahren eingebürgert, SQL-Anweisungen wie SELECT und FROM, zur besseren Lesbarkeit komplett in Großbuchstaben zu schreiben, wie dies auch bei HTML Skripten üblich ist. • Ein SQL-Befehl kann sich über mehrere Zeilen erstrecken, was in der Praxis übrigens fast die Regel ist. • Jeder SQL-Befehl wird mit einem Semikolon abgeschlossen. • Der Stern (*) in der obigen Abfrage dient dabei als Platzhalter für alle Spalten der Relation c d _ r e l a t i o n . Wird der obenstehende SQL-Befehl auf unsere Beispiel-Datenbank angewendet, hat dies zur Folge, dass alle Spalten aller Tupel, also die komplette Relation c d _ r e l a t i o n , ausgegeben werden. Zumeist möchte man allerdings nicht alle Spalten einer Relation in die Abfrage einbeziehen, sondern nur einige Spalten anzeigen. Möchten Sie sich beispielsweise aus der Relation r e c o r d _ l a b e l _ r e l a t i o n die Attribute rl_name und r l _ o r t anzeigen lassen, ist der SELECT-Befehl wie folgt einzugeben. 15 d B A S E zählt eigentlich nicht zu den relationalen Datenbank-Systemen, unterstützt aber eine Grundmenge an SQL-Statements. In der Industrie besitzt dieses Datenbank-System immer noch eine recht hohe Verbreitung, besonders in der Version IV. Der Autor verwendet dieses System nach wie vor gerne für kleine Projekte.
85
7.2 Die Abfragesprache SQL SELECT rl.name, rl_ort FROM record_label_relation;
Es müssen also unmittelbar nach dem Schlüsselwort SELECT die Attribute (Spalten) durch Kommas getrennt angegeben werden, die im Ergebnis angezeigt werden sollen. Bei der Ausgabe werden die Attributsnamen als Spaltenüberschriften angezeigt. Eine SQL-Ausgabe in MySQL könnte daher ungefähr wie folgt aussehen. I Record_Label_Name I Record_Label_Ort | I Virgin I Hopsing
I London I Peking
I I
Manchmal kann es vorkommen, dass man nicht die Attributsnamen als Spaltenbeschriftung der Ausgabe sondern kürzere oder prägnantere Namen wünscht. Auch dies lässt sich mit Hilfe des SELECT-Befehls bewirken. SELECT rl.name AS Label, rl.ort AS Ort FROM record_label_relation;
Diese neuen Namen werden als AZias-Namen bezeichnet. Möchte man während der folgenden Arbeit auf die Ergebnismenge, das sogenannte Dynaset, zugreifen, beispielsweise von einem Perl-Skript oder einer anderen Abfrage aus, hat dies über den Alias-Namen zu geschehen. In allen bisherigen Beispielen wurde jeweils der gesamte Tabellen-Inhalt ausgegeben. Dies kann zur Folge haben, dass im Ergebnis mitunter recht lange Listen, von manchmal mehreren tausend Einträgen entstehen. Da die einzelnen Tupel in der Reihenfolge ausgegeben werden, in der sie in der Relation gespeichert sind 16 , kann das Suchen eines bestimmten Eintrages recht aufwändig sein. Hier wäre es hilfreich, die Ergebnismenge nach bestimmten Attributen zu sortieren. Genau das lässt sich mit dem Befehl ORDER BY bewirken. SELECT rl.name, rl_ort FROM record_label_relation ORDER BY rl.name;
Im obigen Beispiel wird die Ergebnismenge aufsteigend, alphabetisch nach dem Feldinhalt der Spalte rl_name sortiert, ausgegeben. Wünscht man hingegen eine absteigende Sortierung, so ist dies mit dem Schlüsselwort DESC ausdrücklich anzugeben, wie folgendes Beispiel zeigt. 16
U n d das ist in der Regel in der Reihenfolge der Eingabe oder gar völlig willkürlich.
86
7 Datenbank-Programmierung mit Perl/DBI
SELECT rl.name, rl.ort FROM record_label_relation ORDER BY rl_name DESC;
Zudem ist es möglich, die Sortierung auf mehrere Spalten auszudehnen. Stellen Sie sich beispielsweise vor, Sie möchten aus einer Adressdatenbank alle Adressen ausgedruckt bekommen. Dabei möchten Sie nicht nur nach dem Feld name sortieren, sondern auch nach dem Feld vorname, damit im Ergebnis eine "Duck Daisy" vor einem "Duck Donald" steht. Geben Sie hierzu den folgenden SELECT-Befehl ein.
SELECT name, vorname FROM adressen ORDER BY name, vorname;
Die ORDER BY-Klausel wird dabei von links nach rechts ausgewertet. Das bedeutet, das Ergebnisset wird zunächst nach dem Feld Name sortiert, "Maus" steht also hinter "Duck", aber vor "Valiant" 17 . Gibt es in einer Datenbank mehrere Personen mit dem Namen "Duck", werden diese nun erneut nach dem Feld Vorname sortiert, also "Duck Daisy" wird vor "Duck Donald" angezeigt. Auch wenn Sie nach mehreren Feldern sortieren, können Sie für jedes Feld nach dem Feldnamen die Sortier-Richtung bestimmen. Möchten Sie im obigen Beispiel die Namen absteigend, aber die Vornamen aufsteigend sortieren, ist der folgende SQL-Begriff zu verwenden.
SELECT name, vorname FROM adressen ORDER BY name DESC, vorname;
7.2.4
Abfrageeinschränkung mit
WHERE
In den vorhergehenden Beispielen haben wir jeweils den gesamten Tabelleninhalt abgefragt und ausgegeben. In den weitaus meisten Situationen möchten Sie aber nur eine Teilmenge des Datenbestandes einer Tabelle im Dynaset enthalten sehen. So möchten Sie etwa aus der c d _ r e l a t i o n nur diejenigen Einträge sehen, bei denen im Attribut c d _ e r s c h _ j a h r der Wert 2002 eingetragen ist. Um Abfragen auf diese Weise einzuschränken, verwenden Sie den Befehl WHERE. Eine derartige Abfrage könnte wie die folgende aussehen. 17
Für nicht Comic-Fans: Prince Valiant ist der englische Original-Name von Prinz
Eisenherz.
7.2 Die Abfragesprache SQL
87
SELECT cd_titel, cd_ersch_jahr FROM cd_relation WHERE cd_ersch_jahr = 2002;
Beachten Sie dabei, dass Zeichenketten (Strings) anders als Zahlen in einfachen Hochkommas eingeschlossen werden müssen. Bei Microsoft Access ist es auch möglich, stattdessen Anführungszeichen (") zu verwenden. Schauen wir uns im nächsten Beispiel eine Abfrage an, bei der nicht nach Gleichheit, sondern nach größer als gesucht wird. Wir möchten aus der CD-Datenbank alle RecordLabels herausfiltern, bei denen im Feld r l _ p l z ein Wert steht, der größer als 80000 ist. SELECT rl_name, rl_adresse, rl_plz, rl_ort FR0M record_label_relation WHERE rl.plz > 80000;
Beachten Sie bitte: Wird in Abfragen nach Werten in Zahlenfeldern gesucht, dürfen die Zahlen nicht in Anführungszeichen oder Hochkommas stehen. Im vorangehenden Beispiel ist zudem zu bedenken, dass ein Datensatz, der genau den Wert '80000' enthält, nicht gefunden würde, da nur Datensätze, bei denen die Postleitzahl größer als 80000 ist, in das Dynaset aufgenommen werden 18 . Manchmal ist es aber notwendig, mehrere Bedingungen miteinander zu verknüpfen. Nehmen wir an, Sie möchten alle Adressen von Plattenfirmen ausgeben, die ihren Firmensitz im Postleitzahlenbereich von 80000 (einschließlich) bis 89999 haben. Um dies zu erreichen, müssen Sie zwei Bedingungen miteinander verknüpfen: Die Postleitzahl muss größer oder gleich 80000 und gleichzeitig kleiner als 90000 sein. In SQL sieht die entsprechende Abfrage folgendermaßen aus. SELECT rl_name, rl_adresse, rl_plz, rl_ort rl_name, rl_adresse, rl_plz, rl_ort FR0M record_label_relation WHERE rl_plz >= 80000 AND rl_plz < 90000;
Neben der vorgestellten AND-Verknüpfung beherrscht SQL auch die aus Perl bekannte 0R-Verknüpfung. Im folgenden Beispiel werden alle Plattenfirmen angezeigt, die Ihren Sitz in München oder Landshut haben. 18
Zu den logischen Operatoren von SQL schlagen Sie bitte weiter unten nach.
88
7 Datenbank-Programmierung mit Perl/DBI
SELECT Name, Vorname, Anrede, Stra"se, PLZ, Ort rl_name, rl_adresse, rl_plz, rl_ort FROM record_label_relation WHERE rl_ort = 'M"unchen' OR rl_ort = 'Landshut';
7.2.5
Die Vergleichs-Operatoren von SQL
In den oben genannten Beispielen haben wir in den Bedingungsklauseln sogenannte Vergleichs-Operatoren verwendet. Vergleichs-Operatoren dienen dazu, einen Ausdruck auf seine Wahrheit zu testen. Alle Ausdrücke, die auf Wahrheit testen, geben einen sogenannten booleschen Wahrheitswert zurück. Die booleschen Wahrheitswerte sind entweder true (wahr), das heißt, der Ausdruck ist wahr, oder false (falsch), das heißt der Ausdruck ist nicht wahr. In der folgenden Tabelle haben wir die wichtigsten VergleichsOperatoren und Ihre Bedeutung zusammengestellt.
Vergleichsoperator =
> >=
< = und M_ier';
Dabei steht das Zeichen " _ " (Unterstrich) für genau ein beliebiges Zeichen. Manchmal kann es allerdings vorkommen, dass Sie nicht wissen, wieviele Zeichen "beliebig" sein sollen. Nehmen Sie einmal an, Sie möchten alle Adressen finden, bei denen im Ortsnamen die Zeichenkette »hall« vorkommt 19 . Verwenden Sie in SQL folgende Abfrage. SELECT name, vorname, einrede, strasse, plz, ort FROM adressen WHERE ort LIKE ''/.hall'/.';
Dieser Ausdruck findet alle Ortsnamen, die mit beliebig vielen Zeichen beginnen, anschließend die Zeichenfolge "hall" enthalten und schließlich mit beliebig vielen Zeichen enden. Einfach ausgedrückt, werden alle Ortsnamen gefunden, die an beliebiger Stelle die Zeichenfolge "hall" enthalten. lg "Hall" ist d a s keltische W o r t für Salz. W e n n Sie e i n e n Ort finden, der d i e S i l b e Hall in s e i n e m N a m e n t r ä g t (z. B. H a l l s t a t t , B a d R e i c h e n h a l l o d e r S c h w ä b i s c h H a l l ) , d a n n w i s s e n Sie, d a s s a n d i e s e m O r t in keltischer Zeit (ca. 6 0 0 v. Chr. bis ca. 15 v . C h r . ) S a l z a b b a u s t a t t g e f u n d e n h a t .
90
7 Datenbank-Programmierung mit Perl/DBI
Beachten Sie bitte, dass bei einigen PC-Datenbanken, wie MS-Access, anstelle des Operators '/, das Zeichen * und anstelle von _ das Zeichen ? verwendet werden muss! Schließlich möchten wir Ihnen noch den Operator NULL erläutern. NULL testet, ob in einem Datenfeld überhaupt ein Wert eingetragen wurde. Um beispielsweise alle Adressen zu finden, bei denen Ihnen kein Vornamen bekannt ist, verwenden Sie den folgenden SQL-Begriff.
SELECT name, vorname, anrede, strasse, plz, ort FROM Adressen WHERE vorname IS NULL;
Alle genannten Operatoren lassen sich mit dem Operator NOT negieren. Das bedeutet, IS NOT NULL ergibt im Gegensatz zu IS NULL nur dann true, wenn im betreffenden Feld Werte eingetragen sind. Die entsprechende Abfrage wird also in Ihr Gegenteil gewendet.
7.2.6
Die Verknüpfung von Relationen (JOIN)
In unseren bisherigen Beispielen haben sich die Abfragen immer auf eine einzelne Tabelle bezogen. Die Stärke von relationalen Datenbanken liegt aber gerade in der Möglichkeit, die Informationen auf mehrere Relationen zu verteilen. Diese Datenverteilung macht jedoch nur dann Sinn, wenn es auch möglich ist, die Daten aus mehreren Tabellen verknüpft abzufragen. Zu diesem Zweck kennt SQL die sogenannten Joins oder Verknüpfungen, die im Folgenden erläutert werden sollen. Die Verknüpfungen werden dabei immer über die WHERE-Klausel aufgebaut. Natural Join. Der sogenannte Natural Join ist die einfachste Art, zwei oder mehrere Tabellen miteinander zu verknüpfen. Eine einfache Verknüpfung zweier Tabellen besitzt folgende Form.
SELECT cd_titel, song_name, song_laenge FROM cd_relation, song_relation WHERE cd_relation.cd_id = song_relation.cd_id;
Im obigen Beispiel konnten bei der Aufzählung der Spalten die Tabellennamen fortgelassen werden, da sie nur bei nicht eindeutigen Spaltennamen angegeben werden müssen. Die allgemeine Form lautet daher.
SELECT relation.attribut, relation.attribut, relation.attribut FROM relation, relation, ... WHERE relation.attribut = relation.attribut;
7.2 Die Abfragesprache SQL
91
Die Verknüpfung von zwei oder mehreren Relationen mit Hilfe des Natural Join ist zwar sehr einfach anzuwenden, besitzt aber auch eine Reihe von Nachteilen. Der wohl entscheidenste Nachteil ist, dass diese Verknüpfungsmethode als Ergebnis einen sogenannten Snapshot und kein Dynaset liefert. Ein Snapshot ist eine sogenannte Menge, das heißt, Datensätze aus einem Snapshot können nicht verändert werden. Dies kann natürlich bei Abfragen, die nur der Information oder dem Ausdruck dienen, von Vorteil sein, da der Umgang mit Snapshots erheblich schneller ist als der mit Dynasets. Ein Dynaset ist eine dynamische Menge, was bedeutet, dass alle Daten auch geändert werden können. I N N E R J O I N . Wie wir oben gesehen haben, lässt sich mit einem Natural Join nur ein Snapshot erzeugen, um ein Dynaset zu erzeugen, muss ein expliziter JOIN-Befehl verwendet werden. Die häufigste Verknüpfungsmethode von zwei oder mehr Tabellen ist der sogenannte INNER JOIN. Mit diesem wird nach Möglichkeit ein Dynaset erstellt. Einen INNER JOIN erzeugen Sie folgendermaßen.
SELECT cd_titel, song_name, song_laenge FROM cd.relation INNER JOIN song.relation ON cd_relation.cd_id = song_relation.cd_id;
Beim INNER JOIN werden nur Tupel ausgewählt, bei denen die im ON-Teil angegebenen Attribute übereinstimmende Werte aufweisen. In unserem Beispiel also werden alle Songs aufgelistet und mit dem zugehörigen CD-Titel angegeben. Im folgenden Beispiel stellen wir alle CD-Titel mit zugehörigen Liedern da, die von der Sängerin "Carla Bruni" aufgenommen wurden.
SELECT kuenstler_name, cd_titel, cd_ersch_jahr, song_name FROM kuenstler.relation INNER JOIN cd.relation ON kuenstler.relation.kuenstler_id = cd_relation.kuenstler_id INNER JOIN song_relation ON cd_relation.cd_id = song_relation.cd_id WHERE kuenstler_name = 'Caria Bruni';
Wie Sie sehen, benötigen wir bei dieser Abfrage einen zweifachen INNER JOIN, da an der Abfrage insgesamt drei Relationen beteiligt sind. Die Ausgabe können Sie in der zugehörigen Abbildung betrachten. O U T E R J O I N . Neben dem INNER JOIN gibt es noch die Möglichkeit der äußeren Verknüpfung (OUTER JOIN). Der Unterschied zur inneren Verknüpfung liegt darin, dass bei der äußeren Verknüpfung alle Datensätze der einen Relation angezeigt werden und nur die Datensätze der zweiten Relation angezeigt werden, bei denen das Verknüpfungsattribut übereinstimmt. Beim OUTER JOIN lassen sich zwei Spielarten unterscheiden.
92
7 Datenbank-Programmierung mit Perl/DBI helmuffi nb Miiiel." Date!
gearbeiten
Ansicht
lerminal
£ehe zu
Hilfe
mysql> use CD_DATA Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> SELECT kuenstler_name, cdLtitel, cd_ersch_Jahr, song_name -> FROM kuenstler_relation INNER JOIN -> cd_relation ON kuenstler_relation.kuenstler_id = -> cd_relation.kuenstler._id INNER JOIN -> song_relation ON ccLrelation.ccLid = -> song_relation.cd_id -> WHERE kuenstler_name = 'Carla Bruni'; kuenstler_name
cd_titel
Carla Carla Carla Carla Carla
quelqu quelqu quelqu quelqu quelqu
Bruni Bruni Bruni Bruni Bruni
un un un un un
ccLersch_jahr m m M m m
a a a a a
dit dit dit dit dit
2003 2003 2003 2003 2003
song_nane Quelqu un n a dit Raphael Tout le monde La noy ee Le toi du noi
5 rows in set (0.01 sec) |TCYsql>
d
Abb. 7.4•' Alle Titel der Sängerin Carla Bruni
LEFT JOIN: Es werden alle Datensätze der Tabelle links vom Befehlswort LEFT JOIN ermittelt und zusätzlich aus der rechten Tabelle nur die, die eine Entsprechung auf der linken Seite haben. SELECT cd_titel, song_name, song_laenge FROM cd.relation LEFT JOIN song.relation ON cd_relation.cd_id = song_relation.cd_id;
RIGHT JOIN: Hier werden alle Zeilen der rechten Tabelle und nur die verknüpften der linken Tabelle angezeigt. RIGHT JOIN ist also der Gegenbefehl zu LEFT JOIN.
7.2.7
Funktionen
Standard-SQL kennt fünf eingebaute Standardfunktionen, die als Aggregatfunktionen bezeichnet werden: Mittelwert (AVG20), Anzahl (COUNT), Summe (SUM), Maximalwert (MAX) und Minimalwert (MIN). Um beispielsweise die Anzahl aller CD-Titel, die in der Datenbank gespeichert sind, zu erhalten, geben Sie die folgende SELECT-Klausel ein. SELECT COUNT(cd.titel) AS AnzahlTitel FROM cd_relation; °Im Englischen bedeutet average soviel wie Durchschnitt oder Mittelwert.
93
7.2 Die Abfragesprache SQL
Im obigen Beispiel wurde als Parameter der Funktion ein Spaltennamen verwendet. Sie können aber als Parameter auch einen beliebigen Ausdruck verwenden 21 . Nehmen wir an, Sie möchten den Wert Ihres Lagerbestandes an CDs ermitteln. In der cd relation haben Sie im Feld ek_preis den Netto-Einkaufspreis (ohne Mehrwertsteuer) eingepflegt, ausgegeben werden soll aber die Summe der Preise inklusive Mehrwertsteuer. Führen Sie hierzu den folgenden SQL-Befehl aus. SELECT SUM(ek_preis * FROM
1.16)
cd_relation;
Mit Hilfe von Funktionen ist es auch möglich, neue Spalten zu erzeugen. Diese Spalten existieren nur in der Abfrage und haben keine Rückwirkung auf die zugrundeliegende Datenbank. Wenn Sie beispielsweise aus einer Relation die Mitarbeiterdaten speichert, eine Liste erzeugen möchten, in der die wichtigsten Mitarbeiterdaten sowie das Jahreseinkommen ausgegeben werden, wobei das Jahreseinkommen aus 12 Monatsgehältern und einem Weihnachtsgeld in Höhe von 500 Euro errechnet wird. In der Tabelle selbst ist allerdings lediglich das Monatsgehalt eingepflegt. Eine Lösung bietet der folgende SQL-Befehl.
SELECT persnr, name, FROM
12 * g e h a l t + 5 0 0 A S
Jahreseinkommen
personal;
7.2.8
Datenbanken mit SQL-Befehlen pflegen
Bisher haben wir SQL-Befehle lediglich dazu verwendet, Daten aus Datenbanken abzufragen. Mit Hilfe von SQL ist es aber auch möglich, Datenbanken und Relationen zu erstellen oder zu löschen, Datensätze einzugeben, zu ändern und zu löschen. Diese Möglichkeiten sollen im Folgenden kurz beschrieben werden. Sofern Sie mit DatenbankManagement-Systemen wie MySQL oder PostgreSQL arbeiten, sind Sie sogar auf die im Folgenden beschriebenen Befehle angewiesen 22 . Datenbanken erstellen und löschen. Um eine Datenbank erstellen zu können, ist es notwendig, dass der Datenbankserver-Prozess aktiv ist. Da die Syntax zur Erstellung von Datenbanken von einem Datenbank-Management-System zum anderen leicht unterschiedlich sein kann, beziehen sich alle folgenden Beschreibungen auf das DatenbankSystem MySQL. 21
solange er syntaktisch korrekt ist Oder Sie verwenden Tools wie das kostenlose phpMyAdmin, das auf der Webseite http://www.phpmyadmin.net heruntergeladen werden kann. Auf Windows-Systemen kann der Versuch, diese Anwendung zu installieren, leicht zu einem Alptraum werden, da es notwendig ist, eine funktionierende php-Installation auf Ihrem System zu besitzen. Diese wiederum setzt einen aktiven Web-Server wie Apache voraus ... 22
94
7 Datenbank-Programmierung mit Perl/DBI
Um den Datenbankserver zu starten geben Sie in der Regel in einer Befehlskonsole (bei Windows ein MS-Dos-Fenster, bei LINUX ein Terminal-Fenster) den folgenden Befehl ein. mysqld Nachdem der Server aktiv ist, können Sie in einem zweiten Konsolen-Fenster mit einer Datenbank kommunizieren. Bei MySQL arbeiten Sie hierzu mit dem Programm mysql. Um eine neue Datenbank anzulegen, geben Sie den folgenden Befehl ein. mysql CREATE l i t e r a t u r Um eine Datenbank zu löschen verwenden Sie den Befehl, mysql DROP l i t e r a t u r Natürlich wird die Datenbank nicht einfach gelöscht, zunächst wird eine Warnung ausgegeben, aber dann ...! Um mit MySQL zu arbeiten, benötigen Sie übrigens die entsprechenden Rechte. Das bedeutet, nicht jeder x-beliebige Anwender hat die Möglichkeit, Datenbanken anzulegen oder zu löschen 23 . Damit ein Benutzer "heinz" auf einem LINUX-System das Recht erhält, Datenbanken anzulegen und wieder zu löschen, geben Sie in einem Root-Terminal-Fenster in den mysql-Monitor die folgende Anweisung ein.
GRANT ALL on * . * TO heinz;
Die meisten Datenbank-Management-Systeme besitzen übrigens ein ausgeklügeltes Sicherheitssystem, mit dem genau festgelegt werden kann, wer was an einer Datenbank ändern darf. Das gilt übrigens auch für das Anlegen und Ändern von Relationen und Daten. Tabellen erstellen und löschen. Zum Erstellen von Datenbanktabellen wird der SQL-Befehl CREATE TABLE verwendet. Voraussetzung hierfür ist bei Client-Server-Datenbanken, dass der Datenbankserver läuft, und dass Sie die entsprechenden User-Rechte auf die Datenbank besitzen. Um in einer Datenbank eine Tabelle anzulegen, müssen Sie zunächst die Datenbank öffnen. Dies geschieht wiederum in einer Befehlskonsole. Bei MySQL geben Sie hierzu den folgenden Befehl ein. mysql CD.DATA Nach dem Betätigen der Eingabe-Taste erscheint der MySQL-Eingabe-Prompt des Befehlsmonitors. mysql > 23
Dies gilt allerdings nur für Anwender von LINUX- und UNIX-Systemen. Nutzer von WindowsSystemen besitzen in der Regel Administrator-Rechte, das heißt, hier hat tatsächlich jeder x-beliebige User das Recht, Datenbanken zu löschen!
7.2 Die Abfragesprache SQL
95
Nun können Sie die Befehle zum Erstellen von Tabellen eingeben. Um in der Datenbank CD_DATA die Tabelle song_relation anzulegen, geben Sie die folgende Zeichenfolge in den Befehlsmonitor ein. CREATE TABLE song_relation song.id INT NOT NULL, cd_id INT, song.name VARCHAR(IOO), song.laenge VARCHAR(20), PRIMARY KEY(song.id) );
Mit dem obenstehenden Befehl haben Sie eine neue Datentabelle in Ihre CD-Datenbank eingefügt. Die Tabelle hat vier Attribute: song_id (als Primärschlüssel), cd_id, song_name u n d song_laenge.
Falls Sie die soeben angelegte Tabelle wieder löschen möchten, verwenden Sie den Befehl DROP TABLE. DROP TABLE song.relation;
Mit Hilfe dieses Befehls wird die Tabelle unwiderruflich aus der Datenbank gelöscht! Werte in Tabellen eintragen und löschen. Wenn Sie in die soeben erstellte Tabelle Daten eingeben möchten (ich hoffe Sie haben die Tabelle nicht wirklich gelöscht), dann geschieht auch dies mit Hilfe von SQL-Anweisungen. Mit dem folgenden Befehl fügen Sie einen Datensatz in die Tabelle song_relation ein. INSERT INTO song.relation (song_id, cd_id, song_name, song_laenge) VALUES (1, 4, 'Mama', '2:35');
Nachdem Sie die obenstehende Befehlsfolge in den MySQL-Monitor eingegeben haben, wird der neue Datensatz in die Datenbank aufgenommen. Um sich davon zu überzeugen, geben sie den folgenden "herkömmlichen" SQL-Befehl in den Monitor ein. SELECT * FROM song_relation
Am Bildschirm wird der soeben eingetragene und einzige Datensatz unserer Tabelle angezeigt. Um den frisch angelegten Datensatz wieder aus der Datenbank zu entfernen, verwenden Sie den Befehl DELETE. DELETE FROM song.relation WHERE song_name = 'Mama';
7 Datenbank-Programmierung mit Perl/DBI
96
Achtung! Wie Ihnen sicher gleich aufgefallen ist, werden mit diesem Befehl alle Datensätze gelöscht, bei denen der Name des Songs "Mama" heißt. In unserem Beispiel ist dies allerdings unerheblich, da nur ein Datensatz dieses Titels vorhanden ist. In der Realität sollten Sie allerdings bei Löschabfragen erheblich vorsichtiger zu Werke gehen. Ein Datenbankprofi würde zunächst eine 'normale' SQL-Abfrage durchführen, um die Wirkung der WHERE-Klausel zu testen. Erst nachdem man sicher ist, dass wirklich nur die gewünschten Datensätze gelöscht werden, wird die eigentliche DELETE- Abfrage durchgeführt. Im Übrigen sei darauf hingewiesen, dass in einer gut programmierten Datenbank eigentlich ohnehin keine Datensätze gelöscht werden sollten! Datensätze ändern. Es kann durchaus vorkommen, dass Sie einen Datensatz abändern müssen ohne ihn dabei zu löschen. Beispielsweise, wenn Sie feststellen, dass der Heintje-Klassiker nicht einfach "Mama", sondern "Mama, du sollst doch nicht um deinen Jungen weinen" heißt. Verwenden Sie hierzu das folgende SQL-Statement. UPDATE song.relation SET song_name = ' Mama, du sollst doch nicht um deinen Jungen weinen' WHERE song_name = 'Mama';
Wie Sie sehen, verwenden wir zum Ändern von Datensätzen die beiden SQL-Befehle UPDATE u n d SET.
Nun haben wir Ihnen die Grundlagen der Abfragesprache erläutert, die notwendig sind, um mit Hilfe von Perl und DBI auf Datenbanken zugreifen zu können. In den folgenden Abschnitten können wir uns nun auf die Programmierung von Datenbanken konzentrieren.
7.3
Die Programmierung der Datenbank-Schnittstelle Perl/DBI
Wie bereits im Vorwort zu unserem Datenbank-Kapitel gesagt, verfügt Perl über eine sehr schnelle, komfortable und gut abstrahierte Datenbankschnittstelle, die Perl/DBI genannt wird. Die Abkürzung DBI steht für den englischen Begriff DataBase Interface. Die DBI-Schnittstelle wurde von Tim Bunce geschrieben und der Perl-Gemeinschaft unter den gleichen Bedingungen wie Perl selbst zur Verfügung gestellt. Neben der Fähigkeit von Perl, besonders gut mit CGI24 zusammenarbeiten zu können, ist sicherlich DBI einer der Gründe, warum Perl mitunter als 11 der Klebstoff" bezeichnet wird, " der das Internet zusammenhält".
7.3.1
Das Konzept von Perl/DBI
Die meisten Datenbank-Systeme, die sich derzeit auf dem Markt befinden, sind Relationale Datenbanksysteme und bedienen sich der Abfragesprache SQL zur Abfrage und 24
Englisch für Common
Gateway
Interface.
7.3 Die Programmierung der Datenbank-Schnittstelle Perl/DBI
97
Pflege der Daten. Da SQL, wie wir bereits gesehen haben, standardisiert ist, scheint es auf den ersten Blick eine leicht zu lösenden Aufgabe zu sein, eine allgemeingültige Datenbankschnittstelle zu entwickeln, um mit Hilfe von Perl auf Datenbanken zugreifen zu können. Doch dies scheint eben wirklich nur auf den ersten Blick so zu sein. Bei genauer Betrachtung tun sich vor dem Entwickler "wahre Gräben" auf. So ist, wie wir weiter oben gesehen haben, SQL mehrfach standardisiert worden und kaum ein Datenbank-System unterstützt den gesamten SQL-Standard. Fast jeder Hersteller lockt zudem mögliche Kunden mit speziellen Erweiterungen zu diesem Standard. An anderen Stellen weichen Hersteller vom Standard ab. So wird beispielsweise der Standard-Wildcard-Operator'/. in MS-Access durch den Operator * ersetzt. Als würden sich durch diese babylonische Sprachverwirrung nicht schon genügend Probleme auftun, hat sich Tim Bunce auch noch zum Ziel gesetzt, eine Möglichkeit zu schaffen, mit Hilfe von Perl und SQL auf "Datenbank-Systeme" zugreifen zu können, die weder relational organisiert, noch in der Lage sind, SQL-Anweisungen zu verstehen. Ein Beispiel hierfür sind die sogenannten CS K 25 -Dateien. Bei CSV-Dateien handelt es sich um reine Text-Files, bei denen die einzelnen Attributswerte durch ein bestimmtes Textzeichen, wie beispielsweise ein Komma oder Semikolon, voneinander getrennt sind. Perl erreicht diese Flexibilität dadurch, dass eine vierschichtige Architektur eingesetzt wird, bei der zwischen die Schnittstelle zur Programmiersprache Perl (DBI) und das Datenbank-Management-System eine Treiberschicht eingeschoben wird. Diese Treiber werden DBD genannt, was für den englischen Begriff DataBase Driver (zu Deutsch Datenbank Treiber) steht. Die Architektur stellt sich wie in der Abbildung 7.5 dar.
Abb.
7.5:
Die Datenbankarchitektur
von
Perl/DBI
Diese Treiber übernehmen die Aufgabe, die SQL-Anweisungen, die von Perl/DBI an die Datenbank abgesendet werden, in eine für das jeweilige System verständliche Form zu übersetzen, wie dies in der Abbildung 7.6 dargestellt ist. Bei "Datenbank-Systemen", wie beispielsweise CSV-Dateien, die nicht in der Lage sind, SQL-Anweisungen zu verstehen, werden diese vom DBD-Modul in für das entsprechende System verständliche Anweisungen übersetzt. 25 D i e englische Abkürzung CSV Kommata getrennte Werte.
steht für Comma
Separated
Values,
zu deutsch ungefähr durch
98
7 Datenbank-Programmierung mit Perl/DBI
Abb.
7.6: Das Treibersystem
von
Perl/DBI
Möchten Sie das Datenbank-System, das von Ihrer Anwendung angesprochen werden soll, wechseln, sind hierfür lediglich zwei Schritte notwendig: • Den DBD-Treiber für das Datenbank-System aus dem CPAN herunterladen und installieren. • Im Programmcode die Zeile ändern, in welcher der Datenbank-Treiber angegeben wird oder, falls Sie eine Initialisierungsdatei verwenden, die entsprechende Zeile in dieser Datei ändern. Und schon arbeitet Ihre Anwendung mit einem anderen Datenbank-Server 26 .
7.4
Arbeiten mit DBI/DBD
Wir möchten nun, nachdem wir Ihnen einiges über Datenbanken und die Perl-Datenbankschnittstelle erklärt haben, Perl/DBI einsetzen. Wir gehen davon aus, dass Sie das Modul DBI sowie die von Ihnen benötigten DBD-Treiber auf Ihrem System eingerichtet haben. Zudem muss auf Ihrem System ein Datenbankserver wie MySQL oder PostgreSQL gestartet sein. Ist eine der Bedingungen nicht erfüllt, lesen Sie bitte den entsprechenden Abschnitt, weiter oben in diesem Kapitel. Beachten Sie zudem, dass auf Ihrem System die vorgestellte CD-Datenbank CD DATA eingerichtet sein muss. In der Datenbank sollten selbstverständlich auch einige Tupel angelegt sein.
7.4.1
Vorhandene Datenbank-Treiber abfragen
Um mit Perl/DBI auf eine Datenbank zugreifen zu können, müssen Sie zunächst das DBI-Modul, wie im Kapitel Module in Perl beschrieben, in Ihre Anwendung einbinden.
26
Eine andere Sache ist natürlich der Aufwand, den Sie betreiben müssen, um die Datenbank selber von einem in das andere System zu übertragen. Aber auch hier hilft Ihnen Perl mit dem Modul: SQL::Translator, das genau diese Aufgabe für Sie übernimmt.
7.4 Arbeiten mit DBI/DBD
99
#!/usr/bin/perl -w # Einbinden des DBI-Moduls use DBI;
Wir können nun abfragen, welche Perl-Datenbank-Treiber auf Ihrem System vorhanden sind. #!/usr/bin/perl -w # dbd_treiber.pl # Ermitteln der auf dem System vorhandenen DBD-Triber-Module # Einbinden des DBI-Moduls use DBI; # installierte Datenbank-Treiber ermitteln ... m y Streiber = DBI->available_drivers(); # ... u n d ausgeben print "Auf Ihren System sind die folgenden DBD-Module foreach my $treiber(@treiber){ print "$treiber\n";
installierten";
> Nachdem Sie das Skript abgetippt und ausgeführt haben, wird einen Liste der auf Ihrem System installierten DBD-Treiber-Module ausgegeben.
7.4.2
Verbindung zu einer Datenbank aufbauen
Nachdem wir nun wissen, dass der für den Zugriff auf das gewünschte Datenbanksystem benötigte Treiber (hoffentlich) installiert ist, die gewünschte Datenbank vorhanden und mit den notwendigen Zugriffsrechten versehen ist, können wir die Verbindung zur Datenbank aufbauen. Hierfür benötigen wir ein Connection-Objekt27. Dieses erstellen wir, indem wir die Methode connectO der DBI-Klasse aufrufen und die benötigten Parameter übergeben. Dies sind im Einzelnen: • Der zu verwendende Treiber. • Der Name der zu öffnenden Datenbank. 27
Sie haben sicher bereits gemerkt, dass es sich bei dem Perl-DBI-Modul um ein Objektorientiertes Modul handelt!
7 Datenbank-Programmierung mit P e r l / D B I
100
helrnul-' nb sti.-Jfci: •,perl. bu< h/dbi examples ßatel
Bearbeiten
Ansicht
Terminal
Gehe zu
hyffe
[helnutQnb-seidel helnut]$ c d perl, büch [helmutCnb-seidel perl_buch]$ cd dbi/examples/ [helmutönb-seide1 exaniples]$ perl dbd_treiber.pl Auf Ihren System sind die folgenden DBD-Module installiert: ExampleP Pg Proxy niysql [helnutCnb-seidel exaniples]S
Abb. 7 . 7 : Ausgabe des Skriptes
|
dbd_treiber.pl
• Der Name des Datenbank-Users, der die notwendigen Zugriffsrechte auf die Datenbank besitzt. Dies muss nicht der Name des am System angemeldeten Users sein (dies ist vor allem für Linux-Anwender von Bedeutung). • Das Passwort des Anwenders, der die Datenbank öffnen möchte. Aus diesen Angaben wird der sogenannte Connection-String erstellt, welci dem Datenbank-Treiber übergeben wird. Das folgende Skript baut eine Verbindung zu unserer CD-Datenbank auf.
#!/usr/bin/perl -w #connect.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Parameter fuer Datenbankverbindung my $treiber = 'mysql'; my $datenbank = 'CD.DATA'; my $anwender = 'helmut'; my $passwort = ''; # Verbindung erstellen oder Programm beenden
7.4 Arbeiten mit DBI/DBD
101
my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $pas swort) or die ("Datenbankverbindung kann nicht erstellt werden: $!"); print "Datenbankverbindung zu $datenbank erfolgreich erstellt!\n"; # . . .
Wir haben in unserem Skript zunächst die Daten, die den Connection-String bilden, in lokalen Variablen hinterlegt. Dies sollten Sie, obwohl es etwas weitschweifig erscheint, stets tun. Der größte Vorteil dieses Vorgehens liegt darin, dass die Anwendung leicht zu pflegen ist. Soll beispielsweise der Datenbank-Treiber ausgewechselt werden, ist lediglich der Wert der entsprechenden Variablen zu ändern. Um Datenbank-Anwendungen vom verwendeten System gänzlich unabhängig zu machen und um zu verhindern, dass bei jeder derartigen Änderung der Programmcode angepasst werden muss 28 , ist es sinnvoll, die genannten Verbindungsdaten aus einer externen Text-Datei einzulesen. Der Anwender muss nun lediglich in dieser Datei die entsprechenden Änderungen vornehmen; die Programmdatei selbst bleibt unangetastet. Ändern Sie also den Programmcode in der folgenden Weise ab. #!/usr/bin/perl -w # connect2.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort) or die ("Datenbankverbindung kann nicht erstellt werden: $!"); print "Datenbankverbindung zu $datenbank erfolgreich erstellt!\n";
Diese Programmversion setzt voraus, dass sich im selben Verzeichnis, in dem das Skript liegt, auch die Initialisierungsdatei "verbdat.txt" befindet. Diese muss folgendes Aussehen besitzen. 28
W a s erhebliche Risiken für die Integrität der Anwendung in sich birgt!
7 Datenbank-Programmierung mit Perl/DBI
102
mysql CD.DATA helmut
Nach dem Eintrag des User-Namens sollte noch eine Leerzeile für das Passwort vorhanden sein, falls Ihre Datenbank kein Passwort benötigt. Es ist zudem eine gute Idee, noch einige "Kommentarzeilen" anzufügen, um dem Anwender die Handhabung dieser Datei zu erklären. Besondere Beachtung verdient zudem der Parameter $datenbank. So reicht es bei MySQL-Datenbanken aus, den Connection-String in der folgenden Form zu übergeben. my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort);
Bei einigen DBD-Modulen, wie zum Beispiel dem DBI :PG-Treiber, der zur Verbindung mit PostgreSQL-Datenbanken benötigt wird, muss allerdings die folgende allgemeine Form verwendet werden. my $dbh = DBI->connect("DBI:$treiber:database=$datenbank", $anwender, $passwort);
Um diesem Umstand gerecht zu werden, sind zwei Lösungsansätze möglich: • Sie können in der Initialisierungsdatei jeweils den gesamten Ausdruck angeben, also bei Verwendung einer MySQL-Datenbank
mysql CD.DATA helmut
beziehungsweise bei Verwendung einer PostgreSQL-Datenbank
Pg database=CD_DATA helmut
• Dieser Ansatz kann allerdings, besonders dann, wenn "Nicht-Programmierer" mit der Pflege der Initialisierungsdateien befasst sind, zu Irritationen führen. Daher ist es besser, prinzipiell die zweite Form zu wählen, also auch bei Verwendung von MySQL-Datenbanken. Sie ersparen sich zudem die Mühe, bei jedem DatenbankTreiber in der Dokumentation die korrekte Syntax nachschlagen zu müssen.
7.4 Arbeiten mit DBI/DBD
103
Es hat sich eingebürgert, für das Connection-Objekt den Namen $dbh zu verwenden, der die Abkürzung des englischen Begriffes DataBase-Handle darstellt. Sie sollten, sofern nicht irgendwelche widrigen Umstände dem entgegenstehen, von dieser Tradition nicht abweichen. Sie erleichtern anderen damit erheblich die Arbeit mit Ihrem Code. Die Methode connect der Klasse DBI gibt bei Erfolg den booleschen Wert true zurück bzw. f alse bei Misserfolg. Dies gilt auch für alle im Weiteren zu besprechenden DBI-Methoden. Sie sollten sich dies, wie in unserem Beispiel gezeigt, stets zu Nutze machen und auf Fehler entsprechend reagieren. Alle Datenbank-Aktionen sollten stets vom Erfolg der vorhergehenden abhängig sein, wie im folgenden Beispiel.
#!/usr/bin/perl -w # connect3.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # boolsche Variable success mit false initialisieren my $success = 0; # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); # Erfolg abfragen und entsprechend reagieren if ($dbh) {$success = 1} if ($success) { print "Datenbankverbindung zu $datenbank erfolgreich erstellt!\n"; # Weitere Datenbankaktionen # . . . } eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren
> #
...
104
7 Datenbank-Programmierung mit Perl/DBI
Für das Finden von Fehlern ist die Variable $DBI: : e r r s t r der DBI-Klasse von besonderer Bedeutung, denn sie enthält jeweils eine kurze Beschreibung des aufgetretenen Fehlers. Ihr Inhalt sollte daher in jedem Falle mit der Fehlermeldung ausgegeben werden 29 . Zudem sollte die von Ihnen formulierte Fehlermeldung eindeutig die Stelle im Programmcode bezeichnen, an welcher der Fehler auftrat 3 0 . Zum Abschluss dieses Abschnittes möchten wir Ihnen noch eine Syntax-Variante der connect-Anweisung vorstellen, mit der es möglich ist, auf Datenbanken zuzugreifen, die sich auf einem entfernten Rechner befinden. # ... my $dbh = BBI->connect("DBI:$treiber:database=$datenbaiik: host=$host:port=$port); # .. .
Es werden also zwei zusätzliche Parameter, nämlich der Hostname des entfernten Rechners sowie die Portadresse, über welche die Verbindung erstellt wird, angegeben.
7.4.3
Die Datenbankverbindung schließen
Einige Datenbank-Server erlauben nur eine begrenzte Anzahl von gleichzeitigen Zugriffen. Sie sollten daher stets, wenn Sie nicht mehr auf eine Datenbank zugreifen müssen, das Connection-Objekt freigeben, das heißt, die Verbindung zur Datenbank schließen. Hierzu verwenden Sie die Methode disconnectO der DBI-Klasse in der folgenden Weise. # ... Ihre Anwendung ... # Datenbank-Handle schliessen $dbh->disconnect(); # ... der Rest Ihrer Anwendung ...
Auf keinen Fall sollten Sie sich darauf verlassen, dass Perl das Connection-Objekt automatisch löscht, wenn die Skript-Ausführung beendet wird. Dies stimmt nämlich nur bedingt: Das Objekt wird zwar gelöscht, die Datenbankverbindung muss allerdings bei einigen Datenbank-Systemen explizit "gekappt" werden. Und dies ist nicht immer der Fall, wenn das Objekt einfach gelöscht wird. Mit anderen Worten: es kann durchaus vorkommen, dass die Datenbank-Verbindung nach Beenden Ihres Perl-Skriptes bestehen 29 Der Wert dieser Fehlerbeschreibungen ist allerdings nicht immer besonders hoch, zumeist bieten sie nur einen Anhaltspunkt für das Auftreten des Fehlers. 30 Manche Programmierer neigen dazu, Fehlermeldungen sehr allgemein zu formulieren, etwa "es trat ein Fehler auf!" - Aussagen wie diese können im Ernstfall stundenlanges Suchen verursachen (und in der Folge einen Nervenzusammenbruch!).
7.4 Arbeiten mit DBI/DBD
105
bleibt, wenn die disconnect ()-Methode nicht aufgerufen wird. Ein besonderes Augenmerk sollten Sie in diesem Zusammenhang auf Ihre Fehlerbehandlungs-Routinen legen: auch wenn Ihre Anwendung "unplanmäßig" terminiert 31 wird, sollten alle bestehenden Datenbank-Verbindungen geschlossen werden.
7.4.4
Ausführen einfacher SELECT-Abfragen
Nachdem wir, wie im vorangegangenen Abschnitt beschrieben, erfolgreich eine DatenbankVerbindung aufgebaut haben, können wir das so erstellte Datenbank-Handle verwenden, um die Daten abzufragen oder zu manipulieren. Zur Abfrage und Manipulation der Daten benötigen wir ein sogenanntes StatementHandle. Wir erstellen dieses in zwei Schritten: • Im ersten Schritt wird die Abfrage vorbereitet, indem die prepare-Methode des Datenbank-Handle-Objektes mit einer SQL-Anweisung als Argument aufgerufen wird. • War das Vorbereiten der Abfrage erfolgreich, kann diese mit Hilfe der executeMethode des Statement-Handies ausgeführt werden. Betrachten wir hierzu das folgende Beispiel. #!/usr/bin/perl -w # selectl.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # boolsche Variable success mit false initialisieren my $success = 0; # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); 31
terminiert: neudeutsch für beenden
106
7 Datenbank-Programmierung mit Perl/DBI
# Erfolg abfragen und entsprechend reagieren if ($dbh){ print "Datenbankverbindung zu $datenbank erfolgreich erstellt!\n"; # SQL-Befehl deklarieren my $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten my $sth=$dbh->prepare($sql); # Bei Erfolg Abfrage ausfuehren if ($sth) { print "Statement-Handle erfolgreich erstellt!\n"; $sth->execute(); # bei Erfolg koennen die Daten extrahiert werden if ($sth) { print "Abfrage erfolgreich ausgefuehrt!\n"; # ... Daten extrahieren ... } eise { # Fehler beim Ausfuehren der Abfrage print "Beim Ausfuehren der Abfrage trat ein Fehler auf: $!"; print $sth->errstr . "\n";
>
} eise { # Fehler beim Vorbereiten der Abfrage print "Beim Vorbereiten der Abfrage trat ein Fehler auf: $!"; print $dbh->errstr . "\n";
>
} eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren
> # ... weiteres Programm ...
In unserem Beispiel sehen Sie, wie die Anwendungsstruktur geschachtelt ist, so dass jeder Schritt vom Erfolg des vorhergehenden abhängig ist. In einer "wirklichen" Anwendung müssten Sie natürlich, wie im letzten Abschnitt besprochen, nicht mehr benötigte Datenbank-Handies wieder schließen.
7.4.5
Datensätze aus der Abfrage lesen
Wurde die Abfrage erfolgreich ausgeführt, können die Datensätze mit Hilfe des StatementObjektes gelesen werden. Hierfür gibt es zwei grundsätzliche Möglichkeiten. Die Datensätze können einerseits einzeln abgefragt und bearbeitet werden, oder andererseits werden alle Tupeln des Ergebnisses auf einmal mit Hilfe einer Referenz eingelesen. Zu beachten ist dabei, dass Perl/DBI lediglich einen sogenannten F0RWARD_0NLY Cursor bietet. Das bedeutet, Sie können nicht, wie dies bei anderen Datenbank-Systemen möglich ist, im Resultset vor- und zurückblättern. Jeder Datensatz kann folglich nur ein einziges Mal abgefragt werden.
7.4 Arbeiten mit DBI/DBD
107
Welche der beiden genannten Möglichkeiten Sie verwenden hängt zum einen davon ab, auf welche Weise Sie die Datensätze weiterverarbeiten müssen. Zum anderen ist aber auch die Datenmenge, die das Resultset liefert, ein wichtiger Faktor. Es ist nicht immer möglich, mehrere tausend Datensätze in einem Array im Arbeitsspeicher zu halten. Dies wäre notwendig, wenn Sie alle Daten auf einmal über eine Referenz abfragten. Wir schauen uns daher zunächst an, wie die Tupeln einzeln abgefragt werden. Dies wird die Methode sein, die Sie in der Regel verwenden.
7.4.6
Datensätze des Resultsets einzeln übernehmen
Datensätze eines Resultsets werden, wenn Sie einzeln gelesen werden, in Form eines Arrays, einer Referenz auf ein Array oder eines Hashes zurückgegeben. Der Zugriff auf die Daten erfolgt zumeist innerhalb einer Schleife. In einem Array oder einer Array-Referenz werden die Attribute in der Reihenfolge abgelegt, in der sie im SELECT-Statement angegeben wurden. Haben Sie im SELECT-Statement den Wildcard-Operator * verwendet, entspricht die Reihenfolge der Attribute im Resultset der, wie sie in der DatenbankTabelle definiert ist. Es ist daher stets sinnvoll, die gewünschte Ausgabe-Reihenfolge im SELECT-Statement festzulegen. Haben Sie dies nicht getan, kommen Sie in der Regel nicht drum herum, bei der Ausgabe mit einem Haufen Indizes zu jonglieren. Als Alternative können Sie, wie wir weiter unten zeigen, Hashes für die Rückgabe verwenden. Im folgenden Beispiel geben wir der Reihe nach alle Zeilen des Resultsets unformatiert aus. Möchten Sie die einzelnen Attribute formatieren, müsste dies in einer inneren Schleife geschehen 32 . #!/usr/bin/perl - w # select2.pl # Verbindung z u einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # boolsche Variable success mit false m y $success = 0;
initialisieren
32 Zur Formatierung der Ausgabe eines Resultsets sollten Sie auch die folgenden Kapitel lesen: Formate (Band 1), CGI-Programmierung mit Perl und Programmieren mit Perl/Tk.
108
7 Datenbank-Programmierung mit P e r l / D B I
# Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); # Erfolg abfragen und entsprechend reagieren if ($dbh){ # SQL-Befehl deklarieren my $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten my $sth=$dbh->prepare($sql); # Bei Erfolg Abfrage ausfuehren if ($sth) { $sth->execute(); # bei Erfolg koennen die Daten extrahiert werden if ($sth) { # in einer Schleife ResultSet ausgeben while (my Ozeile = $sth->fetchrow_array()) { print "@zeile\n";
} # Anwender ueber Ende der Ausgabe informieren print "Alle Datensaetze des Resultsets wurden ausgegeben!\n"; } eise { # Fehler beim Ausfuehren der Abfrage print "Beim Ausfuehren der Abfrage trat ein Fehler auf: $!"; print $sth->errstr . "\n";
> } eise { # Fehler beim Vorbereiten der Abfrage print "Beim Vorbereiten der Abfrage trat ein Fehler auf: $!"; print $dbh->errstr . "\n";
> } eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren
> #
...
Die vorstehende Ausgabe, bei welcher das Array, das den Datensatz darstellt, als Ganzes ausgegeben wird, ist nicht besonders schön, allenfalls ist es für eine Testausgabe zu gebrauchen. Wie bereits angedeutet, werden Sie die einzelnen Attribute wohl eher formatieren. Dies kann, wie im Folgenden gezeigt wird, mit Hilfe einer inneren f oreachSchleife geschehen.
#!/usr/bin/perl -w # select2.pl # Verbindung zu einer Datenbank aufbauen
7.4 Arbeiten mit DBI/DBD
109
# Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # boolsche Variable success mit false initialisieren my $success = 0; # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); # Erfolg abfragen und entsprechend reagieren if ($dbh){ # SQL-Befehl deklarieren my $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten my $sth=$dbh->prepare($sql); # Bei Erfolg Abfrage ausfuehren if ($sth) { $sth->execute(); # bei Erfolg koennen die Daten extrahiert werden if ($sth) { # in einer Schleife ResultSet ausgeben while (my Szeile = $sth->fetchrow_array()) { # oder alternativ foreach-Schleife: foreach $attr (Qzeile) { print "$attr ";
> print "\n";
>
# Anwender ueber Ende der Ausgabe informieren print "Alle Datensaetze des Resultsets wurden ausgegeben!\n"; } eise { # Fehler beim Ausfuehren der Abfrage print "Beim Ausfuehren der Abfrage trat ein Fehler auf: $!"; print $sth->errstr . "\n";
} } eise { # Fehler beim Vorbereiten der Abfrage
110
7 Datenbank-Programmierung mit Perl/DBI
>
print "Beim Vorbereiten der Abfrage trat ein Fehler auf: $!"; print $dbh->errstr . "\n";
} eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren
>
# ...
Ein weiteres Beispiel soll demonstrieren, wie es möglich, ist einzelne Attribute eines Datensatzes zu extrahieren und auszugeben. #!/usr/bin/perl -w # select3.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # Array zum Speichern von cd_id und cd_titel initialisieren ®cd_titel = (); # boolsche Variable success mit false initialisieren my $success = 0; # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); # Erfolg abfragen und entsprechend reagieren if ($dbh){ # SQL-Befehl deklarieren my $sql = "SELECT cd_id, cd_titel, cd_ersch_jahr "; $sql .= "FROM cd_relation"; # Abfrage vorbereiten my $sth=$dbh->prepare($sql); # Bei Erfolg Abfrage ausfuehren
7.4 Arbeiten mit DBI/DBD
111
if ($sth) { $sth->execute(); # bei Erfolg koennen die Daten extrahiert werden if ($sth) { # in einer Schleife ResultSet ausgeben while (my Gzeile = $sth->fetchrow_array()) { # Attribute Zwischenspeichern my ($id, $titel, $ersch_jahr) = Ozeile;
# Titel und Erscheinungsjahr ausgeben print "$titel\t$ersch_jahr\n"; # id und titel zur spaeteren Bearbeitung Zwischenspeichen my $id_titel = join /;/, $id, $titel; push(@cd_titel, $id_titel);
} # Anwender ueber Ende der Ausgabe informieren print "Alle Datensaetze des Resultsets wurden ausgegeben!\n"; }• eise { # Fehler beim Ausfuehren der Abfrage print "Beim Ausfuehren der Abfrage trat ein Fehler auf: $!"; print $sth->errstr . "\n";
> > eise { # Fehler beim Vorbereiten der Abfrage print "Beim Vorbereiten der Abfrage trat ein Fehler auf: $!"; print $dbh->errstr . "\n";
> } eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren
> # . . .
Anstelle eines "normalen" Arrays ist es auch möglich, eine Referenz auf ein Array zurückgeben zu lassen, indem die Methode fetchrow_arrayref () verwendet wird. Sie müssen dann die einzelnen Attribute der Rückgabe entsprechend der Regeln, die im ersten Band dieses Buches vorgestellt wurden, dereferenzieren.
# Datenbankverbindung erstellen ... # Statementhandle erstellen und Abfrage durchfuehren # . . . # in einer Schleife ResultSet ausgeben while (my $ref_zeile = $sth->fetchrow_arrayref()) {
7 Datenbank-Programmierung mit Perl/DBI
112 # Zeile dereferenzieren my Szeile = ®$ref_zeile; # ... weitere Bearbeitung ...
#
...
Beachten Sie, dass wir im vorherigen Beispiel nur den relevanten Teil der while-Schleife dargestellt haben. Ändern Sie also den Code der Anwendung s e l e c t 3 . p l entsprechend ab und führen Sie anschließend den Code aus. Besonders dann, wenn die Datensätze des Resultsets an eine Unterroutine weitergereicht werden müssen, können Sie die beschriebene Rückgabemethode sinnvoll einsetzen. Weit interessanter als die Abfrage einer Array-Referenz ist die Möglichkeit, eine Referenz auf einen Hash zurückgeben zu lassen. Sie brauchen nun nicht mehr mit nicht aussagekräftigen Indizes zu hantieren, um auf die einzelnen Attribute zuzugreifen. Sie können bequem über den Attributsnamen auf die einzelnen Elemente zugreifen. Betrachten Sie hierzu folgendes Beispiel. #!/usr/bin/perl -w # select4.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # Array zum Speichern von cd_id und cd_titel initialisieren ®cd_titel = (); # boolsche Variable success mit false initialisieren my $success = 0; # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); # Erfolg abfragen und entsprechend reagieren
7.4 Arbeiten mit DBI/DBD
113
if ($dbh){ # SQL-Befehl deklarieren my $sql = "SELECT cd_id, cd_titel, cd_ersch_jahr "; $sql .= "FROM cd.relation"; # Abfrage vorbereiten my $sth=$dbh->prepare($sql); # Bei Erfolg Abfrage ausfuehren if ($sth) { $sth->execute(); # bei Erfolg koennen die Daten extrahiert werden if ($sth) { # in einer Schleife ResultSet ausgeben while (my $ref_zeile = $sth->fetchrow_hashref()) { # Titel und Erscheinungsjahr ausgeben print $ref_zeile->{'cd_titel'} . "\t"; print $ref_zeile->{'cd_ersch_jahr'}- . "\n"; # id und titel zur spaeteren Bearbeitung Zwischenspeichen my $id_titel = join /;/, $ref_zeile->{'cd_id'}, $ref_zeile->{'cd_titel'>; push(@cd_titel, $id_titel);
> # Anwender ueber Ende der Ausgabe informieren print "Alle Datensaetze des Resultsets wurden ausgegebenen"; } eise { # Fehler beim Ausfuehren der Abfrage print "Beim Ausfuehren der Abfrage trat ein Fehler auf: $!"; print $sth->errstr . "\n";
} } eise { # Fehler beim Vorbereiten der Abfrage print "Beim Vorbereiten der Abfrage trat ein Fehler auf: $!"; print $dbh->errstr . "\n";
> } eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren
> # . . .
Diese Variante unseres Programmes ist nicht nur kompakter als die weiter oben vorgestellte Lösung, sondern auch für andere besser lesbar. Abschließend sei nochmals darauf hingewiesen, dass jedes Element des Resultsets nur einmal abgerufen werden kann. Wenn Sie also mehrfach auf ein Element zugreifen möchten, müssen Sie das Resultset beispielsweise in einem Array sichern, wie wir das in unserem letzten Beispielskript getan haben.
114
7.4.7
7 Datenbank-Programmierung mit Perl/DBI
Metadaten zum Resultset ermitteln
Nachdem Sie die execute ()-Methode des Statement-Handies ausgeführt haben, können Sie mit Hilfe der verschiedenen Eigenschaften des Handies Informationen über das Resultset abfragen. Die Anzahl der Spalten, die das Resultset umfasst, kann mit Hilfe der Eigenschaft NUM_OF_FIELDS abgefragt werden. # numOfFields.pl use DBI; # ... Programmcode ... # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # Statement-Handle vorbereiten und ausfuehren my $sth = $dbh->prepare("SELECT * FROM cd_relation"); $sth->execute(); # Anzahl der Attribute im Resultset ermitteln und ausgeben my $zahl_Felder = $sth->{NUM_OF_FIELDS>; print "Anzahl der Felder im Resultset: $zahl_Felder\n"; # ... Programmcode ...
Falls die Abfrage, die Sie ausgeführt haben, kein SELECT-Statement war, ergibt die Eigenschaft NUM_0F_FIELDS den Wert 0. Dies ist vor allem dann von Bedeutung, wenn Sie Abfragen in Funktionen bearbeiten und dabei ermitteln müssen, ob die Abfrage ein Resultset geliefert hat. Eine weitere wichtige Eigenschaft des Statement-Handies ist NAME, die eine Referenz auf ein Array zurückgibt, das die Namen aller Felder des Resultsets enthält. Betrachten Sie hierzu das folgende Beispiel.
7.4 Arbeiten mit DBI/DBD
115
# nameOfFields.pl use DBI; # ... Programmcode ... # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # Statement-Handle vorbereiten und ausfuehren my $sth = $dbh->prepare("SELECT * FROM cd.relation"); $sth->execute(); # Anzahl der Attribute im Resultset ermitteln und ausgeben my $zahl_Felder = $sth->{NUM_OF_FIELDS>; print "Anzahl der Felder im Resultset: $zahl_Felder\n"; # In Schleife Feldnamen im Resultset ausgeben foreach $feldname (@{$sth->{NAME») { print "$feldname\n";
> # ... Programmcode ...
Im Beispiel haben wir in einer f oreach-Schleife die Namen der einzelnen Attribute des Resultsets ausgegeben. Beachten Sie hierbei die Art, in der die Referenz auf das Array mit den Feldnamen im Parameter der foreach-Funktion dereferenziert wurde. Die Eigenschaft NAME ist vor allem dann einsetzbar, wenn mit Hilfe von Formaten oder CGI-Anwendungen die Daten eines Resultsets ausgegeben werden sollen. Mit Hilfe des NAME-Arrays ist es möglich, Spaltenüberschriften auszugeben. Besonders interessant ist dabei, dass es mit Hilfe der Eigenschaften NAME_uc möglich ist, die Spaltennamen in Großbuchstaben und mit NAME_lc in Kleinbuchstaben auszugeben. Mit der Eigenschaft TYPE ist es möglich, den Datentyp der Attribute eines Resultsets abzufragen. Dabei gibt TYPE eine Referenz auf ein Array von Integer-Werten zurück, welche die in der folgenden Tabelle vorgestellten Bedeutungen haben.
116 Wert 1 2 3 4 5 6 7 8 9 10 11 12 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10
7 Datenbank-Programmierung mit Perl/DBI Bedeutung SQL CHAR SQL NUMERIC SQL DECIMAL SQL INTEGER SQL SMALLINT SQL FLOAT SQL REAL SQL DOUBLE SQL DATE SQL TIME SQL TIMESTAMP SQL VARCHAR SQL LONGVARCHAR SQL BINARY SQL VARBINARY SQL LONGVARBINARY SQL BIGINT SQL TINYINT SQL BIT SQL WCHAR SQL WVARCHAR SQL WLONGVARCHAR
Eine weitere interessante Eigenschaft des Statement-Handies ist NULLABLE, die eine Referenz auf ein Array mit Integerwerten zurückgibt, welche darüber Auskunft geben, ob das entsprechende Attribut Null werte enthalten darf oder nicht. Dabei haben die Werte die in nachfolgender Tabelle dargestellte Bedeutung.
Wert 0 1 2
Bedeutung Im Attribut darf kein Nullwert enthalten sein Die Spalte kann Nullwerte enthalten Es sind keine Informationen zu dieser Spalte vorhanden
Schließlich ist es möglich, mit Hilfe der Eigenschaft NUM_GF_PARAMS zu ermitteln, wieviele Parameter im SQL-Begriff der Abfrage definiert sind 33 . Die meisten der vorgestellten Eigenschaften sind dann für Sie von Interesse, wenn Ihre Datenbankanwendung einen sehr hohen Abstraktionsgrad besitzt. Das heißt, dann, wenn Sie Abfragen mit Hilfe von Unterroutinen bearbeiten, die sehr stark verallgemei33
L e s e n Sie hierzu bitte den Abschnitt Abfragen
optimieren
weiter unten in diesem Kapitel.
117
7.4 Arbeiten mit DBI/DBD
nert sind. Beispiele für den Einsatz dieser Eigenschaften finden Sie weiter unten im Abschnitt, der sich mit der CGI-Programmierung unter Perl befasst. Zum Abschluss dieses Abschnittes möchten wir noch auf eine Eigenschaft des StatementHandies aufmerksam machen, die zwar in fast allen Büchern zu Perl/DBI beschrieben wird, allerdings nicht bei allen Treibern tatsächlich so funktioniert wie erwartet. Es handelt sich um die Eigenschaft rows des Statement-Handies, welche die Anzahl der im Resultset enthaltenen Zeilen zurückliefern soll. Arbeiten Sie mit einer MySQL-Datenbank, gibt diese Funktion, wie wir im folgenden Beispiel sehen, die Anzahl der im Resultset enthaltenen Datensätze zurück. #!/usr/bin/perl -w # rows.pl use DBI;
# Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen - alle Indizes abfragen my $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten u n d ausfuehren my $sth = $dbh->prepare($sql); $sth->execute(); # Versuchen die Anzahl der Datensaetze im Resultset zu ermitteln print $sth->rows() . "\n"; # Zum Vergleich alle Datensaetze anzeigen while (my Stupel = $sth->fetchrow_array()) print "@tupel\n";
{
> Bei einigen Datenbank-Treibern 34 gibt allerdings der Versuch, die Anzahl der Datensätze auf diese Weise zu ermitteln, den Wert - 1 zurück. Dieser Wert bedeutet, dass es nicht möglich war, die Anzahl der Datensätze zu ermitteln. Verlassen kann man sich ^Beispielsweise der ODBC-Treiber der ActiveState Distribution.
118
7 Datenbank-Programmierung mit P e r l / D B I
auf den zurückgegebenen Wert prinzipiell nur im Zusammenhang mit SQL-Statements wie UPDATE oder DELETE. Auf SELECT-Statements wie im vorhergehenden Beispiel sollte man die Eigenschaft niemals anwenden 3 5 . U m die Anzahl der Datensätze in einem Resultset zu ermitteln, sollte man daher sicherheitshalber eine SELECT C0UNT(*)-Abfrage wie im folgenden Beispiel durchführen. #!/usr/bin/perl -w # rows2.pl use DBI;
# Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen - alle Indizes abfragen my $sql = "SELECT COUNT(cd_id) FROM cd.relation"; # selectrow_array - Methode ausfuehren my Qresult = $dbh->selectrow_array($sql); # Anzahl der Datensaetze ausgeben print "Datensaetze: $result[0]\n"; # Erneut abfragen und ausgeben $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten und ausfuehren my $sth = $dbh->prepare($sql); $sth->execute(); # Zum Vergleich alle Datensaetze anzeigen while (my Qtupel = $sth->fetchrow_array()) { print "Otupel" . "\n";
> Die vorgestellte Lösung ist zwar deutlich länger und nicht so elegant wie der erste Algorithmus, hat aber dafür den Vorteil, dass sie immer funktioniert. Sie ist sicher ein Kandidat für eine Funktion in einer allgemeinen Bibliothek mit Datenbank-Routinen. 35 D e r Autor hat diese Lektion auch erst dann gelernt, als es auf Grund der Fehlfunktion bei einem Kunden zu einem massiven Problemen kam!
7.4 Arbeiten mit D B I / D B D
119
Die Funktion s e l e c t r o w _ a r r a y ( ) stellen wir weiter unten im Detail vor.
7.4.8
Den ersten Datensatz abfragen
Mitunter kommt es vor, dass ein Resultset nur einen einzigen Datensatz enthält, beispielsweise dann, wenn man mit Hilfe der SQL-Funktion MAX die letzte (hier: höchste) ID-Nummer einer Tabelle ermitteln möchte. In diesem Falle können Sie die Methode s e l e c t r o w _ a r r a y ( ) des Datenbank-Handies aufrufen. Der Vorteil dieser Methode liegt darin, dass Sie die p r e p a r e O - , e x e c u t e O sowie die f e t c h r o w _ * ( ) - M e t h o d e n des Datenbank- und des Statement-Handies in sich vereint. Betrachten Sie hierzu den folgenden Code-Ausschnitt, mit dem wir den höchsten Index der c d _ r e l a t i o n ermitteln und um 1 inkrementieren.
# selectrow_array.pl use DBI; # ... Programmcode ... # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen my $sql = "SELECT MAX(cd_id) FROM cd_relation"; # selectrow_array - Methode ausfuehren my Qresult = $dbh->selectrow_array($sql); # alte Id zur Kontrolle ausgeben print "Alte Id: " . $result[0] . "\n"; # id inkrementieren und ausgeben my $new_id = ++$result [0]; print "Neue Id: $new_id\n"; # Datenbank-Handle schliessen $dbh->disconnect(); # ... weiterer Programmcode . . .
120
7 Datenbank-Programmierung mit Perl/DBI
Die gezeigte Methode lässt sich stets dann sinnvoll einsetzen, wenn man sicher ist, dass im Resultset lediglich ein Datensatz enthalten ist. In anderen Situationen ist der Wert dieser Methode eher zweifelhaft. Ein Einsatz kann in diesem Fall erhebliche Risiken für Ihre Programmlogik bergen. Beachten Sie zudem, dass es über diese Methode nicht möglich ist, Metadaten des Resultsets wie etwa Feldnamen zu ermitteln. Zum Ermitteln von Indizes leistet diese Methode allerdings unschätzbare Werte und bewahrt den Programmierer vor einer vorzeitigen Sehnenscheidenentzündung auf Grund übermäßiger Tipparbeit.
7.4.9
Einzelne Spalten abfragen
Wie wir bereits erläutert haben, besitzt Perl/DBI keinen beweglichen Cursor, das heißt, es ist nicht möglich, sich in einem Resultset hin und her zu bewegen. Für manche Anwendungsbereiche ist aber gerade eine solche "Blätter-Funktion" sinnvoll. Perl/DBI bietet mit der Funktion selectcol_arrayref () die Möglichkeit, einzelne Spalten eines Resultsets in eine Array-Referenz einzulesen. Mit Hilfe dieser Funktion ist es recht leicht, einen sogenannten beweglichen Datenbank-Cursor zu erstellen, indem wir die Indizes aller Datensätze in ein Array einlesen. Im folgenden Beispiel stellen wir Ihnen hierzu die Grundversion vor, die wir dann weiter unten optimieren 36 . # selectcol_array.pl use DBI; # ... Programmcode ... # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; m y $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->conn.ect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen - alle Indizes abfragen my $sql = "SELECT cd_id FROM cd.relation"; # alle indizes in Array index einlesen my $index_ref = $dbh->selectcol_arrayref($sql); 36
Um diese Punktion in Datenbank-Anwendungen einsetzen zu können, müssen Sie darauf achten, dass die Indizes regelmäßig neu eingelesen werden müssen, damit Änderungen an der Datenstruktur entsprechend gespiegelt werden.
7.4 Arbeiten mit D B I / D B D my Sindex = @$index_ref; # Ersten Datensatz anzeigen Datensatz_zeigen(0index[0] ); # Zeiger auf aktuellen Datensatz my $cur = 0; # Endlosschleife zur Datennavigation while () { print "Eingabe vor , zurueck , beenden : "; chomp(my $nav = ); if ($nav eq 'e') { last; } elsif ($nav eq '+') { # Naechsten Index holen if ($cur < $#index) { # auf naechsten Index zeigen ++$cur; # Aktuellen Datensatz anzeigen Datensatz_zeigen(@index[$cur]); } eise { # Meldung ausgeben print "letzter Datensatz erreicht!\n";
>
> elsif ($nav eq '-') { # vorhergehenden Index holen if ($cur > 0) { # auf vorhergehenden Index zeigen --$cur; # Aktuellen Datensatz anzeigen Datensatz_zeigen(9index[$cur]); } eise { # Meldung ausgeben print "erster Datensatz erreicht!\n";
>
} eise { # Meldung ausgeben print "Falsche Eingabe!\n";
>
>
# Datenbank-Handle schliessen $dbh->disconnect(); # Routine zum Anzeigen eines Datensatzes sub Datensatz_zeigen { # Datensatz-Index uebernehmen
121
7 Datenbank-Programmierung mit Perl/DBI
122 my $index = $_[0];
# Bildschirm loeschen (nur bei LINUX) # unter Windows muss in der Datei Config.sys die # folgende Zeile stehen: # DEVICE=ANSI.SYS # Nicht alle Windows-Varianten werden unterstuetzt print "\033[2J"; # SQL-Statement zur Abfrage der einzelnen Datensaetze vorbereiten $sql = "SELECT * FROM cd.relation WHERE cd_id = $index"; # Statement-Handle vorbereiten my $sth = $dbh->prepare($sql); # Abfrage ausfuehren $sth->execute(); # Datensatz ausgeben my $ref_zeile = $sth->fetchrow_hashref(); # Titel und Erscheinungsjahr ausgeben print "CD-Titel: " . $ref_zeile->{'cd_titel'} . "\t"; print "Erscheinungsjahr: " . $ref_zeile->{'cd_ersch_jahr'} . "\n";
> # ... weiterer Programmcode ...
Beachten Sie beim voranstehenden Code, dass die vorgestellte Escape-Sequenz zum Löschen des Bildschirmes nicht auf allen Windows-Varianten lauffähig ist 37 . In jedem Falle müssen Sie die ANSI-Treiber in der Conf ig.Sys-Datei einfügen. DEVICE=ANSI.SYS
In der vorgestellten Anwendung werden zunächst mit Hilfe der Funktion selectcol_arrayref () des Datenbank-Handies alle Index-Felder aus der Tabelle cd_relation in ein Array eingelesen. Innerhalb der Endlos-Schleife werden vom Benutzer "NavigationsZeichen" abgefragt. Je nachdem, ob der Anwender auf die Taste + oder die Taste gedrückt hat, wird der nächste bzw. vorherige Index aus dem Array gelesen. Sollten Sie dabei das Ende des Arrays erreichen, erhalten Sie eine entsprechende Meldung. Der gefundene Index wird der Unterroutine Datensatz_zeigen() übergeben. Innerhalb dieser Routine wird zunächst der Bildschirm gelöscht. Anschließend erstellen wir mit Hilfe des übergebenen Indexes einen SQL-Begriff, um den entsprechenden Datensatz abzufragen. In der gezeigten Version wäre es auch möglich gewesen, die im vorangegangenen Abschnitt besprochene Methode selectrow_array() zu nutzen. Für unser Beispiel fanden wir allerdings die Übernahme als Hash übersichtlicher. Schließlich geben wir das Ergebnis aus. 37
Soweit der Autor dies als "Nicht-Fachmann" in Windows-Fragen beurteilen kann!
7.4 Arbeiten mit DBI/DBD
123
Natürlich schreit dieses Programm geradezu nach einer grafischen Benutzerschnittstelle. Hier bitten wir Sie noch um ein wenig Geduld - wir werden dieses Thema weiter unten ausführlich behandeln. Im übernächsten Abschnitt, wenn wir uns damit beschäftigen, wie Abfragen mit Perl/DBI optimiert werden können, kommen wir nochmals auf diese Anwendung zurück und stellen Ihnen dort eine optimierte Version vor.
7.4.10
Das Resultset als Ganzes übernehmen
Bisher haben wir stets einzelne Datensätze aus dem Resultset gelesen. Besonders dann, wenn die Ergebnisse einer Abfrage an eine Punktion weitergereicht werden sollen, kann es aber sinnvoll sein, alle Ergebnisse auf einmal einzulesen und in einer Referenz auf ein Array oder einen Hash zu hinterlegen. Mit Hilfe der Funktion f etchall_arrayref () ist dies möglich. Dabei liefert diese Funktion eine Referenz auf ein Array zurück, das seinerseits, je nach angegebenen Parametern, Referenzen auf Arrays oder Hashes enthält, die wiederum auf die einzelnen Datensätze zeigen. Die Datenstruktur wird in der folgenden Abbildung dargestellt. Standardmäßig gibt die Funktion f etchall_arrayref () dabei eine Referenz auf ein Array mit Referenzen auf ein Array zurück. Betrachten Sie hierzu das folgende Beispiel. #!/usr/bin/perl -w # fetchall_arrayref.pl use DBI;
# Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten und ausfuehren my $sth = $dbh->prepare($sql); $sth->execute(); $array_ref_alle = $sth->fetchall_arrayref(); # Alle Zeilen des Resultsets durchlaufen foreach $array_ref_zeile (0$array_ref_alle) {
124
7 Datenbank-Programmierung mit P e r l / D B I
# alle Spalten ausgeben print "®$array_ref_zeile" . "\n";
> Das Beispiel gibt alle Felder aller Tupel der Tabelle cd_relation aus. Als Argument können Sie der Funktion f etchall_arrayref () zudem in eckigen Klammern die Indizes der Spalten übergeben, die im Resultset enthalten sein sollen. Im folgenden Beispiel übernehmen wir die Spalten cd_id, cd_titel und cd_ersch_jahr, also die drei ersten Spalten der Tabelle.
#!/usr/bin/perl -w # fetchall_arrayref2.pl use DBI;
# Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD.DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten und ausfuehren my $sth = $dbh->prepare($sql); $sth->execute(); $array_ref_alle = $sth->fetchall_arrayref([0,1,2]); # Alle Zeilen des Eesultsets durchlaufen foreach $array_ref_zeile (Q$array_ref_alle) { # alle Spalten ausgeben print "Q$array_ref_zeile" . "\n";
> Alternativ zur oben vorgestellten Syntax ist es auch möglich, den Bereichsoperator in der folgenden Weise zu verwenden.
#
7.4 Arbeiten mit DBI/DBD
125
$array_ref_alle = $sth->fetchall_arrayref([0..2]); # . . .
Wie bereits angedeutet, können die Datenfelder auch als Referenz auf einen Hash übergeben werden. Hierzu geben Sie der Funktion als Argument ein Paar geschweifter leerer Klammern mit, um alle Felder abzufragen, oder Sie geben innerhalb der geschweiften Klammern die Spaltennamen an und weisen diesen jeweils den Wert 1 zu. Betrachten Sie hierzu die folgende Anwendung, die wiederum die Spalten cd_id, c d _ t i t e l und cd_ersch_jahr ausgibt. # ! / u s r / b i n / p e r l -w # fetchall_eurrayref3.pl use DBI; # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD.DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen $sql = "SELECT * FROM cd_relation"; # Abfrage vorbereiten und ausfuehren my $sth = $dbh->prepare($sql); $sth->execute(); $array_ref_alle = $sth->fetchall_arrayref({ cd_id => 1, cd_titel => 1, cd_ersch_jahr => 1>); # Alle Zeilen des Resultsets durchlaufen foreach $array_ref_zeile (®$array_ref_alle) { # Spalten ausgeben print $$array_ref_zeile{'cd_id'} . " " ; print $$array_ref_zeile{'cd_titel'} . " "; print $$array_ref_zeile{'cd_ersch_jahr'} . "\n";
>
7 Datenbank-Programmierung mit Perl/DBI
126
Der zuletzt gezeigte Code ist zwar deutlich länger als der Code der bereits vorgestellten Lösungen, erscheint aber wesentlich übersichtlicher. Dies gilt vor allem dann, wenn Ihre Programme umfangreicher werden. Sie erkennen jeweils auf den ersten Blick, was ausgegeben wird. Andererseits eignet sich die Übernahme als Hash nicht zur Generalisierung, da die einzelnen Feldnamen stets hardcoded38 im Programm angegeben werden müssen. Eine spätere Änderung an der Datenstruktur kann in diesem Falle zu nicht unerheblichen Änderungen am Skript führen und birgt eine Menge von Risiken in sich.
7.4.11
Abfragen optimieren
Sicher haben Sie sich bereits gefragt, warum es mit Perl/DBI so "umständlich" ist, eine Abfrage zu erstellen. Warum also, bevor eine Abfrage mit der executeO-Methode ausgeführt werden kann, diese mit Hilfe der prepareO-Methode vorbereitet werden muss? Dies erscheint zunächst etwas redundant und ist in der Tat nicht besonders schnell 39 . Tatsächlich aber steckt hinter dieser Trennung eine Idee. Betrachten Sie nochmals die Anwendung selectcol_array.plim vorletzten Abschnitt. In der Funktion Datensatz_zeigen() ist Ihnen vielleicht aufgefallen, dass im Prinzip die gleiche Abfrage ausgeführt wird. Lediglich der Parameter, der die gesuchte cd_id angibt, wechselt. Perl/DBI bietet in diesem Fall die Möglichkeit an, die Abfrage zu parametrisieren, was letztlich nicht anderes bedeutet, als dass wir das Statement-Handle nur ein einziges Mal vorbereiten. Der executeO-Methode wird dann jeweils nur der gewünschte Parameter übergeben. Dieses Vorgehen ist nicht nur viel übersichtlicher sondern auch, wie der Autor in Tests mit umfangreichen Datenbanken (mit mehreren 10.000 Datensätzen) festgestellt hat, auch doppelt so schnell wie das früher vorgestellte Verfahren. Betrachten wir zur Demonstration des Gesagten das folgende Skript.
#
selectcol_array_2.pl
use DBI; # ... Programmcode ... # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD.DATA"; my $user = "helmut"; my $dbh = 38
DBI->connect("$treiber:database=$datenbank",
Der Terminus hardcoded bezeichnet den Umstand, dass Parameter, die eigentlich variabel sein sollten, im Programmcode hinterlegt sind. Beispielsweise haben wir im vorgestellten Skript die DatenbankVerbindungsparameter der Bequemlichkeit halber hardgecoded. Weiter oben haben wir Ihnen gezeigt, wie Sie dies mit Hilfe von sogenannten irai-Dateien umgehen können. Generell ist es in großen Projekten sinnvoll, möglichst wenige Parameter hardcoded zu verwenden. 39 Wobei die Aussage "nicht besonders schnell" schon fast einen Euphemismus darstellt!
7.4 Arbeiten mit DBI/DBD $user, ""); # SQL-Begriff erstellen - alle Indizes abfragen my $sql = "SELECT cd_id FROM cd_relation"; # alle indizes in Array index einlesen # direkt dereferenzieren my Oindex = @{$dbh->selectcol_arrayref($sql)}; # Funktion zum Vorbereiten des Statement-Handies aufrufen my $sth = handle_erstellen($dbi); # Ersten Datensatz anzeigen datensatz_zeigen($sth, @index[0]); # Zeiger auf aktuellen Datensatz my $cur = 0; # Endlosschleife zur Datennavigation while () { print "Eingabe vor , zurueck , beenden : "; chomp(my $nav = ); if ($nav eq 'e') { last; > elsif ($nav eq '+') { # naechsten Index holen if ($cur < $#index) { # auf naechsten Index zeigen ++$cur; # Aktuellen Datensatz anzeigen datensatz_zeigen($sth, Qindex[$cur]); } eise { # Meldung ausgeben print "letzter Datensatz erreicht!\n";
>
> elsif ($nav eq '-') { # vorhergehenden Index holen if ($cur > 0) { # auf vorhergehenden Index zeigen --$cur; # Aktuellen Datensatz anzeigen datensatz_zeigen($sth, Oindex[$cur]); } eise { # Meldung ausgeben print "erster Datensatz erreicht!\n";
} } eise {
127
128
7 Datenbank-Programmierung mit Perl/DBI # Meldung ausgeben print "Falsche Eingabe!\n";
>
>
# Routine zum Erstellen des Statement-Handies sub handle_erstellen { # Datenbank-Handle uebernehmen my $dbi = $_ [0]; # SQL-Statement zur Abfrage der einzelnen Datensaetze vorbereiten $sql = "SELECT * FROM cd.relation WHERE cd_id = ?"; # Statement-Handle vorbereiten my $sth = $dbh->prepare($sql); # Statement-Handle zurueckgeben return $sth;
} # Routine zum Anzeigen eines Datensatzes sub datensatz_zeigen { # Statement-Handle uebernehmen my $sth = $_ [0]; # Datensatz-Index uebernehmen my $index = $_[1]; # Bildschirm loeschen (nur bei LINUX) # unter Windows muss in der Datei Config.sys die # folgende Zeile stehen: # DEVICE=ANSI.SYS # Nicht alle Windows-Varianten werden unterstuezt print "\033 [2J"; # Abfrage ausfuehren $sth->execute($index); # Datensatz ausgeben my $ref_zeile = $sth->fetchrow_hashref(); # Titel und Erscheinungsjahr ausgeben print "CD-Titel: " . $ref J .zeile->{'cd_titel'} . "\t"; print "Erscheinungsjahr: " . $ref_zeile->{'cd_ersch_jahr'} . "\n";
> # ... weiterer Programmcode
7.4 Arbeiten mit DBI/DBD
129
Wie Sie sehen, haben wir in der vorstehenden Anwendung das Statement-Handle nur einmal in der Funktion h a n d l e _ e r s t e l l e n O vorbereitet. Innerhalb des SQL-Strings werden alle variablen Werte der WHERE-Klausel durch Fragezeichen ersetzt. Dabei ist es durchaus möglich und üblich, dass mehr als ein Parameter variabel ist. Die Routine gibt als Ergebnis das soeben erstellte Statement-Handle zurück. Wir haben "nebenbei" in dieser zweiten Version auch alle globalen Variablen entfernt, um die Anwendung übersichtlicher und vor allem sicherer zu gestalten. In der Unterroutine d a t e n s a t z _ z e i g e n ( ) können wir uns nun darauf beschränken, den gewünschten Index zu extrahieren und an die executeO-Methode des StatementHandies zu übergeben. Diese zweite Version unserer Anwendung ist nicht nur erheblich schneller und sicherer als die zuvor vorgestellte, sondern auch erheblich übersichtlicher. Dies liegt nicht zuletzt daran, dass wir in dieser Version keine globalen Variablen eingesetzt haben. Allerdings möchten wir darauf hinweisen, dass wir in der vorliegenden Version auf das Abfangen von Fehlern verzichtet haben! Im richtigen Leben müssten Sie diese noch ergänzen.
7.4.12
Datenbanken mit SQL-Statements pflegen
Im Abschnitt Die Abfrage-Sprache SQL haben wir gelernt, dass auch die Datenbankpflege mit Hilfe von SQL-Statements vonstatten geht. Natürlich ist es möglich, diese Anweisungen von Perl/DBI aus zu verwenden. Sie können dies mit den bisher bekannten Methoden p r e p a r e O und executeO bewerkstelligen, was immer dann sinnvoll ist, wenn Sie eine große Zahl von gleichartigen Aktionen in einer Datenbank vornehmen müssen. Der Vorteil dieser Vorgehensweise liegt darin, dass Sie, wie im Abschnitt Abfragen optimieren gezeigt, Parameter-Abfragen verwenden können, was die Effektivität der Anwendung erheblich steigert. Das folgende Beispiel nutzt diese Möglichkeit, um in die c d _ r e l a t i o n eine Reihe von Datensätzen einzufügen, die aus der Textdatei newcd.txt gelesen werden. # ! / u s r / b i n / p e r l -w # db_pflegen.pl use DBI; # Datenbank-Handle e r s t e l l e n my $ t r e i b e r = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, " " ) ; # Statement-Handle v o r b e r e i t e n my $sql = "INSERT INTO c d _ r e l a t i o n ( c d _ i d , c d . t i t e l ,
";
130
7 Datenbank-Programmierung mit Perl/DBI
$sql .= "cd_ersch_jahr, kuenstler_id)"; $sql .= "VALUES(?, ?, ?, ?)"; my $stli = $dbh->prepare($sql); # Daten aus Textdatei lesen und in Datenbank schreiben open (IN, 'newcd.txt'); while () { # Zeile zerlegen chomp; my ($id, $titel, $ersch_jahr, $kuenstler) = split /;/, $_; $sth->execute($id, $titel, $ersch_jahr, $kuenstler);
> # Dateihandle und Datenbankverbindung schliessen close IN; $dbh->disconnect();
Die gezeigte Anwendung setzt voraus, dass sich im Anwendungsverzeichnis die Textdatei newcd.txt befindet, die zeilenweise die anzufügenden Datensätze enthält. Dabei ist wichtig, dass die einzelnen Zeilen jeweils den gleichen Aufbau haben und die einzelnen Einträge durch Semikola voneinander getrennt sind. Leere Felder müssen durch die entsprechende Anzahl von Semikola dargestellt werden. Eine solche Textdatei könnte wie folgt aussehen. 4;Mein Leben mit Mama;1969;4 5;99 Luftballons;1983;1 6;Street Legal;; 7;We believe in God Inc.;1979;5
Beachten Sie, dass im dritten gezeigten Datensatz die Angaben für das Erscheinungsjahr und die Künstler-Id fehlen. Mit dem gezeigten Vorgehen können sehr schnell und effektiv große Datenmengen in eine Datenbank geschrieben werden. Beispielsweise geben viele Behörden Datenbestände in Form solcher Flat-File-Databases40 oder CSV-Dateien weiter. Das gezeigte Vorgehen ist aber nur dann sinnvoll, wenn wirklich eine große Anzahl gleichartiger Änderungen an der Datenbank vorgenommen werden soll. Möchten Sie hingegen nur einzelne Datensätze verändern bzw. einzelne Aktionen ausführen, wie etwa das Löschen eines Datensatzes, so ist es sinnvoller, die do()-Methode des DatenbankHandies zu verwenden. 40 Flat-File-Database oder CSV-Dateien ( C o m m a Separated Values) bezeichnen solche Textdateien, wie wir sie oben vorgestellt haben.
7.4 Arbeiten mit D B I / D B D
131
#!/usr/bin/perl -w # db_pflegen2.pl use DBI; # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank",$user,
"");
# Datensatz 'Street Legal' loeschen my $sql = "DELETE FROM cd.relation WHERE cd_titel = 'Street Legal'"; unless ($dbh->do($sql)) { print "Ich konnte den Datensatz nicht loeschen!\n";
} # Datenbankverbindung schliessen $dbh->disconnect();
Das letzte Beispiel zeigt, wie mit Hilfe der do()-Methode ein Datensatz aus der cd_relation gelöscht wird. Dabei gibt die Methode den Wert 1 zurück, wenn die Ausführung erfolgreich war oder 0, falls nicht. Die Methode do () eignet sich also hervorragend zur Administration einer Datenbank, also beispielsweise um Tabellen anzulegen oder wieder zu löschen. Bei einigen DatenbankSystemen ist es sogar möglich, mit Hilfe dieser Methode neue Datenbanken anzulegen oder zu löschen, wie das folgende Beispiel demonstriert 4 1 .
#!/usr/bin/perl -w # db_pflegen3.pl use DBI; # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", 41 Wir geben zu, dass diese Anwendung nicht sehr sinnvoll ist, normalerweise werden Sie sich für das eine oder andere entscheiden!
132
7 Datenbank-Programmierung mit Perl/DBI $user, "");
# Neue Datenbank anlegen unless ($dbh->do("CREATE DATABASE mein.test")) { print "Ich konnte die Datenbank nicht anlegen!\n";
> # ... und wieder loeschen unless ($dbh->do("DR0P DATABASE mein.test")) { print "Ich konnte die Datenbank nicht loeschen!\n";
> # Datenbankverbindung schliessen $dbh->disconnect();
7.4.13
Metadaten zur Datenbank ermitteln
Weiter oben haben wir gesehen, wie Sie Informationen über ein Recordset erhalten. Diese werden stets über ein Statement-Handle abgerufen. Bevor wir nun das Thema Perl/DBI vorläufig verlassen, möchten wir Ihnen noch erläutern, wie es möglich ist, Metadaten auf der Ebene des Datenbank-Handies abzufragen. Leider verwenden fast sämtliche Datenbank-Management-Systeme unterschiedliche Mechanismen, um diese Daten zu speichern. Perl/DBI verfügt daher nur über zwei Methoden, die mit allen Datenbank-Systemen zusammen arbeiten. Mit Hilfe der Methode tables() kann eine Liste aller in einer Datenbank entbotenen Tabellen abgefragt werden. Betrachten Sie hierzu folgendes Beispiel. #!/usr/bin/perl -w # metadaten.pl use DBI; # Datenbankverbindung herstellen # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # Alle Tabellen der Datenbank abfragen
7.4 Arbeiten mit DBI/DBD
133
my Qtabellen = $dbh->tables(); # Header der Ausgabe print "\nln der Datenbank $datenbank sind folgende Tabellen definiert:\n"; print " \n\n" ; # ... und ausgeben foreach my $tabelle (Qtabellen) { print "Tabelle: $tabelle\n";
> print "\n"; $dbh->disconnect();
Die andere Methode, die mit allen Datenbank-Systemen zusammen arbeitet, ist table_i n f o ( ) . Sie gibt ein Statement-Handle zurück, dass in jedem Falle die folgenden Felder enthält: • TABLE_QUAL IFIER: In den allermeisten Fällen wird dieses Feld den Wert undef bzw. NULL enthalten.
• TABLE_OWNER: Dieses Feld gibt den Namen des Besitzers der Tabelle zurück. Bei den meisten Datenbank-Systemen ist der Besitzer einer Tabelle die Person, die diese angelegt hat und alle Rechte auf die Tabelle besitzt. • TABLE_NAME: Wie der Name schon sagt, ist in diesem Feld der Name der Datentabelle hinterlegt. • TABLE_TYPE: Mit diesem Feld können Sie den Typ der Tabelle ermitteln. Mögliche Werte sind unter anderen: TABLE, VIEW, SYSTEM TABLE. • REMARKS: Bei vielen Datenbank-Management-Systemen ist es möglich, zu den Datentabellen Bemerkungen zu hinterlegen, die den Inhalt oder den Zweck der Tabelle beschreiben. Dieses Feld gibt diese Beschreibung aus; in den meisten Fällen wird dies undef oder NULL sein. Auch zu dieser Methode möchten wir Ihnen ein Beispiel vorstellen. #!/usr/bin/perl -w # metadaten2.pl use DBI; # Datenbankverbindung herstellen
134
7 Datenbank-Programmierung mit Perl/DBI
# Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD.DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # Alle Tabellen der Datenbank abfragen my (Stabellen = $dbh->tables() ; # Header der Ausgabe print "\nln der Datenbank $datenbank sind folgende Tabellen definiert:\n"; print " \n\n"; # ... und ausgeben foreach my $tabelle (®tabellen) { print "Tabelle: $tabelle\n";
> # Tabellen-Informationen ermitteln my $sth = $dbh->table_info(); # exemplarisch einige Informationen ausgeben while (my $table_ref = $sth->fetchrow_hashref()) { print "Tabellenname: " . $table_ref->{'TABLE_NAME'} . "\n"; print "\tBesitzer : " . $table_ref->{'TABLE.OWNER'} . "\n"; print "\tTyp : " . $table_ref->{'TABLE_TYPE'} . "\n"; print "YtBemerkung: " . $table_ref->{'REMARKS'} . "\n\n";
} print "\n"; $dbh->disconnect();
Die Ausgabe dieses Skriptes hängt vom verwendeten Datenbank-System sowie den hinterlegten Daten ab, das heißt, es müssen nicht in allen Feldern Werte enthalten sein. Selbstverständlich ist es auch möglich, die Tabellen-Informationen mit Hilfe der Methode fetchrow_array abzufragen. Vor allem dann, wenn Sie nicht alle verfügbaren Informationen benötigen, erscheint die vorgestellte Lösung übersichtlicher. Im vorliegenden Kapitel haben wir uns ausführlich mit Datenbanken und Perls Datenbankschnittstelle beschäftigt. Damit ist das Thema Datenbanken für dieses Buch allerdings noch nicht abgeschlossen. Vielmehr werden wir an verschiedenen Stellen auf das vorgestellte Wissen zurückgreifen. Denn wie gesagt, eine Anwendung, die nicht auf die eine oder andere Art mit Datenbanken zusammen arbeiten muss, ist heutzutage kaum vorstellbar.
8
CGI-Programmierung mit Perl
Wenn Programmierer über Perl sprechen, meinen Sie in der Regel die Verbindung von Perl und CGI. Auch wenn Perl, wie wir Ihnen in diesem Buch zeigen, viel mehr kann als CGI, so ist die leichte Verknüpfbarkeit dieser beiden Komponenten sicherlich einer der Gründe für die weite Verbreitung von Perl. Eine der bekanntesten Aussagen, die dieses Verhältnis illustrieren ist die, dass "Perl das Klebeband sei, welches das Internet zusammenhält". In diesem Kapitel werden wir uns nun ausführlich mit der Webserver-Programmierung mit Perl und CGI beschäftigen. Dabei werden wir auch randliche Themen streifen, so dass Sie nach der Lektüre dieses Kapitels in der Lage sind, leistungsfähige Webapplikationen zu erstellen. Im ersten Abschnitt dieses Kapitels erläutern wir, wie Sie Ihren Apache-Webserver Linux- (Unix-) und Windows-Systemen installieren und konfigurieren.
auf
Wir werden uns dann mit der Frage beschäftigen, was CGI ist und wie es funktioniert. Da CGI-Skripten ohne HTML 1 -Kenntnisse kaum sinnvoll einzusetzen sind, folgt ein kurzer HTML-Exkurs, der Sie mit den Grundlagen dieser Sprache vertraut macht. Anschließend stellen wir CGI anhand eines einfachen Einführungsbeispiels, quasi als Appetithappen, kurz vor. Im folgenden Abschnitt stellen wir Ihnen ein etwas umfangreicheres Beispiel vor, das einige fortgeschrittene Techniken darstellt, mit denen Sie die Arbeit mit Perl/CGI vereinfachen können. Vor diesem Hintergrund können wir uns intensiv mit dem Einsatz und den Möglichkeiten von Perl/CGI befassen. Zunächst stellen wir dar, wie Sie mit Hilfe von Perl/CGI und HTML Eingabeformulare erstellen und auf Eingaben des Benutzers reagieren können. Daran anschließend erklären wir, wie Sie Ihre Anwendungen mit Hilfe von Templates übersichtlicher und vor allem modularer gestalten können. Die Vorteile von Templates werden Sie vor allem bei größeren Projekten und bei der Arbeit in Teams zu schätzen lernen. Mit Hilfe von Perl und CGI können Sie dynamisch Grafiken in Ihre Webanwendung einbetten und Diagramme erzeugen. Wie dies funktioniert, erklären wir im letzten Teil dieses Kapitels. Natürlich werden reichlich Gebrauch von der im vorangegangenen Kapitel besprochenen Datenbankschnittstelle machen, da Webanwendungen ohne eine Datenbank-Verbindung der einen oder anderen Art kaum mehr vorstellbar sind. 1
Hyper- Text-Markup-Language
- eine Seitenbeschreibungssprache zur Erstellung von Internetseiten
136
8.1
8 CGI-Programmierung mit Perl
Einrichten und Konfigurieren des Apache-Webservers
In unseren Beispielen setzen wir voraus, dass auf Ihrem System ein Webserver installiert und aktiv ist. Wir verwenden den Apache-Webserver aus mehreren Gründen. Zunächst ist Apache mittlerweile der am meisten eingesetzte Webserver weltweit. Der ApacheWebserver liegt in verschiedenen Versionen für die wichtigsten Betriebssysteme vor. Wir werden uns hier allerdings auf die Linux- und die Windows-Version beschränken. Das beste an Apache ist allerdings, dass Sie die Software kostenlos auf der folgenden Website beziehen können: http://www.apache.org/dist/httpd/binaries. Auf der Homepage http://www.apache.org finden Sie auch umfangreiche Dokumentationen und Informationen zu dieser Anwendung. Leider ist die Installation dieser Software auf Ihrem Rechner nicht ganz so einfach, wie die eines Textverarbeitungs-Programmes. Daher beschreiben wir kurz diesen Prozess.
8.1.1
Den Apache-Webserver auf einem Linux-System installieren
Linux-Anwender können sich glücklich schätzen 2 , da der Apache Webserver auf diesem Betriebssystem in der Regel automatisch installiert und konfiguriert wird, so dass nur eine kleine Anzahl von Einstellungen für den Anwender übrig bleiben. Das Starten des Webservers kann von Linux-Distribution zu Distribution leicht abweichend sein. Auf meinem RedHat 9.0-System lautet der entsprechende Befehl 3 /etc/init.d/httpd Start
Je nach Distribution kann der benötigte Befehl auch /etc/init.d/apache Start
lauten. Alle Linux-Distributionen besitzen zudem Tools, mit denen es möglich ist, den Webserver gleichzeitig mit dem Betriebssystem zu starten. Bei RedHat 9.0 finden Sie diesen Punkt im Menü Systemeinstellungen | Servereinstellungen I Dienste. Bei einem SuSE-System nehmen Sie die entsprechende Änderung mit Yast2 im RunlevelEditor vor, den Sie im Bereich System finden. Starten Sie hier den Apache-Webserver im Runlevel 5. Auf einem Debian-System können Sie das SysVinit-Tool verwenden. Auch hier sollte Apache im Runlevel 5 gestartet werden.
2 Nach Meinung des Autors natürlich nicht nur aus diesem Grund, aber das ist natürlich die persönliche Ansicht des Autors. 3 Beachten Sie, dass Sie zum Starten und Konfigurieren des Webservers Administrator-, also rootRechte benötigen! Wenden Sie sich also im Zweifelsfall an Ihren Systemadministrator.
8.1 Einrichten und Konfigurieren des Apache-Webservers
8.1.2
137
Den Apache-Webserver auf einem Windows-System installieren
Zunächst müssen Sie sich von der oben angegebenen Adresse das aktuelle Release des Apache-Webservers herunterladen. Sie finden dort die Versionen 1.3.x und 2.0.x 4 . Auf diversen Internetseiten und bei Informationsdiensten liest man hier und da, die Version 2.x habe noch Sicherheitslücken, daher wird oft zur Installation der Version 1.3.x geraten. Mittlerweile scheinen diese aber weitgehend beseitigt zu sein 5 . Nachdem Sie das Installationspaket aus dem Internet geladen haben, können Sie den Webserver, wie jede andere Windows-Anwendung auch, auf Ihrem Rechner installieren. Während des Installationsvorganges, bei dem Sie weitgehend die Vorgaben übernehmen können, erscheint ein Bildschirm, der drei Angaben von Ihnen fordert 6 : • N e t w o r k D o m a i n : Sofern Sie auf einem Einzelplatzrechner arbeiten, was zum Nachvollziehen dieses Stoffes auf jeden Fall sinnvoll ist, geben Sie hier l o c a l h o s t ein. • S e r v e r N a m e : Auch hier tragen Sie bei einem Einzelplatzrechner l o c a l h o s t ein. • A d m i n i s t r a t o r ^ E m a i l - A d d r e s s : In diesem Feld tragen Sie Ihre E-Mail-Adresse ein 7 . Nach ein paar Klicksen auf die Ok-Taste sollte der Apache-Webserver auf Ihrem System installiert sein. Um den Webserver auf einem Windows-System zu starten, wählen Sie im Start-Menü in der Rubrik Programme, Apache den Eintrag Start Apache aus.
8.1.3
Test Ihrer Apache-Installation
Um zu testen, ob Apache auf Ihrem System korrekt installiert wurde und aktiv ist, starten Sie Ihren Lieblingsbrowser und geben die folgende U R L ein. http://localhost Sollte der Apache Webserver gestartet sein, werden Sie mit einem Begrüßungsbildschirm belohnt. Sollten Sie stattdessen eine Fehlermeldung erhalten, gehen Sie die vorhergehenden Schritte nochmals durch. Unter Linux sollten Sie eventuell das Werkzeug zum Einrichten von Software starten und überprüfen, ob die Option Webserver oder bei einem Debian-System Apache aktiviert ist. Sollte dies nicht der Fall sein, holen Sie dies jetzt nach und versuchen es erneut. 4Zum
Zeitpunkt des Schreibens, Frühjahr 2003. Nur damit Sie gewarnt sind. 6 Dieser Bildschirm hat den Autor in seiner Apache-Anfangszeit schier zum Wahnsinn getrieben! Niemand schien es für Wert zu erachten, den Einsteiger darüber aufzuklären, welche Angaben hier gemacht werden müssen. 7 Gell, darauf wären Sie nie gekommen? 5
138
8 CGI-Programmierung mit Perl
ßatei IjMiWtcn ansieht Sehe Lesezeichen loots Feriste» fcWfe Z # HTML-Footer ausgeben print ""; print "";
sub argumenteExtrahieren { # Die an die Anwendung uebergebenen Argumente extrahieren # und als Hash zurueck-liefern
my $aufruf ; my '/»argumente; # Den Aufruf-String in die locale Variable aufruf einlesen read(STDIN, $aufruf, $ENV{'CONTENT.LENGTH'» ; # Alle Parameter-Wert-Paare in Array Zwischenspeichern my 0argumente = split(/&/, $aufruf); # und in Hash schreiben foreach $argument (®argumente) { # zerlegen my ($schluessel, $wert) = split(/=/, $argument);
149
150
8 CGI-Programmierung mit Perl # u n d anhaengen $argumente{$sch.luessel} = $wert;
> # Hash zurueck-liefern return '/,argumente ;
>
Nachdem Sie das Skript abgetippt haben, speichern Sie es in einem Verzeichnis, auf das ein ScriptAlias verweist. Nun müssen Sie die Ausführungsrechte der Datei ändern, indem Sie die Anweisung. chmod +x einfuehrung.cgi
Q*la
Bnwtenen
Zu^ck " -üSianseitc
inîidrt fiehe l^srackhen
Nii^Ltl
'
Irais
£etist« w-tc
.t MI[I ". . t^.v i.i
-
I_c,tir?i(fxn ¿ R e d Hit Nemo* _jSjohc" J ' j r ^ t i J p i o l u d s Jjlraimrg
l infüliruii^sî>fispi print "
Checkboxen |
$spaltenNamen"; > print " | |
$wert | "; } eise { print ""; |