Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel 12 MySQL und PostgreSQL
  gp 12.1 Relationales Datenbanksystem
  gp 12.2 Relationaler Datenbankserver
  gp 12.3 SQL-Server im Überblick
  gp 12.4 MySQL
    gp 12.4.1 Anwendungsgebiete von MySQL
    gp 12.4.2 Schnittstellen von MySQL
    gp 12.4.3 Installation von MySQL
    gp 12.4.4 MySQL-Server starten und stoppen
    gp 12.4.5 Konfigurationsdatei my.cnf
    gp 12.4.6 Kommandozeilenwerkzeuge für und von mysql
    gp 12.4.7 Grafische Clients
    gp 12.4.8 MySQL-Crashkurs
    gp 12.4.9 Datentypen
    gp 12.4.10 Datenbank anlegen, verwenden und löschen
    gp 12.4.11 Tabelle anlegen
    gp 12.4.12 Schlüsselfelder (Tabellen anlegen)
    gp 12.4.13 Indizes
    gp 12.4.14 Tabellentypen (Tabellen anlegen)
    gp 12.4.15 Autowerte definieren
    gp 12.4.16 Tabellen umbenennen und ändern
    gp 12.4.17 Daten einfügen, ändern und löschen
    gp 12.4.18 Daten importieren
    gp 12.4.19 Datenausgabe
    gp 12.4.20 NULL ist 0 oder undefiniert?
    gp 12.4.21 Unscharfe Suche
  gp 12.5 MySQL C-API
    gp 12.5.1 Verbindung mit dem MySQL-Server aufbauen
    gp 12.5.2 Aufgetretene Fehler ermitteln – mysql_errno() und mysql_error()
    gp 12.5.3 Schließt die Verbindung zum Server – mysql_close()
    gp 12.5.4 Erstes Beispiel
    gp 12.5.5 Verschiedene Informationen ermitteln
    gp 12.5.6 Datenbanken, Tabellen und Felder ausgeben (MYSQL_RES)
    gp 12.5.7 Ergebnismenge zeilenweise bearbeiten (MYSQL_ROW)
    gp 12.5.8 Ergebnismenge spaltenweise einlesen (und ausgeben) (MYSQL_FIELD)
    gp 12.5.9 Ein Beispiel
    gp 12.5.10 Ergebnismenge – weitere Funktionen
    gp 12.5.11 Befehle an den Server – mysql_query() und mysql_real_query()
    gp 12.5.12 Weitere Funktionen
    gp 12.5.13 Veraltete Funktionen
    gp 12.5.14 Neue Funktionen ab Version 4.1.x
  gp 12.6 Beispiel eines Newssystems mit MySQL
    gp 12.6.1 Die Headerdatei my_cgi.h
    gp 12.6.2 (Pseudo-)Planung
    gp 12.6.3 Datenbank und Tabellen anlegen
    gp 12.6.4 MySQL-Clients mit GUI
    gp 12.6.5 Randnotiz
  gp 12.7 Neue SQL-Funktionen für die Shell – MySQL erweitern
  gp 12.8 MySQL-Funktionen mit der UDF-Schnittstelle entwerfen
    gp 12.8.1 UDF-Sequenzen
    gp 12.8.2 UDF_INIT-Struktur
    gp 12.8.3 UDF_ARGS-Struktur
    gp 12.8.4 Rückgabewert
    gp 12.8.5 Benutzerdefinierte Funktionen erstellen
    gp 12.8.6 Benutzerdefinierte Funktion kompilieren, installieren und ausführen
  gp 12.9 PostgreSQL – objektrelationales Datenbankverwaltungssystem
    gp 12.9.1 PostgreSQL im Vergleich zu MySQL
    gp 12.9.2 Unterschiede in der Syntax zwischen MySQL und PostgreSQL
    gp 12.9.3 PostgreSQL installieren
    gp 12.9.4 Konfigurationsdateien bei PostgreSQL – (postgresql.conf, pg_hba_conf)
    gp 12.9.5 CRASHKURS PostgreSQL
    gp 12.9.6 PostgreSQL C-API – libpg
    gp 12.9.7 Umgebungsvariablen und Passwortdatei
    gp 12.9.8 PostgreSQL und Threads
    gp 12.9.9 Ausblick


Rheinwerk Computing

12.5 MySQL C-API  downtop

Mit der MySQL C-API (API = Application Interface) sind Sie in der Lage, eigene Clientanwendungen wie mysql (Shell) oder mysqladmin zu entwickeln, womit Sie Zugriff auf den MySQL-Server haben. Um eigene Clientanwendungen zu schreiben, benötigen Sie die Bibliothek zu MySQL (lib) und die entsprechenden Headerdateien (include). Die Bibliothek sollten Sie gewöhnlich im Verzeichnis /usr/lib, /usr/lib/mysql oder /usr/local/lib/mysql vorfinden. Die Headerdatei(en) befindet sich im Verzeichnis, wo sich auch die anderen Include-Dateien aufhalten sollten (/usr/include/mysql oder /usr/local/include/mysql). Sollte sich bei Ihnen in den eben genannten Verzeichnissen nichts dergleichen befinden, haben Sie wahrscheinlich eine RPM-Installation gemacht. In diesem Fall müssen Sie noch extra ein RPM namens Developer RPM nachinstallieren. Gewöhnlich sollte diese RPM Ihrer Distribution beiliegen. Ansonsten müssen Sie bei http://www.mysql.de vorbeischauen und entsprechende RPMs herunterladen und installieren.

Wenn Sie Ihren MySQL Client-Quellcode erstellt haben, müssen Sie dem Compiler sagen, wo sich die Include-Dateien befinden, und der Linker will ebenfalls wissen, welche Bibliotheken er von wo zum Linken verwenden soll. Die Information für den Compiler geben Sie mit dem Flag -I an. Ist der Programmname z. B. mein_Client.c, dann sieht der Compileraufruf wie folgt aus:

$ gcc -c -I/usr/include/mysql mein_Client.c

Nach erfolgreicher Kompilierung befindet sich jetzt im Verzeichnis eine Objektdatei (*.o). Um daraus jetzt eine ausführbare Datei zu machen, müssen Sie dem Linker sagen, wo er die Bibliothek für seine Arbeit findet und welche Bibliothek er dazu verwenden soll. Das Wo erledigen Sie mit dem Flag -L und der Pfadangabe, und welche Bibliothek dazu verwendet werden soll, geben Sie hinter dem Flag -l an. Die komplette Eingabe lautet hierzu also:

$ gcc -o mein_Client mein_Client.o -L/usr/lib/mysql -lmysqlclient

Wenn beim Compilerlauf und beim anschließenden Linken keine Probleme aufgetreten sind, können Sie die Clientanwendung mit dem Namen starten:

$ ./my_Client

Damit Sie aber nicht dauernd so viel tippen müssen, können Sie auch die Datei .bash_profile anpassen, oder besser noch, Sie verwenden ein Makefile.

Bei manchen Systemen kann es zu Problemen mit dem Linkerlauf kommen. Meistens will der Linker dabei nur mehr Input zu weiteren Bibliotheken. Geben Sie dem Linker, was er will, und Sie können in Ruhe weiterarbeiten. Hier einige Libraries, die Sie eventuell hinzulinken müssen:


Tabelle 12.5    Eventuell weitere benötigte Linkerflags

Flag Bedeutung
-lm Die C-API verwendet einige Funktionen aus der Headerdatei <math.h>, und einige Linux-Systeme wollen dies immer noch extra angegeben haben.
(-lm) –lsocket -lnsl Diese Flags wollen die Solaris-Rechner haben.
-lz (zlib) Diese Bibliothek benötigen Sie bei einem Undefined-Reference-Fehler bei den Funktionen compress und uncompress. Dieser Fehler hat den Autor schon an den Rand eines Nervenzusammenbruchs gebracht.


Hinweis[1]   Auf Linux braucht man keine der o. g. Extra-Bibliotheken anzugeben, da weitere von MySQL benötigte Bibliotheken bereits in der libmysqlclient.so angegeben sind und daher automatisch eingebunden werden.

Hinweis[2]   Sollten Sie dennoch Probleme mit den Pfaden zum Include-Verzeichnis oder den Bibliotheken haben, können Sie hierzu auch das Shellscript mysql_config mit dem Flag --cflags (für die Include-Angaben) und --libs (für die Bibliotheksangaben) verwenden.

Hinweis am Rande   Das Verwenden einer neuen API (unabhängig davon, welche das jetzt ist) lässt den Code anfangs immer etwas komplexer und eben fremd aussehen. Sie sollten sich davon aber nicht abschrecken lassen. Gewöhnlich steckt in jeder API ein gut durchdachtes Design, das schnell zu durchschauen ist. Gewöhnlich funktionieren APIs immer nach demselben Prinzip. Man initialisiert etwas, indem man z. B. Speicher reserviert. Dann stellt man eine Verbindung/Beziehung her, und anschließend kann mit dieser Verbindung/Beziehung gearbeitet werden. Am Ende gibt man alles wieder frei. Wenn Sie ein wenig zurückdenken, wird Ihnen auffallen, dass das Prinzip immer dasselbe ist.



Rheinwerk Computing

12.5.1 Verbindung mit dem MySQL-Server aufbauedowntop

Im ersten Beispiel werden Sie sehen, wie Sie eine Verbindung zum MySQL-Server aufbauen, die Verbindung auf Fehler überprüfen und die Verbindung wieder ordentlich schließen können.

MySQL-Objekt alloziieren oder initialisieren – mysql_init()

Bevor Sie eine Verbindung zum Server aufbauen, müssen Sie erst für ein MySQL-Objekt Speicher alloziieren oder es initialisieren. Diese Aufgabe erledigt die Funktion mysql_init():

#include <mysql.h>
MYSQL *mysql_init(MYSQL *mysql) 

Geben Sie dieser Funktion als Parameter NULL, wird zuerst Speicherplatz für das MySQL-Objekt reserviert und dieses anschließend gleich initialisiert. Der Rückgabewert ist dabei die Adresse eines neuen MySQL-Objekts. Bei diesem MySQL-Objekt handelt es sich intern natürlich nur um eine Struktur, die sich hinter einem typedef versteckt. Dieses MySQL-Objekt, Sie können es auch gerne Handle oder MySQL-Stream nennen, wird für fast alle Funktionen der MySQL C-API benötigt. Geben Sie hingegen für den Parameter ein MySQL-Objekt an, wird das Objekt initialisiert und die Adresse des Objekts zurückgegeben. mysql_init() ist somit das malloc() für MySQL, wenn Sie so wollen. Reicht der Speicher für ein neues Objekt nicht aus, wird NULL zurückgegeben.

Verbindung zum MySQL-Server aufbauen – mysql_real_connect()

Nach erfolgreicher Ausführung der Funktion mysql_init() wird versucht, mit der Funktion mysql_real_connect() eine Verbindung zum MySQL-Server herzustellen. Natürlich ist auch klar, dass ohne das Zustandekommen einer Verbindung keine weiteren API-Funktionen verwendet werden können (abgesehen von mysql_get_client_info()). Die Syntax zu dieser Funktion:

#include <mysql.h>
MYSQL *mysql_real_connect( MYSQL *mysql, const char *host,
                           const char *user, const char *passwd,
                           const char *db, unsigned int port,
                           const char *unix_socket,
                           unsigned int client_flag   );

Auf den ersten Blick wirkt diese Funktion mit den vielen Parametern recht erschlagend. Aber bei genauerem Hinsehen kann man ein wenig die Parallelen beim Einloggen in der MySQL-Shell (mysql) erkennen. Der erste Parameter ist die Adresse der MySQL-Struktur, die Sie zuvor mit der Funktion mysql_init() alloziiert und initialisiert haben.

Mit dem zweiten Parameter können Sie den Hostnamen oder eine IP-Adresse zum MySQL-Server angeben. Der Vorteil ist, dass Sie sich selbst hier nicht die Mühe machen müssen, Sockets oder Named Pipes zu erstellen, um sich mit dem Server zu verbinden (zumindest ist dies unter Linux und BSD so). Bei einer Angabe von NULL oder localhost wird versucht, sich mit einem UNIX-Socket zu verbinden.

