Postgresql, et C++


Table des matières

Développement C++ autour de Postgresql
Mise en place des éléments nécessaires
Configuration de Postgresql
Exemple de programme en C++ qui utilise la base
Compilation et édition de liens
Documentation de la classe PgDatabase
Problématique de la POO associée aux bases de données

Développement C++ autour de Postgresql

Mise en place des éléments nécessaires

Si vous n'avez pas encore installé postgresql, vous pouvez le faire en tapant les commandes suivantes:


	$sudo urpmi postgresql
	$sudo urpmi postgresql-devel

Urpmi durant cette installation a créé un utilisateur: postgres. Il est souhaitable de lui changer son mot de passe : en tant que root tapez passwd postgres et fournissez le futur mot de passe de postgres.

Il faut ensuite installer la librairie libpq++.

Elle n'est pas fournie avec la distribution mandrake classique, par contre elle est fournie en contribution libre. Le plus simple pour cela est de rajouter la source de média logiciel "contrib" aux sources de vos médias logiciels.

comment s'y prendre ?

Lancer le gestionnaire de source de médias logiciels, ajouter une source de donnée avec comme adresse par exemple: ftp://ftp.club-internet.fr/pub/linux/Mandrake/current/i586/media/contrib/ et comme chemin relatif pour hdlist.cz : ./media_info/hdlist.cz

Il faut ensuite installer les paquets: urpmi libpq++4 libpq++4-devel . Cette fois urpmi va les trouver les télécharger et les installer.

Certains fichiers .h qui viennent d'être installés sont à modifier:

Il est ainsi nécessaire de rajouter la ligne: using namespace std; en début du fichier "pgconnection.h". Le fichier pgconnection.h se trouve probablement dans le répertoire "/usr/include/pgsql/libpq++/".

Si vous ne le trouvez pas c'est que la libpq++4-devel n'a pas été installée.

Les .h contiennent aussi une directive de compilation DLLIMPORT qui ne passe pas à la compilation. Il faut donc l'enlever. Vim est votre ami et va se charger de cela tout seul:


$su
#cd /usr/include/pgsql/libpq++/
#vim -c "argdo %s/DLLIMPORT/ /g | wq" *.h 
#exit
$

			

Explications: vous lancez vim sur tous les fichiers ".h" du répertoire courant en lui donnant deux commandes séparées par un"|".La première commande %s/DLLIMPORT/ /g remplace tous les DLLIMPORT du fichier par un espace. Et la deuxième commande: wq quitte aprés avoir sauvé.

Configuration de Postgresql

Administrons Postgresql

Paramétrage, création d'un utilisateur et d'une base pour cet utilisateur

Paramétrage de Postgresql

Par défaut la sécurité de postgres est basée sur l'identification préalable d'un utilisateur dans le système. Nous souhaitons ici les identifier grâce à un mot de passe fourni à postgresql lors de la création de l'utilisateur. Il va donc nous falloir modifier le fichier pg_hba.conf.


	#locate pg_hba.conf

Vous devriez obtenir le résultat suivant:


	/usr/share/pgsql/pg_hba.conf.sample
	/var/lib/pgsql/data/pg_hba.conf

Il faut maintenant l'éditer:

#vim /var/lib/pgsql/data/pg_hba.conf

Modifications à opérer:


local   all     postgres        ident   sameuser	#postgres lui n'a pas besoin de password s'il travaille sur sa base de donnée
local  all     all     md5 				#tous les utilisateurs locaux  sont identifiés par un password md5

Il faut alors procéder à l'activation des changements:


#export PGDATA="/var/lib/pgsql/data"
#pg_ctl reload

création de l'utilisateur

Nous allons maintenant créer l'utilisateur "useressais" et sous cet utilisateur, nous créerons la base de donnée "essais".

Devenons postgres.

#su postgres

Créons l'utilisateur "useressai"

$createuser -d -P useressai

Création en tant que "useressai" de la base de données "essai"

$createdb -Uuseressai -P dbessai

Création des tables dans la base

Nous allons maintenant travailler sur cette base de données en tant qu'utilisateur "useressai".

	$psql -Uuseressai dbessai
	psql>create table bidon(nom varchar(25));
	psql>insert into bidon values('jean');
	psql>insert into bidon values('paul');
	psql>\q

Exemple de programme en C++ qui utilise la base

Examinons maintenant le programme en c++ suivant:

fichier: essaisSql.cpp

	/*
	 *          *  libpq++ programme de démo classe PgDatabase
	 *          */
	using namespace std;
	#include <iostream>
	#include <libpq++.h>		//à consulter pour savoir ce que fait la classe

	int main ()
	{
		PgDatabase maBase ("dbname=essai user=useressai password=p-5K3g9ù");	// connexion à la base

		if (maBase.ConnectionBad ())	// La connexion a-t-elle échoué ?
		{
			cerr << "La connexion à la base a echoué." 	<< endl
			<< "Erreur: "<< maBase.ErrorMessage ()<< endl;
			exit(1);//sortie avec le code d'erreur 1 du programme
		}
		else
		{
			string query="select * from bidon";
			if (!maBase.ExecTuplesOk (query.c_str()))	// on balance la requête et on attend le résultat
			{
				cerr<< "le SELECT a échoué"<<endl;
				exit(2);//sortie avec le code d'erreur 2 du programme
			}
			else
			{
			for (int noTuple = 0; noTuple < maBase.Tuples (); noTuple++)	// pour chaque ligne du résultat
				{
				cout<< maBase.GetValue (noTuple, 0)<< endl;	//  affichage du champ 0 de la ligne noTuple
				}
				return 0;
			}
		}//fin du else
	}//fin du main

Compilation et édition de liens

Pour compiler:


			g++ -c psql.cpp -I/usr/include/pgsql/
		

le -I permet d'indiquer à g++ dans quel répertoire il peut trouver les entêtes (*.h) que l'on a inclus dans notre fichier source. La commande

sudo locate libpq++.h

vous renseignera sur son emplacement.

Pour linker


			g++ -o essais essaisSql.o -lpq++
		

l'option -l permet d'indiquer à g++ qu'il doit lier notre programme à la librairie partagée libpq++.so.

Documentation de la classe PgDatabase

Attention pour la convention de nommage : classes et méthodes ont dans cette librairie leur première lettre en majuscule.

Le constructeur reçoit en paramètre la chaîne de connexion.

Lors de la construction, la connection au moteur tente de s'établir.

bool ConnectionBad(void)

renvoie l'état de la connexion

bool ExecTuplesOK(char * laRequete)

exécute la requête et renvoie le résultat de l'exécution.

int Tupples()

renvoie le nombre de lignes renvoyées par la requête

int Fields()

renvoie le nombre de colonnes renvoyées par la requête.

char* FieldName(int noChamp)

renvoie le nom du champ dont le numéro est passé en paramètre.

char * GetValue(noTuple,noChamp)

renvoie sous forme de vieille chaîne AZT (c) la valeur du champ indiqué dans la ligne indiquée.

D'autres méthodes sont disponibles

locate libpq++|grep doc

vous permet de touver la documentation de la classe. Ensuite un petit

konqueror /usr/share/doc/libpq++4-devel-4.0/libpq++.html

devrait vous permettre de trouver votre bonheur pour apprendre par exemple à récupérer les messages d'erreurs du serveur postgresql.

Petit exercice

Ecrire un programme qui lit des requêtes sql tapées par l'utilisateur et en affiche le résultat. Vous pouvez le faire indifféremment en mode console ou en mode graphique avec QT. Si vous le faîtes avec QT modifiez votre fichier qmake.conf (locate qmake.conf)de façon à rajouter le -lpq++ et le -I/usr/include/pgsql.

Affichage de tous les champs d'une table entière

Ecrire un programme qui affiche le contenu de la table "films".

Vous avez remarqué que la classe PgDatabase utilise des vieux char * en paramètre. De plus, les valeurs issues de la base de donnée sont toutes fournies sous forme de char *. Il sera donc nécessaire d'opérer des conversions de type.Voici un programme qui réalise des conversions dont vous devez vous inspirer.

#include <string>
#include <iostream>
#include <sstream>

using namespace std;

bool isDouble( const std::string & Str )
{
	// créer un flux à partir de la chaîne donnée
	std::istringstream iss( Str );
	// créer un objet temporaire pour la conversion
	double tmp;
	// tenter la conversion et
	// vérifier qu'il ne reste plus rien dans la chaîne
	return ( iss >> tmp ) && ( iss.eof() );
}
bool isLong( const std::string & Str )
{
	// créer un flux à partir de la chaîne donnée
	std::istringstream iss( Str );
	// créer un objet temporaire pour la conversion
	long tmp;
	// tenter la conversion et
	// vérifier qu'il ne reste plus rien dans la chaîne
	return ( iss >> tmp ) && ( iss.eof() );
}

