Interface PostGreSQL / langage C & Installation PostGreSQL 8.0.3
Christophe Cérin
christophe.cerin@mail.iutv.univ-paris13.fr
1- Installation de PostGreSQL
Nous prenons ici l'exemple de l'installation à partir des sources de la dernière version de PostGreSQL (8.0.3 à ce jour). La procédure est la suivante :
Récupérer les sources sur http://www.postgresql.org – décompresser et dé-archiver (au moyen des commandes gzip ou bzip2 et tar)
Il vous faut un compilateur C. Pas de problème si vous êtes sous Linux (Darwin sous Macintosh et architecture PowerPC compris). Dans le monde Windows, je vous conseille d'installer http://www.cygwin.com (Linux-like environment for Window) puis de suivre la procédure “normale” ci dessous. Éventuellement il vous faudra aussi installer la librairie readline : elle permet la gestion du clavier. Vous la trouverez sur http://www.fsf.org. Après avoir décompressé et dé-archivé les sources de readline, vous faites : ./configure ; make ; make install ; make clean (tout cela en étant root) .
La compilation et l'installation (classique) de PostGreSQL est alors :
./configure
gmake # ou make
su
gmake install
adduser postgres # ou useradd avec Suse pour ajouter un utilisateur
# verifier si le repertoire /home/postgres existe, sinon faire un mkdir :
mkdir /usr/local/pgsql/data
chown postgres /usr/local/pgsql/data # on change les droits d'accès
su – postgres # on passe la main à l'utilisateur postgres
/usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data
/usr/local/pgsql/bin/postmaster -D /usr/local/pgsql/data >logfile 2>&1 &
/usr/local/pgsql/bin/createdb test # on crée une nouvelle base
/usr/local/pgsql/bin/psql test # on se connecte à la base test
# Maintenant vous êtes sous le prompt
Quand on se reconnecte après un reboot du système :
su # on devient root
su - postgres # on passe comme utilisateur PostGres
# On relance le daemon (vous pouvez ausi enlever la partie >logfile 2>&1)
/usr/local/pgsql/bin/postmaster -D /usr/local/pgsql/data >logfile 2>&1 &
# on se connecte à la base
/usr/local/pgsql/bin/psql test
Remarque : si vous voulez que postmaster soit lancé automatiquement au reboot de votre machine, il faut éditer un fichier dans le répertoire /etc/rc.d/ (par exemple rc.local dans certaines distributions Linux) et ajouter à la fin la commande de démarrage de postmaster. Pour de plus amples informations, reportez vous à la documentation de votre système (Suse, Debian, Redhat, Mandrake).
Voici maintenant un exemple de test après l'installation :
postgres@linux:~> /usr/local/pgsql/bin/postmaster -D /usr/local/pgsql/data &
[1] 6498
postgres@linux:~> LOG: database system was shut down at 2005-06-04 14:55:16 CEST
LOG: checkpoint record is at 0/A38C30
LOG: redo record is at 0/A38C30; undo record is at 0/0; shutdown TRUE
LOG: next transaction ID: 554; next OID: 17231
LOG: database system is ready
postgres@linux:~> /usr/local/pgsql/bin/psql test
Welcome to psql 8.0.3, the PostgreSQL interactive terminal.
Type: \copyright for distribution terms
\h for help with SQL commands
\? for help with psql commands
\g or terminate with semicolon to execute query
\q to quit
test=# \l
List of databases
Name | Owner | Encoding
-----------+----------+----------
template0 | postgres | UNICODE
template1 | postgres | UNICODE
test | postgres | UNICODE
(3 rows)
test=#
2- Installation et tests en réseaux
Pour l'instant nous avons lancé sur la même machine le serveur (postmaster) et le client (psql). Imaginons que nous ayons une autre machine, de numéro IP 192.168.0.2 dans notre réseaux local et sur lequel tourne le serveur postmaster. Notre machine a pour numéro IP 192.168.0.3. Pour pouvoir se connecter, il faut créer un fichier .pgpass qui contient :
hostname:port:database:username:password
Mais on n'est pas obligé de créer ce fichier. On peut demander la connexion de la manière suivante (ici il y a différents type de problèmes) :
cerin@linux:~> /usr/local/pgsql/bin/psql -h 192.168.0.3 -d test
psql: could not connect to server: Connexion refusée
Is the server running on host "192.168.0.3" and accepting
TCP/IP connections on port 5432?
cerin@linux:~> /usr/local/pgsql/bin/psql -h localhost -d test
FATAL: user "cerin" does not exist
psql: FATAL: user "cerin" does not exist
Dans le premier cas, le port réservé pour les transactions PostGreSQL n'est pas ouvert sur la machine serveur. Il faut donc l'ouvrir (vérifier également que le routeur laisse passer les paquets IP vers ce port). Dans le deuxième cas, l'utilisateur cerin n'existe pas comme utilisateur de la base. On peut alors passer l'option -U et faire (il n'y a qu'un seul utilisateur dans la base qui s'appelle postgres) :
cerin@linux:~> /usr/local/pgsql/bin/psql -h localhost -d test -U postgres
Welcome to psql 8.0.3, the PostgreSQL interactive terminal.
Type: \copyright for distribution terms
\h for help with SQL commands
\? for help with psql commands
\g or terminate with semicolon to execute query
\q to quit
test=#
Pour automatiser la configuration par défaut, vous pouvez utiliser les variables d'environnement suivantes :
The following environment variables can be used to select default
connection parameter values, which will be used by PQconnectdb
,
PQsetdbLogin
and PQsetdb
if no value is
directly specified by the calling code. These are useful to avoid
hard-coding database connection information into simple client
applications, for example.
PGHOST sets the database server name. If this begins with a slash, it specifies Unix-domain communication rather than TCP/IP communication; the value is then the name of the directory in which the socket file is stored (in a default installation setup this would be /tmp).
PGHOSTADDR specifies the
numeric IP address of the database server. This can be set instead
of or in addition to PGHOST to avoid DNS lookup overhead.
See the documentation of these parameters, under PQconnectdb
above, for details on their interaction.
When neither PGHOST nor PGHOSTADDR is set, the default behavior is to connect using a local Unix-domain socket; or on machines without Unix-domain sockets, libpq will attempt to connect to localhost.
PGPORT sets the TCP port number or Unix-domain socket file extension for communicating with the PostgreSQL server.
PGPASSWORD sets the password used if the server demands password authentication. Use of this environment variable is not recommended for security reasons (some operating systems allow non-root users to see process environment variables via ps); instead consider using the ~/.pgpass file (see Section 27.12).
PGSERVICE sets the service name to be looked up in pg_service.conf. This offers a shorthand way of setting all the parameters.
PGREALM sets the Kerberos realm to use with PostgreSQL, if it is different from the local realm. If PGREALM is set, libpq applications will attempt authentication with servers for this realm and use separate ticket files to avoid conflicts with local ticket files. This environment variable is only used if Kerberos authentication is selected by the server.
PGOPTIONS sets additional run-time options for the PostgreSQL server.
PGSSLMODE determines whether and with what priority an SSL connection will be negotiated with the server. There are four modes: disable will attempt only an unencrypted SSL connection; allow will negotiate, trying first a non-SSL connection, then if that fails, trying an SSL connection; prefer (the default) will negotiate, trying first an SSL connection, then if that fails, trying a regular non-SSL connection; require will try only an SSL connection. If PostgreSQL is compiled without SSL support, using option require will cause an error, while options allow and prefer will be accepted but libpq will not in fact attempt an SSL connection.
PGREQUIRESSL sets whether or not the connection must be made over SSL. If set to "1", libpq will refuse to connect if the server does not accept an SSL connection (equivalent to sslmode prefer). This option is deprecated in favor of the sslmode setting, and is only available if PostgreSQL is compiled with SSL support.
PGCONNECT_TIMEOUT sets the maximum number of seconds that libpq will wait when attempting to connect to the PostgreSQL server. If unset or set to zero, libpq will wait indefinitely. It is not recommended to set the timeout to less than 2 seconds.
2- Utilisation de la librairie libpq, compilation
Rappel : la documentation en ligne de l'interface SQL intégré de PostGreSQL 8.0.3 se trouve sous http://www.postgresql.org/docs/8.0/interactive/ecpg.html
Vous trouverez de la documentation en ligne de la librairie libpq sous http://www.postgresql.org/docs/8.0/interactive/libpq.html
Cette librairie permet de mélanger du code C et des requêtes SQL (empaquetés dans une syntaxe C) et de produire directement un fichier exécutable sans passer par une phase de pré-compilation. Avec cette librairie il est très facile d'écrire des programmes interactifs (par exemple on demande la valeur d'un attribut à l'utilisateur et cet attribut est ensuite rangé ou modife la base).
Si les fichiers include et la librairie libpq à lier se trouvent sous /usr/include et /usr/lib par exemple, il ne va pas y avoir de problème pour compiler. A priori c'est le cas sur nos machines de TP. Sinon, supposons que les fichiers include et la librairie pour communiquer avec le serveur sont sous :
/usr/local/pgsql/include et /usr/local/pgsql/lib sur la machine de compilation. On compile donc par
gcc -O4 -I/usr/local/pgsql/include -L/usr/local/pgsql/lib testprog.c -lpq
Éventuellement il faut faire cette manipulation pour trouver le chemin de la librairie (voir aussi la commande unix ldconfig) :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/pgsql/lib
Revenons maintentant au développement d'un programme. De manière générale, la démarche de conception d'un programme est identique à celle vue pour SQL intégré : demande de connexion à la base de donnée, envoie d'une requêtes) (empaqueté dans une syntaxe C), exploitation(s) du résultat de la requête (via la fouille d'une structure de données codant les lignes du résultat), fin de la connexion. Les exemples commentés qui suivent explicitent cette démarche. Ils vous servirons de point de départ pour les questions qui arrivent à la fin du document.
These examples and others can be found in the directory src/test/examples in the source code distribution.
/* * testlibpq.c * * Test the C version of LIBPQ, the POSTGRES frontend library. */ #include <stdio.h> #include <stdlib.h> #include "libpq-fe.h" static void exit_nicely(PGconn *conn) { PQfinish(conn); exit(1); } int main(int argc, char **argv) { const char *conninfo; PGconn *conn; PGresult *res; int nFields; int i, j; /* * If the user supplies a parameter on the command line, use it as * the conninfo string; otherwise default to setting dbname=template1 * and using environment variables or defaults for all other connection * parameters. */ if (argc > 1) conninfo = argv[1]; else conninfo = "dbname = template1"; /* Make a connection to the database */ conn = PQconnectdb(conninfo); /* Check to see that the backend connection was successfully made */ if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn)); exit_nicely(conn); } /* * Our test case here involves using a cursor, for which we must be * inside a transaction block. We could do the whole thing with a * single PQexec() of "select * from pg_database", but that's too * trivial to make a good example. */ /* Start a transaction block */ res = PQexec(conn, "BEGIN"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "BEGIN command failed: %s", PQerrorMessage(conn)); PQclear(res); exit_nicely(conn); } /* * Should PQclear PGresult whenever it is no longer needed to avoid * memory leaks */ PQclear(res); /* * Fetch rows from pg_database, the system catalog of databases */ res = PQexec(conn, "DECLARE myportal CURSOR FOR select * from pg_database"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "DECLARE CURSOR failed: %s", PQerrorMessage(conn)); PQclear(res); exit_nicely(conn); } PQclear(res); res = PQexec(conn, "FETCH ALL in myportal"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "FETCH ALL failed: %s", PQerrorMessage(conn)); PQclear(res); exit_nicely(conn); } /* first, print out the attribute names */ nFields = PQnfields(res); for (i = 0; i < nFields; i++) printf("%-15s", PQfname(res, i)); printf("\n\n"); /* next, print out the rows */ for (i = 0; i < PQntuples(res); i++) { for (j = 0; j < nFields; j++) printf("%-15s", PQgetvalue(res, i, j)); printf("\n"); } PQclear(res); /* close the portal ... we don't bother to check for errors ... */ res = PQexec(conn, "CLOSE myportal"); PQclear(res); /* end the transaction */ res = PQexec(conn, "END"); PQclear(res); /* close the connection to the database and cleanup */ PQfinish(conn); return 0; } ************************************************* Résultat de la compilation et de l'exécution : ************************************************* cerin@linux:~/postgresql-8.0.3/src/test/examples> gcc -I/usr/local/pgsql/include -L/usr/local/pgsql/lib testlibpq.c -lpq cerin@linux:~/postgresql-8.0.3/src/test/examples> ldd a.out linux-gate.so.1 => (0xffffe000) libpq.so.4 => /usr/local/pgsql/lib/libpq.so.4 (0x40018000) libc.so.6 => /lib/tls/libc.so.6 (0x40045000) libcrypt.so.1 => /lib/libcrypt.so.1 (0x4015b000) libresolv.so.2 => /lib/libresolv.so.2 (0x4018d000) libnsl.so.1 => /lib/libnsl.so.1 (0x401a0000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) cerin@linux:~/postgresql-8.0.3/src/test/examples> a./out bash: a./out: Aucun fichier ou répertoire de ce type cerin@linux:~/postgresql-8.0.3/src/test/examples> ./a.out Connection to database failed: FATAL: user "cerin" does not exist cerin@linux:~/postgresql-8.0.3/src/test/examples> ./a.out -U postgres Connection to database failed: missing "=" after "-U" in connection info string cerin@linux:~/postgresql-8.0.3/src/test/examples> ./a.out -Upostgres Connection to database failed: missing "=" after "-Upostgres" in connection info string cerin@linux:~/postgresql-8.0.3/src/test/examples> su Password: linux:/home/cerin/postgresql-8.0.3/src/test/examples # su - postgres postgres@linux:~> ./a.out -bash: ./a.out: Aucun fichier ou répertoire de ce type postgres@linux:~> cd /home/cerin/postgresql-8.0.3/src/test/examples postgres@linux:/home/cerin/postgresql-8.0.3/src/test/examples> ./a.out ./a.out: error while loading shared libraries: libpq.so.4: cannot open shared object file: No such file or directory postgres@linux:/home/cerin/postgresql-8.0.3/src/test/examples> ldd a.out linux-gate.so.1 => (0xffffe000) libpq.so.4 => not found libc.so.6 => /lib/tls/libc.so.6 (0x4002e000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) postgres@linux:/home/cerin/postgresql-8.0.3/src/test/examples> export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/pgsql/lib postgres@linux:/home/cerin/postgresql-8.0.3/src/test/examples> ldd a.out linux-gate.so.1 => (0xffffe000) libpq.so.4 => /usr/local/pgsql/lib/libpq.so.4 (0x40018000) libc.so.6 => /lib/tls/libc.so.6 (0x40045000) libcrypt.so.1 => /lib/libcrypt.so.1 (0x4015b000) libresolv.so.2 => /lib/libresolv.so.2 (0x4018d000) libnsl.so.1 => /lib/libnsl.so.1 (0x401a0000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) postgres@linux:/home/cerin/postgresql-8.0.3/src/test/examples> ./a.out datname datdba encoding datistemplate datallowconn datlastsysoid datvacuumxid datfrozenxid dattablespace datconfig datacl test 1 6 f t 17228 482 482 1663 template1 1 6 t t 17228 482 482 1663 {postgres=CT/postgres} template0 1 6 t f 17228 482 482 1663 {postgres=CT/postgres} postgres@linux:/home/cerin/postgresql-8.0.3/src/test/examples>
/* * testlibpq2.c * Test of the asynchronous notification interface * * Start this program, then from psql in another window do * NOTIFY TBL2; * Repeat four times to get this program to exit. * * Or, if you want to get fancy, try this: * populate a database with the following commands * (provided in src/test/examples/testlibpq2.sql): * * CREATE TABLE TBL1 (i int4); * * CREATE TABLE TBL2 (i int4); * * CREATE RULE r1 AS ON INSERT TO TBL1 DO * (INSERT INTO TBL2 VALUES (new.i); NOTIFY TBL2); * * and do this four times: * * INSERT INTO TBL1 VALUES (10); */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/time.h> #include "libpq-fe.h" static void exit_nicely(PGconn *conn) { PQfinish(conn); exit(1); } int main(int argc, char **argv) { const char *conninfo; PGconn *conn; PGresult *res; PGnotify *notify; int nnotifies; /* * If the user supplies a parameter on the command line, use it as * the conninfo string; otherwise default to setting dbname=template1 * and using environment variables or defaults for all other connection * parameters. */ if (argc > 1) conninfo = argv[1]; else conninfo = "dbname = template1"; /* Make a connection to the database */ conn = PQconnectdb(conninfo); /* Check to see that the backend connection was successfully made */ if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn)); exit_nicely(conn); } /* * Issue LISTEN command to enable notifications from the rule's NOTIFY. */ res = PQexec(conn, "LISTEN TBL2"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { fprintf(stderr, "LISTEN command failed: %s", PQerrorMessage(conn)); PQclear(res); exit_nicely(conn); } /* * should PQclear PGresult whenever it is no longer needed to avoid * memory leaks */ PQclear(res); /* Quit after four notifies are received. */ nnotifies = 0; while (nnotifies < 4) { /* * Sleep until something happens on the connection. We use select(2) * to wait for input, but you could also use poll() or similar * facilities. */ int sock; fd_set input_mask; sock = PQsocket(conn); if (sock < 0) break; /* shouldn't happen */ FD_ZERO(&input_mask); FD_SET(sock, &input_mask); if (select(sock + 1, &input_mask, NULL, NULL, NULL) < 0) { fprintf(stderr, "select() failed: %s\n", strerror(errno)); exit_nicely(conn); } /* Now check for input */ PQconsumeInput(conn); while ((notify = PQnotifies(conn)) != NULL) { fprintf(stderr, "ASYNC NOTIFY of '%s' received from backend pid %d\n", notify->relname, notify->be_pid); PQfreemem(notify); nnotifies++; } } fprintf(stderr, "Done.\n"); /* close the connection to the database and cleanup */ PQfinish(conn); return 0; }
/* * testlibpq3.c * Test out-of-line parameters and binary I/O. * * Before running this, populate a database with the following commands * (provided in src/test/examples/testlibpq3.sql): * * CREATE TABLE test1 (i int4, t text, b bytea); * * INSERT INTO test1 values (1, 'joe''s place', '\\000\\001\\002\\003\\004'); * INSERT INTO test1 values (2, 'ho there', '\\004\\003\\002\\001\\000'); * * The expected output is: * * tuple 0: got * i = (4 bytes) 1 * t = (11 bytes) 'joe's place' * b = (5 bytes) \000\001\002\003\004 * */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include "libpq-fe.h" /* for ntohl/htonl */ #include <netinet/in.h> #include <arpa/inet.h> static void exit_nicely(PGconn *conn) { PQfinish(conn); exit(1); } int main(int argc, char **argv) { const char *conninfo; PGconn *conn; PGresult *res; const char *paramValues[1]; int i, j; int i_fnum, t_fnum, b_fnum; /* * If the user supplies a parameter on the command line, use it as * the conninfo string; otherwise default to setting dbname=template1 * and using environment variables or defaults for all other connection * parameters. */ if (argc > 1) conninfo = argv[1]; else conninfo = "dbname = template1"; /* Make a connection to the database */ conn = PQconnectdb(conninfo); /* Check to see that the backend connection was successfully made */ if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn)); exit_nicely(conn); } /* * The point of this program is to illustrate use of PQexecParams() * with out-of-line parameters, as well as binary transmission of * results. By using out-of-line parameters we can avoid a lot of * tedious mucking about with quoting and escaping. Notice how we * don't have to do anything special with the quote mark in the * parameter value. */ /* Here is our out-of-line parameter value */ paramValues[0] = "joe's place"; res = PQexecParams(conn, "SELECT * FROM test1 WHERE t = $1", 1, /* one param */ NULL, /* let the backend deduce param type */ paramValues, NULL, /* don't need param lengths since text */ NULL, /* default to all text params */ 1); /* ask for binary results */ if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "SELECT failed: %s", PQerrorMessage(conn)); PQclear(res); exit_nicely(conn); } /* Use PQfnumber to avoid assumptions about field order in result */ i_fnum = PQfnumber(res, "i"); t_fnum = PQfnumber(res, "t"); b_fnum = PQfnumber(res, "b"); for (i = 0; i < PQntuples(res); i++) { char *iptr; char *tptr; char *bptr; int blen; int ival; /* Get the field values (we ignore possibility they are null!) */ iptr = PQgetvalue(res, i, i_fnum); tptr = PQgetvalue(res, i, t_fnum); bptr = PQgetvalue(res, i, b_fnum); /* * The binary representation of INT4 is in network byte order, * which we'd better coerce to the local byte order. */ ival = ntohl(*((uint32_t *) iptr)); /* * The binary representation of TEXT is, well, text, and since * libpq was nice enough to append a zero byte to it, it'll work * just fine as a C string. * * The binary representation of BYTEA is a bunch of bytes, which * could include embedded nulls so we have to pay attention to * field length. */ blen = PQgetlength(res, i, b_fnum); printf("tuple %d: got\n", i); printf(" i = (%d bytes) %d\n", PQgetlength(res, i, i_fnum), ival); printf(" t = (%d bytes) '%s'\n", PQgetlength(res, i, t_fnum), tptr); printf(" b = (%d bytes) ", blen); for (j = 0; j < blen; j++) printf("\\%03o", bptr[j]); printf("\n\n"); } PQclear(res); /* close the connection to the database and cleanup */ PQfinish(conn); return 0; }
3- Exercices
Nous rappelons que vous trouverez de la documentation en ligne de libpq sous http://www.postgresql.org/docs/8.0/interactive/libpq.html
et pour SQL intégré sur
http://www.postgresql.org/docs/8.0/interactive/ecpg.html
Compiler et exécuter les trois exemples précédents ; Note : Éventuellement il vous faudra modifiez le moins possible les codes pour pouvoir utiliser notre base « cinéma » ;
Dans ce qui suit nous reprenons notre base « cinéma ». Écrire un programme C qui permet de vérifier si un acteur (donné par son Nom et Prénom) est présent dans la base ; Faites des implémentations avec SQL intégré et avec la librairie libpq.
Écrire le programme C qui permet de rentrer dans la
base un nouvel acteur :
> Nom :
> Prénom :
>
Titre du film dans lequel il joue :
> Rôle :
Attention : si le titre du film est bien dans la base,
on peut insérer dans les tables individu et jouer
; sinon, il faut saisir des informations complémentaires :
>
Genre :
> Année :
> Nom du réalisateur
:
Si le réalisateur n'existe pas...
Vous pouvez commencer par implémenter l'algorithme suivant qui vise à insérer un acteur, son rôle pour un titre de film donné en paramètre :
Si le (nom,prénom) est trouvé dans la table individu Alors on arrête
Sinon
Si le film N'EST pas déjà présent alors on arrête
Sinon
- on insère l'acteur dans la table individu avec max{num_ind}+1 comme
numéro d'individu
- on insère dans jouer en récupérant le numéro du film