Création d’une base de données et connexion à une base de données existante
Exécution d’une instruction SQL sans paramètres d’entrée et avec des lignes renvoyées
Exécution d’instructions SQL avec des paramètres d’entrée
Ouvrir un curseur et en extraire les données
Utilisation de la macros FB_MESSAGE pour les messages statiques
Tout d’abord, nous devons accéder à l’interface IMaster
. IMaster
est l’interface principale de Firebird, nécessaire pour accéder à toutes les autres interfaces. Par conséquent, il existe un moyen simple d’y accéder – la seule chose qui est nécessaire est d’utiliser une fonction de l’API OO appelée fb_get_master_interface()
. Cette fonction n’a pas de paramètres et réussit toujours. Il n’y a qu’une seule instance de IMaster
pour chaque bibliothèque cliente Firebird, il n’y a donc pas besoin de s’inquiéter de libérer de la mémoire utilisée par l’interface maître. Le moyen le plus simple d’y accéder à partir de votre programme est d’utiliser la variable globale ou statique appropriée :
static IMaster* master = fb_get_master_interface();
Pour la plupart des méthodes utilisées dans l’API OO de Firebird, le premier paramètre est IStatus
. Il s’agit d’un remplacement logique de ISC_STATUS_ARRAY
, mais il fonctionne différement avec les erreurs et les avertissements (sans les mélanger dans le même tableau), peut contenir un nombre illimité d’erreurs, et (c’est important si vous envisagez d’implémenter IStatus
vous-même) stocke toujours les chaînes auxquelles il fait référence à l’intérieur de l’interface. Habituellement, au moins une instance de IStatus
est nécessaire pour appeler d’autres méthodes. Vous pouvez l’obtenir à partir de iMaster
:
IStatus* st = master->getStatus();
Si, pour une raison quelconque, la méthode getStatus()
ne fonctionne pas (OOM par exemple), alors elle retourne NULL
– auquel cas il est évident que nous ne pouvons pas utiliser une méthode générique pour les messages d’erreur basée sur IStatus
.
Nous allons maintenant nous intéresser à la première interface, qui est directement liée à l’accès à la base de données. Il s’agit de l’interface IProvider
, ainsi nommée parce que c’est l’interface que tout fournisseur dans Firebird devrait implémenter. La bibliothèque cliente Firebird possède sa propre implémentation de IProvider
qui doit être utilisée pour exécuter toutes les opérations de base de données. Pour l’obtenir, nous appelons la méthode getDispatcher
de l’interface IMaster
:
IProvider* prov = master->getDispatcher();
Lors de la connexion à une base de données existante, ou encore plus lors de la création d’une nouvelle base de données, il est souvent nécessaire de passer de nombreux paramètres supplémentaires à l’appel de l’API (login/mot de passe, taille de page pour la nouvelle base de données, etc.). Il est presque impossible d’avoir des paramètres séparés au niveau de l’appel : nous devrons modifier l’appel trop souvent pour ajouter de nouveaux paramètres, et le nombre de paramètres peut être très important, même si vous n’avez généralement pas besoin d’en passer beaucoup.Par conséquent, une structure de données en mémoire spéciale appelée bloc de paramètres de base de données (DPB) est utilisée pour transmettre des paramètres supplémentaires. Son format est clairement défini, ce qui permet de construire des blocs de paramètres DPB octets par octets. Cependant, il est beaucoup plus facile d’utiliser une interface spéciale IXpbBuilder
, ce qui facilite la création de blocs de divers paramètres.Pour obtenir une instance de IXpbBuilder
, vous devez connaître une autre API générique de Firebird — IUtil
. C’est une sorte de conteneur pour les appels qui ne se prêtent pas bien à d’autres endroits. Donc, ce que nous faisons, c’est
IUtil* utl = master->getUtilInterface();
IXpbBuilder* dpb = utl->getXpbBuilder(&status, IXpbBuilder::DPB, NULL, 0);
Ce code crée un constructeur de bloc de paramètres vide de type DPB. Maintenant, l’ajout du paramètre nécessaire est trivial :
dpb->insertInt(&status, isc_dpb_page_size, 4 * 1024);
créera une base de données avec une taille de page de 4 Ko et des valeurs dont le sens est clair.
dpb->insertString(&status, isc_dpb_user_name, "sysdba");
dpb->insertString(&status, isc_dpb_password, "masterkey");
Ce qui suit est spécifique à C++ : Nous sommes presque prêts à appeler la méthode createDatabase()
de l’instance IProvider
, mais avant cela, nous devons dire quelques mots sur le concept du Status Wrapper. Le Status Wrapper n’est pas une interface, c’est un wrapper très fin sur l’interface IStatus
. Il permet de personnaliser le comportement de l’API C++ (changer la façon dont les erreurs retournées dans l’interface IStatus
sont gérées).Dans un premier temps, nous vous recommandons d’utiliser ThrowStatusWrapper
, qui lève une exception C++ chaque fois qu’une erreur est renvoyée dans IStatus.
ThrowStatusWrapper status(st);
Nous pouvons maintenant créer une nouvelle base de données vide :
IAttachment* att = prov->createDatabase(&status, "fbtests.fdb",
dpb->getBufferLength(&status), dpb->getBuffer(&status));
printf("Database fbtests.fdb created\n");
Notez que nous ne vérifions pas l’état après avoir appelé createDatabase()
, car si une erreur se produit, une exception C++ ou Pascal sera levée (il est donc très utile d’avoir la syntaxe try/catch/except dans votre code). Nous utilisons également deux nouvelles fonctions de IXpbBuilder
– getBufferLength()
et getBuffer()
, qui extraient les données de l’interface au format DPB natif. Comme vous pouvez le voir, il n’est pas nécessaire de vérifier explicitement l’état des fonctions en renvoyant des résultats intermédiaires.
La fermeture de la base de données nouvellement créée est trivial :
att->detach(&status);
Il ne reste plus qu’à entourer toutes les instructions d’un bloc try
et à écrire un gestionnaire dans le bloc catch
. Lorsque vous utilisez ThrowStatusWrapper
, vous devez toujours intercepter la classe FbException
définie dans l’API C++, en Pascal, vous devez également travailler avec la class FbException
. Dans le cas le plus simple, le bloc de gestion des exceptions peut ressembler à ceci :
catch (const FbException& error)
{
char buf[256];
utl->formatStatus(buf, sizeof(buf), error.getStatus());
fprintf(stderr, "%s\n", buf);
}
Notez qu’ici nous utilisons une autre fonction de IUtil
— formatStatus()
. Il renvoie dans le tampon buf
le texte décrivant l’erreur (avertissement) stocké dans le paramètre IStatus
.
Pour vous connecter à une base de données existante, utilisez la méthode attachDatabase()
de l’interface IProvider
au lieu de createDatabase()
. Tous les paramètres sont les mêmes pour les deux méthodes.
att = prov->attachDatabase(&status, "fbtests.fdb", 0, NULL);
Dans cet exemple, il n’utilise aucun paramètre DPB supplémentaire. Gardez à l’esprit que sans identifiant/mot de passe, toute connexion à distance échouera si l’authentification de confiance n’est pas configurée. Bien entendu, les informations de connexion peuvent être fournies par l’environnement (dans les variables ISC_USER
et ISC_PASSWORD
), comme c’était le cas auparavant.
Le dossier examples
contient des exemples terminés, y compris des exemples de création d’une base de données, 01.create.cpp
et en Pascal 01.create.pas
. Lors de la lecture de ce document, il est utile de créer soit même les exemples et d’essayer de les exécuter.
Pour commencer à utiliser les services, vous devez d’abord vous connecter au gestionnaire de services. Cela se fait à l’aide de la méthode attachServiceManager()
de l’interface IProvider
. Cette méthode renvoie l’interface IService
, qui est ensuite utilisée pour communiquer avec le service. Pour préparer le SPB
à se connecter au Service Manager, vous pouvez utiliser IXpbBuilder
:
IXpbBuilder* spb1 = utl->getXpbBuilder(&status, IXpbBuilder::SPB_ATTACH, NULL, 0);
spb1->insertString(&status, isc_spb_user_name, "sysdba");
spb1->insertString(&status, isc_spb_password, "masterkey");
et connectez-vous :
IService* svc = prov->attachServiceManager(&status, "service_mgr",
spb1->getBufferLength(&status), spb1->getBuffer(&status));
À l’aide de IService
, vous pouvez effectuer des actions disponibles pour les services, telles que l’exécution de services, ainsi que demander diverses informations sur les utilitaires en cours d’exécution et du serveur dans son ensemble. Lors de la demande d’informations, il y a une limitation : le format du bloc de paramètres utilisé par la méthode query()
n’est pas pris en charge par IXpbBuilder
dans Firebird 4. Le support sera probablement ajouté dans les futurs versions, dans Firebird 4 vous devrez créer et analyser ce bloc manuellement. Le format de ce bloc rdt identique à l’ancien format (utilisé dans l’API ISC) un pour un.
Pour démarrer le service, vous devez d’abord créer le SPB approprié :
IXpbBuilder* spb2 = utl->getXpbBuilder(&status, IXpbBuilder::SPB_START, NULL, 0);
et y ajouter les éléments nécessaires. Par exemple, pour imprimer les statistiques de chiffrement de la base de données « employé » dans SPB, vous devez mettre ce qui suit :
spb2->insertTag(&status, isc_action_svc_db_stats);
spb2->insertString(&status, isc_spb_dbname, "employee");
spb2->insertInt(&status, isc_spb_options, isc_spb_sts_encryption);
Après cela, le service peut être démarré à l’aide de la méthode start()
de l’interface IService :
svc->start(&status, spb2->getBufferLength(&status), spb2->getBuffer(&status));
De nombreux services en cours d’exécution (y compris le gstat mentionné ici) renvoient des informations textuelles au moment de l’exécution. Pour l’afficher, vous devez demander ces informations au service en cours d’exécution ligne par ligne. Pour ce faire, appelez la méthode query()
de l’interface IService
avec les blocs de paramètres appropriés pour la réception et l’envoi. Le bloc d’envoi peut contenir diverses informations auxiliaires (par exemple, un délai d’expiration de la demande du service) ou des informations qui doivent être transmises à l’utilitaire stdin, où il peut être vide dans le cas le plus simple. Le bloc d’ingestion doit contenir une liste des balises que vous souhaitez recevoir du service. Pour la plupart des services publics, il s’agit de la seule « isc_info_svc_line » :
const unsigned char receiveItems1[] = {isc_info_svc_line};
De plus, il a besoin d’une mémoire tampon pour demander ces informations :
unsigned char results[1024];
Après ces étapes préliminaires, nous sommes prêts à demander des informations au service dans une boucle (chaque ligne est retournée en un seul appel à query()
) :
do
{
svc->query(&status, 0, NULL,
sizeof(receiveItems1), receiveItems1,
sizeof(results), results);
} while (printInfo(results, sizeof(results)));
Dans cet exemple, nous supposons que la fonction printInfo()
renvoie TRUE tant que le service renvoie un bloc de résultat contenant la ligne de sortie suivante, c’est-à-dire jusqu’à la fin du flux de données du service. Le format du bloc de résultat varie d’un service à l’autre, et certains services, tels que gsec, créent des formats historiques qui ne sont pas triviaux pour l’analyse, mais qui dépassent le cadre de ce chapitre. L’exemple de travail minimal printInfo()
est présent dans l’exemple 09.service.cpp
.
La même méthode de requête est utilisée pour récupérer des informations sur le serveur, mais dans ce cas, la fonction de requête n’est pas appelée dans une boucle, c’est-à-dire que le tampon doit être suffisamment grand pour contenir toutes les informations à la fois. Ce n’est pas trop difficile, car ces appels ne renvoient généralement pas beaucoup de données. Comme dans le cas précédent, vous devez commencer par placer les éléments nécessaires dans le bloc d’accueil — dans notre exemple, il s’agit de isc_info_svc_server_version
:
const unsigned char receiveItems2[] = {isc_info_svc_server_version};
Un tampon de résultat existant d’un appel précédent peut être réutilisé. Dans ce cas, vous n’avez pas besoin d’une boucle :
svc->query(&status, 0, NULL,
sizeof(receiveItems2), receiveItems2,
sizeof(results), results);
printInfo(results, sizeof(results));
Une fois les tâches de service terminées, n’oubliez pas de désactiver le service :
svc->detach(&status);
La création de bases de données vides n’est certainement pas suffisante pour travailler avec un SGBDR. Nous voulons être en mesure de créer divers objets dans la base de données (par exemple, des tables, etc.) et d’insérer des données dans ces tables. Dans Firebird, toute opération de base de données est transactionnelle. Par conséquent, tout d’abord, nous devons apprendre à démarrer une transaction. Nous ne parlons pas ici de transactions distribuées (prises en charge par l’interface IDtc
) afin d’éviter une complexité inutile pour la plupart des utilisateurs. Le démarrage d’une transaction non distribuée est très simple et se fait via l’interface de connexion :
ITransaction* tra = att->startTransaction(&status, 0, NULL);
Dans cet exemple, les paramètres de transaction par défaut sont utilisés : le TPB n’est pas transmis à la méthode startTransaction()
. Si vous avez besoin d’une transaction avec des paramètres autres que la valeur par défaut, vous pouvez créer un IXpbBuilder
correspondant et y ajouter les éléments nécessaires :
IXpbBuilder* tpb = utl->getXpbBuilder(&status, IXpbBuilder::TPB, NULL, 0);
tpb->insertTag(&status, isc_tpb_read_committed);
et passez le TPB terminé à startTransaction()
:
ITransaction* tra = att->startTransaction(&status, tpb->getBufferLength(&status),
tpb->getBuffer(&status));
L’interface de transaction est utilisée comme paramètre dans de nombreux autres appels d’API, mais elle ne fait rien d’autre que valider/annuler la transaction, tout en conservant le contexte de transaction :
tra->commit(&status);
Vous pouvez voir comment démarrer et confirmer une transaction dans les exemples 01.create.cpp
ou en Pascal 01.create.pas
.