Контекст соединения и транзакции
Если ваша внешняя процедура, функция или триггер должна получать данныеиз собственной базы данных не через входные аргументы, а например череззапрос, то вам потребуется получать контекст текущего соединения и/илитранзакции. Кроме того, контекст соединения и транзакции необходим есливы будете работать с типом BLOB.
Контекст выполнения текущей процедуры, функции или триггера передаётся вкачестве параметра с типом IExternalContext
в метод execute
триггера илифункции, или в метод open процедуры. Интерфейс IExternalContext
позволяет получить текущее соединение с помощью метода getAttachment
, итекущую транзакцию с помощью метода getTransaction
. Это даёт большуюгибкость вашим UDR, например вы можете выполнять запросы к текущей базеданных с сохранением текущего сессионного окружения, в той же транзакцииили в новой транзакции, созданной с помощью метода startTransactionинтерфейса IExternalContext
. В последнем случае запрос будет выполнентак как будто он выполняется в автономной транзакции. Кроме того, выможете выполнить запрос к внешней базе данных с использованиемтранзакции присоединённой к текущей транзакции, т.е. транзакции сдвухфазным подтверждением (2PC).
В качестве примера работы с контекстом выполнения функции напишемфункцию, которая будет сериализовать результат выполнения SELECT
запросав формате JSON. Она объявлена следующим образом:
create function GetJson (
sql_text blob sub_type text character set utf8,
sql_dialect smallint not null default 3
)
returns returns blob sub_type text character set utf8
external name 'JsonUtils!getJson'
engine udr;
Поскольку мы позволяем выполнять произвольный SQL запрос, то мы не знаемзаранее формат выходных полей, и мы не сможем использовать структуру сфиксированными полями. В этом случае нам придётся работать с интерфейсомIMessageMetadata
. Мы уже сталкивались с ним ранее, но на этот разпридётся работать с ним более основательно, поскольку мы должныобрабатывать все существующие типы Firebird.
Note
|
Замечание
В JSON можно закодировать практически любые типы данных кроме бинарных.Для кодирования типов |
Зарегистрируем фабрику нашей функции:
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr;
AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl;
begin
// регистрируем функцию
AUdrPlugin.registerFunction(AStatus, 'getJson', TFunctionSimpleFactory<TJsonFunction>.Create());
theirUnloadFlag := AUnloadFlagLocal;
Result := @myUnloadFlag;
end;
Теперь объявим структуры для входного и выходного сообщения, а так жеинтерфейсную часть нашей функции:
unit JsonFunc;
{$IFDEF FPC}
{$MODE objfpc}{$H+}
{$DEFINE DEBUGFPC}
{$ENDIF}
interface
uses
Firebird,
UdrFactories,
FbTypes,
FbCharsets,
SysUtils,
System.NetEncoding,
System.Json;
{ *********************************************************
create function GetJson (
sql_text blob sub_type text,
sql_dialect smallint not null default 3
) returns blob sub_type text character set utf8
external name 'JsonUtils!getJson'
engine udr;
********************************************************* }
type
TInput = record
SqlText: ISC_QUAD;
SqlNull: WordBool;
SqlDialect: Smallint;
SqlDialectNull: WordBool;
end;
InputPtr = ^TInput;
TOutput = record
Json: ISC_QUAD;
NullFlag: WordBool;
end;
OutputPtr = ^TOutput;
// Внешняя функция TSumArgsFunction.
TJsonFunction = class(IExternalFunctionImpl)
public
procedure dispose(); override;
procedure getCharSet(AStatus: IStatus; AContext: IExternalContext;
AName: PAnsiChar; ANameSize: Cardinal); override;
{ Преобразует целое в строку в соответствии с масштабом
@param(AValue Значение)
@param(Scale Масштаб)
@returns(Строковое представление масштабированного целого)
}
function MakeScaleInteger(AValue: Int64; Scale: Smallint): string;
{ Добавляет закодированную запись в массив объектов Json
@param(AStatus Статус вектор)
@param(AContext Контекст выполнения внешней функции)
@param(AJson Массив объектов Json)
@param(ABuffer Буфер записи)
@param(AMeta Метаданные курсора)
@param(AFormatSetting Установки формата даты и времени)
}
procedure writeJson(AStatus: IStatus; AContext: IExternalContext;
AJson: TJsonArray; ABuffer: PByte; AMeta: IMessageMetadata;
AFormatSettings: TFormatSettings);
{ Выполнение внешней функции
@param(AStatus Статус вектор)
@param(AContext Контекст выполнения внешней функции)
@param(AInMsg Указатель на входное сообщение)
@param(AOutMsg Указатель на выходное сообщение)
}
procedure execute(AStatus: IStatus; AContext: IExternalContext;
AInMsg: Pointer; AOutMsg: Pointer); override;
end;
Дополнительный метод MakeScaleInteger
предназначен для преобразованиямасштабируемых чисел в строку, метод writeJson
кодирует очередную записьвыбранную из курсора в Json объект и добавляет его в массив такихобъектов.
В этом примере, нам потребуется реализовать метод getCharSet
, чтобы указать желаемую кодировкурезультата запроса выполняемого в контексте текущего подключения внутри внешней функции. По умолчаниюэта внутренний запрос будет выполняться в кодировке текущего подключения. Однако это не совсем удобно.Мы заранее не знаем в какой кодировке будет работать клиент, поэтому придётся определять кодировку каждоговозвращаемого строкового поля и перекодировать в UTF8. Для упрощения задачи, сразу укажем контексту, что мы собираемсяработать внутри процедуры в кодировке UTF8.
procedure TJsonFunction.getCharSet(AStatus: IStatus; AContext: IExternalContext;
AName: PAnsiChar; ANameSize: Cardinal);
begin
// затираем предыдущую кодировку
FillChar(AName, ANameSize, #0);
// ставим желаемую кодировку
StrCopy(AName, 'UTF8');
end;
Эти методы мы опишем позже, а пока приведём основной методexecute
для выполнения внешней функции.
procedure TJsonFunction.execute(AStatus: IStatus; AContext: IExternalContext;
AInMsg, AOutMsg: Pointer);
var
xFormatSettings: TFormatSettings;
xInput: InputPtr;
xOutput: OutputPtr;
att: IAttachment;
tra: ITransaction;
stmt: IStatement;
inBlob, outBlob: IBlob;
inStream: TBytesStream;
outStream: TStringStream;
cursorMetaData: IMessageMetadata;
rs: IResultSet;
msgLen: Cardinal;
msg: Pointer;
jsonArray: TJsonArray;
begin
xInput := AInMsg;
xOutput := AOutMsg;
// если один из входных аргументов NULL, то и результат NULL
if xInput.SqlNull or xInput.SqlDialectNull then
begin
xOutput.NullFlag := True;
Exit;
end;
xOutput.NullFlag := False;
// установки форматирования даты и времени
{$IFNDEF FPC}
xFormatSettings := TFormatSettings.Create;
{$ELSE}
xFormatSettings := DefaultFormatSettings;
{$ENDIF}
xFormatSettings.DateSeparator := '-';
xFormatSettings.TimeSeparator := ':';
// создаём поток байт для чтения blob
inStream := TBytesStream.Create(nil);
{$IFNDEF FPC}
outStream := TStringStream.Create('', 65001);
{$ELSE}
outStream := TStringStream.Create('');
{$ENDIF}
jsonArray := TJsonArray.Create;
// получение текущего соединения и транзакции
att := AContext.getAttachment(AStatus);
tra := AContext.getTransaction(AStatus);
stmt := nil;
rs := nil;
inBlob := nil;
outBlob := nil;
try
// читаем BLOB в поток
inBlob := att.openBlob(AStatus, tra, @xInput.SqlText, 0, nil);
inBlob.SaveToStream(AStatus, inStream);
// метод close в случае успеха совобождает интерфейс IBlob
// поэтому последующий вызов release не нужен
inBlob.close(AStatus);
inBlob := nil;
// подготавливаем оператор
stmt := att.prepare(AStatus, tra, inStream.Size, @inStream.Bytes[0],
xInput.SqlDialect, IStatement.PREPARE_PREFETCH_METADATA);
// получаем выходные метаданные курсора
cursorMetaData := stmt.getOutputMetadata(AStatus);
// откурываем курсор
rs := stmt.openCursor(AStatus, tra, nil, nil, nil, 0);
// выделяем буфер нужного размера
msgLen := cursorMetaData.getMessageLength(AStatus);
msg := AllocMem(msgLen);
try
// читаем каждую запись курсора
while rs.fetchNext(AStatus, msg) = IStatus.RESULT_OK do
begin
// и пишем её в JSON
writeJson(AStatus, AContext, jsonArray, msg, cursorMetaData,
xFormatSettings);
end;
finally
// освобождаем буфер
FreeMem(msg);
end;
// закрываем курсор
// метод close в случае успеха совобождает интерфейс IResultSet
// поэтому последующий вызов release не нужен
rs.close(AStatus);
rs := nil;
// освобождаем подготовленный запрос
// метод free в случае успеха совобождает интерфейс IStatement
// поэтому последующий вызов release не нужен
stmt.free(AStatus);
stmt := nil;
// пишем JSON в поток
{$IFNDEF FPC}
outStream.WriteString(jsonArray.ToJSON);
{$ELSE}
outStream.WriteString(jsonArray.AsJSON);
{$ENDIF}
// пишем json в выходной blob
outBlob := att.createBlob(AStatus, tra, @xOutput.Json, 0, nil);
outBlob.LoadFromStream(AStatus, outStream);
// метод close в случае успеха совобождает интерфейс IBlob
// поэтому последующий вызов release не нужен
outBlob.close(AStatus);
outBlob := nil;
finally
if Assigned(inBlob) then
inBlob.release;
if Assigned(stmt) then
stmt.release;
if Assigned(rs) then
rs.release;
if Assigned(outBlob) then
outBlob.release;
tra.release;
att.release;
jsonArray.Free;
inStream.Free;
outStream.Free;
end;
end;
Первым делом получаем из контекста выполнения функции текущееподключение и текущую транзакцию с помощью методов getAttachment
иgetTransaction
интерфейса IExternalContext
. Затем читаем содержимое BLOBдля получения текста SQL запроса. Запрос подготавливается с помощьюметода prepare интерфейса IAttachment
. Пятым параметром передаётся SQLдиалект полученный из входного параметра нашей функции. Шестымпараметром передаём флаг IStatement.PREPARE_PREFETCH_METADATA
, чтообозначает что мы хотим получить метаданные курсора вместе с результатомпрепарирования запроса. Сами выходные метаданные курсора получаем спомощью метода getOutputMetadata
интерфейса IStatement
.
Note
|
Замечание
На самом деле метод |
Далее открываем курсор с помощью метода openCursor
в рамках текущейтранзакции (параметр 2). Получаем размер выходного буфера под результаткурсора с помощью метода getMessageLength
интерфейса IMessageMetadata
.Это позволяет выделить память под буфер, которую мы освободим сразупосле вычитки последней записи курсора.
Записи курсора читаются с помощью метода fetchNext
интерфейсаIResultSet
. Этот метод заполняет буфер msg
значениями полей курсора ивозвращает IStatus.RESULT_OK
до тех пор, пока записи курсора некончатся. Каждая прочитанная запись передаётся в метод writeJson,который добавляет объект типа TJsonObject
с сериализованной записьюкурсора в массив TJsonArray
.
После завершения работы с курсором, закрываем его методом close
,преобразуем массив Json объектов в строку, пишем её в выходной поток,который записываем в выходной Blob.
Теперь разберём метод writeJson
. Объект IUtil
потребуется нам для того,чтобы получать функции для декодирования даты и времени. В этом методеактивно задействована работа с метаданными выходных полей курсора спомощью интерфейса IMessageMetadata
. Первым дело создаём объект типTJsonObject
в который будем записывать значения полей текущей записи.В качестве имён ключей будем использовать алиасы полей из курсора. Еслиустановлен NullFlag, то пишем значение null для ключа и переходим кследующему полю, в противном случае анализируем тип поля и пишем егозначение в Json.
function TJsonFunction.MakeScaleInteger(AValue: Int64; Scale: Smallint): string;
var
L: Integer;
begin
Result := AValue.ToString;
L := Result.Length;
if (-Scale >= L) then
Result := '0.' + Result.PadLeft(-Scale, '0')
else
Result := Result.Insert(Scale + L, '.');
end;
procedure TJsonFunction.writeJson(AStatus: IStatus; AContext: IExternalContext;
AJson: TJsonArray; ABuffer: PByte; AMeta: IMessageMetadata;
AFormatSettings: TFormatSettings);
var
jsonObject: TJsonObject;
i: Integer;
FieldName: string;
NullFlag: WordBool;
fieldType: Cardinal;
pData: PByte;
util: IUtil;
metaLength: Integer;
// типы
CharBuffer: TBytes;
charLength: Smallint;
charset: TFBCharSet;
StringValue: string;
SmallintValue: Smallint;
IntegerValue: Integer;
BigintValue: Int64;
Scale: Smallint;
SingleValue: Single;
DoubleValue: Double;
Dec16Value: FB_DEC16Ptr;
xDec16Buf: array[0..IDecFloat16.STRING_SIZE-1] of AnsiChar;
xDecFloat16: IDecFloat16;
Dec34Value: FB_DEC34Ptr;
xDec34Buf: array[0..IDecFloat34.STRING_SIZE-1] of AnsiChar;
xDecFloat34: IDecFloat34;
BooleanValue: Boolean;
DateValue: ISC_DATE;
TimeValue: ISC_TIME;
TimeValueTz: ISC_TIME_TZPtr;
TimestampValue: ISC_TIMESTAMP;
TimestampValueTz: ISC_TIMESTAMP_TZPtr;
tzBuffer: array[0..63] of AnsiChar;
DateTimeValue: TDateTime;
year, month, day: Cardinal;
hours, minutes, seconds, fractions: Cardinal;
blobId: ISC_QUADPtr;
BlobSubtype: Smallint;
att: IAttachment;
tra: ITransaction;
blob: IBlob;
textStream: TStringStream;
binaryStream: TBytesStream;
{$IFDEF FPC}
base64Stream: TBase64EncodingStream;
xFloatJson: TJSONFloatNumber;
{$ENDIF}
xInt128: IInt128;
Int128Value: FB_I128Ptr;
xInt128Buf: array[0..IInt128.STRING_SIZE-1] of AnsiChar;
begin
// Получаем IUtil
util := AContext.getMaster().getUtilInterface();
// Создаём объект TJsonObject в которой будем
// записывать значение полей записи
jsonObject := TJsonObject.Create;
for i := 0 to AMeta.getCount(AStatus) - 1 do
begin
// получаем алиас поля в запросе
FieldName := AMeta.getAlias(AStatus, i);
NullFlag := PWordBool(ABuffer + AMeta.getNullOffset(AStatus, i))^;
if NullFlag then
begin
// если NULL пишем его в JSON и переходим к следующему полю
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, TJsonNull.Create);
{$ELSE}
jsonObject.Add(FieldName, TJsonNull.Create);
{$ENDIF}
continue;
end;
// получаем указатель на данные поля
pData := ABuffer + AMeta.getOffset(AStatus, i);
// аналог AMeta->getType(AStatus, i) & ~1
fieldType := AMeta.getType(AStatus, i) and not 1;
case fieldType of
// VARCHAR
SQL_VARYING:
begin
// размер буфера для VARCHAR
metaLength := AMeta.getLength(AStatus, i);
SetLength(CharBuffer, metaLength);
charset := TFBCharSet(AMeta.getCharSet(AStatus, i));
charLength := PSmallint(pData)^;
// бинарные данные кодируем в base64
if charset = CS_BINARY then
begin
{$IFNDEF FPC}
StringValue := TNetEncoding.base64.EncodeBytesToString((pData + 2),
charLength);
{$ELSE}
// Для VARCHAR первые 2 байта - длина в байтах
// поэтому копируем в буфер начиная с 3 байта
Move((pData + 2)^, CharBuffer[0], metaLength);
StringValue := charset.GetString(CharBuffer, 0, charLength);
StringValue := EncodeStringBase64(StringValue);
{$ENDIF}
end
else
begin
// Для VARCHAR первые 2 байта - длина в байтах
// поэтому копируем в буфер начиная с 3 байта
Move((pData + 2)^, CharBuffer[0], metaLength);
StringValue := charset.GetString(CharBuffer, 0, charLength);
end;
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, StringValue);
{$ELSE}
jsonObject.Add(FieldName, StringValue);
{$ENDIF}
end;
// CHAR
SQL_TEXT:
begin
// размер буфера для CHAR
metaLength := AMeta.getLength(AStatus, i);
SetLength(CharBuffer, metaLength);
charset := TFBCharSet(AMeta.getCharSet(AStatus, i));
Move(pData^, CharBuffer[0], metaLength);
// бинарные данные кодируем в base64
if charset = CS_BINARY then
begin
{$IFNDEF FPC}
StringValue := TNetEncoding.base64.EncodeBytesToString(pData,
metaLength);
{$ELSE}
StringValue := charset.GetString(CharBuffer, 0, metaLength);
StringValue := EncodeStringBase64(StringValue);
{$ENDIF}
end
else
begin
StringValue := charset.GetString(CharBuffer, 0, metaLength);
charLength := metaLength div charset.GetCharWidth;
SetLength(StringValue, charLength);
end;
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, StringValue);
{$ELSE}
jsonObject.Add(FieldName, StringValue);
{$ENDIF}
end;
// FLOAT
SQL_FLOAT:
begin
SingleValue := PSingle(pData)^;
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, TJSONNumber.Create(SingleValue));
{$ELSE}
jsonObject.Add(FieldName, TJSONFloatNumber.Create(SingleValue));
{$ENDIF}
end;
// DOUBLE PRECISION
// DECIMAL(p, s), где p = 10..15 в 1 диалекте
SQL_DOUBLE, SQL_D_FLOAT:
begin
DoubleValue := PDouble(pData)^;
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, TJSONNumber.Create(DoubleValue));
{$ELSE}
jsonObject.Add(FieldName, TJSONFloatNumber.Create(DoubleValue));
{$ENDIF}
end;
// DECFLOAT(16)
SQL_DEC16:
begin
Dec16Value := FB_Dec16Ptr(pData);
xDecFloat16 := util.getDecFloat16(AStatus);
xDecFloat16.toString(AStatus, Dec16Value, IDecFloat16.STRING_SIZE, @xDec16Buf[0]);
StringValue := AnsiString(@xDec16Buf[0]);
StringValue := Trim(StringValue);
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, StringValue);
{$ELSE}
jsonObject.Add(FieldName, StringValue);
{$ENDIF}
end;
// DECFLOAT(34)
SQL_DEC34:
begin
Dec34Value := FB_Dec34Ptr(pData);
xDecFloat34 := util.getDecFloat34(AStatus);
xDecFloat34.toString(AStatus, Dec34Value, IDecFloat34.STRING_SIZE, @xDec34Buf[0]);
StringValue := AnsiString(@xDec34Buf[0]);
StringValue := Trim(StringValue);
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, StringValue);
{$ELSE}
jsonObject.Add(FieldName, StringValue);
{$ENDIF}
end;
// INTEGER
// NUMERIC(p, s), где p = 1..4
SQL_SHORT:
begin
Scale := AMeta.getScale(AStatus, i);
SmallintValue := PSmallint(pData)^;
if (Scale = 0) then
begin
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, TJSONNumber.Create(SmallintValue));
{$ELSE}
jsonObject.Add(FieldName, SmallintValue);
{$ENDIF}
end
else
begin
StringValue := MakeScaleInteger(SmallintValue, Scale);
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, TJSONNumber.Create(StringValue));
{$ELSE}
xFloatJson := TJSONFloatNumber.Create(0);
xFloatJson.AsString := StringValue;
jsonObject.Add(FieldName, xFloatJson);
{$ENDIF}
end;
end;
// INTEGER
// NUMERIC(p, s), где p = 5..9
// DECIMAL(p, s), где p = 1..9
SQL_LONG:
begin
Scale := AMeta.getScale(AStatus, i);
IntegerValue := PInteger(pData)^;
if (Scale = 0) then
begin
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, TJSONNumber.Create(IntegerValue));
{$ELSE}
jsonObject.Add(FieldName, IntegerValue);
{$ENDIF}
end
else
begin
StringValue := MakeScaleInteger(IntegerValue, Scale);
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, TJSONNumber.Create(StringValue));
{$ELSE}
xFloatJson := TJSONFloatNumber.Create(0);
xFloatJson.AsString := StringValue;
jsonObject.Add(FieldName, xFloatJson);
{$ENDIF}
end;
end;
// BIGINT
// NUMERIC(p, s), где p = 10..18 в 3 диалекте
// DECIMAL(p, s), где p = 10..18 в 3 диалекте
SQL_INT64:
begin
Scale := AMeta.getScale(AStatus, i);
BigintValue := Pint64(pData)^;
if (Scale = 0) then
begin
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, TJSONNumber.Create(BigintValue));
{$ELSE}
jsonObject.Add(FieldName, BigintValue);
{$ENDIF}
end
else
begin
StringValue := MakeScaleInteger(BigintValue, Scale);
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, TJSONNumber.Create(StringValue));
{$ELSE}
xFloatJson := TJSONFloatNumber.Create(0);
xFloatJson.AsString := StringValue;
jsonObject.Add(FieldName, xFloatJson);
{$ENDIF}
end;
end;
SQL_INT128:
begin
Scale := AMeta.getScale(AStatus, i);
Int128Value := FB_I128Ptr(pData);
xInt128 := util.getInt128(AStatus);
xInt128.toString(AStatus, Int128Value, Scale, IInt128.STRING_SIZE, @xInt128Buf[0]);
StringValue := AnsiString(@xInt128Buf[0]);
StringValue := Trim(StringValue);
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, StringValue);
{$ELSE}
jsonObject.Add(FieldName, StringValue);
{$ENDIF}
end;
// TIMESTAMP
SQL_TIMESTAMP:
begin
TimestampValue := PISC_TIMESTAMP(pData)^;
// получаем составные части даты-времени
util.decodeDate(TimestampValue.timestamp_date, @year, @month, @day);
util.decodeTime(TimestampValue.timestamp_time, @hours, @minutes, @seconds,
@fractions);
// получаем дату-время в родном типе Delphi
DateTimeValue := EncodeDate(year, month, day) +
EncodeTime(hours, minutes, seconds, fractions div 10);
// форматируем дату-время по заданному формату
StringValue := FormatDateTime('yyyy/mm/dd hh:nn:ss', DateTimeValue,
AFormatSettings);
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, StringValue);
{$ELSE}
jsonObject.Add(FieldName, StringValue);
{$ENDIF}
end;
// TIMESTAMP WITH TIME_ZONE
SQL_TIMESTAMP_TZ:
begin
TimestampValueTz := ISC_TIMESTAMP_TZPtr(pData);
// получаем составные части даты-времени и часовой пояс
util.decodeTimeStampTz(AStatus, TimestampValueTz, @year, @month, @day, @hours, @minutes, @seconds,
@fractions, 64, @tzBuffer[0]);
// получаем дату-время в родном типе Delphi
DateTimeValue := EncodeDate(year, month, day) +
EncodeTime(hours, minutes, seconds, fractions div 10);
// форматируем дату-время по заданному формату + часовой пояс
StringValue := FormatDateTime('yyyy/mm/dd hh:nn:ss', DateTimeValue,
AFormatSettings) + ' ' + AnsiString(@tzBuffer[0]);
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, StringValue);
{$ELSE}
jsonObject.Add(FieldName, StringValue);
{$ENDIF}
end;
// DATE
SQL_DATE:
begin
DateValue := PISC_DATE(pData)^;
// получаем составные части даты
util.decodeDate(DateValue, @year, @month, @day);
// получаем дату в родном типе Delphi
DateTimeValue := EncodeDate(year, month, day);
// форматируем дату по заданному формату
StringValue := FormatDateTime('yyyy/mm/dd', DateTimeValue,
AFormatSettings);
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, StringValue);
{$ELSE}
jsonObject.Add(FieldName, StringValue);
{$ENDIF}
end;
// TIME
SQL_TIME:
begin
TimeValue := PISC_TIME(pData)^;
// получаем составные части времени
util.decodeTime(TimeValue, @hours, @minutes, @seconds, @fractions);
// получаем время в родном типе Delphi
DateTimeValue := EncodeTime(hours, minutes, seconds,
fractions div 10);
// форматируем время по заданному формату
StringValue := FormatDateTime('hh:nn:ss', DateTimeValue,
AFormatSettings);
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, StringValue);
{$ELSE}
jsonObject.Add(FieldName, StringValue);
{$ENDIF}
end;
// TIME WITH TIME ZONE
SQL_TIME_TZ:
begin
TimeValueTz := ISC_TIME_TZPtr(pData);
// получаем составные части времени и часовой пояс
util.decodeTimeTz(AStatus, TimeValueTz, @hours, @minutes, @seconds,
@fractions, 64, @tzBuffer[0]);
// получаем время в родном типе Delphi
DateTimeValue := EncodeTime(hours, minutes, seconds,
fractions div 10);
// форматируем время по заданному формату + часовой пояс
StringValue := FormatDateTime('hh:nn:ss', DateTimeValue,
AFormatSettings) + ' ' + AnsiString(@tzBuffer[0]);
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, StringValue);
{$ELSE}
jsonObject.Add(FieldName, StringValue);
{$ENDIF}
end;
// BOOLEAN
SQL_BOOLEAN:
begin
BooleanValue := PBoolean(pData)^;
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, TJsonBool.Create(BooleanValue));
{$ELSE}
jsonObject.Add(FieldName, BooleanValue);
{$ENDIF}
end;
// BLOB
SQL_BLOB, SQL_QUAD:
begin
BlobSubtype := AMeta.getSubType(AStatus, i);
blobId := ISC_QUADPtr(pData);
att := AContext.getAttachment(AStatus);
tra := AContext.getTransaction(AStatus);
blob := att.openBlob(AStatus, tra, blobId, 0, nil);
try
if BlobSubtype = 1 then
begin
// текст
charset := TFBCharSet(AMeta.getCharSet(AStatus, i));
// создаём поток с заданой кодировкой
{$IFNDEF FPC}
textStream := TStringStream.Create('', charset.GetCodePage);
try
blob.SaveToStream(AStatus, textStream);
blob.close(AStatus);
blob := nil;
StringValue := textStream.DataString;
finally
textStream.Free;
end;
{$ELSE}
binaryStream := TBytesStream.Create(nil);
try
blob.SaveToStream(AStatus, binaryStream);
blob.close(AStatus);
blob := nil;
StringValue := TEncoding.UTF8.GetString(binaryStream.Bytes, 0,
binaryStream.Size);
finally
binaryStream.Free;
end;
{$ENDIF}
end
else
begin
{$IFNDEF FPC}
// все остальные подтипытипы считаем бинарными
binaryStream := TBytesStream.Create;
try
blob.SaveToStream(AStatus, binaryStream);
blob.close(AStatus);
blob := nil;
// кодируем строку в base64
StringValue := TNetEncoding.base64.EncodeBytesToString
(binaryStream.Memory, binaryStream.Size);
finally
binaryStream.Free;
end
{$ELSE}
textStream := TStringStream.Create('');
base64Stream := TBase64EncodingStream.Create(textStream);
try
blob.SaveToStream(AStatus, base64Stream);
blob.close(AStatus);
blob := nil;
StringValue := textStream.DataString;
finally
base64Stream.Free;
textStream.Free;
end;
{$ENDIF}
end;
finally
if Assigned(blob) then blob.release;
if Assigned(tra) then tra.release;
if Assigned(att) then att.release;
end;
{$IFNDEF FPC}
jsonObject.AddPair(FieldName, StringValue);
{$ELSE}
jsonObject.Add(FieldName, StringValue);
{$ENDIF}
end;
end;
end;
// добавление записи в формате Json в массив
{$IFNDEF FPC}
AJson.AddElement(jsonObject);
{$ELSE}
AJson.Add(jsonObject);
{$ENDIF}
end;
Note
|
Замечание
Перечисление типа Перечисление |
Для строк типа CHAR
и VARCHAR
проверяем кодировку, если это кодировкаOCTETS
, то кодируем строку алгоритмом base64, в противном случаепреобразуем данные из буфера в строку Delphi. Обратите внимание, что длятипа VARCHAR первые 2 байта содержат длину строки в символах.
Типы SMALLINT
, INTEGER
, BIGINT
могут быть как обычными целыми числами,так масштабируемыми. Масштаб числа можно получить методом getScale
интерфейса IMessageMetadata
. Если масштаб не равен 0, то требуетсяспециальная обработка числа, которая осуществляет методомMakeScaleInteger
.
Типы DATE
, TIME
и TIMESTAMP
декодируются на составные части даты ивремени с помощью методов decodeDate
и decodeTime
интерфейса IUtil
.Используем части даты и времени для получения даты-времени в стандартномDelphi типе TDateTime.
С типом BLOB работаем через потоки Delphi. Если BLOB бинарный, тосоздаём поток типа TBytesStream
. Полученный массив байт кодируем спомощью алгоритма base64. Если BLOB текстовый, то используемспециализированный поток TStringStream
для строк, который позволяетучесть кодовую страницу. Кодовую страницу мы получаем из кодировки BLOBполя.
Для работы с типом данных INT128
существует специальный интерфейс IInt128
. Его можно получитьвызвав метод getInt128
интерфейса IUtil
.Этот тип появился в Firebird 4.0 и предназначен для точного представления очень больших чисел.Не существует непосредственного типа данных в Delphi, который мог бы работать с этим типом,поэтому просто выводим его строковое представление.
Для работы с типами DECFLOAT(16)
и DECFLOAT(34)
существуют специальные интерфейсы IDecFloat16
и IDecFloat34
.Их можно получить вызвав методы getDecFloat16
или getDecFloat34
интерфейса IUtil
.Эти типы доступны начиная с Firebird 4.0. Не существует непосредственных типов данных в Delphi, которые могли бы работать с этим типами.Эти типы можно отобразить в BCD или представить в виде строки.
Типы TIME WITH TIME ZONE
и TIMESTAMP WITH TIME ZONE
декодируются на составные части даты ивремени, а также наименование часового пояса, с помощью методов decodeTimeStampTz
и decodeTimeTz
.Используем части даты и времени для получения даты-времени в стандартномDelphi типе TDateTime. Далее преобразуем значение этого типа в строку и добавляем к ней наименование часового пояса.