Dalitz Datenbanken WS 2012/13 Praktikumsaufgabe 2 Lernziele Wiederholung und weiteres Verständnis der in “Programmierung” behandelten C-Programmierung. Erlernen des Konzepts des Datenbankzugriffs über ein natives Call Level Interfaces. Kennenlernen des Transaktionsbegriffs. Vorbereitung Folgende Informationen müssen Sie recherchieren und als schriftliche Notiz mitbringen: • Wie funktioniert Compilieren und Linken mit dem gcc? Wie werden Bibliotheken verwendet? ([1] Kap. 12) • Wie kann die Projektverwaltung mit make und einem Makefile automatisiert werden? ([1] Kap. 12) Erläutern Sie das bereitgestellte Makefile. • Wie kann man in einem C-Programm die Kommandozeilenargumente auslesen? Erläutern Sie den Code für das Auslesen der Argumente im bereitgestellten Programm dbimp.cpp. ([2] Kap. 11.5) • Die Verarbeitung von Strings und Arrays wird durch die Verwendung der C++-Datentypen string und vector erheblich vereinfacht. Lesen Sie dazu die Hinweise am Ende des Dokuments und notieren Sie die Antworten auf folgende Fragen: Wie kopiert man einen C-String (char*) in einen C++ string? Wie kann man eine C Stringfunktion (z.B. sprintf) mit einem C++ string als Argument auf? Wie iteriert man über einen vector<string>? • Wie kann man eine Datei in der Programmiersprache C oder C++ zeilenweise einlesen? (Hinweis: fopen(), fgets(), fclose()) • Listen Sie auf, welche Verarbeitungsschritte im Programm in welcher Reihenfolge erfolgen sollen. An welcher Stelle wird eine Schleife benötigt? Welche Verarbeitungsschritte laufen innerhalb dieser Schleife ab? Aufgabe Es soll ein Programm “dbimp” geschrieben werden, das Daten aus einer Datei in eine Datenbanktabelle student einspielt. Der Datenbankzugriff soll über die Bibliothek libpq erfolgen. Kommandozeilenoptionen Die Programme sollen folgendermaßen aufgerufen werden: Usage: dbimp [options] <infile> Options: -del delete table contents before import 1 Dalitz Datenbanken WS 2012/13 Die Reihenfolge der Optionen ist egal. Bei fehlerhaftem Aufruf (kein infile oder unbekannte, mit ’-’ beginnende Option) wird die obige Meldung ausgegeben und abgebrochen. Zieltabelle und Dateiformat Die Zieltabelle student müssen Sie von Hand per SQL anlegen mit folgenden Feldern: Feld Typ mtnr# char(6) vorname varchar(30) nachname varchar(30) geburt date Die Import-Datei enthält pro Zeile einen Datensatz, wobei die einzelnen Felder durch ; getrennt sind. Die Felder stehen in der Reihenfolge vorname, nachname, geburt (YYYY/MM/DD), mtnr. Funktionalität dbimp soll sich wie folgt verhalten: • Der ganze Import erfolgt in einer Transaktion: bei Erfolg commit und bei einem Fehler Abbruch und rollback. • Vor dem insert wird anhand des Schlüsselfelds überprüft, ob der Datensatz schon in der Datenbank vorhanden ist. Wenn ja, wird der Satz nicht importiert. • Wenn über die Option -del gewünscht, wird vor dem Import der Tabelleninhalt gelöscht (innerhalb der Transaktion). Am Ende gibt das Programm eine Importstatistik aus mit der Gesamtzahl der gelesenen Datensätze und der Anzahl der davon importierten Sätze. Test Sie können Ihr Programm überprüfen anhand der Testdaten [5]. Hier die Sollergebnisse beginnend mit einer leeren Tabelle student: Kommando dbimp data1 dbimp data2 dbimp -del data2 dbimp data3 Datensätze/ davon importiert 3/3 1/3 3/3 Abbruch wegen Fehler in Zeile 2 von data3 Anzahl Tabellensätze nach dem Import 3 4 3 3 Anleitung Schritt für Schritt Zusammen mit den Testdaten [5] wird bereits ein Rumpfprogramm zur Lösung dieser Aufgabe bereit gestellt. Gehen Sie von diesem Programm aus und erweitern Sie es in den folgenden Schritten: 1) Kompilieren. Kompilieren Sie das bereitgestellte Programm mit dem Befehl make. Dazu müssen Sie in das Verzeichnis des Quellcodes und des Makefiles wechseln. Lassen Sie das Programm laufen mit ./dbimp. 2) Datei lesen. Ergänzen Sie Code nach dem Datenbank-Login zum Lesen der Datei. Geben Sie in einer Schleife jede gelesene Zeile nach stdout aus (Hinweis: printf()). Wenn die Datei nicht geöffnet werden kann, muss mit einer Fehlermeldung abgebrochen werden. 3) Zeilen zerlegen. Zerlegen Sie innerhalb Ihrer Schleife die Zeile in die Felder. Dazu können Sie die bereitgestellte Funktion split() verwenden: 2 Dalitz Datenbanken WS 2012/13 vector<string> fields; split(line, &fields, ’;’); Geben Sie die separierten Felder nach stdout aus. Über den Ergebnisvektor fields können Sie iterieren wie in den Hinweisen am Ende des Dokument angegeben. 4) Matrikelnr vorhanden? Ergänzen Sie in der Schleife eine SQL-Abfrage, die feststellt ob die Matrikelnr schon vorhanden ist. (Hinweis: die Anzahl Ergebnistupel liefert PQntuples() [4].) 5) Einfügen Datensatz. Ergänzen Sie eine if-Abfrage und Code, der den Datensatz einfügt. 6) Transaktion. Ergänzen Sie die Transaktionsstatements begin, commit und rollback. 7) Optionales Delete. Ergänzen Sie innerhalb der Transaktion den delete Befehl für die ganze Tabelle, wenn die Kommandozeilenoption -del gesetzt ist. 8) Importstatistik. Ergänzen Sie Zähler für die Anzahl gelesener und importierter Zeilen und geben Sie eine Importstatistik aus. Hinweise zu string und vector In der Programmiersprache C ist das Arbeiten mit Zeichenketten ziemlich gruselig. Um z.B. eine Zeichenkette s1 zu kopieren, ist in C der folgende Code erforderlich: char* s2; s2 = malloc(sizeof(char)*(strlen(s1)+1)); strcpy(s2, s1); /* Achtung: wenn nicht später free(s2), dann Speicherleck! */ Und man kann an einen String nicht einfach etwas anhängen (das erfordert in C ein realloc). Das geht mit dem C++-Datentyp string viel einfacher: string s1, s2; s1 = "bla"; /*kopiert char* in string s1*/ s2 = s1; /*kein malloc erforderlich*/ s2 += "huhu"; /*kein relloc erforderlich*/ Ein string kann man in einen char* umwandeln mit c str(), z.B. string s1 = "bla"; printf(s1.c_str()); printf("String: %s\n", s1.c_str()); Auch das Arbeiten mit Arrays ist keine Freude in C, weil diese nicht einfach bei Bedarf vergrößert werden, und außerdem nicht einmal die Information enthalten, wie groß sie überhaupt sind. Dafür gibt es in C++ den Datentyp vector, mit dem man Arrays von beliebigen Datentypen anlegen und jederzeit vergrößern und verkleinern kann: vector<string> felder; felder.push_back("bla"); felder.push_back("oho"); int n = felder.size(); felder.clear(); /*füge einen Eintrag hinzu*/ /*füge noch einen Eintrag hinzu*/ /*gibt die Anzahl Werte zurück*/ /*lösche alle Einträge*/ 3 Dalitz Datenbanken WS 2012/13 Auf die einzelnen Komponenten kann man mit dem Indexoperator (eckige Klammern) zugreifen, z.B. /* Schleife über alle Werte */ for (int i=0; i<felder.size(); i++) { printf(felder[i]); } Wie der Datentyp string, gibt auch der vector seinen Speicher frei, wenn er seine Gültigkeit (“Scope”) verliert. Sehr praktisch! Referenzen [1] Welsh, Kaufmann: Linux - Wegweiser zur Installation&Anwendung. Semesterapparat (TWR Wels) [2] Karlheinz Zeiner: Programmieren Lernen mit C. (oder ein anderes, im Fach “Programmierung” verwendetes Buch) [3] Hartwig: PostgreSQL Professionell und Praxisnah. Semesterapparat (TWY Hart) [4] The PostgreSQL Global Development Group: PostgreSQL 8.4.2 Dokumentation. http://www.postgresql.org/docs/ (2009) Kapitel “Client Interfaces, libpq” [5] Die Testdaten aufg2data.tgz unter http://lionel.kr.hsnr.de/ dalitz/data/lehre/DBSeHealth/ können Sie mit dem Befehl tar xzf ... entpacken. Dieses Paket enthälten neben den Testdaten auch ein Rumpfprogramm und ein Makefile. 4