Reading data from BLOB
As an example of reading a BLOB, consider a procedure that splitsstring by delimiter (reverse procedure for the built-in aggregateLIST functions). It is declared like this
create procedure split (
txt blob sub_type text character set utf8,
delimiter char(1) character set utf8 = ','
)
returns (
id integer
)
external name 'myudr!split'
engine udr;
Let’s register our procedure factory:
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr;
AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl;
begin
// register our procedure
AUdrPlugin.registerProcedure(AStatus, 'split', TProcedureSimpleFactory<TSplitProcedure>.Create());
theirUnloadFlag := AUnloadFlagLocal;
Result := @myUnloadFlag;
end;
Here I used a generalized factory for simple cases when thefactory simply creates a copy of the procedure without the use ofmetadata. Such a factory is declared as follows:
...
interface
uses SysUtils, Firebird;
type
TProcedureSimpleFactory<T: IExternalProcedureImpl, constructor> =
class(IUdrProcedureFactoryImpl)
procedure dispose(); override;
procedure setup(AStatus: IStatus; AContext: IExternalContext;
AMetadata: IRoutineMetadata; AInBuilder: IMetadataBuilder;
AOutBuilder: IMetadataBuilder); override;
function newItem(AStatus: IStatus; AContext: IExternalContext;
AMetadata: IRoutineMetadata): IExternalProcedure; override;
end;
...
implementation
{ TProcedureSimpleFactory<T> }
procedure TProcedureSimpleFactory<T>.dispose;
begin
Destroy;
end;
function TProcedureSimpleFactory<T>.newItem(AStatus: IStatus;
AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalProcedure;
begin
Result := T.Create;
end;
procedure TProcedureSimpleFactory<T>.setup(AStatus: IStatus;
AContext: IExternalContext; AMetadata: IRoutineMetadata; AInBuilder,
AOutBuilder: IMetadataBuilder);
begin
...
Now let’s move on to the implementation of the procedure. Let’sfirst declare structures for input and output messages.
TInput = record
txt: ISC_QUAD;
txtNull: WordBool;
delimiter: array [0 .. 3] of AnsiChar;
delimiterNull: WordBool;
end;
TInputPtr = ^TInput;
TOutput = record
Id: Integer;
Null: WordBool;
end;
TOutputPtr = ^TOutput;
As you can see, instead of the BLOB value, the Blob identifier is transmitted, which is described by the ISC_QUAD
structure.
Now let’s describe the procedure class and the returned data set:
TSplitProcedure = class(IExternalProcedureImpl)
private
procedure SaveBlobToStream(AStatus: IStatus; AContext: IExternalContext;
ABlobId: ISC_QUADPtr; AStream: TStream);
function readBlob(AStatus: IStatus; AContext: IExternalContext;
ABlobId: ISC_QUADPtr): string;
public
// Called when destroying a copy of the procedure
procedure dispose(); override;
procedure getCharSet(AStatus: IStatus; AContext: IExternalContext;
AName: PAnsiChar; ANameSize: Cardinal); override;
function open(AStatus: IStatus; AContext: IExternalContext; AInMsg: Pointer;
AOutMsg: Pointer): IExternalResultSet; override;
end;
TSplitResultSet = class(IExternalResultSetImpl)
{$IFDEF FPC}
OutputArray: TStringArray;
{$ELSE}
OutputArray: TArray<string>;
{$ENDIF}
Counter: Integer;
Output: TOutputPtr;
procedure dispose(); override;
function fetch(AStatus: IStatus): Boolean; override;
end;
Additional SaveBlobToStream
and` readBlob` are designed to readBlob. The first reads Blob in a stream, the second is based onthe first and performs a convert for the read flow into a Delphiline. The data set of the lines of the OutputArray
and thecounter of the returned records Counter
are transmitted.
In the `open` method, Blob is read and converted into a line. The resulting line is divided into a separator using the built -in `split` method from a Hellper for lines. The resulting array of lines is transmitted to the resulting data set.
function TSplitProcedure.open(AStatus: IStatus; AContext: IExternalContext;
AInMsg, AOutMsg: Pointer): IExternalResultSet;
var
xInput: TInputPtr;
xText: string;
xDelimiter: string;
begin
xInput := AInMsg;
Result := TSplitResultSet.Create;
TSplitResultSet(Result).Output := AOutMsg;
if xInput.txtNull or xInput.delimiterNull then
begin
with TSplitResultSet(Result) do
begin
// We create an empty array
OutputArray := [];
Counter := 1;
end;
Exit;
end;
xText := readBlob(AStatus, AContext, @xInput.txt);
xDelimiter := TFBCharSet.CS_UTF8.GetString(TBytes(@xInput.delimiter), 0, 4);
// automatically is not correctly determined because the lines
// not completed by zero
// Place the backing of byte/4
SetLength(xDelimiter, 1);
with TSplitResultSet(Result) do
begin
OutputArray := xText.Split([xDelimiter], TStringSplitOptions.ExcludeEmpty);
Counter := 0;
end;
end;
Note
|
Comment
Type of |
Now we will describe the data reading procedure from BLOB to thestream. In order to read data from BLOB, it must be opened. Thiscan be done by calling the openBlob
method` IAttachment`. Sincewe read Blob from our database, we will open it in the context ofthe current connection. The context of the current connection andthe context of the current transaction can be obtained from thecontext of the external procedure, function or trigger (the ``IEXTERNALCONTEXT
).
Blob is read in portions (segments), the maximum size of thesegment is 64 KB. The segment is read by the getSegment
interface` IBlob`.
procedure TSplitProcedure.SaveBlobToStream(AStatus: IStatus;
AContext: IExternalContext; ABlobId: ISC_QUADPtr; AStream: TStream);
var
att: IAttachment;
trx: ITransaction;
blob: IBlob;
buffer: array [0 .. 32767] of AnsiChar;
l: Integer;
begin
try
att := AContext.getAttachment(AStatus);
trx := AContext.getTransaction(AStatus);
blob := att.openBlob(AStatus, trx, ABlobId, 0, nil);
while True do
begin
case blob.getSegment(AStatus, SizeOf(buffer), @buffer, @l) of
IStatus.RESULT_OK:
AStream.WriteBuffer(buffer, l);
IStatus.RESULT_SEGMENT:
AStream.WriteBuffer(buffer, l);
else
break;
end;
end;
AStream.Position := 0;
// CLOSE method in case of success combines the IBLOB interface
// Therefore, the subsequent call is not needed
blob.close(AStatus);
blob := nil;
finally
if Assigned(blob) then
blob.release;
if Assigned(trx) then
trx.release;
if Assigned(att) then
att.release;
end;
end;
Note
|
Comment
Please note that the interfaces |
Important
|
Important
The In the example of the variable |
On the basis of the SaveBlobToStream
method, the Blob readingprocedure in the line is written:
function TSplitProcedure.readBlob(AStatus: IStatus; AContext: IExternalContext;
ABlobId: ISC_QUADPtr): string;
var
{$IFDEF FPC}
xStream: TBytesStream;
{$ELSE}
xStream: TStringStream;
{$ENDIF}
begin
{$IFDEF FPC}
xStream := TBytesStream.Create(nil);
{$ELSE}
xStream := TStringStream.Create('', 65001);
{$ENDIF}
try
SaveBlobToStream(AStatus, AContext, ABlobId, xStream);
{$IFDEF FPC}
Result := TEncoding.UTF8.GetString(xStream.Bytes, 0, xStream.Size);
{$ELSE}
Result := xStream.DataString;
{$ENDIF}
finally
xStream.Free;
end;
end;
Note
|
Comment
Unfortunately, Free Pascal does not provide full reversecompatibility with Delphi for the |
The fetch
method of the output set of data extracts an elementwith the Counter
index from the line and increases it until thelast element of the array is extracted. Each extracted line isconverted to the whole. If this is impossible to do, then anexception will be excited with the isc_convert_error
code.
procedure TSplitResultSet.dispose;
begin
SetLength(OutputArray, 0);
Destroy;
end;
function TSplitResultSet.fetch(AStatus: IStatus): Boolean;
var
statusVector: array [0 .. 4] of NativeIntPtr;
begin
if Counter <= High(OutputArray) then
begin
Output.Null := False;
// Exceptions will be intercepted in any case with the ISC_Random code
// Here we will throw out the standard for Firebird
// error ISC_CONVERT_ERROR
try
Output.Id := OutputArray[Counter].ToInteger();
except
on e: EConvertError do
begin
statusVector[0] := NativeIntPtr(isc_arg_gds);
statusVector[1] := NativeIntPtr(isc_convert_error);
statusVector[2] := NativeIntPtr(isc_arg_string);
statusVector[3] := NativeIntPtr(PAnsiChar('Cannot convert string to integer'));
statusVector[4] := NativeIntPtr(isc_arg_end);
AStatus.setErrors(@statusVector);
end;
end;
inc(Counter);
Result := True;
end
else
Result := False;
end;
Note
|
Comment
In fact, the processing of any errors except |
The performance of the procedure can be checked as follows:
SELECT ids.ID
FROM SPLIT((SELECT LIST(ID) FROM MYTABLE), ',') ids
Note
|
Comment
The main drawback of this implementation is that Blob will alwaysbe read entirely, even if you want to interrupt the extraction ofrecords from the procedure ahead of schedule. If desired, you canchange the procedure code so that smashing into tunes is carriedout in smaller portions. To do this, the reading of theseportions must be carried out in the |