FirebirdSQL logo
 Déclarations d'une UDRMessages 

instance d’un déclencheur

Un déclencheur externe doit implémenter l’interface IExternalTrigger. Pour simplifier, nous héritons simplement de la classe IExternalTriggerImpl.

La méthode dispose est appelée lorsque l’instance du trigger est détruite, ce qui nous oblige à libérer les ressources précédemment allouées. Dans ce cas, nous appelons simplement le destructeur.

La méthode getCharSet est utilisée pour indiquer au contexte de déclenchement externe le jeu de caractères que nous voulons utiliser lorsque nous travaillons avec la connexion du contexte courant. Par défaut, la connexion du contexte courant travaille dans l’encodage de la connexion courante, ce qui n’est pas toujours pratique.

La méthode execute est appelée lorsqu’un trigger est exécuté sur l’un des événements pour lesquels le trigger a été créé. On passe à cette méthode un pointeur sur le vecteur d’état, un pointeur sur le contexte du trigger externe, l’action (événement) qui a provoqué le déclenchement du trigger, et des pointeurs sur les messages pour les anciennes et nouvelles valeurs de champ. Les actions (événements) de déclenchement possibles sont listées par des constantes dans l’interface IExternalTrigger. Ces constantes commencent par le préfixe ACTION_. Il est nécessaire de connaître l’action en cours car Firebird crée des triggers pour plusieurs événements à la fois. Les messages ne sont nécessaires que pour les triggers sur les actions des tables, pour les triggers DDL, ainsi que pour les triggers sur les événements de connexion et de déconnexion de la base de données et les triggers sur les événements de démarrage, de fin et de retour en arrière des transactions.Pour les événements de démarrage, de fin et de retour en arrière des transactions, les pointeurs vers les messages seront initialisés à nil. Contrairement aux procédures et aux fonctions, les messages des déclencheurs sont construits pour les champs de la table sur les événements desquels le déclencheur a été créé. Les structures statiques de ces messages sont construites selon les mêmes principes que les structures de messages pour les paramètres d’entrée et de sortie d’une procédure, mais les champs de la table sont pris à la place des variables.

Note

Veuillez noter que si vous utilisez le mapping message-to-struct, alors vos triggers peuvent être interrompus après avoir changé la composition des champs de la table et leurs types. Pour éviter cela, utilisez le travail avec le message à travers les offsets obtenus à partir de IMessageMetadata. Ce n’est pas aussi vrai pour les procédures et les fonctions, puisque les paramètres d’entrée et de sortie ne changent pas. Ou du moins, vous le faites explicitement, ce qui peut vous amener à penser que vous devez également ré-écrire la procédure/fonction externe.

Dans notre déclencheur le plus simple, nous définissons le type d’événement et, dans le corps du déclencheur, nous exécutons la commande PSQL suivante:

...
  IF (:new.B IS NULL) THEN
    :new.B = :new.A + 1;
...

Fonction : classe

Maintenant, nous devons écrire la classe et la fonction elle-même. Elles seront situées dans le module SumArgsFunc. Des exemples d’écriture de procédures et de triggers seront présentés plus tard.

unit SumArgsFunc;

{$IFDEF FPC}
{$MODE DELPHI}{$H+}
{$ENDIF}

interface

uses
  Firebird;

{ *********************************************************
    create function sum_args (
      n1 integer,
      n2 integer,
      n3 integer
    ) returns integer
    external name 'myudr!sum_args'
    engine udr;
 ********************************************************* }

