FirebirdSQL logo
 Firebird InterfacesÉcriture de plugins 

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 .

Utilisation de la macros FB_MESSAGE pour les messages statiques

Travailler avec des données à l’aide de décalages de champs est assez efficace, mais cela nécessite beaucoup de code. Dans C++, ce problème peut être résolu avec des modèles, mais même par rapport à eux, la façon la plus pratique de travailler avec un message est de le présenter sous sa forme native : une structure en C/C++, un record en Pascal, etc.Bien entendu, cela ne fonctionne que si le format du message est connu à l’avance. Pour créer de telles structures en C++ dans Firebird, il y a une macro spéciale FB_MESSAGE.

FB_MESSAGE a 3 arguments : le nom du message (structure), le type d’enveloppe d’état et la liste des champs. L’utilisation du premier et du deuxième argument est évidente, la liste des champs contient des paires (field_type, field_name), où field_type est l’un des suivants :

  • FB_BIGINT

  • FB_BLOB

  • FB_BOOLEAN

  • FB_CHAR(len)

  • FB_DATE

  • FB_DECFLOAT16

  • FB_DECFLOAT34

  • FB_DOUBLE

  • FB_FLOAT

  • FB_INTEGER

  • FB_INTL_CHAR(len, charSet)

  • FB_INTL_VARCHAR(len, charSet)

  • FB_SCALED_BIGINT(x)

  • FB_SCALED_INTEGER(x)

  • FB_SCALED_SMALLINT(x)

  • FB_SMALLINT

  • FB_TIME

  • FB_TIME_TZ

  • FB_TIME_TZ_EX

  • FB_TIMESTAMP

  • FB_TIMESTAMP_TZ

  • FB_TIMESTAMP_TZ_EX

  • FB_VARCHAR(len)

Dans la structure générée par le préprocesseur, les types integer et float sont mappés aux types C correspondants, les types date et time sont mappés aux classes FbDate et FbTime (toutes les classes mentionnées ici sont dans l’espace de noms Firebird), le type timestamp est mappé à la classe FbTimestamp, qui contient deux membres de données publiques, la date et l’heure des classes respectives, et le type char est mappé à la structure du lien : #fbapi-objects-fbchar[FbChar] et varchar — avec la structure FbVarChar. Pour chaque champ, le préprocesseur crée deux membres de données : name pour la valeur du champ/paramètre et nameNull pour l’indicateur NULL. Le constructeur de message a 2 paramètres : un pointeur vers le wrapper d’état et une interface maître :

FB_MESSAGE(Output, ThrowStatusWrapper,
    (FB_SMALLINT, relationId)
    (FB_CHAR(31), relationName)
    (FB_VARCHAR(100), description)
) output(&status, master);

Pour les messages statiques, l’utilisation de FB_MESSAGE est le meilleur choix, mais ils peuvent facilement être passés aux méthodes execute, openCursor et fetch :

rs = att->openCursor(&status, tra, 0, sqlText,
      SQL_DIALECT_V6, NULL, NULL, output.getMetadata(), NULL, 0);

et est utilisé pour travailler avec les valeurs des champs individuels :

while (rs->fetchNext(&status, output.getData()) == IStatus::RESULT_OK)
{
  printf("%4d %31.31s %*.*s\n", output->relationId, output->relationName.str,
    output->descriptionNull ? 0 : output->description.length,
    output->descriptionNull ? 0 : output->description.length,
    output->description.str);
}

Pour obtenir un exemple d’utilisation de la macro FB_MESSAGE pour travailler avec des messages, consultez l’exemple 06.fb_message.cpp.