long toLong(const std::string & Str )
{
	if(isLong(Str))
	{
		// créer un flux à partir de la chaîne donnée
		std::istringstream iss( Str );
		// créer un objet temporaire pour la conversion
		long tmp;
		// faire la conversion et renvoyer un long
		iss >> tmp;
		return ( tmp); 
	}
	else
		return false;
}


double toDouble(const std::string & Str )
{
	if(isDouble(Str))
	{
		// créer un flux à partir de la chaîne donnée
		std::istringstream iss( Str );
		// créer un objet temporaire pour la conversion
		double tmp;
		// faire la conversion et renvoyer le résultat
		iss >> tmp;
		return ( tmp); 
	}
	else
		return false;
}
int main()
{
	string strChaineAParser;
	long unEntier;
	float unReel;

	//transfo string vers entier
	cout<<"Tapez votre entier : ";
	getline(cin,strChaineAParser);
	if(isLong(strChaineAParser))
	{
		unEntier=toLong(strChaineAParser);
		cout<<"Vous avez tapé l'entier: "<<unEntier<<" 2 * "<<unEntier<<" ="<<2*unEntier<<endl;
	}

	//sinon
	else
		cout<<endl<<strChaineAParser<<" n'est pas un entier"<<endl;


	//transfo string vers réel
	cout<<"Tapez votre réel : ";
	//je saisie une string
	getline(cin,strChaineAParser);
	//je prend ce qu'il y a dans le stringstream pour essayer de le mettre dans un réel
	if(isDouble(strChaineAParser))
	{
		unReel=toDouble(strChaineAParser);
		//si c'est un réel
		cout<<"Vous avez tapé le réel: "<<unReel<<" 2.5 * "<<unReel<<" ="<<2.5*unReel<<endl;
	}
	//sinon
	else
		cout<<endl<<strChaineAParser<<" n'est pas un réel."<<endl;

	//transfo string vers char *
	
	//char* chaineAZT=(char*)malloc(strChaineAParser.length()+1);
	//strcpy(chaineAZT,strChaineAParser.c_str());
	cout<<endl<<strChaineAParser.c_str()<<endl;

	//transfo char * vers string:
	char * nom="dupond";
	string strNom(nom);
	cout<<strNom<<endl;


}				
			

Gestion des acteurs et des rôles

Ecrire un programme qui permet la gestion intégrale des acteurs et des rôles qu'ils jouent dans les films. On entend par gestion intégrale, l'affichage, l'ajout, la modif et la suppression des informations.

Problématique de la POO associée aux bases de données

Essayons maintenant de développer un programme plus sérieux. Nous allons réfléchir grâce à cet exemple sur la viabilité des objets "métiers" en environnement multi-utilisateurs.

Nous povons nous appuyer sur une gestion de cd-théque.

Par définition, les instances des objets sont en mémoire. Les informations concernant les cds, leurs titres et les chanteurs correspondants sont dans la base de données.

Intuitivement, si nous avions à concevoir une classe chanteur, nous l'aurions créée avec un certain nombre de propriétés:

  • identifiant

  • nom

  • prénom

  • date de naissance

  • nom public

Cette façon de concevoir les classes va nous poser, comme nous allons le voir, plusieurs problèmes:

  • Premier souci, on ne peut pas forcément stocker toutes ces informations en mémoire.

  • Une fois que les informations sont en mémoire comment garantir qu'elles sont les mêmes que dans la base de données ?

Scénario catastrophe: Imaginons que plusieurs utilisateurs du programme l'exécutent en même temps. Il y a alors pas mal de versions de la cd-tèque qui sont stockées en mémoire, autant que de programmes en cours d'exécution.

Si un utilisateur modifie le titre d'un cd, cette modification n'apparaîtra pas chez les autres. Or en toute logique une modification effectuée chez l'un doit être répercutée chez l'autre.

Que de difficultés en vue...

Y a-t-il une solution ?

Bien sûr: Il suffit de ne stocker que les identifiants en mémoire et de travailler avec les informations de la base.

On prendra donc bien soin de ne pas créer les propriétés des objets. Mais alors comment connaître le prénom d'un chanteur?

Il suffit de créer un accesseur string prenom() qui grâce à une requête basée sur l'identifiant va chercher le prénom du chanteur dans la base de données.