Mit dem dritten Parameter user geben Sie den User-Namen an, den Sie zur MySQL-Login-Benutzererkennung verwenden. Bei der Angabe von NULL wird versucht, den aktuellen Benutzer einzuloggen – was unter Linux der aktuelle Login-Name wäre.

Mit passwd geben Sie das entsprechende Passwort für user an. Bei einer Angabe von NULL gilt hier, dass in der user-Tabelle auf Übereinstimmung mit den Benutzern überprüft wird, die ein leeres Passwortfeld haben (was aus Sicherheitsgründen allerdings nicht zu empfehlen ist).

Mit db geben Sie den Namen der Datenbank an, mit der Sie gleich arbeiten wollen. Bei einer Angabe von NULL wird eine den Vorgaben entsprechende Datenbank für die Verwendung gesetzt. Natürlich können Sie auch erst nach der Verbindung eine bestimmte Datenbank zum Arbeiten auswählen.

Wenn Sie für port nicht 0 angeben, hängt dieser Parameter von der Portnummer der TCP/IP-Verbindung des host-Parameters ab.

Mit dem Parameter unix_socket können Sie einen String angeben, der ein Socket oder eine Namend Pipe festlegt, womit eine Verbindung hergestellt werden soll. Hierbei ist der Verbindungstyp allerdings ebenfalls von dem Parameter host abhängig. Alternativ kann auch hier NULL angegeben werden.

Für den letzten Parameter wird üblicherweise 0 angegeben. Allerdings lassen sich auch spezielle Optionen einzeln oder auch miteinander kombiniert setzen. Folgende Flags und deren Bedeutung können Sie dabei setzen:

gp  CLIENT_COMPRESS – Komprimiertes Protokoll benutzen
gp  CLIENT_FOUND_ROWS – Die Anzahl übereinstimmender Zeilen zurückgeben
gp  CLIENT_IGNORE_SPACE – Leerzeichen nach Funktionsnamen zulassen. Macht alle Funktionsnamen zu reservierten Wörtern.
gp  CLIENT_INTERACTIVE – Bei interactive_timeout Sekunden der Inaktivität wird die Verbindung beendet – standardmäßig ist das wait_timeout Sekunden.
gp  CLIENT_NO_SCHEMA – Das Flag ist bei der Fehlersuche einiger Anwendungen hilfreich, die ODBC verwenden. Damit wird die Syntax datenbank.tabelle.spalte verboten.
gp  CLIENT_ODBC – Der Client ist ein ODBC-Client.
gp  CLIENT_SSL – SSL benutzen (verschlüsseltes Protokoll)

Wenn mysql_real_connect() ordnungsgemäß eine Verbindung aufbauen konnte, wird derselbe Zeiger auf die Adresse der MySQL-Struktur zurückgegeben, den Sie als ersten Parameter in der Funktion angegeben haben. Bei einem Fehler wird NULL zurückgegeben.

Folgende Fehler, die Sie mit der Funktion mysql_errno() ermitteln könnten (dazu gleich mehr), können bei der Funktion mysql_real_connect() auftreten:

gp  CR_CONN_HOST_ERROR – Verbindung zum MySQL-Server fehlgeschlagen
gp  CR_CONNECTION_ERROR – Verbindung zum lokalen MySQL-Server fehlgeschlagen
gp  CR_IPSOCK_ERROR – IP-Socket konnte nicht erzeugt werden.
gp  CR_OUT_OF_MEMORY – Nicht mehr genügend Speicherplatz vorhanden
gp  CR_SOCKET_CREATE_ERROR – UNIX-Socket konnte nicht erzeugt werden.
gp  CR_UNKNOWN_HOST – IP-Adresse für den Hostnamen konnte nicht gefunden werden.
gp  CR_VERSION_ERROR – Client und Server verwenden eine unterschiedliche Protokollversion (z. B. Client-Bibliothek ist veraltet).
gp  CR_SERVER_LOST – Verbindung zum Server dauerte länger als connect_timeout Sekunden, oder der Server ist während der Ausführung von init-command gestorben.

Hinweis   Sofern Sie in der Dokumentation von MySQL auf die Funktion mysql_connect() gestoßen sind: Diese ist mittlerweile veraltet und wird nur aus Kompatibilitätsgründen zu älteren Programmen erhalten, die diese Funktion verwenden.



Rheinwerk Computing

12.5.2 Aufgetretene Fehler ermitteln – mysql_errno() und mysql_error()  downtop

Alleine bei der Funktion mysql_real_connect() kann eine Menge Fehler auftreten, wie Sie soeben lesen konnten. Da sich in den Headerdateien <errmsg.h> (Fehlermeldungen vom Client) und <mysqld_error.h> (Fehlermeldungen vom Server), die sich im selben Verzeichnis befindet wie <mysql.h>, mehrere hundert Fehlercodes befinden, könnte ein Suchen danach ohne die Funktionen mysql_errno() und mysql_error() recht schwierig werden. Mit der Funktion

#include <mysql.h>
unsigned int mysql_errno(MYSQL *mysql) ;

können Sie sich, falls ein Fehler auftrat, entsprechenden Fehlercode der zuletzt aufgerufenen API-Funktion zurückgeben lassen. Wird dabei 0 zurückgegeben, ist kein Fehler aufgetreten, ansonsten wird eben entsprechender Fehlercode vom Client oder vom Server (je nach Auftritt des Fehlers) zurückgegeben.

Die Fehlernummern sind einem häufig nicht hilfreich und davon abhängig, ob der Anwender weiß, dass er im Dokumentenverzeichnis der MySQL-Distribution nach der Datei mysqld_error.txt sehen muss. Zum Glück gibt es auch für die MySQL C-API eine perror()-Funktion, die den Fehler im Klartext (zumindest so klar wie möglich) ausgibt:

#include <mysql.h>
const char *mysql_error(MYSQL *mysql) ;

Hierbei wird bei einer Verbindung eine Fehlermeldung (ein String) der zuletzt fehlgeschlagenen API-Funktion zurückgegeben. Wenn keine Fehlermeldung zurückgegeben wird, dann wird ein leerer String (index[0] = '\0') zurückgegeben. Wollen Sie anstatt der Standardsprache Englisch eine andere ausgeben lassen, müssen Sie entweder die MySQL-Client-Bibliothek nochmals neu kompilieren oder den Server mit mysqld und der Option –language=german starten. Was Sie dabei beachten müssen, entnehmen Sie bitte der Dokumentation.


Rheinwerk Computing

