FirebirdSQL logo
 Firebird InterfacesÉcriture de plugins 

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.

Travailler avec les BLOB

Pour les blobs, Firebird stocke un identifiant BLOB dans le tampon de message, qui est un objet de 8 octets qui doit être aligné sur une limite de 4 octets. L’identifiant est de type ISC_QUAD. L’interface IAttachment a 2 méthodes pour travailler avec les BLOBs, openBlob() et createBlob(), qui renvoient l’interface IBlob et ont le même ensemble de paramètres, mais effectuent des actions légèrement différentes : openBlob() prend l’ID BLOB du message et prépare le BLOB pour la lecture, et createBlob() crée un nouveau BLOB, met son identifiant dans le message et prépare le BLOB pour l’écriture.

Pour travailler avec des BLOB, vous devez d`abord inclure leurs identifiants BLOB dans le message. Si vous obtenez des métadonnées d`un champ du moteur Firebird du type approprié, cet identifiant sera déjà présent. Dans ce cas, vous utilisez simplement son offset (à condition que la variable blobFieldNumber contienne le numéro du champ BLOB) (et l`offset NULL correspondant pour vérifier NULL ou mettre le flag NULL) pour obtenir le pointeur dans le tampon du message :

ISC_QUAD* blobPtr =
  (ISC_QUAD*) &buffer[metadata->getOffset(&status, blobFieldNumber)];
ISC_SHORT* blobNullPtr =
  (ISC_SHORT*) &buffer[metadata->getNullOffset(&status, blobFieldNumber)];

Si vous utilisez les messages de macro statiques FB_MESSAGE, le champ BLOB sera déclaré comme étant de type FB_BLOB :

FB_MESSAGE(Msg, ThrowStatusWrapper,
    (FB_BLOB, b)
) message(&status, master);

ISC_QUAD* blobPtr = &message->b;
ISC_SHORT* blobNullPtr = &message->bNull;

Pour créer un nouveau BLOB, appelez la méthode createBlob() :

IBlob* blob = att->createBlob(status, tra, blobPtr, 0, NULL);

Les deux dernières options ne sont requises que si vous souhaitez utiliser des filtres d’objets blob ou un flux d’objets blob, qui ne sont pas abordés ici.

L’interface Blob est maintenant prête à accepter des données dans le BLOB. Utilisez la méthode putSegment() pour envoyer des données au moteur :

void* segmentData;
unsigned segmentLength;
while (userFunctionProvidingBlobData(&segmentData, &segmentLength))
    blob->putSegment(&status, segmentLength, segmentData);

Après avoir envoyé des données au BLOB, n`oubliez pas de fermer l`interface du BLOB :

blob->close(&status);

Assurez-vous que l’indicateur null n’est pas défini (non requis si vous avez vidé l’intégralité de la mémoire tampon de message avant de créer le BLOB) :

*blobNullPtr = 0;

et un message qui contient un objet BLOB peut être utilisé dans une instruction d’insertion ou de mise à jour. Une fois cette instruction exécutée, le nouvel objet blob est stocké dans la base de données.

Pour lire un blob, vous devez obtenir son identifiant dans un message du noyau firebird. Cela peut être fait en utilisant les méthodes fetch() ou execute(). Après cela, utilisez la méthode openBlob() :

IBlob* blob = att->openBlob(status, tra, blobPtr, 0, NULL);

L’interface Blob est prête à fournir des données BLOB. Utilisez la méthode getSegment() pour obtenir les données du moteur :

char buffer[BUFSIZE];
unsigned actualLength;

for(;;)
{
  switch (blob->getSegment(&status, sizeof(buffer), buffer, &actualLength))
  {
    case IStatus::RESULT_OK:
      userFunctionAcceptingBlobData(buffer, actualLength, true);
      continue;

    case IStatus::RESULT_SEGMENT:
      userFunctionAcceptingBlobData(buffer, actualLength, false);
      continue;

    default:
      break;
  }
}

Le dernier paramètre de userFunctionAcceptingBlobData() est l’indicateur de fin de segment — lorsque getSegment() renvoie le code de complétion RESULT_SEGMENT, qui sera notifié à la fonction (le dernier paramètre est passé false), c’est-à-dire que ce segment n’est pas lu complètement, et la suite est attendue lors de l’appel suivant.

Lorsque vous avez terminé avec le BLOB, n’oubliez pas de le fermer :

blob->close(&status);