FirebirdSQL logo
 Firebird InterfacesÉcriture de plugins 

Exécution d’instructions SQL avec des paramètres d’entrée

Il existe deux façons d’exécuter une instruction avec des paramètres d’entrée.Le choix de la bonne méthode dépend du fait que vous deviez l’exécuter plus d’une fois et que vous connaissiez ou non le format des paramètres au préalable. Lorsque ce format est connu et que vous n’avez besoin d’exécuter l’instruction qu’une seule fois, vous pouvez utiliser un seul appel à IAttachment::execute(). Sinon, vous devez d’abord préparer une requête SQL, puis vous pouvez l’exécuter à plusieurs reprises avec différents paramètres.

Pour préparer une instruction SQL en vue de son exécution, utilisez la méthode prepare() de l’interface IAttachment :

IStatement* stmt = att->prepare(&status, tra, 0,
    "UPDATE department SET budget = ? * budget + budget WHERE dept_no = ?",
    SQL_DIALECT_V6, IStatement::PREPARE_PREFETCH_METADATA);

Si vous n’avez pas l’intention d’utiliser la description des paramètres de Firebird (c’est-à-dire que vous pouvez fournir cette information vous-même), utilisez IStatement::PREPARE_PREFETCH_NONE au lieu de IStatement::PREPARE_PREFETCH_METADATA – cela réduira légèrement le trafic client/serveur et économisera des ressources.

Dans l’API ISC, la structure XSQLDA est utilisée pour décrire le format des paramètres de l’opérateur. La nouvelle API n’utilise pas XSQLDA — elle utilise IMessageMetadata à la place.Un ensemble de paramètres d’entrée (ainsi qu’un enregistrement extrait du curseur) est décrit dans l’API Firebird de la même manière, ci-après dénommé message.IMessageMetadata est transmis en tant que paramètre aux méthodes de messagerie entre le programme et le moteur de base de données. Il existe de nombreuses façons d’obtenir une instance de IMessageMetadata, en voici quelques-unes :

  • obtenir à partir de IStatement ;

  • construite à l’aide de l’interface IMetadataBuilder ;

  • Avec votre propre implémentation de cette interface.

La récupération des métadonnées à partir d’une requête préparée est très simple : la méthode getInputMetadata() renvoie une interface qui décrit le message d’entrée (c’est-à-dire les paramètres de l’opérateur), l’interface renvoyée par getOutputMetadata() décrit le message de sortie (c’est-à-dire une chaîne de données ou de valeurs sélectionnées renvoyées par la procédure). Dans notre cas, nous pouvons faire ce qui suit :

IMessageMetadata* meta = stmt->getInputMetadata(&status);

Ou nous pouvons créer nous-mêmes le message de métadonnées. Pour ce faire, tout d’abord, nous devons obtenir l’interface du constructeur :

IMetadataBuilder* builder = master->getMetadataBuilder(&status, 2);

Le deuxième paramètre est le nombre attendu de champs dans le message, il peut être modifié ultérieurement, c’est-à-dire qu’il n’est nécessaire que pour l’optimisation.

Ensuite, vous devez définir les caractéristiques individuelles des champs dans le générateur. Les types et longueurs de champ minimum requis sont les suivants :

builder->setType(&status, 0, SQL_DOUBLE + 1);

builder->setType(&status, 1, SQL_TEXT + 1);
builder->setLength(&status, 1, 3);

La nouvelle API utilise les anciennes constantes pour les types SQL, le plus petit bit est toujours utilisé pour indiquer la possibilité de prendre une valeur null. Dans certains cas, il est judicieux de définir un sous-type (pour les BLOB), un jeu de caractères (pour les zones de texte) ou un facteur d’échelle (pour les champs numériques).Enfin, il est temps d’obtenir une instance de IMessageMetadata :

IMessageMetadata* meta = builder->getMetadata(&status);

Nous ne discutons pas ici de notre propre implémentation de IMessageMetadata. Si cela vous intéresse, vous pouvez consulter l’exemple de 05.user_metadata.cpp.

Nous avons donc une instance de la description des métadonnées des paramètres d’entrée. Mais pour travailler avec le message, nous avons aussi besoin d’un tampon. La taille de la mémoire tampon est l’une des principales caractéristiques des messages de métadonnées et est renvoyée par la méthode getMessageLength() de IMessageMetadata :

char* buffer = new char[meta->getMessageLength(&status)];

Afin de traiter les valeurs individuelles à l’intérieur du tampon, le décalage par rapport à celles-ci doit être pris en compte. IMessageMetadata renseigne les décalages pour toutes les valeurs du message, en l’utilisant, nous pouvons créer des pointeurs vers celles-ci :

double* percent_inc = (double*) &buffer[meta->getOffset(&status, 0)];
char* dept_no = &buffer[meta->getOffset(&status, 1)];

N’oubliez pas non plus de traiter les flags NULL :

short* flag = (short*)&buffer[meta->getNullOffset(&status, 0)];
*flag = 0;

flag = (short*) &buffer[meta->getNullOffset(&status, 1)];
*flag = 0;

Une fois les manipulations de décalage terminées, nous sommes prêts à obtenir les valeurs des paramètres :

getInputValues(dept_no, percent_inc);

et exécutez l’instruction préparée :

stmt->execute(&status, tra, meta, buffer, NULL, NULL);