12.5.3 Schließt die Verbindung zum Server – mysql_close(downtop

Wenn Sie die mit mysql_real_connect() eingerichtete Verbindung wieder schließen wollen, dann muss die Funktion mysql_close() verwendet werden:

#include <mysql.h>
void mysql_close(MYSQL *mysql) ;

Damit schließen Sie die Verbindung mit dem MySQL-Handle und geben auch den Speicher dazu wieder frei, der zuvor mit mysql_init() alloziiert oder initialisiert wurde.


Rheinwerk Computing

12.5.4 Erstes Beispiedowntop

Nachdem Sie die grundlegenden Funktionen nun kennen, können Sie den ersten Schritt machen, um sich mit Ihrer (Client-)Anwendung dem MySQL-Server zu nähern. Das Beispiel macht keine großen Aktionen, es versucht lediglich, eine Verbindung mit dem Server einzugehen, wertet ggf. Fehler aus und schließt anschließend diese Verbindung gleich wieder. Bitte passen Sie die Verbindungsdaten für mysql_real_connect() an Ihrem Account an.

/* mysql1.c */
#include <stdio.h>
#include <stdlib.h>
#include <mysql.h>
/* Bitte Daten anpassen */
#define HOST     "localhost"
#define USER     "root"
#define PASSWORT "k4p6m3o3"
#define DBNAME   NULL
#define PORT     0
#define SOCKET   NULL
#define FLAG     0
int main (int argc, char **argv) {
  MYSQL *my;
  /* Handle initialisieren */
  my = mysql_init (NULL);
  if (my == NULL) {
    fprintf (stderr, " Initialisierung fehlgeschlagen\n");
    exit (EXIT_FAILURE);
  }
  /* Mit dem Server verbinden */
  if (mysql_real_connect (my,     /* Zeiger auf MySQL-Handler */
                          HOST,     /* Hostname */
                          USER,     /* User-Name */
                          PASSWORT, /* Passwort für user_name */
                          DBNAME,   /* Name der Datenbank */
                          PORT,     /* Port (default=0) */
                          SOCKET,   /* Socket (default=NULL) */
                          FLAG      /* keine Flags */
                         ) == NULL) {
    fprintf (stderr, "Fehler mysql_real_connect():"
             "%u (%s)\n", mysql_errno (my), mysql_error (my));
  } 
  else
      printf ("Erfolgreich mit dem MySQL-Server verbunden\n");
  /* Hier befindet sich der Code für die Arbeit mit MySQL */
  /* Verbindung trennen */
  mysql_close (my);
  return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

$ gcc -c -I/usr/include/mysql mysql1.c
$ gcc -o mysql1 mysql1.o -L/usr/lib/mysql -lmysqlclient
$ ./mysql1
Fehler mysql_real_connect():2002 (Can't connect to local MySQL server through 
socket '/var/lib/mysql/mysql.sock' (2))
(Hoppala)
$ su
Password: ********
# rcmysql start
Starting service MySQL                                                       done
# exit
exit
$ ./mysql1
Erfolgreich mit dem MySQL-Server verbunden

Sofern Sie nicht die passenden Angaben für die Funktion mysql_real_connect() gemacht haben, bekommen Sie die entsprechende Fehlermeldung ausgegeben, was schief gelaufen ist.

Besser ist es natürlich, die Abfrage erst beim Starten des Programms vorzunehmen. Dazu können Sie entweder die Kommandozeilenoptionen auswerten (man popt) oder eine entsprechende Abfrage bei der Anwendung machen. Wie eine solche Abfrage aussehen könnte, demonstriert Ihnen folgendes Beispiel:

/* mysql2.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <mysql.h>
#define MAX 255
/* Verbindungsdaten für mysql_real_connect */
static char *connect_param[7];
/* globaler Hilfszähler für die Verbindungsdaten */
static int connect_param_count = 0;
/* Eigene fgets-Funktion, die \n entfernt */
static void my_fgets( char *ptr, unsigned int max ) {
  char *help;
  fgets(ptr, max, stdin );
  if( ( help = strchr( ptr, '\n') ) != NULL )
    *help = '\0'; /* Newline durch \0 ersetzen */
}
/* Zusammenstellen der Verbindungsdaten in connect_param */
static void my_cat( char *ptr ) {
  connect_param[connect_param_count] = malloc( strlen (ptr) + 1);
  if (connect_param[connect_param_count] == NULL) {
    printf("Konnte keinen Speicher reservieren ...\n");
    exit (EXIT_FAILURE);
  }
  strcpy( connect_param[connect_param_count], ptr );
  connect_param_count++;
}
/* Einlesen der Verbindungsdaten - Ein Drücken von <ENTER> */
/* bezweckt, dass die Standardwerte verwendet werden -     */
/* Garantiert allerdings keinen Verbindungsaufbau.         */
static void get_connect_param( void ) {
  char puffer[MAX];
  printf("Host-Name (<ENTER> == default) : ");
  my_fgets( &puffer[0], MAX );
  if( puffer[0] == '\0' )
    connect_param[connect_param_count++] = NULL;
  else
    my_cat( &puffer[0] );
  printf("User-Name (<ENTER> == default) : ");
  my_fgets( &puffer[0], MAX );
  if( puffer[0] == '\0' )
    connect_param[connect_param_count++] = NULL;
  else
    my_cat( &puffer[0] );
  printf("Passwort (<ENTER> == default)  : ");
  my_fgets( &puffer[0], MAX );
  if( puffer[0] == '\0' )
    connect_param[connect_param_count++] = NULL;
  else
    my_cat( &puffer[0] );
  printf("Datenbank (<ENTER> == default) : ");
  my_fgets( &puffer[0], MAX );
  if( puffer[0] == '\0' )
    connect_param[connect_param_count++] = NULL;
  else
    my_cat( &puffer[0] );
  printf("Port (<ENTER> == default)      : ");
  my_fgets( &puffer[0], MAX );
  if( puffer[0] == '\0' ) {
    puffer[0] = '0';
    my_cat( &puffer[0] );
  } 
  else
    my_cat( &puffer[0] );
  printf("Socket (<ENTER> == default)    : ");
  my_fgets( &puffer[0], MAX );
  if( puffer[0] == '\0' )
    connect_param[connect_param_count++] = NULL;
  else
    my_cat( &puffer[0] );
  printf("Flag (<ENTER> == default)      : ");
  my_fgets( &puffer[0], MAX );
  if( puffer[0] == '\0' ) {
    puffer[0] = '0';
    my_cat( &puffer[0] );
  } 
  else
    my_cat( &puffer[0] );
}
/* Mit dem MySQL-Server und den entsprechenden */
/* Verbindungsdaten eine Verbindung herstellen  */
static int mein_connect( MYSQL *my ) {
  /* Mit dem Server verbinden */
  if( mysql_real_connect (
        my,                     /* Zeiger auf MySQL-Handler*/
        connect_param[0],       /* Hostname*/
        connect_param[1],       /* User-Name*/
        connect_param[2],       /* Passwort für user_name */
        connect_param[3],       /* Name der Datenbank*/
        strtoul(connect_param[4], NULL, 0),/* Port(default=0) */
        connect_param[5],       /* Socket (default=NULL)*/
        strtoul(connect_param[6], NULL, 0)  /* keine Flags */
      )  == NULL) {
    fprintf (stderr, "Fehler mysql_real_connect():"
             "%u (%s)\n",mysql_errno (my), mysql_error (my));
    return -1;
  }
  return 1;
}
/* Speicher freigeben und Verbindung beenden */
static void clean_up_shutdown( MYSQL * my ) {
  free( connect_param );
  mysql_close (my);
}
int main (int argc, char **argv) {
  MYSQL  *my;
  /* Handle initialisieren */
  my = mysql_init(NULL);
  if(my == NULL) {
    fprintf(stderr, " Initialisierung fehlgeschlagen\n");
    exit (EXIT_FAILURE);
  }
  /* Verbindungsdaten für die Datenbank abfragen */
  get_connect_param( );
  /* Mit dem Server verbinden */
  if( mein_connect( my ) != -1 )
    printf("Erfolgreich mit dem MySQL-Server verbunden\n");
  /* Hier befindet sich der Code für die Arbeit mit MySQL */
  /* Verbindung trennen */
  clean_up_shutdown( my );
  return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

$  gcc -c -I/usr/include/mysql mysql2.c
$  gcc -o mysql2 mysql2.o -L/usr/lib/mysql -lmysqlclient
$ ./mysql2
Hostname (<ENTER> == default)  : localhost
User-Name (<ENTER> == default) : root
Passwort (<ENTER> == default)  : k4p6m3o3
Datenbank (<ENTER> == default) : news
Port (<ENTER> == default)      : ENTER
Socket (<ENTER> == default)    : ENTER
Flag (<ENTER> == default)      : ENTER
Erfolgreich mit dem MySQL-Server verbunden

Da ja C-Kenntnisse Voraussetzung für das Buch sind, denke ich, dass Sie den Code selbst nachvollziehen können. Bezüglich der MySQL C-API finden Sie hierbei nichts Neues, außer dass die Ausführung des Programms ein wenig modularisiert wurde.


Rheinwerk Computing

12.5.5 Verschiedene Informationen ermitteldowntop

Die MySQL C-API bietet auch Funktionen, mit denen Sie diverse Informationen abfragen können. Die Funktionen sind alle recht einfach aufgebaut, weshalb die Erklärung hierfür etwas kürzer ausfällt als gewöhnlich.

Benötigen Sie die Versionsnummer der Client-Bibliothek, dann ist die folgende Funktion Ihr Fall:

#include <mysql.h>
const char *mysql_get_client_info(void) ;

Diese Funktion gibt eine Adresse zurück, die auf eine Zeichenkette mit der Versionsnummer der Client-Bibliothek verweist.

Benötigen Sie Informationen zur benutzten Verbindung (TCP, UNIX-Socket ...), dann können Sie folgende Funktion verwenden:

#include <mysql.h>
const char *mysql_get_host_info(MYSQL *mysql) ;

Die Funktion liefert Ihnen neben dem Typ der benutzten Verbindung auch den Server-Hostnamen zurück. Als Parameter wird ein Zeiger auf die verbundene MySQL-Struktur übergeben.

Um die Protokollversion der aktuellen Verbindung zu ermitteln, wird folgende Funktion verwendet:

#include <mysql.h>
unsigned int mysql_get_proto_info(MYSQL *mysql) ;

Als Parameter wird ein Zeiger auf die verbundene MySQL-Struktur übergeben. Der Rückgabewert (eine vorzeichenlose Ganzzahl) gibt dabei die Protokollversion an.

Um die Versionsnummer des Servers zu ermitteln, steht Ihnen die folgende Funktion zur Verfügung:

#include <mysql.h>
const char *mysql_get_server_info(MYSQL *mysql) ;

Dabei wird eine Zeichenkette von der angegebenen Verbindung (MySQL-Struktur), welche die Serverversionsnummer angibt, zurückgegeben.

Informationen über zuletzt ausgeführte Anfragen erhalten Sie mit der Funktion:

#include <mysql.h>
const char *mysql_info(MYSQL *mysql) ;

Damit wird ein String abgerufen, der einem der folgenden Statements entspricht:

gp  INSERT INTO ... SELECT ... Zeichenkettenformat: Records: 100 Duplicates: 0 Warnings: 0 
gp  INSERT INTO ... VALUES (...),(...),(...)... Zeichenkettenformat: Records: 3 Duplicates: 0 Warnings: 0 
gp  LOAD DATA INFILE ... Zeichenkettenformat: Records: 1 Deleted: 0 Skipped: 0 Warnings: 0 
gp  ALTER TABLE Zeichenkettenformat: Records: 3 Duplicates: 0 Warnings: 0 
gp  UPDATE Zeichenkettenformat: Rows matched: 40 Changed: 40 Warnings: 0 

Bei anderen Statements wird NULL zurückgegeben. Das Format der Zeichenkette variiert außerdem vom Anfragetyp, wie eben gesehen (die Zahlen sind dabei nur Beispiele). Beachten Sie, dass mysql_info() einen Nicht-NULL-Wert für das INSERT ... VALUES-Statement nur dann zurückgibt, wenn im Statement mehrfache Wertlisten angegeben sind. Wenn keine Anfrageinformationen zur Verfügung stehen, gibt diese Funktion NULL zurück.

Die eben erwähnten Funktionen sollen anhand eines einfachen Beispiels demonstriert werden.

/* mysql3.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <mysql.h>
#define MAX 255
/* Verbindungsdaten für mysql_real_connect */
static char *connect_param[7];
/* globaler Hilfszähler für die Verbindungsdaten */
static int connect_param_count = 0;
/*
 *  Übernehmen Sie die my_fgets()-, my_cat()-, get_connect_param()-,
 *  mein_connect()-, cleanup_shutdown()-Funktionen von oben
 *  (mysql2.c) -> oder den kompletten Code auf der Buch-CD
 */
int main (int argc, char **argv) {
  MYSQL *my;
  int auswahl;
  /* Handle initialisieren */
  my = mysql_init (NULL);
  if (my == NULL) {
    fprintf (stderr, " Initialisierung fehlgeschlagen\n");
    exit (EXIT_FAILURE);
  }
  /* Verbindungsdaten für die Datenbank abfragen */
  get_connect_param ();
  /* Mit dem Server verbinden */
  if (mein_connect (my) != -1)
    printf ("Erfolgreich mit dem MySQL-Server verbunden\n");
  do {
    printf ("Informationen zum MySQL-Server auf"
            " Ihrem System\n");
    printf ("-----------------------------------"
            "------------\n\n");
    printf ("- 1 - Aktiven Zeichensatz ausgeben \n");
    printf ("- 2 - Versionsnummer des Clients\n");
    printf ("- 3 - Hostinformationen\n");
    printf ("- 4 - Protokollversion\n");
    printf ("- 5 - Versionsnummer des Servers\n");
    printf ("- 6 - Infos über die letzten MySQL-Abfragen\n");
    printf ("- 0 - Beenden\n\n");
    printf ("Ihre Auswahl : ");
    scanf ("%d", &auswahl);
    switch (auswahl) {
    case 1:
      printf ("\nZeichensatz auf Ihrem MySQL-System : %s\n\n",
         mysql_character_set_name (my));
      getchar ();
      break;
    case 2:
      printf ("\nVersionsnummer : %s\n\n",
         mysql_get_client_info ());
      getchar ();
      break;
    case 3:
      printf ("\nInfos zum Host : %s\n\n",
         mysql_get_host_info (my));
      getchar ();
      break;
    case 4:
      printf ("\nProtokollversion : %d\n\n",
         mysql_get_proto_info (my));
      getchar ();
      break;
    case 5:
      printf ("\nVersionsnummer : %s\n\n",
         mysql_get_server_info (my));
      getchar ();
      break;
    case 6:
      printf ("\nDie letzten Aktionen : %s\n\n",
         mysql_info (my));
      getchar ();
      break;
    case 0:
      printf ("Bye\n");
      break;
    default:
      printf ("Fehlerhafte Eingabe\n");
    }
  } while (auswahl != 0);
  /* Verbindung trennen */
  clean_up_shutdown (my);
  return EXIT_SUCCESS;
}

Rheinwerk Computing

12.5.6 Datenbanken, Tabellen und Felder ausgeben (MYSQL_RESdowntop

Mit den gleich folgenden API-Funktionen können Sie sich die vorhandenen Datenbanken, Tabellen oder Felder mit einer unscharfen Suche ausgeben lassen. Nebenbei werden noch Funktionen der API beschrieben, welche die aktuellen Prozesse auflistet, was sonst von den Aufrufen mysqladmin processlist oder SHOW PROCESSLIST möglich ist.

Alle gleich hier beschriebenen Funktionen geben einen Datentypen von MYSQL_RES zurück. Dabei handelt es sich um eine Struktur, die das Ergebnis einer Anfrage repräsentiert, die Zeilen zurückgibt (SELECT, SHOW, DESCRIBE, EXPLAIN). Bei der Rückgabe spricht man hierbei von einer Ergebnismenge (auch »Resultset« genannt).


Hinweis   Wenn Sie die Ergebnismenge in MYSQL_RES nicht mehr benötigen, müssen Sie diese wieder freigeben, um Memory Leaks zu vermeiden. Die Ergebnismenge wird immer mit der Funktion mysql_free_result() oder (natürlich) bei Beendigung der Anwendung freigegeben.


Datenbanknamen eines Servers – mysql_list_dbs()

Um Datenbanknamen abzufragen, wird die folgende Funktion verwendet:

#include <mysql.h>
MYSQL_RES *mysql_list_dbs(MYSQL *mysql, const char *wild) ;

Hiermit bekommen Sie eine Ergebnismenge zum MySQL-Handle mysql zurück, die aus den Datenbanknamen besteht, die mit dem regulären Ausdruck des wild-Parameters übereinstimmen. Dabei dürfen Sie für wild die Platzhalterzeichen '%' und '_' verwenden.

Wird hingegen der NULL-Zeiger für wild angegeben, werden alle vorhandenen Datenbanken ausgegeben. Wenn ein Fehler auftrat, wird NULL zurückgegeben. Sie müssen die Ergebnismenge mit mysql_free_result() freigeben.

Tabellennamen einer Datenbank – mysql_list_tables()

Weiter geht's mit:

#include <mysql.h>
MYSQL_RES *mysql_list_tables(MYSQL *mysql, const char *wild) ;

Mit dieser Funktion bekommen Sie als Ergebnismenge Tabellennamen der aktuellen Datenbank zurück, die mit dem regulären Ausdruck wild übereinstimmen. Auch hierbei können für wild die Platzhalterzeichen '%' oder '_' verwendet werden, und auch hierbei werden bei einer Angabe von NULL für den Parameter wild alle Tabellen einer Datenbank aufgelistet. Bei einem Fehler wird NULL zurückgegeben. Natürlich müssen Sie auch hier die Ergebnismenge mit mysql_free_result() freigeben.

Feldnamen einer Tabelle – mysql_list_fields()

Mit der Funktion

#include <mysql.h>
MYSQL_RES *mysql_list_fields( MYSQL *mysql, 
                              const char *table,
                              const char *wild );

wird eine Ergebnismenge zurückgegeben, die aus den Feldnamen in der angegebenen Tabelle bestehen, die auf den regulären Ausdruck wild passen. Auch hierbei können Sie als Platzhalterzeichen '%' oder '_' angeben. Bei der Verwendung des NULL-Zeigers für wild werden alle Feldnamen der Tabelle ausgegeben. Bei einem Fehler liefert auch diese Funktion NULL zurück. Sie müssen die Ergebnismenge mit mysql_free_result() freigeben.

Aktuelle Server-Threads – mysql_list_processes()

Mit der nächsten Funktion

#include <mysql.h>
MYSQL_RES *mysql_list_processes(MYSQL *mysql) ;

können Sie die Ergebnismenge der aktuellen Server-Threads zur aktuellen Verbindung des MySQL-Handles ermitteln. Das Ergebnis ist dasselbe wie von mysqladmin processlist oder einer SHOW PROCESSLIST-Anfrage. Bei einem Fehler wird NULL zurückgegeben. Auch diese Ergebnismenge müssen Sie mit mysql_free_result() freigeben.

Die Rückgaben der letzten Befehle können durch die Benutzerverwaltung von MySQL eingeschränkt sein. Wenn Sie sich zum Beispiel alle Datenbanknamen auflisten lassen, bekommen Sie nicht zwangsläufig alle Datenbanknamen zurückgeliefert.


Rheinwerk Computing

12.5.7 Ergebnismenge zeilenweise bearbeiten (MYSQL_ROWdowntop

Wollen Sie eine Ergebnismenge, die sich in der Struktur MYSQL_RES befindet, nur zeilenweise einlesen oder ausgeben lassen, benötigen Sie auf jeden Fall hierzu den Datentyp oder besser die Struktur MYSQL_ROW. Dieser Datentyp repräsentiert eine Zeile von Daten, die als ein Array von gezählten Byte-Zeichenketten implementiert ist.

Um einzelne Zeilen (bei einer Datenbank spricht man ja vom Datensatz) aus der Ergebnismenge zu holen, wird folgende Funktion verwendet:

#include <mysql.h>
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result) ;

Damit wird immer der nächste Datensatz (Zeile) einer Ergebnismenge abgerufen. Als Rückgabewert erhalten Sie einen Zeiger auf die Werte einer Zeile, worauf Sie mit feld[0] bis feld[n-1] zugreifen können. Sind keine Datensätze mehr einzulesen oder trat ein Fehler auf, wird NULL zurückgegeben.

Um auf die einzelnen Felder zuzugreifen, haben Sie soeben erfahren, dass dies mit feld[0] bis feld[n-1] realisierbar ist. Nur wenn Sie bei einer Datenbank die Tabelle um ein weiteres Feld erweitern, bringt Ihnen der konstante Wert n für die Anzahl der Felder nichts mehr. Dann benötigen Sie zum Ermitteln der einzelnen Felder, die sich im Datensatz (Zeile) befinden und die Sie soeben mit mysql_fetch_row() ermittelt haben, die folgende Funktion:

#include <mysql.h>
unsigned int mysql_num_fields(MYSQL_RES *result) ;

Mit dieser Funktion erhalten Sie die Anzahl von Spalten (Felder) der Ergebnismenge, die sich in MYSQL_RES befindet. Somit können Sie jetzt auf die einzelnen Spalten mit feld[0] bis feld[mysql_num_fields(result)-1] zugreifen. Somit sähe ein zeilenweises Auslesen eines Datensatzes wie folgt aus:

MYSQL_RES *res;
MYSQL_ROW row;
/* Datensatz einlesen */
while ((row = mysql_fetch_row (res)) != NULL)
{ /* Anzahl der Spalten ermitteln und Feld für Feld ausgeben */
   for (i = 0; i < mysql_num_fields (res); i++)
      printf ("%s | ", row[i]);
    printf ("\n");
}

Benötigen Sie jetzt auch noch die Länge der Feldwerte in der Zeile, dann können Sie diese mit der Funktion mysql_fetch_lengths() bestimmen. Mehr dazu später.

Wollen Sie hingegen die Anzahl der Datensätze (Zeilen) ermitteln, die sich in der Ergebnismenge befinden, so können Sie auf die Funktion mysql_num_rows() zurückgreifen:

#include <mysql.h>
my_ulonglong mysql_num_rows(MYSQL_RES *result) ;

Hinweis   Die Dokumentation der MySQL API empfiehlt aus Portabilitätsgründen des Typs my_ulonglong, den Rückgabewert mit unsigned long zu casten! Dieser Typ stellt einen Bereich von 0 bis 1.84e19 zur Verfügung. Auf manchen »Systemen« funktioniert der Versuch, einen Wert des Typs my_ulonglong auszugeben, nicht – was allerdings mehr ein Problem des Compilers statt des Systems ist.


Platzieren und Abfragen eines Zeilencursors – MYSQL_ROW_OFFSET

MYSQL_ROW_OFFSET ist eine typensichere Repräsentation eines Offsets für einen Zeilencursor. Das Offset sollte ein Wert sein, der von einem Aufruf von mysql_row_tell() oder mysql_row_seek() zurückgegeben wird. Dieser Wert ist nicht einfach eine Zeilennummer.

Den Zeilencursor können Sie mit der folgenden Funktion verschieben:

#include <mysql.h>
MYSQL_ROW_OFFSET mysql_row_seek( MYSQL_RES *ergebnis,
                                 MYSQL_ROW_OFFSET offset ) ;

Damit setzen Sie den Zeilencursor auf eine beliebige Zeile in der Anfrage-Ergebnismenge. Zurückgegeben wird der vorherige Wert des Zeilencursors. Diesen Wert können Sie wiederum bei einer späteren Verwendung von mysql_row_seek() als zweiten Parameter übergeben.

Wollen Sie hingegen die aktuelle Position des Zeilencursors ermitteln, so steht Ihnen die folgende Funktion zur Verfügung:

#include <mysql.h>
MYSQL_ROW_OFFSET mysql_row_tell(MYSQL_RES *ergebnis) ;

Mit mysql_row_tell() erhalten Sie die aktuelle Position des letzten mysql_fetch_row()-Aufrufs zurück. Den Rückgabewert können Sie auch hier für eine spätere Verwendung von mysql_row_seek() als zweiten Parameter verwenden. Als Rückgabewert erhalten Sie hierbei die aktuelle Position des Zeilencursors.


Hinweis   Da mysql_row_tell() und mysql_row_seek() als Parameter eine Ergebnismenge der Struktur MYSQL_RES benötigen, können Sie diese Funktionen nur nach einem mysql_store_result() und nicht nach einem mysql_use_result() verwenden. Mehr dazu später.



Rheinwerk Computing

12.5.8 Ergebnismenge spaltenweise einlesen (und ausgeben) (MYSQL_FIELDdowntop

Sie werden es sicherlich erahnen, wenn es eine Struktur für die Verbindung zur Datenbank (MYSQL), eine für die Ergebnismenge (MYSQL_RES) und eine für die Zeile/den Datensatz (MYSQL_ROW) gibt, dann ist das nächstkleinste Glied in der Kette das Feld. Dafür steht Ihnen die Struktur MYSQL_FIELD zur Verfügung. Diese Struktur beinhaltet Informationen zu einem Feld wie den Namen, den Feldtyp und die Feldgröße. Sie erhalten solch eine MYSQL_FIELD-Struktur für jedes Feld durch einen wiederholten Aufruf von mysql_fetch_field(). Um Missverständnisse zu vermeiden, Feldwerte sind nicht Teil der MYSQL_FIELD-Struktur. Die Werte selbst sind in der zuvor beschriebenen Struktur MYSQL_ROW enthalten.

Die Elemente der Struktur MYSQL_FIELD sind im Folgenden erklärt.

MYSQL_FIELD – Struktur

gp  char *name – Name des Feldes als terminierte Zeichenkette
gp  char *table – Name der Tabelle, die das Feld enthält, sofern es sich nicht um ein berechnetes Feld handelt – bei berechneten Feldern findet sich hier eine leere Zeichenkette.
gp  char *def – Vorgabewert des Feldes als terminierte Zeichenkette. Wird nur gesetzt, wenn Sie die Funktion mysql_list_fields() verwenden.
gp  enum enum_field_types – Der Typ des Feldes, der folgende Werte haben kann:

Tabelle 12.6    Typ des Feldes in enum enum_field_types

Wert Bedeutung
FIELD_TYPE_TINY TINYINT-Feld
FIELD_TYPE_SHORT SMALLINT-Feld
FIELD_TYPE_LONG INTEGER-Feld
FIELD_TYPE_INT24 MEDIUMINT-Feld
FIELD_TYPE_LONGLONG BIGINT-Feld
FIELD_TYPE_DECIMAL DECIMAL- oder NUMERIC-Feld
FIELD_TYPE_FLOAT FLOAT-Feld
FIELD_TYPE_DOUBLE DOUBLE- oder REAL-Feld
FIELD_TYPE_TIMESTAMP TIMESTAMP-Feld
FIELD_TYPE_DATE DATE-Feld
FIELD_TYPE_TIME TIME-Feld
FIELD_TYPE_DATETIME DATETIME-Feld
FIELD_TYPE_YEAR YEAR-Feld
FIELD_TYPE_STRING CHAR- oder VARCHAR-Feld
FIELD_TYPE_BLOB BLOB- oder TEXT-Feld (benutzen Sie max_length, um die maximale Länge festzulegen)
FIELD_TYPE_SET SET-Feld
FIELD_TYPE_ENUM ENUM-Feld
FIELD_TYPE_NULL NULL-Feld
FIELD_TYPE_CHAR veraltet; benutzen Sie stattdessen FIELD_TYPE_TINY

    Außerdem können Sie das Makro IS_NUM() verwenden, um zu testen, ob ein Feld einem nummerischen Typ entspricht oder nicht. Beispiel einer solchen Anwendung:
       
if (IS_NUM(field->type))     printf("Feld ist nummerisch\n");
gp  unsigned int length – Gibt die Breite des Feldes an, die in der Tabellendefinition festgelegt wurde.
gp  unsigned int max_length – Gibt die maximale Breite des Feldes für die Ergebnismenge (die Länge des längsten Feldwertes für die Zeilen, die tatsächlich in der Ergebnismenge enthalten sind) an. Wenn Sie mysql_store_result() oder mysql_list_fields() benutzen, enthält die Variable die maximale Länge für das Feld. Wenn Sie mysql_use_result() benutzen, ist sie 0.
gp  unsigned int flags – Hier finden Sie die verschiedenen Flags. Wenn dieser Wert nicht 0 ist, dann ist mindestens eines der folgenden Bits gesetzt:

Tabelle 12.7    Spezielle Feldattribute für die Strukturvariable flags

Flag Bedeutung
NOT_NULL_FLAG Feld darf nicht NULL sein.
PRI_KEY_FLAG Feld ist Teil eines Primärschlüssels.
UNIQUE_KEY_FLAG Feld ist Teil eines eindeutigen Schlüssels.
MULTIPLE_KEY_FLAG Feld ist Teil eines nicht eindeutigen Schlüssels.
UNSIGNED_FLAG Feld hat das UNSIGNED-Attribut.
BINARY_FLAG Feld hat das BINARY-Attribut.
AUTO_INCREMENT_FLAG Feld hat das AUTO_INCREMENT-Attribut.
ENUM_FLAG Feld ist ein ENUM (veraltet).
BLOB_FLAG Feld ist ein BLOB oder TEXT (veraltet).
TIMESTAMP_FLAG Feld ist ein TIMESTAMP (veraltet).
ZEROFILL_FLAG Feld hat das ZEROFILL-Attribut.

    Sie können diese Flags auch mit den folgenden Makros testen, um einen flag-Wert zu bestimmen:
       
IS_NOT_NULL(flags) – WAHR, wenn der Feldwert als NOT NULL definiert ist. IS_PRI_KEY(flags) – WAHR, wenn der Feldwert ein Primärschlüssel ist. IS_BLOB(flags) – (veraltet; testen Sie stattdessen field->type)
gp  unsigned int decimals – Die Anzahl von Dezimalstellen für nummerische Felder

Um die Definition einer Spalte aus der Ergebnismenge zu holen, kann die Funktion

#include <mysql.h>
MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result) ;

verwendet werden. Damit Sie die Informationen zu allen Spalten erhalten, müssen Sie diese Funktion mehrmals aufrufen. Bei einem Fehler gibt diese Funktion NULL zurück. NULL wird auch zurückgegeben, wenn keine weiteren Felder mehr vorhanden sind. Ein Beispiel der Anwendung:

MYSQL_FIELD *field;
while((field = mysql_fetch_field(ergebnis))) {
    printf("Feldname %s\n", field->name);
}

Wollen Sie ein Array aller MYSQL_FIELD-Strukturen für eine Ergebnismenge auf einmal ermitteln, so können Sie die folgende Funktion verwenden:

#include <mysql.h>
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *result) ;

Jede Struktur stellt hierbei Felddefinitionen für eine Spalte der Ergebnismenge zur Verfügung. Beispiel einer solchen Verwendung:

unsigned int num_fields;
unsigned int i;
MYSQL_FIELD *fields;
/* Anzahl der Felder ermitteln */
num_fields = mysql_num_fields(ergebnis);
fields = mysql_fetch_fields(ergebnis);
for(i = 0; i < num_fields; i++) {
   printf("Feld %u ist %s\n", i, fields[i].name);
}

Wollen Sie innerhalb einer Ergebnismenge eine bestimmte Spalte direkt abrufen, dann steht Ihnen die folgende Funktion zur Verfügung:

#include <mysql.h>
MYSQL_FIELD *mysql_fetch_field_direct( MYSQL_RES *result, 
                                       unsigned int feldnr ) ;

Mit der Angabe einer Feldnummer feldnr für eine Spalte innerhalb einer Ergebnismenge gibt diese Funktion die Felddefinition dieser Spalte als MYSQL_FIELD-Struktur zurück. Sie können diese Funktion verwenden, um die Definition für eine beliebige Spalte abzurufen. Der Wert von feldnr sollte im Bereich von 0 bis mysql_num_fields(ergebnis)-1 liegen. Beispiel einer solchen Aktion:

unsigned int num_fields;
unsigned int i;
MYSQL_FIELD *field;
num_fields = mysql_num_fields(ergebnis);
for(i = 0; i < num_fields; i++) {
    field = mysql_fetch_field_direct(ergebnis, i);
    printf("Feld %u ist %s\n", i, field->name);
}

Wie bei der Ergebnismenge von Zeilen mit dem Zeilencursor kann auch bei den Spalten der Feldcursor abgefragt und versetzt werden. Hierfür wird der Datentyp MYSQL_FIELD_OFFSET benötigt. Das ist eine typsichere Repräsentation eines Offsets in einer MySQL-Feldliste (benutzt von mysql_field_seek()). Offsets sind Feldnummern innerhalb einer Zeile, beginnend mit 0.

Um einen Feldcursor zu versetzen, kann die Funktion

#include <mysql.h>
MYSQL_FIELD_OFFSET mysql_field_seek(MYSQL_RES *result, 
                                    MYSQL_FIELD_OFFSET offset) ;

verwendet werden. Damit wird der Feldcursor auf das angegebene Offset des zweiten Parameters gesetzt. Folgt anschließend ein Aufruf von mysql_fetch_field(), wird die Felddefinition der Spalte aufgerufen, die mit diesem Offset verknüpft ist. Wollen Sie (wieder) an den Anfang der Zeile springen, können Sie den offset-Wert 0 angeben.

Um die aktuelle Position des Feldcursors zu ermitteln, kann die Funktion mysql_field_tell() verwendet werden:

#include <mysql.h>
MYSQL_FIELD_OFFSET mysql_field_tell(MYSQL_RES *result) ;

Den Rückgabewert von der aktuellen Position des Feldcursors können Sie anschließend auch wieder für die Funktion mysql_field_seek() verwenden.

Benötigen Sie die Anzahl der Spalten, die für die letzte Anfrage zu einer Verbindung (Achtung! Nicht Ergebnismenge!) gestellt wurde, dann können Sie die folgende Funktion verwenden:

#include <mysql.h>
unsigned int mysql_field_count(MYSQL *mysql) ;

Gewöhnlich wird diese Funktion benutzt, wenn mysql_store_result() NULL zurückgab (und Sie somit keinen Ergebnismengen-Zeiger haben). In diesem Fall können Sie mysql_field_count() aufrufen, um festzustellen, ob mysql_store_result() ein leeres Ergebnis hätte zurückgeben sollen oder nicht. Dies ermöglicht es Ihnen, die richtigen Entscheidungen zu treffen, ohne dass Sie wissen müssen, ob es sich bei der Anfrage um ein SELECT handelte oder nicht.


Hinweis   Die Funktion ist erst bei der MySQL-Version 3.2.24 eingeführt worden. Daher sollten Sie bei einer älteren Version die Funktion mysql_num_fields() stattdessen verwenden (oder noch besser, schnellstens ein Update machen).



Rheinwerk Computing

12.5.9 Ein Beispiedowntop

Bevor Sie auf den nächsten Seiten weitere API-Funktionen zu MySQL kennen lernen, folgt hierzu wieder ein Beispiel, das einige der Funktionen, über die Sie auf den Seiten zuvor etwas gelesen haben, in der Praxis demonstriert. Mit dem Listing können Sie alle vorhandenen Datenbanken, alle Tabellen einer Datenbank und alle Spalten einer Tabelle auflisten lassen. Außerdem können Sie alle vorhandenen Prozesse auflisten lassen – einfach alles, was eben in der (etwas langatmigen) Theorie beschrieben wurde.

/* mysql4.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql.h>
#define MAX 255
/* Verbindungsdaten für mysql_real_connect */
static char *connect_param[7];
/* globaler Hilfszähler für die Verbindungsdaten */
static int connect_param_count = 0;
static void print_line (MYSQL_RES * res);
/*
 *  Übernehmen Sie die my_fgets()-, my_cat()-, get_connect_param()-,
 *  mein_connect()-, cleanup_shutdown()-Funktionen von oben
 *  (mysql2.c) -> oder den kompletten Code auf der Buch-CD
 */
/* Listet alle vorhandenen Datenbanken auf */
static void list_DB (MYSQL * my) {
  MYSQL_RES *res;
  MYSQL_ROW row;
  int zeilen;
  unsigned int i;
  res = mysql_list_dbs (my, NULL);
  zeilen = (unsigned long) mysql_num_rows (res);
  printf ("Datenbanken gefunden : %d\n", zeilen);
  while ((row = mysql_fetch_row (res)) != NULL)
    /* mysql_num_flields() wäre hier  nicht nötig */
    for (i = 0; i < mysql_num_fields (res); i++)
      printf ("%s\n", row[i]);
  mysql_free_result (res);
  getchar ();
}
/* Listet alle Tabellen einer Datenbank auf */
static void list_table_DB (MYSQL * my) {
  MYSQL_RES *res;
  MYSQL_ROW row;
  int zeilen;
  unsigned int i;
  res = mysql_list_tables (my, NULL);
  if (res == NULL) {
    fprintf (stderr, "Fehler bei mysql_list_tables():"
             "%u (%s)\n", mysql_errno (my), mysql_error (my));
    return;
  }
  zeilen = (unsigned long) mysql_num_rows (res);
  printf ("Tabellen in der Datenbank : %d\n", zeilen);
  while ((row = mysql_fetch_row (res)) != NULL) {
    for (i = 0; i < mysql_num_fields (res); i++)
      printf ("%s", row[i]);
    printf ("\n");
  }
  mysql_free_result (res);
  getchar ();
}
/* Listet alle Felder einer Tabelle auf */
static void list_fields_DB (MYSQL * my) {
  unsigned int i, col_len;
  MYSQL_RES *res;
  MYSQL_FIELD *field;
  char buffer[MAX];
  printf ("Bitte eine Tabelle auswählen: ");
  my_fgets (buffer, MAX);
  res = mysql_list_fields (my, buffer, NULL);
  if (res == NULL) {
    fprintf (stderr, "Fehler bei mysql_list_tables():"
             "%u (%s)\n", mysql_errno (my), mysql_error (my));
    return;
  }
  /* offset = 0 bedeutet, auf den Anfang der Zeile setzen */
  mysql_field_seek (res, 0);
  /* Damit bei der Ausgabe ein einheitliches Bild entsteht,
   * sollen die Daten für die max. Länge einer Spalte bei jeder
   * einzelnen (MYSQL_FIELD)-Spalte verändert werden   */
  for (i = 0; i < mysql_num_fields (res); i++) {
     field = mysql_fetch_field (res);
     /* Länge des Namens in der Spalte ermitteln */
     col_len = strlen (field->name);
     /* Ist die Länge des Elements in der Spalte kleiner als
      * die max. Länge ... */
     if (col_len < field->max_length)
        /* ... dann bekommt col_len den Wert der max. erl. Länge
         * der Spalte */
        col_len = field->max_length;
     /* Für den Fall, dass eine Spalte keine Daten
      * beinhaltet ... */
     if (col_len < 4 && !IS_NOT_NULL (field->flags))
        /* col_len bekommt den Wert 4 für den String
         * "NULL" ->keine Daten */
        col_len = 4;
     /* max. Länge von Spalten-Info verändern */
     field->max_length = col_len;
  }
  /* Namen der Tabelle ausgeben */
  printf ("Daten der Tabelle: [ %s ]\n", field->table);
  printf("Mit mysql_fetch_field()\n");
  /* Demonstriert mysql_fetch_field() */
  print_line (res);
  printf ("|");
  /* Alles wieder auf den Anfang stellen */
  mysql_field_seek (res, 0);
  /* Jetzt den Tabellenkopf ausgeben  */
  for (i = 0; i < mysql_num_fields (res); i++) {
     field = mysql_fetch_field (res);
     printf (" %-*s |", field->max_length, field->name);
  }
  printf ("\n");
  print_line (res);
  printf("Gleiches mit mysql_fetch_fields()\n");
  /* Dasselbe nochmals mit mysql_fetch_fields() */
  print_line (res);
  printf ("|");
  /* Alles wieder auf den Anfang stellen */
  mysql_field_seek (res, 0);
  field = mysql_fetch_fields (res);
  for (i = 0; i < mysql_num_fields (res); i++) {
     printf (" %-*s |", field[i].max_length, field[i].name);
  }
  printf ("\n");
  print_line (res);
  mysql_free_result (res);
}
static void print_line (MYSQL_RES * res) {
  MYSQL_FIELD *field;
  unsigned int i, j;
  mysql_field_seek (res, 0);
  /* Erstes Zeichen der Linie */
  printf ("+");
  for (i = 0; i < mysql_num_fields (res); i++) {
     field = mysql_fetch_field (res);
     /* max_length '-' Zeichen jeder Spalte ausgeben */
     for (j = 0; j < field->max_length + 2; j++)
        printf ("-");
     /* Am Ende der Spalte '+' ausgeben */
     printf ("+");
  }
  printf ("\n");
}
/* Listet alle vorhandenen Prozesse auf */
static void list_process (MYSQL * my) {
  MYSQL_RES *res;
  MYSQL_ROW row;
  int zeilen;
  unsigned int i, col_len;
  MYSQL_FIELD *field;
  res = mysql_list_processes (my);
  zeilen = (unsigned long) mysql_num_rows (res);
  printf ("Prozesse gefunden : %d\n", zeilen);
  /* offset = 0 bedeutet, auf den Anfang der Zeile setzen */
  mysql_field_seek (res, 0);
  /* Damit bei der Ausgabe ein einheitliches Bild entsteht,
   * sollen die Daten für die max. Länge einer Spalte bei jeder
   * einzelnen (MYSQL_FIELD)-Spalte verändert werden */
  for (i = 0; i < mysql_num_fields (res); i++) {
     field = mysql_fetch_field (res);
     /* Länge des Namens in der Spalte ermitteln */
     col_len = strlen (field->name);
     /* Ist die Länge des Elements in der Spalte kleiner als
      * die max. Länge ... */
     if (col_len < field->max_length)
        /* ... dann bekommt col_len den Wert der max. erl. Länge
         * der Spalte */
        col_len = field->max_length;
     /* Für den Fall, dass eine Spalte keine Daten
      * beinhaltet ... */
     if (col_len < 4 && !IS_NOT_NULL (field->flags))
        /* col_len bekommt den Wert 4 für den String
         * "NULL" ->kein Daten */
        col_len = 4;
     /* max. Länge von Spalten-Info verändern */
     field->max_length = col_len;
  }
  print_line (res);
  printf ("|");
  /* Alles wieder auf den Anfang stellen */
  mysql_field_seek (res, 0);
  /* Jetzt den Tabellenkopf ausgeben (titel, hauptrolle, fsk,
   * gedreht) */
  for (i = 0; i < mysql_num_fields (res); i++) {
      field = mysql_fetch_field (res);
      printf (" %-*s |", field->max_length, field->name);
  }
  printf ("\n");
  print_line (res);
  mysql_field_seek (res, 0);
  while ((row = mysql_fetch_row (res)) != NULL) {
     printf ("|");
     for (i = 0; i < mysql_num_fields (res); i++) {
        field = mysql_fetch_field (res);
        printf (" %-*s |",field->max_length, row[i]);
     }
     printf ("\n");
     mysql_field_seek (res, 0);
  }
  print_line (res);
  mysql_free_result (res);
}
int main (int argc, char *argv[]) {
  MYSQL *my;
  int auswahl;
  /* Handle initialisieren */
  my = mysql_init (NULL);
  if (my == NULL) {
    fprintf (stderr, " Initialisierung fehlgeschlagen\n");
    exit (EXIT_FAILURE);
  }
  /* Verbindungsdaten für die Datenbank abfragen */
  get_connect_param ();
  /* Mit dem Server verbinden */
  if (mein_connect (my) != -1)
    printf ("Erfolgreich mit dem MySQL-Server verbunden\n");
  do {
    printf ("\nInformationen zu den Datenbanken auf dem"
            " MySQL-Server\n\n");
    printf ("- 1 - Alle vorhandenen Datenbanken ausgeben \n");
    printf ("- 2 - Tabellen einer Datenbank auflisten\n");
    printf (" -3 - Spalten der Tabelle auflisten\n");
    printf ("- 4 - Prozessliste anzeigen\n");
    printf ("- 0 - Beenden\n\n");
    printf ("Ihre Auswahl : ");
    scanf ("%d", &auswahl);
    switch (auswahl) {
    case 1:
      list_DB (my);
      getchar ();
      break;
    case 2:
      getchar ();
      list_table_DB (my);
      break;
    case 3:
      getchar ();
      list_fields_DB (my);
      break;
    case 4:
      getchar ();
      list_process (my);
      break;
    case 0:
      printf ("Bye\n");
      break;
    default:
      printf ("Fehlerhafte Eingabe\n");
    }
  } while (auswahl != 0);
  /* Verbindung trennen */
  clean_up_shutdown (my);
  return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

$  gcc -c -I/usr/include/mysql mysql4.c
$  gcc -o mysql4 mysql4.o -L/usr/lib/mysql -lmysqlclient
$ ./mysq4
Hostname (<ENTER> == default) : localhost
User-Name (<ENTER> == default) : root
Passwort (<ENTER> == default)  : k4p6m3o3
Datenbank (<ENTER> == default) : adressen
Port (<ENTER> == default)      : ENTER
Socket (<ENTER> == default)    : ENTER
Flag (<ENTER> == default)      : ENTER
Erfolgreich mit dem MySQL-Server verbunden
Informationen zu den Datenbanken auf dem MySQL-Server
-----------------------------------------------
- 1 - Alle vorhandenen Datenbanken ausgeben
- 2 - Tabellen einer Datenbank auflisten
 -3 - Spalten der Tabelle auflisten
- 4 - Prozessliste anzeigen
- 0 - Beenden
Ihre Auswahl : 1
Datenbanken gefunden : 7
adressen
dvd_archiv
mail_archiv
mein_test
mysql
news
test
...
...
Ihre Auswahl : 2
Tabellen in der Datenbank : 1
privat
...
...
Ihre Auswahl : 3
Bitte eine Tabelle auswählen : privat
Daten der Tabelle: [ privat ]
+-------+-------+------+------+---------+--------+-------+
| vname | nname | plz  | ort  | strasse | hausnr | phone |
+-------+-------+------+------+---------+--------+-------+
...
...
Ihre Auswahl : 4
Prozesse gefunden : 2
+--+----+---------+--------+-----------+----+------+-------+
|Id|User|Host     |db      |Command    |Time|State | Info  |
+--+----+---------+--------+-----------+----+------+-------+
|1 |root|localhost|adressen|Processlist|0   |(null)| (null)|
|3 |root|localhost|(null)  |Sleep      |6   |      | (null)|
+--+----+---------+--------+-----------+----+------+-------+

Rheinwerk Computing

12.5.10 Ergebnismenge – weitere Funktionedowntop

Jetzt folgen hier noch weitere Funktionen im Bezug auf die Ergebnismenge (MYSQL_RES), die sich recht allgemein einsetzen lassen.

Besitzt eine Ergebnismenge das gesamte Anfrageergebnis, dann können Sie mit der Funktion

#include <mysql.h>
void mysql_data_seek( MYSQL_RES *result,
                      my_ulonglong offset );

bis zu einer beliebigen Zeile in der Ergebnismenge suchen. Die Funktion kann daher nur in Verbindung mit der Funktion mysql_store_result() verwendet werden – nicht mit mysql_use_result(). Als Offset sollte ein Wert zwischen dem Bereich 0 bis mysql_num_row(max)-1 verwendet werden.

Benötigen Sie die Länge der Spalten der aktuellen Zeile einer Ergebnismenge, um etwa Feldwerte zu kopieren, dann kann hierfür folgende Funktion verwendet werden:

#include <mysql.h>
unsigned long *mysql_fetch_lengths(MYSQL_RES *result) ;

Der Vorteil dieser Funktion ist, dass diese nicht die Funktion strlen() aufruft, was sich positiv in der Performance auswirkt und sich somit auch bestens für Binärdaten eignet. Denn bei Binärdaten würde die Funktion strlen() ein falsches Ergebnis für die Felder zurückgeben, die Nullzeichen enthalten. Ist das Feld leer oder beinhaltet Nullwerte, gibt mysql_fetch_length() 0 zurück – was dabei zutrifft, können Sie mit mysql_fetch_row() ermitteln. Hier ein Beispiel, wie das in der Praxis geht:

MYSQL_ROW zeile;
unsigned int anzahl_felder;
unsigned int i;
anzahl_felder = mysql_num_fields(ergebnis);
while ((zeile = mysql_fetch_row(ergebnis))) {
   unsigned long *laengen;
   laengen = mysql_fetch_lengths(ergebnis);
   for(i = 0; i < anzahl_felder; i++) {
       printf("[%.*s] ",
           (int) laengen[i], zeile[i] ? zeile[i] : "NULL");
   }
   printf("\n");
}

Als Rückgabewert liefert Ihnen mysql_fetch_length() ein Array von vorzeichenlosen Ganzzahlen zurück, das die Größe einer jeden Spalte beinhaltet. Bei einem Fehler wird NULL zurückgegeben.


Rheinwerk Computing

12.5.11 Befehle an den Server – mysql_query() und mysql_real_query(downtop

Sicherlich haben Sie sich schon überlegt, wie Sie wohl SQL-Anfragen ŕ la SELECT, INSERT ... an den MySQL-Server mithilfe der C-API stellen können.

Eine SQL-Anfrage können Sie mit der Funktion mysql_query() bzw. mysql_real_query() stellen. Der Vorgang einer solchen Anfrage ist folgender:

gp  Eine SQL-Anfrage an den Server
gp  Server empfängt die Anfrage und überprüft diese auf syntaktische Fehler.
gp  Server führt die angefragte Aktion aus und gibt abhängig von der Art der Anfrage ein Resultat zurück. So wird, im Gegensatz zu einer SELECT-Anfrage, bei einer INSERT-Anfrage nichts zurückgegeben.

Die Funktion, mit der Sie eine SQL-Anfrage an den Server stellen können, ist:

#include <mysql.h>
int mysql_query(MYSQL *mysql, const char *anfrage); 
int mysql_real_query( MYSQL *mysql, const char *anfrage, 
                      unsigned long laenge );

Damit können Sie SQL-Anweisungen (Anfragen) als nullterminierten String ausführen, der am Ende allerdings im Gegensatz zur MySQL-Shell kein Semikolon oder \g haben darf. Verläuft bei der Anfrage alles planmäßig, wird 0, ansonsten bei einem Fehler ungleich 0 zurückgegeben.

Da mysql_query() wegen der Bedeutung des String-Endezeichens nicht für Binärdaten geeignet ist, müssen Sie hierfür die Funktion mysql_real_query() mit der Angabe der Länge (laenge) des Strings als extra Parameter verwenden.


Hinweis   Da mit mysql_query() keinerlei Angaben zur Länge des Strings gemacht werden, ist diese Funktion ein Kandidat für einen Buffer Overflow – deshalb sollte immer die Funktion mysql_real_query() bevorzugt werden.


Wenn die SQL-Anfrage an den Server nicht klappen will, sollten Sie den Fehler mit mysql_errno() und mysql_error() überprüfen. Folgende Fehler können dabei auftreten:

gp  CR_COMMANDS_OUT_OF_SYNC – Befehle wurden nicht in der korrekten Reihenfolge ausgeführt.
gp  CR_SERVER_GONE_ERROR – Der MySQL-Server ist weg.
gp  CR_SERVER_LOST – Die Verbindung zum Server ging während der Anfrage verloren.
gp  CR_UNKNOWN_ERROR – Ein unbekannter Fehler trat auf.

Für Anfragen an den Server mit z. B. INSERT, wo ja nichts zurückgegeben wird, reichen mysql_query() und mysql_real_query() aus. Allerdings bei Anfragen mittels SELECT, SHOW, DESCRIBE oder EXPLAIN, wo etwas zurückgegeben wird, benötigen Sie eine Funktion, um die Daten der SQL-Abfrage abzuholen. Hierfür müssen Sie die Funktionen mysql_store_result() oder mysql_use_result() aufrufen. Es ist allerdings auch möglich, die beiden Funktionen bei SQL-Anfragen aufzurufen, die keinen Wert zurückgeben. Damit können Sie z. B. feststellen, ob die Anfrage eine Ergebnismenge hatte, wenn Sie prüfen, ob mysql_store_result() 0 zurückgibt.

Beginnend mit mysql_store_result() – die Syntax:

#include <mysql.h>
MYSQL_RES *mysql_store_result(MYSQL *mysql) ;

Damit lesen Sie das gesamte Ergebnis einer SQL-Anfrage (meist mit mysql_real_query()) zum Client ein. Als Rückgabewert erhalten Sie eine Ergebnismenge vom Typ der MYSQ_RES-Struktur zurück. Wie Sie diese Ergebnismenge behandeln können, haben Sie ja bereits gelesen. Hat die Anfrage keine Ergebnismenge zurückgegeben, gibt mysql_store_result() NULL zurück (z. B. wenn die Anfrage ein INSERT-Statement war). NULL wird allerdings auch zurückgegeben, wenn das Lesen der Ergebnismenge fehlgeschlagen ist. Sie können überprüfen, ob es sich dabei um einen Fehler handelt, wenn mysql_error() keinen NULL-Zeiger zurückgibt und/oder mysql_errno() ungleich 0 zurückgab. Eine leere Ergebnismenge wird zurückgegeben, wenn keine Zeilen zurückgegeben werden. (Eine leere Ergebnismenge unterscheidet sich als Rückgabewert von einem NULL-Zeiger.)

Wenn Sie mittels mysql_store_result() ein Ergebnis erhalten haben, das kein NULL-Zeiger ist, können Sie mit der Funktion mysql_num_rows() herausfinden, wie viele Zeilen in der Ergebnismenge enthalten sind. Mit mysql_fetch_row() können Sie die einzelnen Zeilen der Ergebnismenge holen oder mit mysql_row_seek() und mysql_row_tell() die Zeilenposition (Zeilencursor) innerhalb der Zeile setzen oder abfragen (funktioniert auch mit mysql_data_seek()).

Die Behandlung des Erhalts der Ergebnismenge mit mysql_store_result() kann also ebenso gehandhabt werden, wie Sie dies schon in den Seiten zuvor mit der Behandlung der Mengen zeilenweise und spaltenweise gesehen und (hoffentlich auch) gemacht haben.

Sie müssen mysql_free_result() aufrufen, wenn Sie mit der Ergebnismenge fertig sind.

Natürlich kann es auch passieren, dass mysql_store_result() NULL zurückliefert, obwohl der Aufruf von mysql_real_query() erfolgreich war. Dies kann mehrere Ursachen haben. So war die Rückgabe der Ergebnismenge zu groß, und es konnte dafür kein Speicher mehr reserviert werden, oder die Daten konnten nicht gelesen werden. Und – der plausibelste Grund – die Anfrage gibt keine Daten zurück, wie dies bei INSERT, UPDATE oder DELETE der Fall ist.

Um zu überprüfen, ob das Statement eine leere Ergebnismenge zurückgegeben hat, sollten Sie mysql_field_count() aufrufen. Wenn mysql_field_count() 0 zurückgibt, ist die Ergebnismenge leer, und bei der Anfrage handelte es sich um ein SQL-Statement, das keinen Rückgabewert liefert (INSERT, UPDATE, DELETE).

Es empfiehlt sich außerdem, mit mysql_error() und mysql_errno() auf Fehler zu überprüfen. Folgende Fehler können dabei auftreten:

gp  CR_COMMANDS_OUT_OF_SYNC – Befehle wurden nicht in der korrekten Reihenfolge ausgeführt.
gp  CR_OUT_OF_MEMORY – Kein Speicher mehr.
gp  CR_SERVER_GONE_ERROR – Der MySQL-Server ist weg.
gp  CR_SERVER_LOST – Die Verbindung zum Server ging während der Anfrage verloren.
gp  CR_UNKNOWN_ERROR – Ein unbekannter Fehler trat auf.

Die Funktion mysql_use_result() initiiert einen Ergebnismengen-Abruf, aber liest die Ergebnismenge nicht in die Clientanwendung ein wie mysql_store_result().

#include <mysql.h>
MYSQL_RES *mysql_use_result(MYSQL *mysql);

Mit mysql_use_result() muss jede Zeile extra abgerufen werden, indem hierzu Aufrufe mit mysql_fetch_row() ausgeführt werden. Damit wird das Ergebnis einer Anfrage direkt vom Server geholt, ohne (wie bei mysql_store_result()) zuvor eine temporäre Tabelle oder einen lokalen Puffer zu belasten – was daher logischerweise schneller sein kann als mysql_store_result(). Der Clientanwendung wird nur Speicher für die aktuelle Zeile zugewiesen sowie ein Kommunikationspuffer, der bis zu max_allowed_packet Bytes groß werden kann.

Auf der anderen Seite sollten Sie mysql_use_result() nicht verwenden, wenn Sie viele Verarbeitungen für jede Zeile auf der Clientseite durchführen oder wenn die Ausgabe auf den Bildschirm geschickt wird, auf dem der Benutzer (STRG)+(S) (stop scroll) eingeben kann. Das bindet den Server und sorgt dafür, dass andere Threads irgendwelche Tabellen nicht aktualisieren können, von denen gerade Daten geholt werden.

Bei der Funktion mysql_use_result() müssen Sie außerdem mysql_fetch_row() ausführen, bis ein NULL-Wert zurückgegeben wird, da sonst die nicht geholten Zeilen als ein Teil der Ergebnismenge bei der nächsten Anfrage zurückgegeben werden. Wird dies nicht beachtet, bekommen Sie einen Fehler wie Commands out of sync; You can't run this command now zurück.

Da die Funktion mysql_use_result() jede Zeile extra aufruft, können Sie Funktionen wie mysql_data_seek(), mysql_row_seek(), mysql_row_tell(), mysql_num_rows() oder mysql_affected_rows() nicht bei einem Ergebnis verwenden. Des Weiteren darf keine Anfrage gesetzt werden, wenn mysql_use_result() nicht beendet ist. Wenn alle Zeilen geholt wurden, wird die Funktion mysql_num_row() aber dennoch die exakte Anzahl der geholten Zeilen zurückgeben. Sie müssen mysql_free_result() aufrufen, wenn Sie mit der Ergebnismenge fertig sind.

Die Fehler, die Sie mit mysql_error() und mysql_errno() abfragen können, sind dieselben wie bei der Funktion mysql_store_result().

Um herauszufinden, wie viele Zeilen von einem SQL-Statement wie UPDATE, DELETE oder INSERT betroffen waren, kann man direkt nach mysql_real_query() die Funktion

#include <mysql.h>
my_ulonglong mysql_affected_rows(MYSQL *mysql) ;

aufrufen. Bei SELECT-Statements funktioniert mysql_affectetd_rows() wie mysql_num_rows(). Ein Wert größer als 0 gibt die Anzahl der Zeilen, die vom Abruf betroffen waren. Bei 0 wurde kein Datensatz geändert, und bei -1 ist ein Fehler aufgetreten. So wird auch ein Fehler zurückgegeben, wenn Sie bei einem SELECT-Statement die Funktion mysql_affected_row() vor der Funktion mysql_store_result() aufrufen. Ein Beispiel einer solchen Abfrage könnte so aussehen:

char
str[BUF] = "UPDATE adressen SET plz=86316 WHERE ort='Friedberg'";
mysql_real_query(&mysql, str, strlen(str));
printf("%ld Adressen vom UPDATE betroffen",
         (long) mysql_affected_rows(&mysql));

Bei der Verwendung eines REPLACE-Statements und für den Fall, dass ein Datensatz verändert wurde, gibt mysql_affected_row() 2 zurück. Dies liegt daran, dass erst eine neue Zeile eingefügt und dann die alte gelöscht wird.

Wenn Sie mit der Abarbeitung der Ergebnismenge von mysql_store_result(), mysql_use_result(), mysql_list_dbs(), mysql_list_fields(), mysql_list_tables() ... fertig sind, sollten Sie den Speicher wieder freigeben, um Memory Leaks zu vermeiden. Eine Ergebnismenge freigeben können Sie mit der Angabe der Ergebnismenge als Parameter mit der Funktion:

#include <mysql.h>
void mysql_free_result(MYSQL_RES *result) ;

Rheinwerk Computing

12.5.12 Weitere Funktionedowntop

Bevor Sie ein etwas umfangreicheres Beispiel sehen werden, folgen hier noch zu Referenzzwecken weitere API-Funktionen im Schnelldurchlauf, die allerdings im anschließenden Beispiel nicht verwendet werden.

Soll der Benutzer geändert wurden, kann die Funktion

#include <mysql.h>
my_bool mysql_change_user(MYSQL *mysql, const char *user,
                          const char *password, const char *db);

verwendet werden. Damit wird der Benutzer für die Verbindung mit dem MySQL-Handle geändert. Als weitere Parameter werden der Username, das Passwort und eventuell die Datenbank benötigt. Wenn Sie keine den Vorgaben entsprechende Datenbank haben wollen, können Sie hierfür auch NULL angeben. Kann der Benutzer nicht authentifiziert werden oder fehlen die entsprechenden Zugriffsrechte auf die Datenbank, schlägt die Funktion mysql_change_user() fehl. Bei einem Fehler wird ein Wert ungleich 0 zurückgegeben – bei Erfolg 0. Um zu ermitteln, welcher Fehler aufgetreten ist, empfiehlt es sich, die Funktionen mysql_error() und mysql_errno() zu verwenden. Die Fehler, die Sie dabei erhalten können, entsprechen denselben, die Sie bei der Funktion mysql_real_connect() erhalten könnten.


Hinweis   Bei einer API-Version vor 3.23.3 ist diese Funktionen noch nicht implementiert.


Ein Codeausschnitt zur Anwendung sieht wie folgt aus:

if (mysql_change_user(&mysql, "nobody",
                              "x4i4d1k4",
                              "datenbank_3")) {
   fprintf(stderr, "Änderung des Benutzers fehlgeschlagen. "
                   " Fehler: %s\n", mysql_error(&mysql));
}

Um eine korrekte SQL-Zeichenkette für ein SQL-Statement zu erzeugen, kann folgende Funktion verwendet werden:

#include <mysql.h>
unsigned int mysql_escape_string( MYSQL *mysql, char *nach,
                                  const char *von,
                                  unsigned int laenge );
unsigned int mysql_real_escape_string( MYSQL *mysql, char *nach,
                                       const char *von,
                                       unsigned int laenge );

Die Zeichenkette von wird in eine escape-SQL-Zeichenkette kodiert. Kodierte Zeichen sind NUL (ASCII 0), \n, \r, \, ', " und (STRG)+(Z); wobei die Funktion mysql_real_escape_string() der Funktion mysql_escape_string() vorzuziehen ist, da diese Funktion den aktuellen Zeichensatz, der verwendet wird, berücksichtigt. Bei mysql_escape_string() wird die aktuelle Zeichenstellung ignoriert. Das Ergebnis finden Sie als einen terminierten String in nach. Der String, auf den von zeigt, muss laenge Bytes lang sein. Sie müssen den nach-Puffer so zuweisen, dass er mindestens laenge*2+1 Bytes lang ist. Der Rückgabewert ist die Länge der kodierten Zeichenkette ohne das Nullzeichen am Ende.

Wenn Sie eine Kennung für eine AUTO_INCREMENT-Spalte benötigen, die durch eine vorherige Anfrage erzeugt wurde, dann kann die Funktion

#include <mysql.h>
my_ulonglong mysql_insert_id(MYSQL *mysql) ;

verwendet werden. Benutzen sollten Sie die Funktion unmittelbar nach einer INSERT-Anfrage für eine Tabelle, die den AUTO_INCREMENT-Wert erzeugt hat. mysql_insert_id() wird nach jedem INSERT- und UPDATE-Statement aktualisiert, die einen AUTO_INCREMENT-Wert erzeugen oder einen Spaltenwert auf LAST_INSERT_ID(ausdruck) setzen. Beachten Sie auch, dass der Wert der LAST_INSERT_ID()-Funktion immer den aktuellsten erzeugten AUTO_INCREMENT-Wert enthält und zwischen den Anfragen nicht zurückgesetzt wird, weil der Wert dieser Funktion beim Server erwartet wird. Zurückgegeben wird der Wert des AUTO_INCREMENT-Feldes, der durch die vorherige Anfrage verändert wurde – ansonsten 0, wenn es keine Anfrage gab oder wenn die Anfrage keinen AUTO_INCREMENT-Wert aktualisiert hat.

Wollen Sie einen bestimmten Thread mit einer bestimmten Prozess-ID beenden, können Sie folgende Funktion verwenden:

#include <mysql.h>
int mysql_kill(MYSQL *mysql, unsigned long pid) ;

Damit wird der Thread der aktuellen Verbindung mysql mit der Erkennung pid beendet. Konnte der Thread erfolgreich »getötet« werden, gibt diese Funktion 0, ansonsten bei einem Fehler ungleich 0 zurück.

Es besteht auch die Möglichkeit, das Verhalten einer Verbindung zu verändern. Mit der Funktion mysql_options() können Sie zusätzlich Optionen zu einer Verbindung setzen.

#include <mysql.h>
int mysql_options(MYSQL *mysql, enum mysql_option option,
                  const char *arg) ;

mysql_options() kann zwar mehrfach aufgerufen werden, um verschiedene Optionen zu setzen, allerdings muss dies nach mysql_init() und noch vor mysql_real_connect() (bzw. mysql_connect()) geschehen. Ist eine Verbindung erst hergestellt, gibt es keine Möglichkeit mehr, die laufende Verbindung mit den Optionen zu modifizieren. Die Funktion gibt 0 bei Erfolg und ungleich 0 zurück, wenn Sie eine unbekannte Option verwenden. Das option-Argument ist die Option, die Sie setzen wollen. Das arg-Argument ist der Wert für die Option. Wenn die Option eine Ganzzahl ist, sollte arg auf den Wert der Ganzzahl zeigen, ist die Option ein String, dann sollte arg auch auf einen String zeigen. Hier die möglichen Optionswerte mit den Argumenttypen und deren Bedeutungen:


Tabelle 12.8    Optionen und deren Argumenttypen für mysql_options()

Option arg-Typ Bedeutung
MYSQL_OPT_CONNECT_TIMEOUT unsigned int * Verbindungszeitüberschreitung (Timeout) in Sekunden
MYSQL_OPT_COMPRESS Unbenutzt Das komprimierte Client-Server-Protokoll verwenden
MYSQL_OPT_NAMED_PIPE Unbenutzt Named Pipes benutzen, um sich mit einem MySQL-Server unter NT zu verbinden.
MYSQL_INIT_COMMAND char * Befehl, der beim Verbinden mit dem MySQL-Server ausgeführt werden soll. Wird beim erneuten Verbinden automatisch wieder ausgeführt.
MYSQL_READ_DEFAULT_FILE char * Optionen aus der benannten Optionsdatei anstelle von my.cnf einlesen.
MYSQL_READ_DEFAULT_GROUP char * Optionen aus der benannten Gruppe von my.cnf oder der Datei einlesen, die durch MYSQL_READ_DEFAULT_FILE angegeben wurde.

Beachten Sie, dass die Gruppe client immer gelesen wird, wenn Sie MYSQL_READ_DEFAULT_FILE oder MYSQL_READ_DEFAULT_GROUP benutzen. Die angegebene Gruppe in der Optionsdatei kann folgende Optionen enthalten:

gp  connect_timeout – Zeitüberschreitung (Timeout) für die Verbindung in Sekunden. Unter Linux wird dieser Wert zusätzlich für die Wartezeit auf die erste Antwort vom Server benutzt. Beachten Sie, dass timeout durch connect_timeout ersetzt wurde. Dennoch wird timeout noch für eine Weile funktionieren.
gp  compress – Das komprimierte Client-Server-Protokoll benutzen
gp  database – Mit dieser Datenbank verbinden, wenn im Verbindungsbefehl keine Datenbank angegeben wurde.
gp  debug – Debug-Optionen
gp  host – Vorgabemäßiger Hostname
gp  init-commund – Befehl, der bei der Verbindung zum MySQL-Server ausgeführt wird. Wird automatisch beim erneuten Verbinden noch einmal ausgeführt.
gp  interactive-timeout – Dasselbe wie die Angabe von CLIENT_INTERACTIVE für mysql_real_connect()
gp  password – Vorgabemäßiges Passwort
gp  pipe – Named Pipes benutzen, um sich mit einem MySQL-Server unter NT zu verbinden.
gp  port – Vorgabemäßige Portnummer
gp  return-found-rows – Weist mysql_info() an, gefundene Zeilen anstelle von aktualisierten Zeilen zurückzugeben, wenn UPDATE benutzt wird.
gp  socket – Vorgabemäßige Socket-Nummer
gp  user – Vorgabemäßiger Benutzer

Ein Code-Ausschnitt einer solchen Anwendung:

MYSQL my;
mysql_init(&my);
mysql_options(&my,MYSQL_OPT_COMPRESS,0);
mysql_options(&my,MYSQL_READ_DEFAULT_GROUP,"odbc");
if (mysql_real_connect(&my, "host",
                            "benutzer",
                            "passwort",
                            "datenbank"
                            ,0,NULL,0) == 0 ) {
    fprintf(stderr, "Fehler bei mysql_real_connect: %s\n",
          mysql_error(&my));
    exit(EXIT_FAILURE);
}

Im obigen Beispiel wird der Client angewiesen, das komprimierte Client-Server-Protokoll zu benutzen und zusätzliche Optionen aus dem odbc-Abschnitt in my.cnf zu lesen.

Um zu überprüfen, ob eine Verbindung zum Server noch steht oder nicht, können Sie folgende Funktion verwenden:

#include <mysql.h>
int mysql_ping(MYSQL *mysql) ;

Besteht keine Verbindung mehr zum Server, wird versucht, von Clientseite erneut eine Verbindung zum Server herzustellen. Dies ist z. B. nötig, wenn Ihre Clientanwendung lange Zeit im Leerlauf mit dem Server verbunden ist und der Server die Verbindung getrennt hat und Sie sich erneut verbinden wollen. Die Funktion gibt bei Erfolg 0, ansonsten, wenn der Server nicht mehr da ist oder ein Fehler auftrat, ungleich 0 zurück. Was genau passiert ist, sollten Sie am besten mit mysql_error() und mysql_errno() überprüfen. Folgende Fehler können dabei aufgetreten sein:

gp  CR_COMMANDS_OUT_OF_SYNC – Befehle wurden nicht in der korrekten Reihenfolge ausgeführt.
gp  CR_SERVER_GONE_ERROR – Der MySQL-Server ist weg.
gp  CR_UNKNOWN_ERROR – Ein unbekannter Fehler trat auf.

Wollen Sie für eine Verbindung eine vorgabemäßige Datenbank angeben, dann können Sie folgende Funktion verwenden:

#include <mysql.h>
int mysql_select_db(MYSQL *mysql, const char *db) ;

Damit wird die Datenbank, die Sie mit db angeben, zur aktuellen Datenbank der Verbindung mysql. Alle nachfolgenden Anfragen beziehen sich auf diese Datenbank. Die Funktion schlägt fehl, sofern der Benutzer keine Zugriffsrechte auf diese Datenbank hat. Die Funktion gibt bei Erfolg 0, ansonsten bei einem Fehler ungleich 0 zurück.

Den MySQL-Server herunterfahren können Sie, sofern entsprechende Berechtigungen vorhanden sind, mit der folgenden Funktion:

#include <mysql.h>
int mysql_shutdown(MYSQL *mysql) ;

Konnte der Server erfolgreich heruntergefahren werden, wird 0, ansonsten bei einem Fehler ungleich 0 zurückgegeben.

Benötigen Sie Informationen, ähnlich wie die der Ausgabe von mysqladmin status, können Sie folgende Funktion verwenden:

#include <mysql.h>
const char *mysql_stat(MYSQL *mysql);

Dabei erhalten Sie einen terminierten String zurück, der Informationen wie die Betriebszeit (Uptime) in Sekunden und die Anzahl laufender Threads, Anfragen (Questions), Neuladen (Reloads) und offene Tabellen ausgibt. Konnte der Status nicht erfragt werden oder trat sonst ein Fehler auf, wird NULL zurückgegeben.

Die Kennung (pid) eines aktuellen Threads können Sie mit der Funktion

#include <mysql.h>
unsigned long mysql_thread_id(MYSQL *mysql) ;

ermitteln. Diesen zurückgegebenen Wert können Sie anschließend z. B. verwenden, um den Thread mit der Funktion mysql_kill() zu beenden. Beachten Sie allerdings, wenn eine Verbindung beendet wird und Sie mit mysql_ping() eine neue Verbindung aufbauen, dass sich die Thread-ID ebenfalls verändert hat und Sie somit die Funktion mysql_thread_id() erneut aufrufen sollten, sofern Sie die Kennung benötigen.

Sofern Sie Probleme mit dem Server oder der Clientanwendung haben, können Sie mit der Funktion mysql_debug() ein DBUG_PUSH mit einem angegebenen String durchführen, z. B.:

mysql_debug("d:t:O,/tmp/client.trace");

Mit dieser Funktion wird die Debug-Bibliothek von Fred Fish benutzt. Voraussetzung dafür, dass Sie diese Funktion verwenden können, ist, dass Sie die Client-Bibliothek so kompilieren, dass diese das Debuggen unterstützt. Mit der Funktion mysql_dump_debug_info() weisen Sie den Server an, Debug-Informationen in die Log-Datei zu schreiben (entsprechende Zugriffsrechte vorausgesetzt!). Ich will allerdings auf die Debug-Geschichte nicht näher eingehen, da dies ein umfangreicheres Unterfangen ist und nicht Diskussionspunkt in diesem Buch ist.


Rheinwerk Computing

12.5.13 Veraltete Funktionedowntop

Zum Abschluss folgen hier noch einige Funktionen, die allerdings nicht mehr verwendet werden sollten, da sie als veraltet gelten. Damit ältere Clientanwendungen noch mit einer neueren API-Bibliothek übersetzt werden können, sind diese Funktionen noch vorhanden. Falls Sie eine ältere Clientanwendung verändern müssen und Sie auf eine dieser Funktionen treffen, wissen Sie wenigstens Bescheid, worum es sich handelt.

Mit der Funktion

#include <mysql.h>
int mysql_create_db(MYSQL *mysql, const char *db) ;

kann eine Datenbank, die mit dem Parameter db angegeben wird, erzeugt werden. Als Alternative wird empfohlen, die Funktion mysql_real_query() und das SQL-Statement CREATE DATABASES zu verwenden, z. B.:

const char buf[BUF] = "CREATE DATABASE datenbank";
mysql_real_query( my, buf, strlen(buf));

Ebenso sieht es mit der Funktion

#include <mysql.h>
int mysql_drop_db(MYSQL *mysql, const char *db) ;

aus. Damit wird eine Datenbank, die mit dem Parameter db angegeben wird, gelöscht. Auch hierzu sollte mysql_real_query() mit dem SQL-Statement DROP_DATABASES bevorzugt werden:

const char buf[BUF] = "DROP DATABASE datenbank";
mysql_real_query(my, buf, strlen(buf));

Mit der Funktion

#include <mysql.h>
my_bool mysql_eof(MYSQL_RES *result) ;

kann festgestellt werden, ob die letzte Zeile einer Ergebnismenge gelesen wurde oder nicht. Es wird allerdings empfohlen, die Funktionen mysql_error() und mysql_errno() zu verwenden, da diese Funktionen diesen Fehler ebenfalls überprüfen und auch mehr Auskunft zur Verfügung stellen als mysql_eof(), die nur einen booleschen Wert zurückgibt und keine Meldung darüber, warum der Fehler auftrat.

Um die Anforderung an den Server zu stellen, dass er die Berechtigungstabelle neu laden soll, kann die Funktion

#include <mysql.h>
int mysql_reload(MYSQL *mysql) ;

verwendet werden. Voraussetzung ist natürlich eine entsprechende Berechtigung dazu. Auch hierbei sei empfohlen, die Funktion mysql_real_query() mit dem SQL-Statement FLUSH_PRIVILEGS zu verwenden.


Rheinwerk Computing

12.5.14 Neue Funktionen ab Version 4.1.toptop

Die Weiterentwicklung der MySQL C-API schläft nicht und wird munter vorangetrieben. Schon seit der Version 4.1.x ist wieder bereits eine Menge neuer Funktionen hinzugekommen – und ein Ende ist nicht in Sicht. Leider hat man bei einem Buch das Problem, je nach Verkaufszahlen, höchstens jedes Jahr ein Update (Neuauflage) zu machen. In der folgenden Tabelle finden Sie einen kurzen Überblick über neuere Funktionen ab Version 4.1.x:


Tabelle 12.9    Weitere neuere Funktionen ab MySQL C-API 4.1.x

Funktion Bedeutung
mysql_sqlstate() Gibt einen nullterminierten String mit einem SQLSTATE-Fehlercode für den letzten aufgetretenen Fehler zurück. Der Fehlercode beinhaltet fünf Zeichen. So bedeutet '00000', dass kein Fehler aufgetreten ist. Dieser Wert wird von ANSI SQL und ODBC vorgegeben. Für eine Liste der Fehlercodes sei auf die (englische) Dokumentation hingewiesen.
mysql_warning_count() Gibt die Anzahl der Warnungen zurück, die bei einer Ausführung eines SQL-Statements erzeugt wurden.
mysql_commit() Legt die aktuellen Transaktionen fest.
mysql_rollback() Macht die letzte Transaktion rückgängig.
mysql_autocommit() Kann gesetzt oder nicht gesetzt werden, damit eine Transaktion automatisch durchgeführt wird.
mysql_more_results() Funktion gibt TRUE zurück, wenn eine SQL-Anfrage mehrere Resultate enthält. Falls dies der Fall ist, muss mysql_next_result() aufgerufen werden, um die Menge abzuholen.
mysql_next_result() Wenn mehrere Ergebnismengen existieren, liest mysql_next_result() immer das Resultat der nächsten Anfrage und gibt den Status an die Anwendung zurück (0 = es sind noch mehr Resultate vorhanden; -1 = kein Resultat mehr vorhanden).

 << zurück
  
  Zum Rheinwerk-Shop
Neuauflage: Linux-UNIX-Programmierung
Neuauflage:
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-
 Programmierung


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern