Praktikumsaufgabe 2

Werbung
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
Herunterladen