ZEOSDBO , une bibliothèque Open Source de composants d'accès aux Bases de Données

Sous-titre

Auteur

Serge Girard

L'aventure des composants ZEOS a commencée dès Delphi 3 , dans l'esprit de fournir des accès à plusieurs types de SGBDs aussi simplement qu'avec ceux de la palette BDE . L'objectif de ce tutoriel est de passer en revue les différents composants de cette bibliothèque . A chacun sera consacré un ou plusieurs exemples d'utilisation . Ceux-ci seront fait avec Delphi 7 et attaqueront une base Firebird (ce qui ne veut pas dire qu'ils ne peuvent s'appliquer à d'autres SGBD ! ) .

Chaque fois que possible , et selon mon expérience , j'indiquerais ce qui pourrait être spécifique .

- Titre : ZEOSDBO , une bibliothèque Open Source de composants d'accès aux Bases de Données

- Auteur : Serge Girard

- Parution : 13 novembre 2014

- Mise à jour : 17 novembre 2014

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 Serge Girard. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Préambule

- II. Téléchargements et Installations

- II-A. Installation de la version stable

- II-B. Les versions S.V.N.

- III. Introduction

- IV. La palette des composants ZEOSDBO

- V. TZConnection : La connexion à la Base de Données

- V-A. Les propriétés de TZConnection

- V-B. Les évènements

- V-C. Les méthodes

- V-C-1. Les procédures et propriétés en lecture seule

- V-C-2. les fonctions

- V-D. En pratique

- V-E. TZConnection , Le coin du mécano

- V-E-1. La création / les modifications à l'exécution

- V-E-2. Les Transactions

- V-E-3. L'encodage

- V-E-4. Les propriétés supplémentaires

- VI. TZQuery : L'instruction SQL par excellence

- VI-A. Les propriétés

- VI-B. Les événements

- VI-C. Les méthodes

- VI-C-1. Exécution du SQL

- VI-C-2. Paramétrage d'une instruction SQL

- VI-C-3. Navigation dans l'ensemble de données

- VI-C-4. Les repères (Bookmarks)

- VI-C-5. Les modifications de l'ensemble de données

- VI-D. La pratique

- VI-E. Le coin du mécano

- VI-E-1. Les propriétés supplémentaires : Properties

- VI-E-2. FetchRows

- VI-E-3. Utilisation de la mémoire cache

- VII. TZReadOnlyQuery : Un ensemble de données en lecture seule

- VIII. TZTable

- IX. TZUpdateSQL

- IX-A. Les propriétés

- IX-B. Les événements

- IX-C. Les méthodes

- IX-D. La pratique

- IX-E. Le coin du mécano

- IX-F. Le coin du curieux

- X. TZStoredProc

- XI. TZSQLProcessor

- XI-A. Les propriétés

- XI-B. Les événements

- XI-C. Les méthodes

- XI-D. La pratique

- XI-E. Le coin du mécano

- XII. TZSQLMetadata

- XIII. TZSequence

- XIV. TZIBEventAlerter

- XIV-A. Les propriétés

- XIV-B. Les événements

- XIV-C. Les méthodes

- XIV-D. La pratique

- XV. TZSQLMonitor

- XV-A. Les propriétés

- XV-B. Les événements

- XV-C. Les méthodes

- XVI. La gestion des erreurs

- XVII. Appendice A : Structure de la Base de Données exemple

- XVIII. Appendice B : Composants d'accès au données Delphi 7

I - Préambule

Ami lecteur , ce qui suit peut être sauter sans nuire à la lecture du tutoriel.

J'ai rédigé cette partie uniquement dans le but de te donner une idée d'ensemble .

J'ai découvert les composants ZEOSDBO , il y a environ cinq ans , suite à l'achat d'un D2010 pro (au lieu d'une version entreprise) et depuis je les utilise régulièrement pour tous mes nouveaux programmes professionnels en lieu et place des composants d'accès aux bases de données fournis .

Que sont les composants ZEOSDBO (aussi connus comme la ZEOSLIB) ?

Comme déjà indiqué dans le résumé de l'article , c'est un ensemble de composants open source , gratuits, permettant d'accéder à plusieurs types de bases de données, dont les principales sont :

- MySQL

- PostgresSQL

- Interbase/Firebird

- Oracle

- SQLite

- MS SQL

- Sybase

- Oracle

Cette liste peut être sujet à changement selon les versions , vous vous référerez au fichier readme de votre version .

La plus ancienne version que j'ai pu télécharger (5.5 commencée en 2001 pour D3-D7, C++ Builder 5-4 et Kylix) proposait une palette de composants spécifiques pour chaque SGBD . Depuis , les auteurs ont préféré faire une couche commune d'accès , ce qui implique moins de composants et surtout une simplification importante . Chaque version majeure voit aussi sont lot de changements sur les IDE supportés et les versions des Bases de Données .

A l'heure où j'écris , la dernière version stable (7.1.3) supporte les IDEs

- Delphi 7,9 et 2005-2010 ainsi que les versions XE à XE7

- Lazarus (FreePascal)

- C++ Builder 12

La version 7.2 est encore en test, j'en parlerais d'avantage lors du prochain chapitre .

II - Téléchargements et Installations

Vous avez certainement remarqué que j'utilise le pluriel . En effet plusieurs options sont à envisager : Soit vous optez pour le téléchargement du zip de la version stable sur le site du projet [http://sourceforge.net/projects/zeoslib] soit vous préférez une version SVN et donc plus récente svn://svn.code.sf.net/p/zeoslib/code-0/trunk voire même une des branches

II-A - Installation de la version stable

Téléchargez le fichier zip et extraire les fichiers où vous le souhaitez , et ouvrez ensuite votre IDE et recherchez le package que vous souhaitez installer (répertoire packages)

Prenez le temps de :

- Ajouter le chemin de la construction du package (<répertoire d'installation>\packages\<version>\build) aux chemins de des bibliothèques de Delphi.

- Modifier les variables d'environnement de votre poste pour y ajouter ce même chemin

- Jeter un œil sur le fichier zeos.inc dans le répertoire des sources et de le modifier en fonction de vos besoins (inutile d'utiliser tous les protocoles si vous n'en utilisez qu'un par exemple) .

Cela vous prendra peu de temps mais pourrait vous en faire gagner par la suite .

Pour l'exemple , j'ai installé , sur un poste windows 8 64 bits , Delphi dans le répertoire C:\BORLAND et les composants ZEOS dans un sous répertoire de ce dernier C:\BORLAND\ZEOSDBO (ceci pour éviter tout ennui avec l'U.A.C. des windows vista et +).

Ouvrez votre IDE et ouvrez le package correspondant : ici, C:\BORLAND\ZEOSDBO\packages\delphi7\zeosdbo.bpg)

Ouverture du projet

(cliquez ici pour aggrandir l'image)

le projet contient 6 unités dont une seule est installable : ZcomponentDesign70.bpl

Arborescence du projet

(cliquez ici pour aggrandir l'image)

Compilez dans l'ordre

- ZCore.bpl

- ZParseSql.bpl

- ZPlain.bpl

- ZDbc.bpl

- ZComponent.bpl

ou utilisez l'option du menu 'Projet\Compiler tous les projets'

À ce stade et selon les versions de Delphi vous aurez peut être obtenu un nombre impressionnants d'erreurs d'attention (warnings) . La plupart seront dues au passage à l'Unicode

Puis, si les compilations se sont déroulées sans erreurs graves , installez ZcomponentDesign.bpl . Un message de confirmation d'enregistrement des composants plus tard , vous aurez sur votre palette de composants , la bibliothèque Zeos

Palette ZeosDBO

(cliquez ici pour aggrandir l'image)

II-B - Les versions S.V.N.

Je ne me pencherais sur l'utilisation des programmes de subversions trop nombreux (à toutes fins utiles je vous renvois ce Wiki ainsi qu'à la FAQ et au Forums dédiés à ce sujet ) . Pour ma part j'utilise Tortoise SVN .

Dans le cas d'utilisation d'un dépôt SVN , pas besoin de décompresser d'archive , sinon , suivez les mêmes étapes de compilation .

Dans ce cas , me demanderez vous , qu'est-ce qui change ?

En fait , surtout la possibilité d'avoir une version à jour et de pouvoir tester les versions bêta , les auteurs font régulièrement des corrections . Mais aussi la possibilité d'utiliser/garder plusieurs versions . Bien sur cela amène aussi son lot de tracas :

- Pas de documentation construite

- Les chemins de construction et de recherche à ajouter à chaque démarrage de nouveau projet , sinon gares aux erreurs de ce style

Erreurs Compilation

(cliquez ici pour aggrandir l'image)

- Une prolifération de DCUs inutiles (sauf si vous avez affiné les options de votre projet)

- Plus d'erreurs d'avertissements (en particulier avec les versions Unicode de Delphi)

Pour pallier , aux erreurs , vous les options de projet et d'environnement comme suit

Options de projet

(cliquez ici pour aggrandir l'image)

Options d'environnement

(cliquez ici pour aggrandir l'image)

III - Introduction

Pressé d'en découdre ? Maintenant que les composants sont installés, avant même de rentrer dans la description de chacun des composants , passons à une première petite démonstration .

A mon avis , cet exemple , souvent proposé est exactement ce qu'il ne faut pas faire pour des programmes d'exploitation . Outre le fait qu'aucun des composant n'est nommé , l'utilisation d'une Table est à proscrire dès que l'on sort du cadre local . Ne prenez pas de mauvaises habitudes .

Pour ceci nous allons faire notre premier programme avec les composants ZEOS . Cet exemple part du requis que :

- Le moteur SGBD est installé (et démarré) sur le poste de développement (Firebird 2.5 dans notre cas)

- La base de données est déjà crée , et se trouve également sur le poste (local), la structure de la base de données se retrouvera dans l'Annexe A

Ouvrez un nouveau projet Delphi

Sur la forme ajoutez :

- 1 TZConnection

- 1 TZTable

- 1 TdataSource

- 1 TDBGrid

- 1 TDBNavigator

exemple basique

(cliquez ici pour aggrandir l'image)

Modifions ensuite les propriétés de certains composants

Propriétés à changer

ZConnection1.Protocol=firebird_2_5
ZConnection1.HostName=localhost
ZConnection1.User=SYSDBA
ZConnection1.Password=masterkey
ZConnection1.Database=F:\DataBase\JudoClub.fdb

ZTable1.Connection=ZConnection1
ZTable1.TableName=CLASSEMENT_COMPETITION
Dataset1.Dataset=ZTable1
DBGrid1.Datasource=Dataset1
DBNavigator1.DataSource=Dataset1

Une fois cela effectué , revenons sur ZTable1 et mettons sa propriété Active=True .

Si tous les prérequis ont été respectés , et toujours en mode design , nous obtenons ceci :

Passage à Active True

(cliquez ici pour aggrandir l'image)

IV - La palette des composants ZEOSDBO

La genèse de ce tutoriel part des constatations suivantes :

- il existe de nombreux tutoriels et exemples de programmes d'accès aux base de données via le BDE

- de nombreux messages sur notre forum font état de l'utilisation de ce dernier pour attaquer des tables paradox

- les composants de la bibliothèque ZEOS se sont voulus 'BDE similaire' mais en enlevant cette couche de connexion

- BDE a été déclaré obsolète il y a environ 10 ans par Borland (à l'époque) et son installation n'est (enfin) plus fournie depuis XE7

J'ai tout d'abord voulu , en faisant le recensement des composants de la bibliothèque ZEOS , faire un tableau comparatif avec BDE . Le lecteur pourra trouver un comparatif BDE/ADO/DBExpress/Interbase Express, s'il a la chance d'avoir le manuel papier de Delphi7 ,page 15-25 , sinon vous pouvez toujours aller voir Appendice B : Composants d'accès au données Delphi 7 .

Comparatif ZEOSDBO et la palette BDE

Composant

Objectif

Équivalent BDE

ZConnection

Connexion à la base de données

TDataBase

ZQuery

Accès aux données en utilisant une instruction SQL

TQuery*

ZReadOnlyQuery

Accès aux données en utilisant une instruction SQL
mais en lecture seule

TQuery

ZTable

Accès a une table de la base de données

TTable

ZSQLUpdate

Ensemble d'instructions SQL permettant la modification d'un ensemble non dynamique

TUpdateSQL

ZStoredProc

Accès aux procédures stockées

TStoredProc

ZSQLProcessor

Permet l'exécution de scripts SQL (plusieurs instructions)

-

ZSQLMetadata

Accès aux métadonnées (structure) de la base de données

-

ZSequence

Accès aux compteurs (autoincréments)

-

ZSQLMonitor

Monitoring de la base de données

-

ZIBEventAlerter
ZPgEventAlerter

Certaines base de données (Interbase/Firebird)
PostGres
permettent d'envoyer des événements , ce composant spécifique permet de les gérer

-
-

ZConnectionGroup
ZGroupedConnection

Objets permettant de fournir un accès aux bases de données dans un environnement multithread

-

Pas d'équivalent, quoique les groupes de connexion pourrait s'en approcher ?

TSession

Pas d'équivalent (ce qui est fort dommage)

TBatchMove

Les prochains chapitres seront consacrés à chacun de ces composants .

Quelques conventions avant de commencer :

- En gras les propriétés, méthodes importantes

- En gras italique celles qui bien que moins importantes peuvent être utiles

- En rouge , importantes, à voir dans le « coin du mécano »

- le reste , ne sera pas forcément mis en avant

V - TZConnection : La connexion à la Base de Données

C'est le premier composant que vous mettrez sur votre fiche ou dans le DataModule . En tout cas , tous les autres composants ZEOS ou presque devront y être rattachés .

Ce composant créé de manière implicite une transaction . Cette combinaison est logique parce que tous les accès à une base de données Firebird (mais aussi à d'autres bases de données) se font toujours dans une transaction en cours d'exécution. Une telle opération est lancée par la Bibliothèque ZEOS chaque fois qu'une connexion (méthode Connect de TZConnection) à une base de données est ouverte.

Un recensement c'est bien , du concret c'est mieux.

Sautez directement à la partie En pratique

V-A - Les propriétés de TZConnection

Auto commit

Mode de la transaction

AutoEncodeStrings

Auto encodage des chaînes de caractères

Catalog

Catalogue des données pour PostgresSQL

ClientCodePage

Encodage coté client ASCII,NONE,WIN1252

Connected

État de la connexion

ControlsCodePage

Concerne de fait votre version d'IDE
cGET_ACP Delphi de version antérieure à L'unicode
cCP_UTF8 Lazarus qui est en UTF8
cCP_UTF16 Delphi 2009 et plus , les versions Unicode

DataBase

Nom de la base de données , pour Firebird on pourra utiliser son nom d'alias

DesignConnection

Utilisable uniquement au moment du design (donc dans l'IDE)
ouvre la connexion sans pour autant mettre la propriété Connected à True

HostName

Nom de l'hôte (poste/serveur hébergeant la base de données)
par défaut localhost

LibraryLocation

Emplacement explicite de la bibliothèque de connexion à la base de données (driver)

LoginPrompt

Ouverture du dialogue d'identification

Name

Nom du composant

Password

Mot de passe de connexion à la base de données

Port

Port d'écoute . Si rien n'est indiqué des valeurs seront prise par défaut (3050 pour Firebird,

Properties

Propriétés supplémentaires , distinct selon chaque type de BDD

Protocol

Protocole de connexion

ReadOnly

Mode en lecture seule

SQLHourGlass

Curseur d'attente

Tag

-

TransactIsolationLevel

Niveau d'isolement de la transaction

UseMetadata

Utilisation des métadonnées

User

Utilisateur de la BDD

Version

Version de la ZEOSLIB (7.1.3-stable) lecture seule

V-B - Les évènements

AfterConnect

Après la connexion

AfterDisconnect

Après la déconnexion

AfterReconnect *

Après l'instruction Reconnect
Notez ici , qu'il ne s'agit pas d'un perte de connexion entre le serveur et le client (problèmes LAN, Arrêt du serveur etc..)

BeforeConnect

Avant la connexion

BeforeDisconnect

Avant la déconnexion

BeforeReconnect *

Avant la reconnection

OnCommit

Lors de l'utilisation de l'instruction Commit

OnLogin

Au moment de l'identification

OnRollBack

Lors de l'instruction RollBack

OnStartTransaction

Au commencement d'une transaction explicite

V-C - Les méthodes

V-C-1 - Les procédures et propriétés en lecture seule

Connect

Connexion

Disconnect

Déconnexion

Reconnect

Reconnexion

GetProtocolNames

Liste des protocoles utilisables

RegisterDataSet(DataSet: TDataset);

UnregisterDataSet(DataSet: TDataset);

GetCatalogNames(List: TStrings);

Liste des catalogues ( PostgresSQL)

GetSchemaNames(List: Tstrings);

Liste des schémas (PostgresSQL)

InTransaction: Boolean;

Transaction en cours

StartTransaction

Démarrer une transaction

Commit

Appliquer la transaction

Rollback;

Revenir à l'état avant la transaction

PrepareTransaction(<nom transaction>)

Préparer une transaction identifiée

CommitPrepared(<nom transaction>)

Appliquer la transaction identifiée

RollbackPrepared(<nom transaction>)

Revenir sur la transaction identifiée

DbcDriver: IZDriver

DbcConnection: IZConnection

ClientVersion

Numéro de version du client (PostgresSQL)

ServerVersion

Numéro de version du client (PostgresSQL)

ClientVersionStr

Version du client

ServerVersionStr

Version du serveur

GetTableNames

Liste des tables

GetStoredProcNames

Liste des procédures stockées

GetTriggerNames

Liste des triggers

ShowSQLHourGlass

Afficher le sablier, en relation avec la propriété SQLHourGlass

HideSQLHourGlass

Cacher le sablier, en relation avec la propriété SQLHourGlass

RegisterSequence

UnregisterSequence

V-C-2 - les fonctions

Ping: Boolean

PingServer: Boolean

Ces deux instructions me semblent identiques , la première implique en plus qu'il faut que la ZConnection soit établie . De fait , pour Interbase/Firebird PingServer ne renvoie une valeur positive que si la connexion est active donc …

Attention il ne s'agit pas d'un ping « classique » sur une adresse IP (comme pingserver le suggèrerai) mais d'une opération permettant de savoir le moteur de base de données est actif . J'utilise le mot moteur a escient pour bien insister sur le fait qu'il s'agit du SGBD et non du poste hébergeant la base de données. On a souvent tendance à parler du poste en tant que serveur d'où les confusions possibles. Il est bien sur évident que sur un réseau local , si vous perdez la connexion IP vous perdrez également la connexion avec le SGBD, mais il se peut également que le SGBD soit arrêté (shutdown) ou tout simplement pas démarré !

ExecuteDirect(SQL:string):boolean

ExecuteDirect(SQL:string; var RowsAffected:integer)

Les deux méthodes permettent d'exécuter directement une instruction SQL , la version surchargée permet , en plus , d'obtenir le nombre de lignes affectées .

GetBinaryEscapeStringFromString(const BinaryString: AnsiString): String

GetBinaryEscapeStringFromStream(const Stream: TStream): String

GetBinaryEscapeStringFromFile(const FileName: String): String;

GetAnsiEscapeString(const Ansi: AnsiString): String;

permettent d'obtenir la séquence d'échappement ?

GetURL: String;

GetNamePath : String;

V-D - En pratique

Pour comprendre , créons un programme (téléchargeable ici) dont voici l'image écran . Bien sur , toute la connexion peut être figée (propriétés remplies) au moment du design comme lors de l'Introduction . Cet exemple permet de comprendre les mécanismes de TZConnection et comment fournir les propriétés lors de l'exécution . J'ai essayé de le faire le plus général possible , afin de permettre de tester d'autres SGBD que Firebird , d'autres exemples sont disponibles dans le répertoire « examples » de l'installation du package.

(cliquez ici pour aggrandir l'image)

Le premier onglet nous permet de maîtriser la partie connexion au sens strict du terme, le mémo nous sert de traceur à l'exécution .

Pour obtenir les protocoles disponibles (qui je le rappelle peuvent changer en fonction de la version des composants mais aussi en fonction de vos propres modifications dans le fichier zeos.inc) j'ai utilisé la fonction GetProtocolNames

OnCreate de la forme

procedure TForm1.FormCreate(Sender: TObject);
begin
// récupération des protocles disponibles
Zconnection1.GetProtocolNames(Protocoles.Items);
// positionnement sur le protocole
Protocoles.ItemIndex:=Protocoles.Items.IndexOf('firebird-2.5');
// Effacement de toutes traces
memo1.lines.clear;
end;

Les zones de saisies permettront de renseigner les différentes propriétés pour établir la connexion .

Tout d'abord , l'hôte (hostname) , c'est le nom ou l'adresse (TCP/IP ou Internet) du poste qui héberge la base de données . C'est un parti pris , je suis résolument pour une explication sur une utilisation des bases de données en réseau ! Vous pourriez me rétorquer que vous ne voulez qu'une application sur votre poste (versions embarquées), qu'a cela ne tienne , qui peut le plus peut le moins , il suffira d'indiquer localhost ou l'adresse 127.0.0.1 (exemples 1 et 2) . Pour ce qui est d'un réseau local on utilisera au choix l'adresse IP fixe ou le nom du poste (exemples 3 et 4) . Mais vous remarquerez également que j'ai indiqué, en exemple, une adresse de site Internet ! Hé oui , c'est possible , sous certaines conditions toutefois (que l'hébergement le permette, que le port soit bien redirigé sur le routeur etc ….) .

Ce qui nous amène tout naturellement à la zone suivante : le port . Généralement chaque SGBD a son port par défaut (3050 pour Firebird) , si vous n'indiquez rien ce sera celui-ci qui sera utilisé . Cependant certaines SGBD permettent de changer et donc dans ce cas vous devrez l'indiquer explicitement .

Je ne reviendrai pas sur le protocole , sauf peut être pour vous faire noter le nombre et indiquer que le « d » suivant le nom de la SGBD est pour indiquer le mode embarqué du serveur . Par exemple firebird-2.5 correspond à la version client serveur , firebirdd-2.5 à sa version embarqué .

Passons au nom de la base de données , attention il s'agit du nom sur le poste hébergeant la base . Pour Firebird , on indiquera le chemin complet ainsi que le nom de la base de données

par exemple : F:\DataBase\TEST.FDB , mais , subtilité de Firebird, on peut aussi indiquer un nom d'alias si celui-ci a été renseigné dans aliases.conf .

Nom d'utilisateur et mot de passe se passe de commentaires , passons directement au vif du sujet : la connexion

Bouton Connect

procedure TForm1.ConnectBtnClick(Sender: TObject);
begin
Memo1.Lines.Add('Clic sur bouton Connect');
try
Zconnection1.Connect;
memo1.lines.add('La connexion est établie') ;
except
on E:Exception do
memo1.lines.add('Erreur Connexion :'+E.message);
end;
end;

procedure TForm1.ZConnection1BeforeConnect(Sender: TObject);
begin
// renseignement de l'hote
Zconnection1.HostName:=Hote.Text;
// renseignement du port
ZConnection1.Port:=StrToIntDef(port.text,0);
// Choix du protocole
Zconnection1.Protocol:=Protocole.Items[Protocole.ItemIndex];
// nom de la base de données
ZConnection1.Database:=Nombase.Text;
// informations de sécurité
ZConnection1.User:=Utilisateur.Text;
Zconnection1.Password:=mdp.Text;
// méthode de login
ZConnection1.LoginPrompt:=DialogPrompt.ItemIndex>0;
case DialogPrompt.ItemIndex of
1 : ZConnection1.OnLogin:=nil
else ZConnection1.Onlogin:=ZConnection1Login;
end;
Memo1.Lines.Add('Événement BeforeConnect')
end;

procedure TForm1.ZConnection1AfterConnect(Sender: TObject);
begin
Memo1.Lines.Add('Événement AfterConnect');
// Modification état des boutons
Connect.Enabled:=False;
Disconnect.Enabled:=True;
Reconnect.Enabled:=True;
end;

J'ai fait le choix d'utiliser les événements pour pouvoir tracer plus aisément ce qui se passe. Si tout se passe correctement on obtient ceci :

Clic sur bouton Connect
Événement BeforeConnect
Événement AfterConnect
La connexion est établie

Nous pouvons maintenant tester la déconnexion

Bouton Déconnexion

procedure TForm1.ZConnection1BeforeDisconnect(Sender: TObject);
begin
Memo1.Lines.Add('Événement BeforeDisconnect');
end;

procedure TForm1.DisconnectBtnClick(Sender: TObject);
begin
Memo1.Lines.Add('Clic sur bouton Disconnect');
ZConnection1.Disconnect;
end;

procedure TForm1.ZConnection1AfterDisconnect(Sender: TObject);
begin
Memo1.Lines.Add('Événement AfterDisconnect');
// Modification état des boutons
Connect.Enabled:=True;
Disconnect.Enabled:=False;
Reconnect.Enabled:=False;
end;

On aura les lignes suivantes :

Clic sur bouton Disconnect
Événement BeforeDisconnect
Événement AfterDisconnect

Puis une reconnexion

Bouton Reconnexion

procedure TForm1.ZConnection1BeforeReconnect(Sender: TObject);
begin
Memo1.Lines.Add('Événement BeforeReconnect');
end;

procedure TForm1.ReconnectBtnClick(Sender: TObject);
begin
Memo1.Lines.Add('Clic sur bouton ReConnect');
ZConnection1.Reconnect;
end;

procedure TForm1.ZConnection1AfterReconnect(Sender: TObject);
begin
Memo1.Lines.Add('Événement AfterReconnect');
end;

Une reconnexion ne pourra fonctionner que si la connexion est établie (propriété connected=true)

Résultat :

Clic sur bouton Connect
Événement BeforeConnect
Événement AfterConnect
La connexion est établie
Clic sur bouton ReConnect
Événement BeforeReconnect
Événement AfterReconnect

Revenons maintenant sur le Login , que j'avais laissé de côté . Trois options sont possibles , renseigner directement les informations , utiliser le dialogue standard d'identification ou utiliser l'événement OnLogin . Nous avons déjà vu le premier , le second se passe de commentaire, reste l'événement .

Événement OnLogin

procedure TForm1.ZConnection1Login(Sender: TObject; var Username,
Password: String);
begin
UserName:=Utilisateur.text;
PassWord:=mdp.text;
Memo1.Lines.add('Utilisation de OnLogin pour passer le couple Utilisateur/Mot de passe');
end;

Que se passe t-il dans ce dernier cas , lorsque l'on se connecte :

Clic sur bouton Connect
Evènement BeforeConnect
Utilisation de OnLogin pour passer le couple Utilisateur/Mot de passe
Evènement AfterConnect
La connexion est établie

Bien sûr, on ne va pas pour chaque programme demander à l'utilisateur d'indiquer ces renseignements , on préférera utiliser un fichier ini ou tout autre équivalence pour stocker les informations de connexion et rendre ceci transparent . J'aborde ce point plus loin , dans le coin du mécano ,chapitre : La création / les modifications à l'exécution

La deuxième partie du programme (deuxième volet) est une tentative pour obtenir/voir ce que font les différentes fonctions ou propriétés en lecture seule proposées . Malheureusement toutes ne fonctionnent pas encore avec Firebird (l'équipe Zeos cherche toujours des volontaires pour améliorer leur code , d'expérience , entre la soumission de la correction, ou de l'ajout, d'une fonctionnalité et la mise à disposition dans la version SVN le délai est court).

V-E - TZConnection , Le coin du mécano

Pourquoi un coin du mécano ? Parce que cette partie s'adresse plus a des programmeurs confirmés voire expert . Aller sur toutes les possibilités et subtilités demande souvent à plonger dans le code source (cela tombe bien il est fourni) .

V-E-1 - La création / les modifications à l'exécution

Dans ce paragraphe , je ne parle pas de la création de la base de données , mais bien d'une création du composant à l'exécution (run-time) ou de modification de propriétés , par exemple la saisie ou l'utilisation d'un fichier ini pour changer les informations de connexion . Nous avons déjà vu de manière détaillée , les informations nécessaires à la connexion dans la section En pratique . Faisons maintenant au plus simple, en passant par l'intermédiaire d'un fichier .ini les données nécessaires .

[Connexion] Hote=localhost Port= Protocole=firebird-2.5 Utilisateur=SYSDBA mdp=masterkey base=c:\test.fdb

Cette méthode est contestable . Donner la possibilité de voir , en clair , le couple utilisateur/mot de passe est plus que déraisonnable ! Ce type de fichier ini n'est là qu'à titre de démonstration

Pour cette démonstration, j'ai placé la connexion dans un module de données afin de pouvoir la partager . Le DataModule ne contient qu'un seul composant : un TZConnection et j'utilise l'événement OnCreate du DataModule pour l'initialiser .

DataModule

unit Datamodule;

interface

uses
SysUtils, Classes, ZAbstractConnection, ZConnection, inifiles;

type
TDataModule1 = class(TDataModule)
ZConnection1: TZConnection;
procedure DataModuleCreate(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;

var
DataModule1: TDataModule1;

implementation

{$R *.dfm}

procedure TDataModule1.DataModuleCreate(Sender: TObject);
var F : TIniFile;
begin
F:=TInifile.Create(ChangeFileExt(paramstr(0),'.INI'));
try
ZConnection1.Host:=F.ReadString('Connexion','Hote','localhost');
ZConnection1.Port:=F.ReadInteger('Connexion','Port',0);
ZConnection1.Protocol:=F.ReadString('Connexion','protocole','firebird-2.5');
ZConnection1.Database:=F.ReadString('Connexion','base','c:\test.fdb');
ZConnection1.User:=F.ReadString('Connexion','Utilisateur','SYSDBA');
ZConnection1.Password:=F.ReadString('Connexion','mdp','masterkey');
finally
F.Free;
end;
end;

end.

Que manque t-il pour créer la connexion à l'exécution ? Pas grand-chose en fait , on devra juste s'assurer de quelques points : le mode autocommit à true et du niveau d'isolation de la transaction (propriété TransactIsolationLevel mise à tiNone par défaut) . Ce dernier point nécessite l'inclusion dans les uses de ZDBcintfs

Création au runtime

ZC:=TZConnection.Create(nil);
try
ZC.Autocommit:=True;
ZC.TransactIsolationLevel:=tiRepeatableRead;
… //suite des propriétés comme au-dessus

Ce dernier point me permet d'introduire le chapitre suivant

V-E-2 - Les Transactions

Un peu de théorie n'a jamais fait de mal et certains concepts sur les transactions doivent être connus afin de comprendre comment TZConnection fonctionne de l'intérieur et comment travailler avec. En général, vous n'avez accès à la base de données qu'à l'intérieur d'une transaction valide (« ouverte ») . Une transaction doit remplir 4 caractéristiques connues sous le nom de A.C.I.D.

Atomicité : La propriété d'atomicité assure qu'une transaction se fait au complet ou pas du tout . Si une partie d'une transaction ne peut être faite, toute trace de la transaction doit être effacée et les données remises dans l'état où elles étaient avant cette transaction. Le principe du « tout ou rien »

Cohérence : La propriété de cohérence assure que chaque transaction amènera le système d'un état valide à un autre état valide.

Isolation : La propriété d'isolation assure que l'exécution simultanée de transactions produit le même état que celui qui serait obtenu par l'exécution en série des transactions.

Durabilité : La propriété de durabilité assure que lorsqu'une transaction a été confirmée, elle demeure enregistrée.

Concrètement, une transaction encapsule un ensemble (consécutif) d'accès à la base . Par accès à la base on entend aussi bien une lecture (SELECT) qu'une écriture (INSERT, UPDATE, DELETE) ou un changement à la structure de la base (CREATE,ALTER). Une transaction est terminée par un COMMIT ou un ROLLBACK . Un COMMIT confirme tous les changements dans la base de données depuis le début de la transaction. Un ROLLBACK remettra la base de données à son état d'avant la transaction.

En résumé , des transactions s'exécutant sur un serveur doivent être isolées les unes des autres, de façon a être indépendantes.

En utilisant divers niveaux d'isolation (TIL pour Transaction Isolation Level) un développeur peut protéger les données d'un ensemble de données, résultat d'une requête, de l'accès par d'autres transactions. Ce comportement, le plus standard, est connu sous le nom de SERIALISABLE (l'état d'isolation le plus proche avec Firebird est connu sous le nom de SNAPSHOT) .

Comme je l'ai écrit lors de la présentation du composant TZConnection , dès la création de la connexion une transaction est incluse . Du point de vue des auteurs c'est tout à fait logique car tout accès à une base de données doit se faire via une transaction . Dès que la connexion à la base de données est ouverte (méthode Connect) la transaction est démarrée. Chaque accès à la base est fait à l'intérieur de cette transaction et ce automatiquement . Ce comportement est nommée l'autocommit (propriété autocommit vrai par défaut) , ce qui correspond au comportement standard du composant BDE correspondant. Donc , si autocommit est activé, chaque changement à la base de données sera confirmé (COMMIT) après une exécution réussie.

Cependant , ce comportement peut être désactivé en démarrant de manière explicite une transaction via la méthode StartTransaction . A l'intérieur de cette transaction il est alors possible d'exécuter un ensemble d'instructions SQL qui seront ensuite confirmés par la méthode Commit. Appeler la méthode Rollback annulera toutes les opérations.

StartTransaction met la propriété autocommit à false , Commit ou Rollback la retournera à l'état initial (true) et bien sur arrêtera la transaction.

Retaining

Généralement, après la confirmation d'une transaction via un Commit ou l'annulation via un Rollback la transaction est, normalement, terminée et l'ensemble de données résultats d'une requête ou d'une procédure est jeté. Cela s'appelle le « hard commit » ou « hard rollback ».

Avec la bibliothèque ZEOS ce comportement est légèrement différent , ZEOS garde l'ensemble de données « en vie » . Ceci est fait en terminant la transaction non pas en « hard » mais en « soft » COMMIT ou ROLLBACK , ce mode est le mode « Retaining ».De fait, les commits ou rollbacks d'une transaction Zeos sont faits avec ce mode par défaut, cela s'effectue , de manière transparente, en ré-ouvrant ,tout de suite après la fermeture de la transaction, une nouvelle transaction avec toutes les données et ressources (en particulier les ensemble de données résultants) de la « vieille » transaction.

Cependant le mode retaining peut devenir un problème sur de grosses tables .

Par exemple , ce mode empêche le mécanisme de nettoyage interne de Firebird (garbage collection) ce qui amène a un surcroît d'anciennes données gardées mais inutilement . De part là même cela pénalisera les performances du serveur, qu'un nettoyage (sweep) aurait géré. Mais ce « sweep » n'est fait que en « hard » commit ou rollback , ce que ZEOS ne fait qu'à la fermeture de la connexion . Un contournement possible serait donc de fermer puis rouvrir la connexion régulièrement !

Le comportement « soft commit » peut être être désactivé , via les propriétés de la connexion , voir plus loin Les propriétés supplémentaires

Niveaux d'isolation de la transaction d'une TZConnection (T.I.L.)

Le composant TZConnection fourni des TILs prédéfinis :

tiRepeatableRead

Correspond au « SNAPSHOT » (standard des serveurs Firebird) . C'est une combinaison des paramètres de transaction « concurrency » et « nowait » . Un cliché de la base de données est fait . Les autres utilisateurs sont seulement contraints si deux transactions travaillent en concurrence sur un même enregistrement . Si un conflit est détecté , un message d'erreur sra renvoyé , les changements à l'intérieur des autres transactions dûment notifiés . Ce mode couvre largement le standard SQL nommé « SERIALIZABLE ».

tiReadCommitted

Correspond au mode « READ COMMITTED ». C'est une combinaison des paramètres « read committed » , « rec_version » et « nowait » . Ce niveau prend en compte tout changement validé par d'autres transactions (COMMIT). Le paramètre « rec_version » indique l'on doit prendre compte les valeurs les plus récentes validées par d'autres utilisateurs , « no_wait » qu'il n'y a pas de délai avant de libérer un enregistrement verrouillé.

Ce mode sollicitera d'avantage le serveur car il aura besoin de rafraîchir très souvent les données.

tiSerializable

Correspond au mode « SNAPSHOT TABLE STABILITY » , utilisé pour obtenir un accès exclusif à l'ensemble de données. Obtenu par le biais du paramètre de transaction « consistency » il pare à l'accès aux données de tout autre transaction. Seule la transaction qui a écrit les données peut y accéder, ce qui empêche également tout autre utilisateur d'accéder aux données écrites.

Ce niveau , très restrictif, doit être utilisé avec précaution.

tiNone

Aucun niveau d'isolation pour la transaction

tiReadUncommitted

bien qu'existant , ce dernier n'est pas reconnu par Firebird, l'utiliser lèvera uneerreur et la transaction ne sera pas isolée (comme avec tiNone).

Comme il est recommandé d'utiliser des niveaux d'isolation pour les transactions, tiRepeatableRead sera le choix le plus recommandé .

En second choix , dépendant de l'application , on utilisera tiReadCommitted.

Il est enfin possible de faire ses propres niveaux ou en modifier un existant. On peut le faire en utilisant les paramètres de la TZConnection . Pour ce faire , il vous faudra consulter les documents de référence des API de votre SGBD . Ci-dessous , un exemple de personnalisation à partir de tiNone pour Interbase/Firebird

Personnalisation d'un niveau de transaction

ZConnection1.TransactIsolationLevel := tiNone;
ZConnection1.Properties.Add('isc_tpb_concurrency');
ZConnection1.Properties.Add('isc_tpb_wait');
ZConnection1.Connect;

La théorie c'est bien , mais souvent indigeste , passons donc a un peu de pratique

V-E-3 - L'encodage

V-E-4 - Les propriétés supplémentaires

Je vous engage fortement à lire le fichier (pdf ou html) fourni dans le sous répertoire doc de l'installation de la ZEOSLIB. En effet chaque SGBD a ses propres paramétrages.

Le remplissage de ces dernières (il s'agit d'une TStrings) se fait soit au moment du design soit par programme, on ne mettra qu'une propriété par ligne.

Un seul est commun le paramètre defaults=[yes,no] permettant d'indiquer si les valeurs pas défauts doivent être calculés pour les champs NULL . Cependant ce dernier pourra être invalidé par le paramètre au niveau de TZQuery .

ajouter une propriété à la connexion

Zconnection1.Properties.Add('defaults=yes') ;
// ou encore
Zconnection1.Properties.Values['defaults'] :='yes' ;

Pour Interbase/Firebird on trouvera ces propriétés :

- RoleName=<nom du role> . Permet d'indiquer le role à utiliser pour travailler avec la base de données et ainsi d'avoir les privilèges de ce dernier.

- hard_commit=[yes,no] - Use hard commits instead of soft commits.

Plus annecdotiques de mon point de vue , mais peut être utiles :

- codepage=<nom de la langue> - Si cette langue est installée pour le SGBD alors les messages d'erreurs seront revoyés dans cette langue.

- Dialect=<1,2,3> c'est le paramètre du dialect SQL d'Interbase/Firebird.

- CreateNewDatabase=<commande sql de création de la base de données> - Crée la nouvelle base de données décrite dans le TZConnexion, avant d'ouvrir celle-ci.

Création base de données

ZConnection1.Properties.Add ('CreateNewDatabase=CREATE DATABASE ' +
QuotedStr ('c:\test.fdb') + ' USER ' +
QuotedStr ('sysdba') + ' PASSWORD ' + QuotedStr ('masterkey') +
' PAGE_SIZE 4096 DEFAULT CHARACTER SET ISO8859_1');}
try
Zconnection1.Connect;
Zconnection1.Disconnect ;
Zconnection1.Properties.Clear;
IntroMemo.Lines.Add('Création réussie') ;
Zconnection1.Connect;
IntroMemo.Lines.Add('Connexion à la base créée') ;

except
Intromemo.Lines.Add('la création à échoué') ;
end ;
end;

VI - TZQuery : L'instruction SQL par excellence

Cette classe permet d'accéder à une base de données en utilisant des instructions SQL . Pratique , ce composant, permet d'accéder à plusieurs tables (via les jointures) et/ou un sous-ensemble de lignes (clause WHERE) et colonnes au lieu de tout renvoyer comme le ferait un TZTable . TZQuery et TZReadOnlyQuery sont à mon avis les deux composants à privilégier pour obtenir un ensemble de résultats .

En fait , TZQuery, TZReadOnlyQuery, TZTable et même TZStoredProc sont tout quatre dérivés des mêmes classes . Je vais donc m'employer à décrire le plus possible TZQuery, pour les autres je me contenterai plus , après recensement , d'en souligner les différences et spécificités.

VI-A - Les propriétés

Active

État de l'ensemble de données , ouvert ou fermé respectivement vrai ou faux , faux par défaut

AutoCalcFields

CachedUpdates

Activation de la mise à jour en mémoire cache (faux par défaut)

Connection

Lien à la TZconnection , obligatoire

DataSource

Liaison a une source de données

FetchRow

Nombre de lignes à récupérer en une lecture

Filter

Filtre de l'ensemble de données

Filtered

Activation du filtre et/ou de l'événement FilterRecord

IndexFieldNames

LinkedFields

MasterFields

MasterSource

Name

Nom du composant

Options

Options particulières

DoOemTranslate
doCalcDefaults Remplacer les valeurs nulles par leur valeur par défaut
doAlwaysDetailResync Toujours synchroniser l'ensemble de données détail
doSmartOpen Ouverture « intelligente »
doPreferPrepared
doDontSortOnPost Ne pas trier , à la suite d'une opération validée
doUpdateMasterFirst Mettre à jour l'ensemble de données maître d'abord

ParamChar

Caractère signalant un paramètre ( ':' par défaut)

ParamCheck

Active le contrôle des paramètres

Params

Liste des paramètres

Properties

Propriétés supplémentaires

ReadOnly

Mise en lecture seule (faux par défaut)

Sequence

Lien vers un TZSequence

SequenceField

Champ concerné par la séquence (autoincrémentation)

ShowRecordTypes

Montrer les enregistrements qui :

UsUnmodified N'ont pas été modifiés
usModified Ont été modifiés
usInserted Ont été ajoutés
usDeleted Ont été supprimés

SortedFields

Champs du tri , séparés par des virgules

SortType

Type de tri
stAscending Ascendant , par défaut
stDescending Descendant
stIgnored Aucun

SQL

Instruction SQL

Tag

Sans commentaire

UpdateMode

Type de mode de mise à jour voir TZUpdateSQL
umUpdateAll tous les champs seront mis à jour
umUpdateChanged Seul les champs modifiés seront mis à jour

UpdateObject

Lien vers un TZUpdateSQL

WhereMode

Type de clause where utilisé pour la mise à jour, voir TZUpdateSQL
wmWhereKeyOnly seule la clé sera utilisée
wmWhereAll tout les champs seront utilisés

VI-B - Les événements

AfterApplyUpdates

Après application des mises à jour en cache

AfterCancel

Après annulation de l'opération en cours

AfterClose

Après fermeture de l'ensemble de données

AfterDelete

Après une suppression réussie

AfterEdit

Après une modification réussie

AfterInsert

Après une insertion (ajout) réussie

AfterOpen

Après l'ouverture de l'ensemble de données

AfterPost

Après validation d'un ajout ou d'une modification

AfterRefresh

Après rafraîchissement de l'ensemble de données

AfterScroll

Après déplacement dans l'ensemble de données

BeforeApplyUpdates

Avant d'appliquer les mises à jour en cache

BeforeCancel

Avant d'annuler l'opération en cours

BeforeClose

Avant de fermer l'ensemble de données

BeforeDelete

Avant de faire une suppression dans l'ensemble de données

BeforeEdit

Avant de faire une modification sur la ligne en cours

BeforeInsert

Avant de faire une insertion ou un ajout dans l'ensemble de données

BeforeOpen

Avant d'ouvrir l'ensemble de données

BeforePost

Avant validation d'un ajout ou d'une modification

BeforeRefresh

Avant de rafraîchir l'ensemble de données

BeforeScroll

Avant de se déplacer à l'intérieur de l'ensemble de données

OnApplyUpdateError

En cas d'erreur lors de l'application des mises à jour en mémoire cache

OnCalcFields

Action pour renseigner la valeur des champs « calculés »

OnDeleteError

En cas d'erreur lors d'une tentative de suppression

OnEditError

En cas d'erreur lors d'une tentative de mise à jour

OnFilterRecord

Action de filtre personnalisé

OnNewRecord

Lors de la demande de création d'une nouvelle ligne

OnPostError

En cas d'erreur lors de la validation (méthode Post)

OnUpdateRecord

En cas de mise à jour d'une ligne

VI-C - Les méthodes

La liste en est trop longue pour toutes les énumérées , je me contenterai de grouper par thèmes les plus intéressantes/utiles

VI-C-1 - Exécution du SQL

Open

Ouverture de l'ensemble de données

Close

Fermeture de l'ensemble de données

D'aucun se pose la question de l'utilité de ces instructions , qui ne font que changer la propriété Active à True ou False respectivement, je dirais que ce n'est qu'une question de style. Dans les deux cas , c'est une action à effectuer sur un ensemble de données (SELECT) , à contrario de l'instruction suivante.

ExecSQL

Exécution de l'instruction SQL

On l'utilisera chaque fois que l'instruction SQL ne renverra aucun résultat (DELETE,UPDATE).

Notez bien la différence entre Open et ExecSQL.

FetchAll

Récupère la totalité des lignes de l'ensemble de données

Pour quoi faire ? En fait c'est en relation avec la valeur de FetchRows pour plus d'informations allez dans Le coin du mécano

EmptyDataset

Vide l'ensemble de de données

RowsAffected

Propriété en lecture seule, donnant le nombre de lignes « touchées » par l'instruction SQL

VI-C-2 - Paramétrage d'une instruction SQL

Prepare

Prepared

UnPrepare

ParamByName()

VI-C-3 - Navigation dans l'ensemble de données

First, FindFirst

Next, FindNext

Prior, FindPrior

Last, FindLast

Locate

Lookup

VI-C-4 - Les repères (Bookmarks)

VI-C-5 - Les modifications de l'ensemble de données

Insert

InsertRecord

Append

AppendRecord

Edit

Post

Cancel

RevertRecord

RefreshCurrentRow

Les relations maître détail

VI-D - La pratique

VI-E - Le coin du mécano

VI-E-1 - Les propriétés supplémentaires : Properties

Encore une fois , je vous invite à vous référer à la documentation du package (<répertoire de ZEOSDBO>\doc\html\parameters.html ou …\doc\pdf\parameters.pdf)

Deux de ces propriétés sont applicables quelque soit le SGBD :

defaults=[yes,no] - Calcule ou non les valeurs par défaut indiquées pour les champs NULL. Nous avons déjà vu ce dernier pour la TZConnection , la propriété indiquée dans le TZQuery sursoit alors celle qui aurait pu être indiquée pour le TZConnection mais ce , uniquement pour ce composant.

ValidateUpdateCount=[true,false] - Vérifie que la Clause UpdateSQL générée par le composant ZUpdateSQL (ou plus exactement son wizard) affecte une seule ligne. Je vous convie à lire le chapitre TZUpdateSQL, si ce n'est déjà fait .

Deux autres sont spécifiques à Interbase/Firebird :

cursor=<nom du curseur> - Le nom indiqué sera envoyé au serveur de base de données afin de l'utiliser. Pratique pour le monitoring Firebird par exemple (tables MON$xxxxxx)

cashedblob=[yes,no] -Indique si les données contenues dans les blobs seront récupérées immédiatement [yes] ou par l'intermédiaire des API d'Interbase/Firebird [no] .

Cette dernière disparaît au profit de CachedLobs dans la version 7.2 , mais est également plus facile d'accès (toujours dans la version 7.2) grâce à l'ajout de l'option doCachedLobs (propriétés Options)

VI-E-2 - FetchRows

Attention , FetchRows ne veut pas dire lecture d'un nombre déterminé d'enregistrements , ce que l'on obtient avec une requête Firebird ainsi

Les dix premiers

SELECT FIRST 10 FROM UNETABLE

Mais plutôt d'un nombre d'enregistrements par paquet envoyé sur le réseau . Si cette propriété est intéressante pour diminuer le trafic (seuls les enregistrements nécessaires transitent) elle peut avoir des effets de bords inattendus : la valeur de recordcount sera « inexacte » , le curseur vertical d'une DBGrid est fonction du nombre de lignes lues et non de la totalité des lignes de la table.

Pour illustrer ce point, rien ne vaut un petit exemple . Pour cela nous allons interroger une table contenant 19 lignes pour l'afficher dans une grille de 5 lignes . Un SpinEdit , permet de régler la valeur FetchRow avant ouverture de la table , le mémo et quelques gestions d'évènements (AfterOpen et AfterScroll) permettront de tracer le nombre d'enregistrements dans le DataSet , un navigateur complète le tout .

Design

(cliquez ici pour aggrandir l'image)

démo FetchRow

procedure TZQForm.FetchRownBtnClick(Sender: TObject);
begin
if ZQCeintures.Active then QCeintureMemo.Lines.Add('Fermeture de la table');
ZQCeintures.Active:=False;
ZQCeintures.FetchRow:=nbRows.Value;
QCeintureMemo.Lines.Add(Format('Ouverture de la table , FetchRow=%d',[nbRows.Value]));
ZQCeintures.Active:=True;
end;

procedure TZQForm.FetchAllBtnClick(Sender: TObject);
begin
ZQCeintures.FetchAll;
QCeintureMemo.Lines.Add(Format('FetchAll RecordCount = %d',[ZQCeintures.RecordCount]));
end;

procedure TZQForm.ZQCeinturesAfterScroll(DataSet: TDataSet);
begin
QCeintureMemo.Lines.Add(Format('Event AfterScroll RecordCount = %d',[ZQCeintures.RecordCount]))
end;

procedure TZQForm.ZQCeinturesAfterOpen(DataSet: TDataSet);
begin
QCeintureMemo.Lines.Add(Format('Event AfterOpen RecordCount = %d',[ZQCeintures.RecordCount]))
end;

Étudions le résultat de l'exécution si FetchRow à la valeur 2 (donc inférieure au nombre de lignes espérée pour la grille)

(cliquez ici pour aggrandir l'image)

Malgré (et heureusement) notre demande 5 lignes ont été retournées , le déplacement à l'intérieur de la grille (via le navigateur) , nous montre le comportement suivant

Event AfterScroll RecordCount = 5
Event AfterScroll RecordCount = 5
Event AfterScroll RecordCount = 5
Event AfterScroll RecordCount = 5
Event AfterScroll RecordCount = 5
Event AfterScroll RecordCount = 6
Event AfterScroll RecordCount = 7

Nous remarquons que , tant qu'il n'y a pas besoin de lecture (dans notre cas jusqu'à 5) , rien n'est fait , puis au fur et à mesure du déplacement le nombre d'enregistrements dans l'ensemble de données augmente . Bref , tout serait parfait sans ce petit « défaut » de la DBGrid (regardez la taille du curseur de la barre verticale de cette dernière) .

Que se passerait-il dans le cas où la valeur de Fetchrow dépassait le nombre de lignes nécessaires ? Comme vous le devinez certainement , c'est le nombre d'enregistrements demandés qui serais renvoyé .

Si FetchRow=0 (valeur par défaut) c'est la table entière qui est envoyée .

FetchAll permet également d'obtenir tout l'ensemble restant à lire

Pour faire bonne mesure , rajoutons une opération de recherche

delphi

0

1

procedure TZQForm.LocateLigne9Click(Sender: TObject);
begin
if ZQCeintures.Locate('IDUNIQUE',vararrayof([9]),[])
then QCeintureMemo.Lines.Add('Locate de la ligne 9 réussie')
else QCeintureMemo.Lines.Add('ligne 9 non trouvée')
end;

Pas de surprise , l'enregistrement sera bien trouvé , le nombre d'enregistrements lu ,par contre, dépendra de deux facteurs : la position du curseur (tiens , le revoilà) dans la base de données et, bien sur la valeur de Fetchrow.

Si pour un nombre d'enregistrements peu important la propriété FetchRow à peu d'importance , pour une table de taille importante je vous laisse imaginer les avantages sur le trafic réseau.

VI-E-3 - Utilisation de la mémoire cache

ApplyUpdates

CommitUpdates

CancelUpdates

UpdatesPending

VII - TZReadOnlyQuery : Un ensemble de données en lecture seule

Les propriétés

Les méthodes

La pratique

Le coin du mécano

Pour les tatillons sur l'utilisation de la mémoire , la version 7.2 amène une nouvelle propriété isUnidirectional , permettant de mettre l'ensemble de données uni-directionnel et ainsi de le rendre moins gourmand .

VIII - TZTable

Les propriétés

Les méthodes

La pratique

Le coin du mécano

IX - TZUpdateSQL

Élément incontournable pour une TZQuery modifiable , TZUpdateSQL encapsule un ensemble d'instructions SQL permettant d'actualiser l'ensemble de données . Une requête multitables est par définition selon ,la norme SQL-92, en lecture seule , TZUpdateSQL permet de contourner ce que certains peuvent considérer comme une limitation.

IX-A - Les propriétés

DeleteSQL

Instruction(s) pour l'effacement de ligne

InsertSQL

Instruction(s) d'ajout

ModifySQL

Instruction(s) de modification(s)

RefreshSQL

Instruction pour le rafraîchissement de l'ensemble de données

MutiStatements

Indique si les ordres SQL peuvent contenir plusieurs instruction (vrai par défaut)

Name

Nom du composant

Tag

Pas de commentaire

ParamsCheck

Params

UseSequenceFieldForRefreshSQL

IX-B - Les événements

Les noms de ces événement sont explicites , je n'entrerai pas trop dans les détails

AfterDeleteSQL
AfterInsertSQL
AfterModifySQL

Événements après exécution du SQL concerné

BeforeDeleteSQL
BeforeInsertSQL
BeforeModifySQL

Événements avant exécution du SQL concerné

Chaque SQL peut comporter plusieurs instructions (si la propriété MutiStatements est à True). Les événements suivants ajoute la possibilité de distingué les différentes instructions contenues , via la valeur de StatementIndex .

AfterDeleteSQLStatement
AfterInsertSQLStatement
AfterModifySQLStatement

Événements après exécution du SQL concerné

BeforeDeleteSQLStatement
BeforeInsertSQLStatement
BeforeModifySQLStatement

Événements avant exécution du SQL concerné

IX-C - Les méthodes

SQL

ExecuteAction

IX-D - La pratique

IX-E - Le coin du mécano

IX-F - Le coin du curieux

X - TZStoredProc

Les propriétés

Les méthodes

Les événements

La pratique

Le coin du mécano

XI - TZSQLProcessor

Nous avons vu que TZQuery , ou TZReadonlyQuery permettait d'exécuter une instruction SQL ne retournant aucun ensemble de données grâce à la fonction ExecSQL . TZSQLProcessor va plus loin , il permet d'exécuter un ensemble d'instructions SQL , autrement nommé : un script.

L'intérêt ? Outre lors de la création de la base de données pour créer ensuite sa structure, on pourra utiliser ce composant pour l'ajout ou la mise à jour de données en masse.

Une limite toutefois , les blobs binaires seront difficiles (voire impossible selon la taille, les contraintes du SGBD etc ...) à traiter .

XI-A - Les propriétés

CleanupStatement

Connection

Delimiter

DelimiterType

dtDefault
dtDelimiter
dtEmptyLine
dtGo
dtSetTerm

Name

ParamChar

ParamCheck

Params

Script

Tag

XI-B - Les événements

AfterExecute

BeforeExecute

OnError

XI-C - Les méthodes

LoadFromStream

Chargement du script par l'intermédiaire d'un flux ou directement à partir d'un fichier contenant des instructions SQL

LoadFromFile

Execute

Exécution du script

Parse

Interprétation/Analyse du script

Clear

Effacer le script

ParamByName

Accès à un paramètre

StatementCount

Nombre d'instructions contenues dans le script

Statements[index]

Accès a une instruction particulière par son index

XI-D - La pratique

XI-E - Le coin du mécano

XII - TZSQLMetadata

Les propriétés

Les méthodes

La pratique

Le coin du mécano

XIII - TZSequence

Les propriétés

Les méthodes

La pratique

Le coin du mécano

XIV - TZIBEventAlerter

Tous les SGBDs ne proposent pas de créer des événements spécifiques comme c'est le cas d'Interbase et Firebird . Tout d'abord qu'est-ce que c'est ? à quoi cela sert ?

Les événements sont des messages simples de notification envoyés de manière asynchrone à partir du serveur aux clients. Chaque fois que l'événement se produit dans la base de données, le serveur avertit tous les clients (connectés) qui ont déclaré leur intérêt pour cet événement. Les événements sont de simples notifications, sans données supplémentaires .

Pour le besoin de l'explication sur l'intérêt , étudions une utilisation commerciale simple :
- une application client-serveur à deux niveaux (2 tiers)
- un poste client crée de nouveaux enregistrements de type "bon d'achats"
- les autres postes clients ont besoin d'être averti des nouveaux dossiers "bon d'achats"

Dans ce scénario, répéter les lectures (par l'intermédiaire d'un timer dans un thread par exemple) de la table «ordre d'achat» provoquerait un trafic réseau à partir de tous les postes de travail clients (même si il n'y a pas de nouvelles commandes) vers tous , et donc trop de postes clients provoquerait une surcharge du réseau et / ou des ressources du serveur. Du fait que l'application soit à deux niveaux, il n'y a pas de couche de service ou de serveur d'application entre la base de données et le logiciel client qui pourrait gérer ces notifications. Pour ce faire nous devons côté serveur envoyer des notifications, afin de ne provoquer un trafic réseau que lorsque de nouvelles données sont envoyées.

Avec les événements Firebird, le code de notification fera partie d'un objet de métadonnées de base de données (un déclencheur ou une procédure stockée) . Par exemple :

CREATE TRIGGER ORDREACHAT_INSERT FOR ORDRES_ACHAT
AFTER INSERT
AS
BEGIN
POST_EVENT 'AJOUT_ORDRE_ACHAT';
END

Tous les postes clients intéressés par cet événement s'inscriront sur cet événement par son nom via une API Interbase/Firebird.

TZIBEventAlerter nous faciliteras cette tâche .

Je dois avouer que dans le cadre de mon application exemple , utilisation de la base de données JudoClub j'ai eu du mal à trouver un cas acceptable. Je me suis rabattu sur un événement sur la création/modification de membre.

Voir le trigger « NOUVEAU_MEMBRE »

La solution de surveiller les événements ainsi n'est pas forcément la plus efficace . Plusieurs objections peuvent être levées :

1. Un poste client non connecté via le programme ne sera pas averti

2. On n'obtiendra pas l'identification de l'enregistrement ayant provoqué l'événement

3. On ne pourra pas faire de choix . Dans le scenario « Bon d'achats »exposé plus haut , il serait par exemple impossible pour un client de n'être averti que pour son département (sauf à créer des événements distincts , ce qui implique de la « lourdeur »)

4. En dernier lieu , une opération de masse déclencherai à chaque fois l'événement alors qu'une seule notification en fin suffirai !

XIV-A - Les propriétés

AutoRegister

Connection

Events

Name

Registered

Tag

XIV-B - Les événements

OnError

OnEventAlerter

XIV-C - Les méthodes

RegisterEvents

UnRegisterEvents

XIV-D - La pratique

XV - TZSQLMonitor

L'objet TZSQLMonitor va nous permettre de tracer tous les opérations qui seront effectuées

XV-A - Les propriétés

Active

AutoSave

FileName

MaxTraceCount

Name

Tag

XV-B - Les événements

OnLogTrace

Les deux permettent d'indiquer ce qu'il faut faire en cas d'événement traçable . La seule différence est la possibilité de filtrer le log , via son type, dans le second .

OnTrace

OnTrace / OnLogTrace

procedure TForm1.ZSQLMonitor1LogTrace(Sender: TObject;
Event: TZLoggingEvent);
begin
Memo1.lines.add(Event.AsString);
end;

procedure TForm1.ZSQLMonitor1Trace(Sender: TObject;
Event: TZLoggingEvent; var LogTrace: Boolean);
begin
LogTrace:= Event.Category=lcOther; // ne trace que les events de catégorie autre
end;

Ce code nous amènera à parler des types TZLoggingEvent, TZLoggingCategory et TZLoggingFormatter dans le coin du mécano .

XV-C - Les méthodes

LogEvent(Event : TZLoggingEvent)

Save

SaveToFile

TraceCount

TraceList[index]

LoggingFormatter

La pratique

Le coin du mécano

XVI - La gestion des erreurs

XVII - Appendice A : Structure de la Base de Données exemple

J'ai longtemps hésité sur le choix à faire :

- utiliser la base exemple fournie avec la bibliothèque ZEOS

- utiliser une autre base comme celle proposée par SQLPro (voir « En complément sur Developpez.com » à la fin du tutoriel)

qui ont l'avantage de proposer des scripts pour chaque SGBD .

J'ai finalement opté pour une base de données sans trop de prétentions (la structure n'en est certainement pas optimale) pour pouvoir y travailler également des images (blobs) . Je vous présente le « Firebird Judo Club » , qui va nous servir de terrain (devrais-je dire Tatami) d'entraînement !

Logo du Club

(cliquez ici pour aggrandir l'image)

Structure Base

/********************* ROLES **********************/

CREATE ROLE RDB$ADMIN;
/********************* UDFS ***********************/

/****************** GENERATORS ********************/

/******************** DOMAINS *********************/

/******************* PROCEDURES ******************/

/******************** TABLES **********************/

CREATE TABLE CATEGORIES
(
CODEINTERNE Integer NOT NULL,
AGE Integer NOT NULL,
LIBELLE Varchar(30)
);
CREATE TABLE CEINTURES
(
RANG Smallint NOT NULL,
DAN Smallint NOT NULL,
LIBELLE Varchar(30),
CONSTRAINT PK_CEINTURES PRIMARY KEY (RANG,DAN)
);
CREATE TABLE CLASSEMENT_COMPETITION
(
RANG Integer NOT NULL,
LIBELLE Varchar(20),
MEDAILLE Varchar(10)
);
CREATE TABLE COMPETITIONS
(
ID Bigint NOT NULL,
EVENEMENT Bigint NOT NULL,
PARTICIPANT Bigint NOT NULL,
CLASSEMENT Smallint
);
CREATE TABLE COTISATIONS
(
CODEUNIQUE Bigint NOT NULL,
NUMEROCARTE Bigint NOT NULL,
CODEPERIODE Integer NOT NULL,
MONTANT_PAYE Numeric(15,2) DEFAULT 0.00,
ASSURANCE Smallint DEFAULT 0,
PASSEPORT Smallint DEFAULT 0,
CERTIFICAT_MEDICAL Smallint DEFAULT 0
);
CREATE TABLE EVENEMENTS
(
CODE Bigint NOT NULL,
PERIODE Bigint NOT NULL,
TYPE_EVENT Smallint,
NOM Varchar(40),
LIEU Varchar(40),
MONTANT Numeric(15,2),
AGE_MINI Smallint,
AGE_MAXI Smallint,
CEINTURE_MINI Smallint,
CEINTURE_MAXI Smallint,
DATE_EVENEMENT Date,
CONSTRAINT PK_EVENEMENT PRIMARY KEY (CODE)
);
CREATE TABLE FONCTIONS
(
CODE Integer NOT NULL,
LIBELLE Varchar(30)
);
CREATE TABLE MEMBRES
(
NUMEROCARTE Bigint NOT NULL,
NOM Varchar(30),
PRENOM Varchar(30),
ADRESSE_1 Varchar(38),
ADRESSE_2 Varchar(38),
ADRESSE_3 Varchar(38),
ADRESSE_4 Varchar(38),
CODE_POSTAL Varchar(5),
VILLE Varchar(38),
CEINTURE Smallint,
PASSEPORT Varchar(20) CHARACTER SET NONE,
DATE_NAISSANCE Date,
TELEPHONE Varchar(15),
A_CONTACTER Blob sub_type 1,
PHOTO Blob sub_type 0
);
CREATE TABLE PERIODES
(
CODE_PERIODE Bigint NOT NULL,
DATE_DEBUT Date NOT NULL,
DATE_FIN Date NOT NULL,
LIBELLE Varchar(30)
);
/********************* VIEWS **********************/

/******************* EXCEPTIONS *******************/

/******************** TRIGGERS ********************/

UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'Servira de clé unique'  where RDB$FIELD_NAME = 'CODEINTERNE' and RDB$RELATION_NAME = 'CATEGORIES';
UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'Age maximum de la catégorie au 31.12 de l'année en cours'  where RDB$FIELD_NAME = 'AGE' and RDB$RELATION_NAME = 'CATEGORIES';
UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'Nom de la catégorie'  where RDB$FIELD_NAME = 'LIBELLE' and RDB$RELATION_NAME = 'CATEGORIES';
UPDATE RDB$RELATIONS set
RDB$DESCRIPTION = 'La cétagorie dépend de l''âge du judoka'
where RDB$RELATION_NAME = 'CATEGORIES';
UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'Niveau du Judoka'  where RDB$FIELD_NAME = 'RANG' and RDB$RELATION_NAME = 'CEINTURES';
UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'Le Dan ne devrait servir qu''à la ceinture noire
Dans notre cas , il servira aussi pour indiquer les ceintures intermédiaires pour les jeunes judokas'  where RDB$FIELD_NAME = 'DAN' and RDB$RELATION_NAME = 'CEINTURES';
UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'Nom de la ceinture '  where RDB$FIELD_NAME = 'LIBELLE' and RDB$RELATION_NAME = 'CEINTURES';
UPDATE RDB$RELATIONS set
RDB$DESCRIPTION = 'Ceintures des judokas'
where RDB$RELATION_NAME = 'CEINTURES';
UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = '0 - Entrainements
1 - Compétitions Inter-Clubs
2 - Compétitions officielles '  where RDB$FIELD_NAME = 'TYPE_EVENT' and RDB$RELATION_NAME = 'EVENEMENTS';
UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'Catégorie minimale concernée'  where RDB$FIELD_NAME = 'AGE_MINI' and RDB$RELATION_NAME = 'EVENEMENTS';
UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'Niveau minimal'  where RDB$FIELD_NAME = 'CEINTURE_MINI' and RDB$RELATION_NAME = 'EVENEMENTS';
UPDATE RDB$RELATION_FIELDS set RDB$DESCRIPTION = 'En cas de compétition date unique
En cas d''entrainement , seul le jour sera pris en compte'  where RDB$FIELD_NAME = 'DATE_EVENEMENT' and RDB$RELATION_NAME = 'EVENEMENTS';
GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON CATEGORIES TO  SYSDBA WITH GRANT OPTION;

GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON CEINTURES TO  SYSDBA WITH GRANT OPTION;

GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON CLASSEMENT_COMPETITION TO  SYSDBA WITH GRANT OPTION;

GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON COMPETITIONS TO  SYSDBA WITH GRANT OPTION;

GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON COTISATIONS TO  SYSDBA WITH GRANT OPTION;

GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON EVENEMENTS TO  SYSDBA WITH GRANT OPTION;

GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON FONCTIONS TO  SYSDBA WITH GRANT OPTION;

GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON MEMBRES TO  SYSDBA WITH GRANT OPTION;

GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE
ON PERIODES TO  SYSDBA WITH GRANT OPTION;

XVIII - Appendice B : Composants d'accès au données Delphi 7

Tableau Comparatif

Composants
Interbase Express

Composants BDE

Composants ADO

Composants
DBExpress

TIBDataBase

TDataBase

TADOConnection

TSQLConnection

TIBTable

TTable

TADOTable

TSQLTable

TIBQuery

TQuery

TADOQuery

TSQLQuery

TIBStoredProc

TStoredProc

TADOStoredProc

TSQLStoredProc

TIBDataset

TADODataset

TSQLDataset

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article