Store a choice procedure
Now let’s add a simple selection procedure to our UDR module. To do this, we will change the registration function firebird_udr_plugin
.
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr;
AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl;
begin
// We register our functions
AUdrPlugin.registerFunction(AStatus, 'sum_args',
TSumArgsFunctionFactory.Create());
// We register our procedures
AUdrPlugin.registerProcedure(AStatus, 'sum_args_proc',
TSumArgsProcedureFactory.Create());
AUdrPlugin.registerProcedure(AStatus, 'gen_rows', TGenRowsFactory.Create());
// We register our triggers
//AUdrPlugin.registerTrigger(AStatus, 'test_trigger',
// TMyTriggerFactory.Create());
theirUnloadFlag := AUnloadFlagLocal;
Result := @myUnloadFlag;
end;
Note
|
Comment
Don’t forget to add the |
The procedure factory is completely identical as for the case with an executable storedprocedure. The procedure instance methods are also identical, with the exception of theopen
method, which we will analyze in a little more detail.
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;
// Factory for creating an instance of the external procedure TGenRowsProcedure
TGenRowsFactory = class(IUdrProcedureFactoryImpl)
// Called when the factory is destroyed
procedure dispose(); override;
{ Executed each time an external function is loaded into the metadata cache.
Used to change the format of the input and output messages.
@param(AStatus Status vector)
@param(AContext External function execution context)
@param(AMetadata External function metadata)
@param(AInBuilder Message builder for input metadata)
@param(AOutBuilder Message builder for output metadata)
}
procedure setup(AStatus: IStatus; AContext: IExternalContext;
AMetadata: IRoutineMetadata; AInBuilder: IMetadataBuilder;
AOutBuilder: IMetadataBuilder); override;
{ Create a new instance of the external procedure TGenRowsProcedure
@param(AStatus Status vector)
@param(AContext External function execution context)
@param(AMetadata External function metadata)
@returns(External function instance)
}
function newItem(AStatus: IStatus; AContext: IExternalContext;
AMetadata: IRoutineMetadata): IExternalProcedure; override;
end;
// External procedure TGenRowsProcedure.
TGenRowsProcedure = class(IExternalProcedureImpl)
public
// Called when the procedure instance is destroyed
procedure dispose(); override;
{ This method is called just before open and tells
to the kernel our requested set of characters to exchange data within this
method. During this call, the context uses the character set obtained from
ExternalEngine::getCharSet.
@param(AStatus Status vector)
@param(AContext External function execution context)
@param(AName Character set name)
@param(AName Character set name length)
}
procedure getCharSet(AStatus: IStatus; AContext: IExternalContext;
AName: PAnsiChar; ANameSize: cardinal); override;
{ Execution of external procedure
@param(AStatus Status vector)
@param(AContext External function execution context)
@param(AInMsg Pointer to input message)
@param(AOutMsg Pointer to output message)
@returns(Data set for selective procedure or
nil for run procedures)
}
function open(AStatus: IStatus; AContext: IExternalContext; AInMsg: Pointer;
AOutMsg: Pointer): IExternalResultSet; override;
end;
// Output data set for the TGenRowsProcedure procedure
TGenRowsResultSet = class(IExternalResultSetImpl)
Input: PInput;
Output: POutput;
// Called when the dataset instance is destroyed
procedure dispose(); override;
{ Retrieve the next record from the dataset. Somewhat analogous to
SUSPEND. In this method, the next record from the data set should
be prepared.
@param(AStatus Status vector)
@returns(True if the dataset has an entry to retrieve,
False if there are no more entries)
}
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;
// if one of the input arguments is NULL, return nothing
if PInput(AInMsg).startNull or PInput(AInMsg).finishNull then
begin
POutput(AOutMsg).nNull := True;
// intentionally set the output so that
// TGenRowsResultSet.fetch method returned false
Output.n := Input.finish;
exit;
end;
// checks
if PInput(AInMsg).start > PInput(AInMsg).finish then
raise Exception.Create('First parameter greater then second parameter.');
with TGenRowsResultSet(Result) do
begin
// initial value
Output.nNull := False;
Output.n := Input.start - 1;
end;
end;
{ TGenRowsResultSet }
procedure TGenRowsResultSet.dispose;
begin
Destroy;
end;
// If it returns True, then the next record from the data set is retrieved.
// If it returns False, then the records in the data set are over
// new values in the output vector are calculated each time
// when calling this method
function TGenRowsResultSet.fetch(AStatus: IStatus): Boolean;
begin
Inc(Output.n);
Result := (Output.n <= Input.finish);
end;
end.
In the open
method of the TGenRowsProcedure
procedure instance, we check the firstand second input arguments for the value NULL
, if one of the arguments is NULL
,then the output argument is NULL
, in addition, the procedure should not return anyrow when fetching via the SELECT
statement, so we assign Output.n
such a value thatthe TGenRowsResultSet.fetch` method returns False
.
In addition, we check that the first argument does not exceed the value of the second,otherwise we throw an exception. Don’t worry, this exception will be caught in the UDRsubsystem and converted to a Firebird exception. This is one of the advantages of thenew UDRs over Legacy UDFs.
Since we are creating a selection procedure, the open method must return a datasetinstance that implements the IExternalResultSet
interface. To simplify, let’s inheritour data set from the IExternalResultSetImpl
class.
The dispose
method is designed to release allocated resources. In it, we simply callthe destructor.
The fetch
method is called when the next record is retrieved by the SELECT
statement. This method is essentially analogous to the SUSPEND
statement used inregular PSQL stored procedures. Each time it is called, it prepares new values for theoutput message. The method returns true
if the record should be returned to thecaller, and false
if there is no more data to retrieve. In our case, we simplyincrement the current value of the output variable until it is greater than the maximumlimit.
Note
|
Comment
Delphi does not support the
You can use any collection class, populate it in the |