Enregistrement d’une procédure sélective
Ajoutons maintenant une procédure sélective simple à notre module UDR. Pour ce faire, nous allons modifier la fonction d’enregistrement firebird_udr_plugin
.
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr;
AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl;
begin
// Nous enregistrons la fonction
AUdrPlugin.registerFunction(AStatus, 'sum_args',
TSumArgsFunctionFactory.Create());
// Nous enregistrons notre procédure
AUdrPlugin.registerProcedure(AStatus, 'sum_args_proc',
TSumArgsProcedureFactory.Create());
AUdrPlugin.registerProcedure(AStatus, 'gen_rows', TGenRowsFactory.Create());
// Nous enregistrons notre déclencheur
//AUdrPlugin.registerTrigger(AStatus, 'test_trigger',
// TMyTriggerFactory.Create());
theirUnloadFlag := AUnloadFlagLocal;
Result := @myUnloadFlag;
end;
Note
|
N’oubliez pas d’ajouter le module |
La classe de procédure est complètement identique à celle d’une procédure stockée exécutable. Les méthodes d’instance de procédure sont également identiques, à l’exception de la méthode open
, que nous allons analyser un peu plus en détail.
unit GenRowsProc;
{$IFDEF FPC}
{$MODE DELPHI}{$H+}
{$ENDIF}
interface
uses
Firebird, SysUtils;
type
{ **********************************************************
create procedure gen_rows (
start integer,
finish integer
) returns (n integer)
external name 'myudr!gen_rows'
engine udr;
********************************************************* }
TInput = record
start: Integer;
startNull: WordBool;
finish: Integer;
finishNull: WordBool;
end;
PInput = ^TInput;
TOutput = record
n: Integer;
nNull: WordBool;
end;
POutput = ^TOutput;
// Classe pour la création d'une instance de la procédure externe TGenRowsProcedure
TGenRowsFactory = class(IUdrProcedureFactoryImpl)
// Appelé lors de la destruction de la classe
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éer une nouvelle instance de la procédure externe TGenRowsProcedure
@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): IExternalProcedure; override;
end;
// Procédure externe TGenRowsProcedure.
TGenRowsProcedure = class(IExternalProcedureImpl)
public
// Appelé lorsque l'instance de procédure est détruite
procedure dispose(); override;
{ Cette méthode est appelée juste avant l'ouverture et indique au noyau le jeu de caractères
requis pour l'échange de données dans le cadre de cette méthode. Lors de cet appel, le contexte
utilise le jeu de caractères obtenu à partir de 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;
{ Execution de la procédure externe
@param(AStatus Vecteur de statut)
@param(AContext Contexte d'exécution de la fonction externe)
@param(AInMsg Pointeur du message (`IMessageMetadata`) d'entrée)
@param(AOutMsg Pointeur du message (`IMessageMetadata`) de sortie)
@returns(Ensemble de données pour une procédure sélective ou Nil pour les procédures)
}
function open(AStatus: IStatus; AContext: IExternalContext; AInMsg: Pointer;
AOutMsg: Pointer): IExternalResultSet; override;
end;
// Ensemble de données de sortie pour la procédure TGenRowsProcedure
TGenRowsResultSet = class(IExternalResultSetImpl)
Input: PInput;
Output: POutput;
// Appelé lorsque l'instance du jeu de données est détruite
procedure dispose(); override;
{ Récupère l'enregistrement suivant de l'ensemble de données. Cette méthode est quelque peu analogue
à SUSPEND. Dans cette méthode, l'enregistrement suivant de l'ensemble de données doit être préparé.
@param(AStatus Vecteur de statut)
@returns(True si l'ensemble de données comporte une entrée à extraire,
False s'il n'y a plus d'entrées)
}
function fetch(AStatus: IStatus): Boolean; override;
end;
implementation
{ TGenRowsFactory }
procedure TGenRowsFactory.dispose;
begin
Destroy;
end;
function TGenRowsFactory.newItem(AStatus: IStatus; AContext: IExternalContext;
AMetadata: IRoutineMetadata): IExternalProcedure;
begin
Result := TGenRowsProcedure.create;
end;
procedure TGenRowsFactory.setup(AStatus: IStatus; AContext: IExternalContext;
AMetadata: IRoutineMetadata; AInBuilder, AOutBuilder: IMetadataBuilder);
begin
end;
{ TGenRowsProcedure }
procedure TGenRowsProcedure.dispose;
begin
Destroy;
end;
procedure TGenRowsProcedure.getCharSet(AStatus: IStatus;
AContext: IExternalContext; AName: PAnsiChar; ANameSize: cardinal);
begin
end;
function TGenRowsProcedure.open(AStatus: IStatus; AContext: IExternalContext;
AInMsg, AOutMsg: Pointer): IExternalResultSet;
begin
Result := TGenRowsResultSet.create;
with TGenRowsResultSet(Result) do
begin
Input := AInMsg;
Output := AOutMsg;
end;
// si l'un des arguments d'entrée est NULL, ne rien renvoyer
if PInput(AInMsg).startNull or PInput(AInMsg).finishNull then
begin
POutput(AOutMsg).nNull := True;
// a intentionnellement défini la sortie de manière à ce que
// la méthode TGenRowsResultSet.fetch renvoie un résultat faux.
Output.n := Input.finish;
exit;
end;
// vérifications
if PInput(AInMsg).start > PInput(AInMsg).finish then
raise Exception.Create('First parameter greater then second parameter.');
with TGenRowsResultSet(Result) do
begin
// valeur initiale
Output.nNull := False;
Output.n := Input.start - 1;
end;
end;
{ TGenRowsResultSet }
procedure TGenRowsResultSet.dispose;
begin
Destroy;
end;
// Si elle renvoie la valeur True, l'enregistrement suivant de l'ensemble de données est récupéré.
// S'il renvoie False, les enregistrements de l'ensemble de données sont terminés ;
// les nouvelles valeurs du vecteur de sortie sont calculées à chaque appel de cette méthode.
function TGenRowsResultSet.fetch(AStatus: IStatus): Boolean;
begin
Inc(Output.n);
Result := (Output.n <= Input.finish);
end;
end.
Dans la méthode open
de l’instance de la procédure TGenRowsProcedure
, nous vérifions la valeur NULL
du premier et second arguments d’entrée, si l’un des arguments est NULL
, alors l’argument de sortie est NULL
, de plus, la procédure ne doit retourner aucune ligne lors de la récupération via l’instruction SELECT
, nous assignons donc à Output.n
une valeur telle que la méthode TGenRowsResultSet.fetch
renvoie False
.
De plus, nous vérifions que le premier argument ne dépasse pas la valeur du second, sinon nous lançons une exception. Ne vous inquiétez pas, cette exception sera capturée dans le sous-système UDR et convertie en une exception Firebird. C’est l’un des avantages des nouveaux UDR par rapport aux anciennes UDF.
Puisque nous créons une procédure de sélection, la méthode open doit retourner une instance de dataset qui implémente l’interface IExternalResultSet
. Pour simplifier, héritons notre jeu de données de la classe IExternalResultSetImpl
.
La méthode dispose
est conçue pour libérer les ressources allouées. Dans cette méthode, nous appelons simplement le destructeur.
La méthode fetch
est appelée lorsque l’enregistrement suivant est récupéré par l’instruction SELECT
. Cette méthode est essentiellement analogue à l’instruction SUSPEND
utilisée dans les procédures stockées PSQL ordinaires. Chaque fois qu’elle est appelée, elle prépare de nouvelles valeurs pour le message de sortie. La méthode retourne vrai
si l’enregistrement doit être retourné à l’appelant, et faux
s’il n’y a plus de données à récupérer. Dans notre cas, nous incrémentons simplement la valeur actuelle de la variable de sortie jusqu’à ce qu’elle soit supérieure à la limite maximale.
Note
|
Delphi ne supporte pas l’opérateur
Vous pouvez utiliser n’importe quelle classe de collection, la remplir dans la méthode |