Creating instances of UDRs depending on their declaration
In the newItem
method, you can create different instances of an externalprocedure or function, depending on its declaration in PSQL. To do this, you canuse the information obtained from IMessageMetadata
.
Suppose we want to implement a PSQL package with the same set of externalfunctions for squaring a number for various data types and a singleentry point.
SET TERM ^ ;
CREATE OR ALTER PACKAGE MYUDR2
AS
begin
function SqrSmallint(AInput SMALLINT) RETURNS INTEGER;
function SqrInteger(AInput INTEGER) RETURNS BIGINT;
function SqrBigint(AInput BIGINT) RETURNS BIGINT;
function SqrFloat(AInput FLOAT) RETURNS DOUBLE PRECISION;
function SqrDouble(AInput DOUBLE PRECISION) RETURNS DOUBLE PRECISION;
end^
RECREATE PACKAGE BODY MYUDR2
AS
begin
function SqrSmallint(AInput SMALLINT) RETURNS INTEGER
external name 'myudr2!sqrt_func'
engine udr;
function SqrInteger(AInput INTEGER) RETURNS BIGINT
external name 'myudr2!sqrt_func'
engine udr;
function SqrBigint(AInput BIGINT) RETURNS BIGINT
external name 'myudr2!sqrt_func'
engine udr;
function SqrFloat(AInput FLOAT) RETURNS DOUBLE PRECISION
external name 'myudr2!sqrt_func'
engine udr;
function SqrDouble(AInput DOUBLE PRECISION) RETURNS DOUBLE PRECISION
external name 'myudr2!sqrt_func'
engine udr;
end
^
SET TERM ; ^
To test the functions, we will use the following query
select
myudr2.SqrSmallint(1) as n1,
myudr2.SqrInteger(2) as n2,
myudr2.SqrBigint(3) as n3,
myudr2.SqrFloat(3.1) as n4,
myudr2.SqrDouble(3.2) as n5
from rdb$database
To make it easier to work with IMessageMetadata
and buffers, you can write aconvenient wrapper or try to use IMessageMetadata
and structures to displaymessages together. Here we will show the use of the second method.
The implementation of this idea is quite simple: in the function factory, we willcreate different function instances depending on the type of the input argument.In modern versions of Delphi, you can use generics to generalize code.
.......................
type
// the structure to which the input message will be mapped
TSqrInMsg<T> = record
n1: T;
n1Null: WordBool;
end;
// the structure to which the output message will be mapped
TSqrOutMsg<T> = record
result: T;
resultNull: WordBool;
end;
// Factory for instantiating external function TSqrFunction
TSqrFunctionFactory = class(IUdrFunctionFactoryImpl)
// 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;
{ Creating a new instance of an external TSqrFunction
@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): IExternalFunction; override;
end;
// External function TSqrFunction.
TSqrFunction<TIn, TOut> = class(IExternalFunctionImpl)
private
function sqrExec(AIn: TIn): TOut; virtual; abstract;
public
type
TInput = TSqrInMsg<TIn>;
TOutput = TSqrOutMsg<TOut>;
PInput = ^TInput;
POutput = ^TOutput;
// Called when the function instance is destroyed
procedure dispose(); override;
{ This method is called just before execute and
tells the kernel our requested character set to communicate 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;
{ Executing an external function
@param(AStatus Status vector)
@param(AContext External function execution context)
@param(AInMsg Pointer to input message)
@param(AOutMsg Pointer to output message)
}
procedure execute(AStatus: IStatus; AContext: IExternalContext;
AInMsg: Pointer; AOutMsg: Pointer); override;
end;
TSqrExecSmallint = class(TSqrFunction<Smallint, Integer>)
public
function sqrExec(AIn: Smallint): Integer; override;
end;
TSqrExecInteger = class(TSqrFunction<Integer, Int64>)
public
function sqrExec(AIn: Integer): Int64; override;
end;
TSqrExecInt64 = class(TSqrFunction<Int64, Int64>)
public
function sqrExec(AIn: Int64): Int64; override;
end;
TSqrExecFloat = class(TSqrFunction<Single, Double>)
public
function sqrExec(AIn: Single): Double; override;
end;
TSqrExecDouble = class(TSqrFunction<Double, Double>)
public
function sqrExec(AIn: Double): Double; override;
end;
implementation
uses
SysUtils, FbTypes, System.TypInfo;
{ TSqrFunctionFactory }
procedure TSqrFunctionFactory.dispose;
begin
Destroy;
end;
function TSqrFunctionFactory.newItem(AStatus: IStatus;
AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalFunction;
var
xInputMetadata: IMessageMetadata;
xInputType: TFBType;
begin
// get the type of the input argument
xInputMetadata := AMetadata.getInputMetadata(AStatus);
xInputType := TFBType(xInputMetadata.getType(AStatus, 0));
xInputMetadata.release;
// create an instance of a function depending on the type
case xInputType of
SQL_SHORT:
result := TSqrExecSmallint.Create();
SQL_LONG:
result := TSqrExecInteger.Create();
SQL_INT64:
result := TSqrExecInt64.Create();
SQL_FLOAT:
result := TSqrExecFloat.Create();
SQL_DOUBLE, SQL_D_FLOAT:
result := TSqrExecDouble.Create();
else
result := TSqrExecInt64.Create();
end;
end;
procedure TSqrFunctionFactory.setup(AStatus: IStatus;
AContext: IExternalContext; AMetadata: IRoutineMetadata;
AInBuilder, AOutBuilder: IMetadataBuilder);
begin
end;
{ TSqrFunction }
procedure TSqrFunction<TIn, TOut>.dispose;
begin
Destroy;
end;
procedure TSqrFunction<TIn, TOut>.execute(AStatus: IStatus;
AContext: IExternalContext; AInMsg, AOutMsg: Pointer);
var
xInput: PInput;
xOutput: POutput;
begin
xInput := PInput(AInMsg);
xOutput := POutput(AOutMsg);
xOutput.resultNull := True;
if (not xInput.n1Null) then
begin
xOutput.resultNull := False;
xOutput.result := Self.sqrExec(xInput.n1);
end;
end;
procedure TSqrFunction<TIn, TOut>.getCharSet(AStatus: IStatus;
AContext: IExternalContext; AName: PAnsiChar; ANameSize: Cardinal);
begin
end;
{ TSqrtExecSmallint }
function TSqrExecSmallint.sqrExec(AIn: Smallint): Integer;
begin
Result := AIn * AIn;
end;
{ TSqrExecInteger }
function TSqrExecInteger.sqrExec(AIn: Integer): Int64;
begin
Result := AIn * AIn;
end;
{ TSqrExecInt64 }
function TSqrExecInt64.sqrExec(AIn: Int64): Int64;
begin
Result := AIn * AIn;
end;
{ TSqrExecFloat }
function TSqrExecFloat.sqrExec(AIn: Single): Double;
begin
Result := AIn * AIn;
end;
{ TSqrExecDouble }
function TSqrExecDouble.sqrExec(AIn: Double): Double;
begin
Result := AIn * AIn;
end;
.................