Les deux derniers paramètres NULL sont pour les messages de sortie et sont généralement utilisés pour l’instruction EXECUTE PROCEDURE.

Si vous n’avez pas besoin de récupérer les métadonnées de l’instruction et que vous prévoyez de ne l’exécuter qu’une seule fois, vous pouvez choisir une méthode plus simple — utilisez la méthode execute() de l’interface IAttachment :

att->execute(&status, tra, 0,
    "UPDATE department SET budget = ? * budget + budget WHERE dept_no = ?",
    SQL_DIALECT_V6, meta, buffer, NULL, NULL);

Dans ce cas, vous n’avez pas du tout besoin d’utiliser IStatement.

Un exemple d’exécution de l’instruction UPDATE avec des paramètres est présent dans 02.update.cpp ou l’exemple en Pascal 02.update.pas, vous verrez également comment une exception levée dans un déclencheur / procédure peut être interceptée dans un programme C++ ou Pascal.

Ouvrir un curseur et en extraire les données

La seule façon d’obtenir les lignes de données renvoyées par l’instruction SELECT dans l’API OO est d’utiliser IResultSet. Cette interface est retournée par la méthode openCursor() accessible à la fois dans IAttachment et IStatement. openCursor() est similaire à execute() dans la plupart des aspects, et décider comment ouvrir le curseur (en utilisant une instruction préparée ou directement à partir de l’interface de connexion) est la même chose. Les exemples 03.select.cpp version en Pascal 03.select.pas et 04.print_table.cpp utilisent les deux approches.Notez qu’une différence entre la méthode openCursor() et la méthode execute() est que rien n’est passé au tampon pour le message de sortie à openCursor(), il sera passé plus tard lorsque les données seront récupérées à partir du curseur. Cela vous permet d’ouvrir un curseur avec un format de message de sortie inconnu (NULL est passé à la place des métadonnées de sortie). Dans ce cas, Firebird utilise le format de message par défaut, qui peut être interrogé via l’interface IResultSet :

const char* sql = "select * from ..."; // some select statement

IResultSet* curs = att->openCursor(&status, tra, 0, sql, SQL_DIALECT_V6,
    NULL, NULL, NULL, NULL, 0);

IMessageMetadata* meta = curs->getMetadata(&status);

Par la suite, ces métadonnées peuvent être utilisées pour allouer une mémoire tampon aux données et analyser les lignes extraites.

Vous pouvez également préparer l’instruction, récupérer les métadonnées à partir de l’instruction préparée, puis ouvrir le curseur. Il s’agit de la méthode à privilégier si vous vous attendez à ce que le curseur soit ouvert plus d’une fois.

IStatement* stmt = att->prepare(&status, tra, 0, sql, SQL_DIALECT_V6,
    IStatement::PREPARE_PREFETCH_METADATA);

IMessageMetadata* meta = stmt->getOutputMetadata(&status);

IResultSet* curs = stmt->openCursor(&status, tra, NULL, NULL, NULL, 0);

Nous avons obtenu (d’une manière ou d’une autre) une instance de la description des métadonnées des champs de sortie (lignes dans le jeu de données). Pour travailler avec le message, nous avons également besoin d’un tampon :

unsigned char* buffer = new unsigned char[meta->getMessageLength(&status)];

Il existe de nombreuses méthodes de récupération différentes dans l’interface IResultSet, mais lorsque le curseur n’est pas ouvert avec l’option SCROLL, seule fetchNext() fonctionne, c’est-à-dire que vous ne pouvez avancer que dans les enregistrements suivant. En plus des erreurs et des avertissements dans l’état, la méthode fetchNext() renvoie un code de complétion qui est RESULT_OK (lorsque le tampon est rempli avec des valeurs pour la ligne suivante) ou RESULT_NO_DATA (lorsqu’il n’y a plus de lignes après le curseur). RESULT_NO_DATA n’est pas un état d’erreur, c’est un état normal une fois la méthode terminée, qui signale qu’il n’y a plus de données dans le curseur. Si un wrapper d’état est utilisé, aucune exception n’est levée si une erreur est renvoyée.Une autre valeur, RESULT_ERROR, peut être renvoyée, ce qui signifie qu’il n’y a pas de données dans le tampon et des erreurs dans l’état du vecteur. La méthode fetchNext() est généralement appelée dans une boucle :

while (curs->fetchNext(&status, buffer) == IStatus::RESULT_OK)
{
    // row processing
}

Ce qui se passe lorsque les enregistrements sont traitées dépend de vos besoins.Pour accéder à un champ spécifique, utilisez le décalage du champ :

unsigned char* field_N_ptr = buffer + meta->getOffset(&status, n);

n est le numéro du champ dans le message. Ce pointeur doit être affecté au type approprié, en fonction du type de champ. Par exemple, pour le champ VARCHAR, vous devez utiliser le cast dans la structure vary :

vary* v_ptr = (vary*) (buffer + meta->getOffset(&status, n));

Nous pouvons maintenant afficher la valeur du champ :

printf("field %s value is %*.*s\n",
       meta->getField(&status, n),
       v_ptr->vary_length,
       v_ptr->vary_length,
       v_ptr->vary_string);

Si vous voulez obtenir les meilleures performances, il est utile de mettre en cache les valeurs de métadonnées dont vous avez besoin, comme nous le faisons dans nos exemples 03.select.cpp version en Pascal 03.select.pas et 04.print_table.cpp .