Embedded SQL mit C und PostgreSQL Holger Jakobs – [email protected], [email protected] 2008-06-26 Inhaltsverzeichnis 1 Einleitung 2 SQL in Programmen 2.1 SQL-Kommandos im Quelltext 2.2 Verbindung zur Datenbank . . . 2.3 Datenbank ↔ Programm . . . . 2.4 Transaktionen . . . . . . . . . . 1 . . . . 2 3 3 3 5 3 Erstes Beispielprogramm 3.1 Compilation eines SQL-Programms . . . . . . . . . . . . . . . . . . . . . . 3.2 Precompiler-Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 5 4 Das SELECT-Kommando in Programmen 5 5 Weitere Embedded-SQL-Kommandos 5.1 Programmieraufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Hinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 7 7 6 mehrere Ergebnistupel 6.1 Bulk Select . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Cursor-Select . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 8 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Implizite Fehlerbehandlung 11 8 weitere Aufgaben 11 Hinweis: Alle Beispielprogramme finden Sie im Datenbank-Portal unter Quelltexte“. ” 1 Einleitung SQL kann nicht nur mit interaktiven Kommandos in psql benutzt werden, sondern SQLKommandos können auch in Programme eingebettet werden, die in klassischen Programmiersprachen geschrieben wurden, z. B. C oder C++. Voraussetzung ist, dass neben dem 1 2 SQL IN PROGRAMMEN Compiler für die jeweilige Sprache der passende Precompiler vorhanden ist, der die eingebetteten SQL-Kommandos in Quelltext der jeweiligen Sprache umsetzt. Im folgenden wird nur C verwendet, aber es ist mit C++ genauso möglich. Für C++ bietet sich allerdings auch die Verwendung eines objektorientierten Interfaces an, was unter dem Namen libpq++ zur Verfügung steht. Für C gibt es wahlweise ein Interface mit Bibliotheksaufrufen unter dem Namen libpq. Diese Interfaces sind nicht so portabel wie Embedded SQL. Darüber hinaus ist der Zugriff auf Datenbanken über genormte Schnittstellen von Bedeutung, hierbei ist besonders das plattformunabhängige JDBC zu erwähnen, das in einem separaten Dokument erläutert wird. Grundsätzlich funktioniert die Programmierung mit Embedded SQL mit allen Programmiersprachen und mit allen SQL-Datenbanken im wesentlichen gleich – sofern sie Embedded SQL überhaupt unterstützen. In Details gibt es aber Unterschiede. Dieser Text bezieht sich ausdrücklich auf PostgreSQL und C auf Linux-Rechnern. 2 Verwendung von SQL in Programmen In Programmen werden vorwiegend Datenmanipulationsbefehle benutzt, seltener Administrationskommandos für die Datenbank, die die Struktur verändern. Es gibt davon vier: SELECT Suchen von Daten INSERT Einfügen von Tupeln DELETE Löschen von Tupeln UPDATE Ändern von Attributwerten Diese Befehle können auf verschiedene Arten Datenmanipulationen durchführen. Nicht alle Kombinationen sind möglich. Die möglichen Operationen sind angegeben (S=SELECT, F=FETCH, I=INSERT, D=DELETE, U=UPDATE, WC=DELETE/UPDATE WHERE CURRENT): 1. Einfache Datenmanipulation: (S, I, D, U) Alle Operationen betreffen die Übertragung von max. einem Tupel aus der Datenbank ins Programm oder umgekehrt. 2. Sequentielle Tabellenverarbeitung: (S, F, WC) Mit Hilfe eines Cursors (Tupelzeiger) können viele Tupel nacheinander bearbeitet werden. 3. Bulk-Operation: (S, F, I) Es werden viele Tupel mit einem Befehl bearbeitet. 4. Dynamische Operation: (S, F, I, D, U) Die Operation wird erst zur Laufzeit spezifiziert. Obwohl bei PostgreSQL alle Ope- 2 2 SQL IN PROGRAMMEN 2.1 SQL-Kommandos im Quelltext rationen vom Prinzip her dynamisch sind, wird die Erstellung voll dynamischer Programme in diesem Dokument nicht erläutert. 2.1 SQL-Kommandos im Quelltext Die eingebetteten SQL-Kommandos müssen als solche für den Precompiler kenntlich gemacht werden. Ein SQL-Kommando sieht so aus: exec sql ... ; Alles zwischen exec sql und dem Semikolon wird vom SQL-Precompiler in reinen CQuelltext umgewandelt. Das Original-Kommando wird also durch C-Code ersetzt. Gelegentlich kann man es noch im C-Code erkennen, aber das ist nicht immer der Fall. Der SQL-Precompiler-Output wird anschließend mit dem C-Compiler ganz normal compiliert und unter Verwendung datenbankspezifischer Bibliotheken gelinkt. 2.2 Verbindung zur Datenbank Vom fertigen Programm muss eine Verbindung zur Datenbank hergestellt werden, so wie das bei psql auch der Fall ist. Bei psql wird allerdings beim Aufruf ohne Angabe einer Datenbank versucht, eine Datenbank zu öffnen, die genauso heißt wie der aufrufende Benutzer. Dies ist bei Embedded-SQL-Programmen nicht der Fall – hier muss man ausdrücklich ein connect to verwenden. Dies sieht im Programm so aus: exec sql connect to spieler; Hierbei ist das Wort to wahlfrei, d. h. es kann auch weggelassen werden. Beim Aufbau der Verbindung zur Datenbank spieler wird ggf. die Umgebungsvariable PGHOST verwendet, die den Namen des Datenbankservers enthält. Ist die Variable nicht gesetzt, wird eine lokale Datenbank dieses Namens angesprochen. Möchte man die Variable PGHOST nicht berücksichtigen, gibt man für die Datenbank dbname @server an. Wahlweise kann man sogar einen ganzen URL angeben: (tcp|unix):postgresql://server [:port ][/dbname ][?options ] Beispiel: tcp:postgresql://localhost/spieler 2.3 Kommunikation zwischen Datenbank und Programm Zwischen Programm und Datenbank werden Daten ausgetauscht, z. B. Datenbank → Programm beim select und umgekehrt beim insert. Dies geschieht über sogenannte HostVariablen, die in den SQL-Kommandos durch einen vorangestellten Doppelpunkt gekennzeichnet werden. Es kann sich bei den Variablen um globale oder auch lokale Variablen handeln. Nullwerte werden, da sie nicht in den Variablen selbst darstellbar sind, über zusätzliche Variablen vom Typ int dargestellt. Diese werden Nullwert-Indikatoren genannt und haben bei Abfragen folgende Werte: 3 2.3 Datenbank ↔ Programm Wert −1 0 >0 2 SQL IN PROGRAMMEN Beschreibung Das Attribut hatte den Nullwert Es war ein Wert vorhanden, der übertragen wurde. Es war ein Zeichenkettenwert vorhanden, der aber abgeschnitten wurde. Man sollte stets Nullwert-Indikatoren verwenden, wenn ein Attribut Nullwerte zulässt, damit es zur Laufzeit keine unerwarteten Fehler gibt. Beim Einfügen von Daten verwendet man ebenfalls Nullwert-Indikatoren, die man mit −1 füllt, wenn man einen Nullwert übertragen möchte, und ansonsten mit 0. Man kann auch bei not-null-Zeichenketten-Attributen Indikatoren verwenden, aber dann ist der Indikatorwert 0, wenn alles gut gegangen ist. Für den Fall, dass die Variable zu kurz war, um den Wert des Attributs aufzunehmen, enthält die Indikatorvariable die Anzahl Zeichen, die hätten übertragen werden sollen. Das Datenbanksystem muss dem Programm über die reinen auszutauschenden Daten hinaus auch noch mitteilen, ob die gewünschten Operationen ausgeführt werden konnten. Dazu dient die SQL-Communication Area, kurz sqlca. Sie muss im Programm global deklariert werden, was durch das Kommando exec sql include sqlca; geschieht. Es handelt sich dabei um eine Datenstruktur, von der u. a. folgende Komponenten zur Status-Überprüfung benutzt werden: sqlcode, sqlerrm.sqlerrmc, sqlerrd[2] (siehe Abschnitt 6.1 auf Seite 8), sqlwarn[0] (siehe Abschnitt 7 auf Seite 11), sqlwarn[1] und sqlwarn[2] (siehe Kommentare in vom Precompiler umgewandeltem Quelltext). Die Bedeutung der Variablen sqlcode ist folgende: Werte sqlcode = 0 sqlcode < 0 sqlcode = 100 Bedeutung kein Fehler, keine Warnung Fehler, Kommando nicht ausgeführt kein passendes Tupel gefunden Weitere Werte sind datenbankabhängig. Hier einige von PostgreSQL: sqlcode-Werte Bedeutung −201 Zu viele Argumente, evtl. vergessene Host-Variablen in einer into :var1, :var2 -Liste. −202 Zu wenige Argumente −203 Mehrere Ergebnistupel bei einem Single Select. −221 Keine Verbindung zur Datenbank. Weitere Werte schauen Sie bitte in den PostgreSQL-Unterlagen nach. Tip: In dem vom Precompiler erzeugten C-Code ist die SQL Communication Area schön kommentiert enthalten. Schauen Sie mal rein! Schon ab dem ANSI-SQL-Standard von 1992 (SQL-92) ist neben dem wenig informativen sqlcode auch die Fehlerbehandlung mit dem umfangreicheren sqlstate vorgesehen. Dieser ist in PostgreSQL seit Version 7.4 ebenfalls enthalten. Der ANSI-SQL-Standard von 1999 (SQL-99) kennt nur noch den 5 Zeichen umfassenden sqlstate. 4 4 DAS SELECT-KOMMANDO IN PROGRAMMEN 2.4 Transaktionen 2.4 Transaktionen Wenn Sie Ihre Programme mit pgc (für C) bzw. pgcpp (für C++) übersetzen, dann wird die Option -t für den Precompiler gesetzt. Diese bewirkt, dass jedes einzelne SQL-Statement sofort ausgführt, d. h. commitet wird. Dies ist auch bei psql der Fall. Wenn Sie mehrere zusammenhängende Änderungen an der Datenbank vornehmen wollen, müssen Sie daher explizit begin work und commit work bzw. rollback work verwenden (jeweils mit exec sql davor). Ohne die Option -t würde automatisch immer eine Transaktion begonnen, so dass Änderungen ohne ein anschließendes commit work immer zurückgefahren würden. Nach einem commit work oder einem rollback work würde auch immer sofort eine neue Transaktion begonnen, so dass das Kommando begin work nicht benutzt werden müsste oder dürfte. Dies entspräche dann dem ANSI-SQL-Standard, der begin work gar nicht kennt. Wenn Sie möchten, können Sie es sich ja entsprechend einrichten oder wahlweise in Ihrem Programm ein exec sql set autocommit to off verwenden. Der ANSI-SQL-Standard von 1999 (SQL-99) kennt allerdings ein START TRANSACTIONStatement, das aber nur dazu dient, einen Isolation Level“ für die Transaktion anzugeben. ” Das geht in PostgreSQL seit Version 7.4 auch, früher statt dessen mit SET TRANSACTION. 3 Erstes Beispielprogramm Das Beispielprogramm finden Sie in der Datei sqlprogc_beispiel1.sql; es wird die bekannte Sportlerdatenbank spieler verwendet (bzw. die Tabelle spieler darin). 3.1 Compilation eines SQL-Programms Ein C-Programm mit Embedded SQL muss mehrfach übersetzt werden. Daher haben wir auf unserer Maschine ein kleines Script für diesen Zweck, das genauso heißt wie die Endungen der Quelltexte: pgc für C und pgcpp für C++. Tatsächlich ist es nur ein einziges Script, siehe /usr/local/bin/pgc 3.2 Vom SQL-Precompiler umgewandelte EXEC-SQL-Aufrufe Übersetzen Sie das Beispielprogramm sqlprogc_beispiel1.pgc und drucken Sie den Quellcode sowie den erzeugen C-Quelltext sqlprogc_beispiel1.c aus. Vergleichen Sie die Dateien und suchen Sie die exec sql-Anweisungen im übersetzten Code. 4 Das SELECT-Kommando in Programmen Nach der Eingabe von Daten mit dem insert-Kommando wollen wir nun Daten aus dem Bestand abfragen. Vom select-Kommando gibt es mehrere Varianten: das Single Select, das Bulk Select und das Sequential Select. Das erste wird hier erläutert, das Bulk Select 5 5 WEITERE EMBEDDED-SQL-KOMMANDOS in Abschnitt 6.1 auf Seite 8 und das Sequential Select (auch Cursor-Select genannt) in Abschnitt 6.2 auf Seite 9. Das Single Select muss so formuliert sein, dass das Ergebnis nicht mehr als ein einziges Tupel umfasst; andernfalls kommt es zu einem Fehler (−203 in sqlca.sqlcode). Das Programm stellt für das Ergebnis des Kommandos nur einen Datensatz zur Verfügung, der sofort gefüllt wird, sofern ein Tupel gefunden wird. Beispiel für eine Deklaration: exec sql begin declare section; char SpielName [16]; /* alle Zeichenketten 1 Byte laenger, */ char Vorname [ 4]; /* damit Platz fuer Terminator \0 ist. */ char PLZ [ 6]; char Ort [21]; exec sql end declare section; Zeichenketten sollten grundsätzlich 1 Byte länger als in SQL deklariert werden, damit Platz für das Terminatorzeichen '\0' ist, was auch automatisch angehängt wird. Das SelectStatement sieht im Programm dann so aus: exec sql select spielname, vorname, plz, ort into :spielname, :vorname, :plz, :ort from spieler where spielnr = :spielnr; Dieses Select erzeugt wegen der Eindeutigkeit der Spielernummer garantiert höchstens ein Tupel. Die Variable sqlcode aus der sqlca kann den Wert 100 enthalten, wenn kein passenden Tupel enthalten ist, oder es kann ein Fehler auftreten, wenn mehr als ein passendes Tupel gefunden wurde. Die Select-Kommandos können auch bei einem Single Select beliebig komplex sein. Ein komplettes Beispielprogramm mit einem Single-Select finden Sie unter singleselect.pgc. Schauen Sie es sich mit besonderem Augenmerk auf die Behandlung von Nullwerten an. Bei allen Attributen, die Nullwerte enthalten können (also nicht mit not null versehen sind), ist es zwingend erforderlich, dass ein Nullwert-Indikator verwendet wird. Dabei handelt es sich um eine Integer-Host-Variable, die hinter dem Attribut ohne Komma dazwischen angegeben wird. Diese Variable wird mit einer Null gefüllt, wenn ein gültiger Wert geliefert wurde (also logisch false). Falls im Attribut ein Nullwert stand, bekommt der Nullwert-Indikator einen Wert ungleich Null (also logisch true). 5 Weitere Embedded-SQL-Kommandos Im folgenden sollen Sie außer dem Single Select und dem Insert auch Delete und Update einsetzen. Delete unterscheidet sich nicht von der interaktiven Version. Bei Update lautet die Syntax: exec sql update tabellenname set attribut = attributwert /:hostvariable where bedingung ; 6 5 WEITERE EMBEDDED-SQL-KOMMANDOS 5.1 Programmieraufgaben 5.1 Programmieraufgaben mit Embedded SQL zur Krankenhaus-Datenbank Schreiben Sie ein C-Programm, das folgendes leistet: 1. Erfassen von Daten (insert) Station Zimmer Medikament Patient (Zimmer darf nicht überbelegt werden!) Angestellter/Arzt (Fach + Telefonnummer oder keins von beiden!) Behandlung Verordnung 2. Lesen von Daten (single select) Daten für ein Zimmer anzeigen, d. h. Stationsname, Zimmernummer und Anzahl der freien und belegten Betten. 3. Verändern von Daten (update) Verlegen eines Patienten von einem Zimmer in ein anderes, wobei auch hier das neue Zimmer nicht überbelegt werden darf. Eintragen/Ändern/Löschen einer Diagnose 4. Löschen von Daten (delete) Entlassung eines Patienten, wobei nach Eingabe der Patientennummer sämtliche den Patienten betreffenden Daten aus der Datenbank gelöscht werden. Möchten Sie Ihren Quelltext in mehrere Dateien zerlegen, verwenden Sie statt des aus C bekannten #include abc.h die Anweisung exec sql include abc , damit das Einfügen bereits beim Precompiling ausgeführt wird. Andernfalls kennt der Precompiler die Inhalte der eingefügten Dateien nicht und meldet Fehler. 5.2 Hinweise zu den Programmieraufgaben Arbeiten Sie mit Haupt- und Untermenüs, wobei das Hauptmenü die obigen Punkte zur Auswahl anbietet. Beginnen Sie jede einzelne Transaktion mit einem begin work und schließen Sie sie mit commit work ab. Ohne dies würde jede einzelne Aktion auf der Datenbank in einer eigenen Mini-Transaktion ablaufen (sogenanntes auto-commit). Möchten Sie zwischendurch eine Transaktion abbrechen, so verwenden Sie rollback work. Vor der Programmierung sollen Struktogramme entworfen werden, die die SQL-Anweisungen bereits enthalten. Dies dient der Konzentration auf Abläufe und Datenbanken, ohne gleichzeitig an die Programmiersprache denken zu müssen. Bauen Sie Fehlerbehandlungen mit Klartext-Meldungen in Deutsch für alle Fehlersituationen ein, die Sie vorhersehen können, beispielsweise Station nicht vorhanden!“ bei der ” Zimmereingabe. Nur für nicht vorhersehbare Fehler soll die Standard-Fehlerbehandlung einer Funktion StatusAnzeige() oder ähnlich verwendet werden. 7 6 MEHRERE ERGEBNISTUPEL 6 Abfragen mit mehreren Ergebnistupeln Wenn bei einem SELECT mehr als ein einziges Tupel herauskommen kann, reicht ein Single Select nicht aus. Man muss ein Bulk Select oder eine sequentielle Tabellenverarbeitung mit cursor und fetch machen. 6.1 Bulk Select – Abfragen mehrerer Tupel auf einmal Bei einem Bulk Select wird die Ergebnistabelle auf einmal in den Datenbereich des Programms übertragen. Das bedeutet, dass das Programm einen ausreichend großen Datenbereich zur Verfügung stellen muss. Der Programmierer muss also schon zur Compilationszeit abschätzen, wieviele Tupel/Datensätze beim Select entstehen können. Im Gegensatz zum Single Select werden hier nicht einzelne Host-Variablen angelegt, sondern Arrays. exec sql begin declare section; int nr [20]; char spielname [20][30]; exec sql end declare section; Bei obiger Deklaration war keine Indikatorvariable notwendig, weil alle Attribute mit not null versehen sind. Werden Indikatorvariablen benötigt, so sind sie ebenfalls als Array zu deklarieren. Die Aufrufsyntax des Bulk Select unterscheidet sich vom gewöhnlichen Select kaum und sieht beispielsweise so aus: exec sql select spielnr, spielname into :nr, :spielname from spieler; In der Variablen sqlerrd [2] der SQL Communication Area teilt uns SQL mit, wieviele Tupel in unser Array übertragen worden sind. Dies ist eine weitere Statusinformation über den sqlcode hinaus. Zur Weiterverarbeitung in unserem Programm benötigen wir diese Angabe z. B. in einer for-Schleife: for (i=0; i<sqlca.sqlerrd[2]; i++) { printf ("Spieler[%d]: %d, %s\n", i, nr[i], spielname[i]); } Sollte es mehr Spieler geben als in die Arrays passen, so gibt es wieder den Fehlerstatus -203 in sqlca.sqlcode. Die Variable sqlca.sqlerrd[2] enthält jetzt zwar auch die Anzahl der passenden Tupel, aber die Daten sind nicht übertragen worden und dürfen daher auch nicht verwendet werden. Ein vollständiges Beispielprogramm finden Sie in bulkselect.pgc 8 6 MEHRERE ERGEBNISTUPEL 6.2 Cursor-Select 6.2 Sequentielle Tabellenverarbeitung mit Cursor Bei der sequentiellen Tabellenverarbeitung wird von SQL im Hintergrund eine Tabelle erzeugt, die das Ergebnis des Select enthält. Das Programm benötigt wie beim Single Select nur die Variablen für ein einziges Tupel, weil diese aus der Hintergrundtabelle wie aus einer sequentiellen Datei gelesen werden. Ein Cursor dient als Satzzeiger auf die Hintergrundtabelle. Bei diesem sequential Select“ muss man nicht vorher wissen, wieviele Tupel die Ergeb” nistabelle enthalten wird. Dafür ist die Anzahl der Zugriffe auf die Datenbank viel höher, und Sperren auf den Datenbestand bestehen im Allgemeinen länger. Folgende Embedded-SQL-Kommandos gibt es für das Arbeiten mit sequential Selects: declare cursorname cursor for selectcommand ; verbindet einen Cursor über seinen Namen mit einem spezifizierten Select-Kommando. Der Cursor ist ein Zeiger auf ein Tupel der Ergebnistabelle, die durch das Select erzeugt werden kann. Die Ergebnistabelle wird durch declare cursor noch nicht erzeugt. (Hinweis: Bei PostgreSQL tut dieses Kommando gar nichts; der Precompiler merkt sich nur die CursorDeklaration für später.) open cursorname ; führt das mit dem Cursor verbundene Select-Kommando tatsächlich durch und erzeugt die Ergebnistabelle (active set) im Hintergrund. Der Cursor steht jetzt vor dem ersten Tupel. Ab jetzt können Sperren auf die Datenbank bestehen. Bei PostgreSQL darf ein Cursor nur innerhalb einer Transaktion geöffnet werden, d. h. nicht im Autocommit-Modus. Andernfalls erscheint die Meldung, dass das Deklarieren (nicht das Öffnen) eines Cursors nur innerhalb einer Transaktion erlaubt sei. (Hinweis: Bei PostgreSQL wird erst jetzt der Cursor tatsächlich deklariert und gleichzeitig geöffnet.) fetch cursorname into hostvariablen ; bewegt den Cursor auf das nächste Tupel der Ergebnistabelle und überträgt die Werte des neuen Tupels in die Hostvariablen. delete from tablename where current of cursorname ; löscht das Tupel, auf das der Cursor zeigt. Der Cursor steht anschließend vor dem nächsten Tupel (nicht auf dem nächsten). Diese Kommandovariante funktioniert bei PostgreSQL erst ab Version 8.3. update tablename set columnname = expression where current of cursorname ; verändert in dem Tupel, auf das der Cursor zeigt, das genannte Attribut entsprechend dem im Ausdruck angegebenen Wert. Es können mehrere Anweisungen columnname = expression verwendet werden, die durch Komma getrennt werden. expression kann ein SQL-Ausdruck, eine Hostvariable (ggf. mit Nullwertindikator), ein Literal oder NULL sein. Diese Kommandovariante funktioniert bei PostgreSQL erst ab Version 8.3. 9 6.2 Cursor-Select 6 MEHRERE ERGEBNISTUPEL close cursorname ; deaktiviert die Ergebnistabelle (active set). Nach close sind fetch, delete where current und update where current nicht mehr möglich. Durch commit work und rollback work werden alle Cursor ebenfalls geschlossen. Tipp: Als Alternative zu update/delete where current empfiehlt es sich bei PostgreSQL bis Version 8.2 einschließlich, den Primärschlüssel beim Select mit abzufragen und ein update/delete mit Bezug auf diesen auszuführen. Da where current ohnehin nur bei sehr einfachen Abfragen (nicht bei Verwendung von Aggregatfunktionen und auch nicht bei Verbundoperationen) funktioniert, ist dies auch bei anderen Datenbanken die gängige Vorgehensweise, z. B. auch empfohlen von Oracle. Um die Strafentabelle mit sequentieller Tabellenverarbeitung anzuzeigen, programmiert man beispielsweise so (ohne die notwendige Fehlerprüfung): exec sql declare kursor cursor for select zahlnr, strafen.spielnr, spielname, datum, betrag from strafen natural join spieler; exec sql open kursor; while (1) { exec sql fetch kursor into :zahlnr, :spielnr, :spielname, :datum, :betrag; if (sqlca.sqlcode != 0) break; printf ("%4d %4d %-30s %10s %10.2f\n", zahlnr, spielnr, spielname, datum, betrag); } exec sql close kursor; Es finden bei der sequentiellen Tabellenverarbeitung dieselben Fehlercodes Anwendung wie beim Single Select. Es wird aus der Ergebnistabelle gelesen, bis kein Tupel mehr übrig ist – dann wird sqlca.sqlcode den Wert 100 erhalten. Das vollständige Programm (mit der notwendigen Fehlerprüfung) finden Sie in der Datei cursor_strafen.pgc Aufgabe: Bauen Sie in die Patientenaufnahme ein, dass bei der Eingabe eines belegten Zimmers eine Liste aller anderen Zimmer derselben Station angezeigt wird, in denen noch Betten frei sind. Sind auf der Station keine Zimmer mehr frei, soll auch dies im Klartext gemeldet werden. 10 8 WEITERE AUFGABEN 7 Implizite Fehlerbehandlung Bislang haben wir alle Fehler, die in den Programmen auftreten konnten, explizit behandelt, d. h. die Rückmeldungen von SQL wurden von unseren Programmcode ausgewertet. SQL bietet aber noch eine andere Art der Fehlerbehandlung an, bei der man weniger schreiben muss, dafür aber auch weniger Möglichkeiten hat. Am Anfang des Programms wird eine whenever-Anweisung verwendet: exec sql whenever condition action ; Die Parameter dieser Anweisung sind: Anweisungsteil Schlüsselwort Bedeutung condition sqlerror sqlcode < 0 sqlwarning sqlwarn [0] = ’W’ not found sqlcode = 100 action stop Programmende mit rollback work continue keine Aktion, evtl. Fehler ignorieren goto label Sprung zur Marke (igitt!!) do break führt break aus (nur PostgreSQL) sqlprint zeigt Meldungen an (nur PostgreSQL) Solange keine whenever-Anweisung im Programm verwendet wurde, gilt die Aktions-Voreinstellung continue. Das Kommando whenever condition continue wird nur dazu gebraucht, ein früheres whenever mit einer anderen Aktion wieder auf diesen voreingestellten Wert zurückzusetzen. Die mit whenever festgelegten Aktion gelten solange, bis ein erneutes whenever verwendet wird. Die whenever-Anweisungen werden vollständig vom Precompiler ausgewertet und bewirken die Erzeugung von entsprechenden C-Anweisungen. Eine stop-Aktion hält das Programm an, zeigt aber leider keinerlei Meldung über den Grund an. Eine continue-Aktion kann man nur dann einstellen, wenn man die Fehlerbehandlung selbst übernimmt. Ignorierte Fehler führen zu nicht absehbaren Reaktionen des Programms. Das Springen zu einer Marke sollte nur in Notfällen verwendet werden. Eine do break-Aktion gibt es nur bei PostgreSQL; außerdem ist hierbei zu berücksichtigen, dass ein break nur in einer Schleife oder einem switch-Statement auftauchen darf. Fügt der Precompiler es an anderer Stelle ein, so erzeugt das einen Fehler beim C-Compiler. Eine sqlprint-Aktion ist ebenfalls nur bei PostgreSQL zu verwenden und zeigt Fehler lediglich an, ohne sie wirklich zu behandeln. Das kann zu Fehlersuche verwendet werden, wenn man ansonsten alle Fehler selbst behandelt. 8 weitere Aufgaben 1. Auch wenn Sie es außerhalb dieser Übung niemals verwenden: Schlagen Sie in einem C-Buch nach, wie man mit goto umgeht und bauen Sie es in ein Programm mit whenever ein. 11 8 WEITERE AUFGABEN 2. Verwenden Sie whenever in einem Ihrer Programme und schauen Sie nach, was der SQL-Precompiler aus Ihren SQL-Statements macht. Was verändert sich gegenüber der Voreinstellung? Vergleichen Sie mit SQL-Precompiler-Outputs ohne whenever. Tip hierzu: Vergleichen Sie die zwei C-Quellcodes mit tkdiff (geht nur unter X). 3. Schauen Sie sich das Beispielprogramm whenever.pgc zur Verwendung von whenever an. Bauen Sie in das Programm folgende Fehler ein und schauen Sie, wie das Programm darauf reagiert: a) connect-Problem: Geben Sie eine Datenbank an, die nicht existiert. b) select-Problem: Geben Sie eine Tabelle oder ein Attribut an, das nicht existiert. c) select-Problem: Verwenden Sie bei einem Attribut einen falschen Datentyp. (Dieser Fehler wird nicht in jedem Fall gefunden, weil das Datenbanksystem oder der vom embedded SQL eingefügte Code hier nach Möglichkeit Typenkonversionen einbaut.) $Id: SQL-Programmierung-C-Postgres.tex,v 1.2 2008-06-26 10:57:13 hj Exp $ 12