type
  // la structure à laquelle le message d'entrée sera associé
  TSumArgsInMsg = record
    n1: Integer;
    n1Null: WordBool;
    n2: Integer;
    n2Null: WordBool;
    n3: Integer;
    n3Null: WordBool;
  end;
  PSumArgsInMsg = ^TSumArgsInMsg;

  // la structure à laquelle le message de sortie sera associé
  TSumArgsOutMsg = record
    result: Integer;
    resultNull: WordBool;
  end;
  PSumArgsOutMsg = ^TSumArgsOutMsg;

  // Classe pour l'instanciation de la fonction externe TSumArgsFunction
  TSumArgsFunctionFactory = class(IUdrFunctionFactoryImpl)
    // Appelé lorsque la classe est détruite
    procedure dispose(); override;

    { Exécuté chaque fois qu'une fonction externe est chargée dans le cache de métadonnées.
       Permet de modifier le format des messages d'entrée et de sortie.

      @param(AStatus vecteur de statut)
      @param(AContext Contexte d'exécution de la fonction externe)
      @param(AMetadata Métadonnées de la fonction externe)
      @param(AInBuilder Constructeur de messages pour les métadonnées d'entrée)
      @param(AOutBuilder Constructeur de messages pour les métadonnées de sortie)
    }
    procedure setup(AStatus: IStatus; AContext: IExternalContext;
      AMetadata: IRoutineMetadata; AInBuilder: IMetadataBuilder;
      AOutBuilder: IMetadataBuilder); override;

    { Création d'une nouvelle instance de fonction externe TSumArgsFunction

      @param(AStatus vecteur de statut)
      @param(AContext Contexte d'exécution de la fonction externe)
      @param(AMetadata Métadonnées de la fonction externe)
      @returns(Instance de la fonction externe)
    }
    function newItem(AStatus: IStatus; AContext: IExternalContext;
      AMetadata: IRoutineMetadata): IExternalFunction; override;
  end;

  // Fonction externe TSumArgsFunction.
  TSumArgsFunction = class(IExternalFunctionImpl)
    // Appelé lorsque l'instance de la fonction est détruite
    procedure dispose(); override;

    { Cette méthode est appelée juste avant l'exécution et indique au noyau le jeu de caractères
      requis pour échanger des données en interne avec cette méthode. Lors de cet appel, le contexte
      utilise le jeu de caractères obtenu par ExternalEngine::getCharSet.

      @param(AStatus Vecteur de statut)
      @param(AContext Contexte d'exécution de la fonction externe)
      @param(AName Nom du jeu de caractères)
      @param(ANameSize Longueur du nom du jeu de caractères)
    }
    procedure getCharSet(AStatus: IStatus; AContext: IExternalContext;
      AName: PAnsiChar; ANameSize: Cardinal); override;

    { Exécution d'une fonction externe

      @param(AStatus Vecteur de statut)
      @param(AContext Contexte d'exécution de la fonction externe)
      @param(AInMsg Pointeur vers le message d'entrée)
      @param(AOutMsg Pointeur vers le message de sortie)
    }
    procedure execute(AStatus: IStatus; AContext: IExternalContext;
      AInMsg: Pointer; AOutMsg: Pointer); override;
  end;

implementation

{ TSumArgsFunctionFactory }

procedure TSumArgsFunctionFactory.dispose;
begin
  Destroy;
end;

function TSumArgsFunctionFactory.newItem(AStatus: IStatus;
  AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalFunction;
begin
  Result := TSumArgsFunction.Create();
end;

procedure TSumArgsFunctionFactory.setup(AStatus: IStatus;
  AContext: IExternalContext; AMetadata: IRoutineMetadata;
  AInBuilder, AOutBuilder: IMetadataBuilder);
begin

end;

{ TSumArgsFunction }

procedure TSumArgsFunction.dispose;
begin
  Destroy;
end;

procedure TSumArgsFunction.execute(AStatus: IStatus; AContext: IExternalContext;
  AInMsg, AOutMsg: Pointer);
var
  xInput: PSumArgsInMsg;
  xOutput: PSumArgsOutMsg;
begin
  // convertir les pointeurs d'entrée et de sortie en pointeurs typés
  xInput := PSumArgsInMsg(AInMsg);
  xOutput := PSumArgsOutMsg(AOutMsg);
  // par défaut, l'argument de sortie est NULL, il faut donc lui donner la valeur nullFlag
  xOutput^.resultNull := True;
  // si l'un des arguments est NULL, le résultat est NULL
  // sinon, nous calculons la somme des arguments
  with xInput^ do
  begin
    if not (n1Null or n2Null or n3Null) then
    begin
      xOutput^.result := n1 + n2 + n3;
      // s'il y a un résultat, réinitialiser le drapeau NULL
      xOutput^.resultNull := False;
    end;
  end;
end;

procedure TSumArgsFunction.getCharSet(AStatus: IStatus;
  AContext: IExternalContext; AName: PAnsiChar; ANameSize: Cardinal);
begin
end;

end.

La classe de fonctions externe doit implémenter l’interface IUdrFunctionFactory. Pour simplifier, nous héritons simplement de la classe IUdrFunctionFactoryImpl. Chaque fonction externe a besoin de sa propre classe.Cependant, si les interfaces n’ont pas de spécificités pour créer une certaine fonction, alors vous pouvez écrire une interface générique en utilisant les generics. Nous donnerons plus loin un exemple de cette méthode.

La méthode dispose est appelée lors de la destruction de la classe, au cours de laquelle nous devons libérer les ressources précédemment allouées. Dans ce cas, nous appelons simplement le destructeur.

La méthode de configuration est exécutée chaque fois qu’une fonction externe est chargée dans le cache de métadonnées. Elle permet d’effectuer diverses actions nécessaires avant de créer une instance d’une fonction, par exemple modifier le format des messages d’entrée et de sortie. Nous y reviendrons plus en détail ultérieurement.

La méthode newItem est appelée pour instancier la fonction externe. Cette méthode reçoit un pointeur sur le vecteur d’état, le contexte de la fonction externe et les métadonnées de la fonction externe. Avec IRoutineMetadata, vous pouvez obtenir le format du message d’entrée et de sortie, le corps de la fonction externe et d’autres métadonnées. Dans cette méthode, vous pouvez créer différentes instances d’une fonction externe en fonction de sa déclaration dans PSQL. Les métadonnées peuvent être transmises à l’instance de fonction externe créée si nécessaire. Dans notre cas, nous créons simplement une instance de la fonction externe TSumArgsFunction.

Instance de la fonction

Une fonction externe doit implémenter l’interface IExternalFunction. Pour simplifier, nous héritons simplement de la classe IExternalFunctionImpl.

La méthode dispose est appelée lorsque l’instance de la fonction est détruite, ce qui nous oblige à libérer les ressources précédemment allouées. Dans ce cas, nous appelons simplement le destructeur.

La méthode getCharSet est utilisée pour indiquer au contexte de la fonction externe le jeu de caractères que nous voulons utiliser lorsque nous travaillons avec la connexion du contexte courant. Par défaut, la connexion du contexte courant fonctionne dans l’encodage de la connexion courante, ce qui n’est pas toujours pratique.

La méthode execute gère l’appel de fonction par elle-même. Elle reçoit un pointeur sur le vecteur d’état, un pointeur sur le contexte de la fonction externe, des pointeurs sur les messages d’entrée et de sortie.

Nous pouvons avoir besoin du contexte d’une fonction externe pour obtenir le contexte de la connexion ou de la transaction en cours. Même si vous n’utilisez pas de requêtes de base de données dans la connexion actuelle, vous pouvez toujours avoir besoin de ces contextes, en particulier lorsque vous travaillez avec le type BLOB. Des exemples de travail avec le type BLOB, ainsi que l’utilisation des contextes de connexion et de transaction, seront présentés plus loin.

Les messages d’entrée et de sortie ont une largeur fixe, qui dépend des types de données déclarés pour les variables d’entrée et de sortie, respectivement. Cela permet d’utiliser des pointeurs typés vers des structures de largeur fixe dont les membres doivent correspondre aux types de données. L’exemple montre que pour chaque variable de la structure, un membre du type correspondant est indiqué, après quoi il y a un membre qui est un signe d’une valeur NULL spéciale (ci-après dénommé "Null flag"). En plus de travailler avec des tampons de messages d’entrée et de sortie à travers des structures, il existe une autre façon d’utiliser l’arithmétique d’adressage sur des pointeurs en utilisant des offsets, dont les valeurs peuvent être obtenues à partir de l’interface IMessageMetadata. Nous reviendrons plus tard sur le travail avec les messages, mais pour l’instant nous nous contenterons d’expliquer ce qui a été fait dans la méthode execute.

Tout d’abord, nous convertissons les pointeurs non typés en pointeurs typés. Pour la valeur de sortie, nous mettons le drapeau Null à True (ceci est nécessaire pour que la fonction retourne NULL si l’un des arguments d’entrée est NULL). Ensuite, nous vérifions les drapeaux Null de tous les arguments d’entrée, si aucun des arguments d’entrée n’est égal à NULL, alors la valeur de sortie sera égale à la somme des valeurs des arguments. Il est important de se rappeler de réinitialiser le drapeau Null de l’argument de sortie à false.

Inscription des procédures

Il est temps d’ajouter une procédure stockée à notre module UDR. Comme vous le savez, il existe deux types de procédures stockées : les procédures stockées exécutables et les procédures stockées pour récupérer des données. Tout d’abord, ajoutons une procédure stockée exécutable, c’est-à-dire une procédure stockée qui peut être appelée avec l’instruction EXECUTE PROCEDURE et qui peut retourner au plus un enregistrement.

Retournez au module UdrInit et changez la fonction firebird_udr_plugin pour qu’elle ressemble à ceci.

function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr;
  AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl;
begin
  // inscrire notre fonction
  AUdrPlugin.registerFunction(AStatus, 'sum_args',
    TSumArgsFunctionFactory.Create());
  // inscrire notre procédure
  AUdrPlugin.registerProcedure(AStatus, 'sum_args_proc',
    TSumArgsProcedureFactory.Create());
  //AUdrPlugin.registerProcedure(AStatus, 'gen_rows', TGenRowsFactory.Create());
  // inscrire notre déclencheur
  //AUdrPlugin.registerTrigger(AStatus, 'test_trigger',
  // TMyTriggerFactory.Create());

  theirUnloadFlag := AUnloadFlagLocal;
  Result := @myUnloadFlag;
end;
Note

N’oubliez pas d’ajouter le module uses SumArgsProc à la liste où se trouve notre procédure.