长期以来,Firebird能够通过编写外部函数-UDF(用户定义函数)来扩展PSQL语言的功能。 UDF可以用几乎任何编译的编程语言编写。
Firebird 3.0引入了插件体系结构以扩展Firebird的功能。 这些插件之一是外部引擎(external engine)。 UDR(用户定义的例程-用户定义的例程)机制在FirebirdExternal引擎界面的顶部添加了一层。
在本指南中,我们将告诉您如何声明UDR,其内部机制,功能,并提供在Pascal中编写UDR的示例。 此外,还将介绍使用新的面向对象的API的某些方面。
备注
本文旨在教您如何使用对象Firebird API编写UDR。
书面功能和程序可能没有实际应用。
与传统UDF相比,UDR具有以下优点:
- 您不仅可以编写返回标量结果的函数,还可以编写存储过程(可执行的和选择性的)以及触发器;
- 改进了对输入和输出参数的控制。 在某些情况下(通过描述符传递),输入参数的类型和其他属性完全不受控制,但是,您可以在UDF中获得这些属性。 UDR提供了更统一的声明输入和输出参数的方式,因为常规PSQL函数和过程就是这种情况。
- UDR当前连接或事务的上下文可用,这使您可以执行
在这种情况下对当前数据库进行一些操作; - 发生异常时可以使用Firebird错误生成;无需返回特殊值;
- 外部过程和函数(UDR)可以分组在PSQL包中;
- 可以用任何编程语言(可选地编译为目标代码)编写UDR,为此,必须编写相应的“外部引擎”插件。 例如,有一些插件可以用Java或任何.NET语言编写外部模块。
备注
当前的UDR实现使用PSQL存根。 例如,它用来
检查参数和返回值是否符合限制。 存根
之所以使用它,是因为不能直接调用内部函数。 结果
比较UDR和UDF性能的测试表明,UDR大约为
以添加两个参数的最简单函数为例,速度慢了2.5倍。 速度
UDR大约等于常规PSQL函数的速度。 也许将来
片刻将被优化。 在更复杂的功能中,这种开销可能变成
难以察觉。
此外,在本手册的各个部分中,使用术语“外部过程”时,
功能或触发器,我们指的是完全UDR(而不是UDF)。
备注
我们所有的示例都适用于Delphi 2009及更高版本以及Free Pascal。 全部
可以在Delphi和Free Pascal中编译示例
没有单独指定。
火鸟API
要用编译的编程语言编写外部过程,函数或触发器,我们需要了解新的面向对象的Firebird API。 本指南不包含Firebird API的完整说明。 您可以在与Firebird一起分发的文档目录( doc/Using_OO_API.html
)中阅读该文档。
包含API的各种编程语言的插件不会作为Windows的Firebird分发的一部分进行分发,但是您可以从Linux分发的压缩tarbar文件( /opt/firebird/include/firebird/Firebird.pas
存档中的路径)中提取它们。
团
CLOOP-跨语言面向对象的编程。 该工具不包含在Firebird中。 可以在源代码https://github.com/FirebirdSQL/firebird/tree/B3_0_Release/extern/cloop中找到 。 组装完该工具后,您可以基于include/firebird/FirebirdInterface.idl
接口描述文件为您的编程语言( IdlFbInterfaces.h
或Firebird.pas
)生成一个API。
对于对象pascal,可通过以下命令完成:
cloop FirebirdInterface.idl pascal Firebird.pas Firebird --uses SysUtils \ --interfaceFile Pascal.interface.pas \ --implementationFile Pascal.implementation.pas \ --exceptionClass FbException --prefix I \ --functionsFile fb_get_master_interface.pas
可以在https://github.com/FirebirdSQL/firebird/tree/B3_0_Release/src/misc/pascal上找到Pascal.interface.pas
, Pascal.implementation.pas
和fb_get_master_interface.pas
。
备注
在这种情况下,将为Firebird API添加前缀I,因为这在Object Pascal中被接受。
常数
在生成的Firebird.pas
文件中没有isc_*
常量。 这些用于C / C ++语言的常量可以在https://github.com/FirebirdSQL/firebird/blob/B3_0_Release/src/include/consts_pub.h中找到。 要获取Pascal语言的常量,我们将使用AWK脚本转换语法。 在Windows上,您将需要安装Windows的Gawk或使用Linux的Windows子系统(在Windows 10上可用)。 这是通过以下命令完成的:
awk -f Pascal.Constants.awk consts_pub.h > const.pas
实现后,必须将结果文件的内容立即复制到Firebird.pas
文件的空const节。 可以在以下位置找到Pascal.Constants.awk
文件
https://github.com/FirebirdSQL/firebird/tree/B3_0_Release/src/misc/pascal 。
终身管理
Firebird接口不是基于COM规范的,因此管理它们的生存期是不同的。
Firebird中有两个接口处理生命周期管理:IDisposable和IReferenceCounted。 后者在创建其他接口时特别有效:IPlugin对链接进行计数,就像插件使用的许多其他接口一样。 这些接口包括描述如何连接到数据库,事务管理和SQL语句的接口。
带有引用计数的接口的额外开销并非总是必要的。 例如,IMaster是调用API其余部分可用功能的主接口,根据定义,它具有无限的生存期。 对于其他API,生存期严格由父接口的生存期决定; IStatus介面不是
多线程的。 对于寿命有限的接口,有一种简单的销毁它们的方法(即dispose()函数)很有用。
提示
如果您不知道如何破坏对象,请查看其层次结构,如果有
接口IReferenceCounted,然后使用引用计数。
对于带有引用计数的接口,在完成对象的工作后,有必要
通过调用release()方法减少参考计数器。
UDR公告
可以使用DDL命令在数据库中添加或删除UDR,就像添加或删除常规PSQL过程,函数或触发器一样。 在这种情况下,将使用EXTERNAL NAME子句指示其在外部模块中的位置,而不是触发器主体。
考虑一下这句话的语法;它将在外部过程,函数和触发器中通用。
语法:
EXTERNAL NAME '<extname>' ENGINE <engine> [AS <extbody>] <extname> ::= '<module name>!<routine name>[!<misc info>]'
此EXTERNAL NAME子句的参数是一个字符串,指示函数在外部模块中的位置。 对于使用UDR引擎的外部模块,通过分隔符的这一行指示外部模块的名称,模块内部功能的名称以及用户定义的信息。 感叹号(!)用作分隔符。
ENGINE子句指定用于处理外部模块连接的引擎的名称。 Firebird使用UDR引擎来处理以编译语言(C,C ++,Pascal)编写的外部模块。 用Java编写的外部函数需要Java引擎。
在关键字AS之后,可以指定字符串文字-外部模块的“正文”(过程,函数或触发器),外部模块可以将其用于各种目的。 例如,可以指定一个SQL查询来访问外部数据库或某种语言的文本,以供您的函数解释。
外部功能
句法 {CREATE [OR ALTER] | RECREATE} FUNCTION funcname [(<inparam> [, <inparam> ...])] RETURNS <type> [COLLATE collation] [DETERMINISTIC] EXTERNAL NAME <extname> ENGINE <engine> [AS <extbody>] <inparam> ::= <param_decl> [{= |DEFAULT} <value>] <value> ::= {literal | NULL | context_var} <param_decl> ::= paramname <type> [NOT NULL] [COLLATE collation] <extname> ::= '<module name>!<routine name> [!<misc info>]' <type> ::= <datatype> | [TYPE OF] domain | TYPE OF COLUMN rel.col <datatype> ::= {SMALLINT | INT[EGER] | BIGINT} | BOOLEAN | {FLOAT | DOUBLE PRECISION} | {DATE | TIME | TIMESTAMP} | {DECIMAL | NUMERIC} [(precision [, scale])] | {CHAR | CHARACTER | CHARACTER VARYING | VARCHAR} [(size)] [CHARACTER SET charset] | {NCHAR |NATIONAL CHARACTER | NATIONAL CHAR} [VARYING] [(size)] | BLOB [SUB_TYPE {subtype_num | subtype_name}] [SEGMENT SIZE seglen] [CHARACTER SET charset] | BLOB [(seglen [, subtype_num])]
可以使用ALTER FUNCTION语句更改外部函数的所有参数。
语法:
ALTER FUNCTION funcname [(<inparam> [, <inparam> ...])] RETURNS <type> [COLLATE collation] [DETERMINISTIC] EXTERNAL NAME <extname> ENGINE <engine> [AS <extbody>] <extname> ::= '<module name>!<routine name>[!<misc info>]'
您可以使用DROP FUNCTION语句删除外部函数。
语法:
DROP FUNCTION funcname
这里我们将不描述输入参数和输出的语法。 它完全符合常规PSQL函数的语法,《 SQL语言指南》中对此进行了详细说明。 相反,我们给出了一些带有说明的声明外部函数的示例。
添加三个参数的功能
create function sum_args ( n1 integer, n2 integer, n3 integer ) returns integer external name 'udrcpp_example!sum_args' engine udr;
该功能的实现在udrcpp_example模块中。 在此模块内部,以名称sum_args注册了一个函数。 外部功能使用UDR引擎。
Java函数
create or alter function regex_replace ( regex varchar(60), str varchar(60), replacement varchar(60) ) returns varchar(60) external name 'org.firebirdsql.fbjava.examples.fbjava_example.FbRegex.replace( String, String, String)' engine java;
该函数的实现在org.firebirdsql.fbjava.examples.fbjava_example.FbRegex
类的replace静态函数中。 外部函数使用Java引擎。
外部程序
句法 {CREATE [OR ALTER] | RECREATE} PROCEDURE procname [(<inparam> [, <inparam> ...])] RETURNS (<outparam> [<outparam> ...]) EXTERNAL NAME <extname> ENGINE <engine> [AS <extbody>] <inparam> ::= <param_decl> [{= | DEFAULT} <value>] <outparam> ::= <param_decl> <value> ::= {literal | NULL | context_var} <param_decl> ::= paramname <type> [NOT NULL] [COLLATE collation] <extname> ::= '<module name>!<routine name>[!<misc info>]' <type> ::= <datatype> | [TYPE OF] domain | TYPE OF COLUMN rel.col <datatype> ::= {SMALLINT | INT[EGER] | BIGINT} | BOOLEAN | {FLOAT | DOUBLE PRECISION} | {DATE | TIME | TIMESTAMP} | {DECIMAL | NUMERIC} [(precision [,scale])] | {CHAR | CHARACTER | CHARACTER VARYING | VARCHAR} [(size)] [CHARACTER SET charset] | {NCHAR | NATIONAL CHARACTER | NATIONAL CHAR} [VARYING] [(size)] | BLOB [SUB_TYPE {subtype_num | subtype_name}] [SEGMENT SIZE seglen] [CHARACTER SET charset] | BLOB [(seglen [, subtype_num])]
可以使用ALTER PROCEDURE语句更改外部过程的所有参数。
语法:
ALTER PROCEDURE procname [(<inparam> [, <inparam> ...])] RETURNS (<outparam> [, <outparam> ...]) EXTERNAL NAME <extname> ENGINE <engine> [AS <extbody>]
您可以使用DROP PROCEDURE语句删除外部过程。
语法:
DROP PROCEDURE procname
这里我们将不描述输入和输出参数的语法。 它完全符合常规PSQL过程的语法,《 SQL语言指南》中对此进行了详细说明。 取而代之的是,我们给出一些带有解释说明外部程序的示例。
create procedure gen_rows_pascal ( start_n integer not null, end_n integer not null ) returns ( result integer not null ) external name 'pascaludr!gen_rows' engine udr;
该功能的实现在pascaludr模块中。 在此模块内部,该过程以gen_rows名称注册。 外部过程使用UDR引擎。
create or alter procedure write_log ( message varchar(100) ) external name 'pascaludr!write_log' engine udr;
该功能的实现在pascaludr模块中。 在此模块内部,该过程以名称write_log注册。 外部过程使用UDR引擎。
create or alter procedure employee_pgsql (
该函数的实现在类的静态函数executeQuery中
org.firebirdsql.fbjava.examples.fbjava_example.FbJdbc
。 在感叹号(!)之后,可以找到Information以通过JDBC连接到外部数据库。 外部函数使用Java引擎。 在此,作为外部过程的“主体”,传递了SQL查询以检索数据。
备注
此过程使用一个存根,在该存根中传递未使用的参数。 这是由于在Firebird 3.0中存在一个无参数处理外部过程的错误。
将外部过程和函数放在包中
在PSQL包中放置一组相互关联的过程和函数很方便。 软件包可以包含外部和常规PSQL过程和函数。
句法 {CREATE [OR ALTER] | RECREATE} PACKAGE package_name AS BEGIN [<package_item> ...] END {CREATE | RECREATE} PACKAGE BODY package_name AS BEGIN [<package_item> ...] [<package_body_item> ...] END <package_item> ::= <function_decl>; | <procedure_decl>; <function_decl> ::= FUNCTION func_name [(<in_params>)] RETURNS <type> [COLLATE collation] [DETERMINISTIC] <procedure_decl> ::= PROCEDURE proc_name [(<in_params>)] [RETURNS (<out_params>)] <package_body_item> ::= <function_impl> | <procedure_impl> <function_impl> ::= FUNCTION func_name [(<in_impl_params>)] RETURNS <type> [COLLATE collation] [DETERMINISTIC] <routine body> <procedure_impl> ::= PROCEDURE proc_name [(<in_impl_params>)] [RETURNS (<out_params>)] <routine body> <routine body> ::= <sql routine body> | <external body reference> <sql routine body> ::= AS [<declarations>] BEGIN [<PSQL_statements>] END <declarations> ::= <declare_item> [<declare_item> ...] <declare_item> ::= <declare_var>; | <declare_cursor>; | <subroutine declaration>; | <subroutine implimentation> <subroutine declaration> ::= <subfunc_decl> | <subproc_decl> <subroutine implimentation> ::= <subfunc_impl> | <subproc_impl> <external body reference> ::= EXTERNAL NAME <extname> ENGINE <engine> [AS <extbody>] <extname> ::= '<module name>!<routine name>[!<misc info>]'
对于外部过程和函数,程序包名称,输入参数,它们的类型,默认值和输出参数在程序包头中指示,并且程序包主体中的所有内容都相同,除了默认值以及外部模块中的位置(EXTERNAL NAME子句) ,引擎名称,以及过程/功能的“主体”。
假设您编写了一个用于正则表达式的UDR,它位于PCRE的外部模块(动态库)中,并且您还有多个执行其他任务的UDR。 如果我们没有使用PSQL包,那么我们所有的外部过程和函数将相互混合,并且与普通的PSQL过程和函数混合在一起。 这使对依赖关系的搜索和对外部模块的更改变得很复杂,此外,还造成了混乱,并至少迫使使用前缀对过程和函数进行分组。 PSQL软件包使我们更容易完成此任务。
RegExp软件包 SET TERM ^; CREATE OR ALTER PACKAGE REGEXP AS BEGIN PROCEDURE preg_match( APattern VARCHAR(8192), ASubject VARCHAR(8192)) RETURNS (Matches VARCHAR(8192)); FUNCTION preg_is_match( APattern VARCHAR(8192), ASubject VARCHAR(8192)) RETURNS BOOLEAN; FUNCTION preg_replace( APattern VARCHAR(8192), AReplacement VARCHAR(8192), ASubject VARCHAR(8192)) RETURNS VARCHAR(8192); PROCEDURE preg_split( APattern VARCHAR(8192), ASubject VARCHAR(8192)) RETURNS (Lines VARCHAR(8192)); FUNCTION preg_quote( AStr VARCHAR(8192), ADelimiter CHAR(10) DEFAULT NULL) RETURNS VARCHAR(8192); END^ RECREATE PACKAGE BODY REGEXP AS BEGIN PROCEDURE preg_match( APattern VARCHAR(8192), ASubject VARCHAR(8192)) RETURNS (Matches VARCHAR(8192)) EXTERNAL NAME 'PCRE!preg_match' ENGINE UDR; FUNCTION preg_is_match( APattern VARCHAR(8192), ASubject VARCHAR(8192)) RETURNS BOOLEAN AS BEGIN RETURN EXISTS( SELECT * FROM preg_match(:APattern, :ASubject)); END FUNCTION preg_replace( APattern VARCHAR(8192), AReplacement VARCHAR(8192), ASubject VARCHAR(8192)) RETURNS VARCHAR(8192) EXTERNAL NAME 'PCRE!preg_replace' ENGINE UDR; PROCEDURE preg_split( APattern VARCHAR(8192), ASubject VARCHAR(8192)) RETURNS (Lines VARCHAR(8192)) EXTERNAL NAME 'PCRE!preg_split' ENGINE UDR; FUNCTION preg_quote( AStr VARCHAR(8192), ADelimiter CHAR(10)) RETURNS VARCHAR(8192) EXTERNAL NAME 'PCRE!preg_quote' ENGINE UDR; END^ SET TERM ;^
外部触发
句法 {CREATE [OR ALTER] | RECREATE} TRIGGER trigname {<relation_trigger_legacy> | <relation_trigger_sql2003> | <database_trigger> | <ddl_trigger> } <external-body> <external-body> ::= EXTERNAL NAME <extname> ENGINE <engine> [AS <extbody>] <relation_trigger_legacy> ::= FOR {tablename | viewname} [ACTIVE | INACTIVE] {BEFORE | AFTER} <mutation_list> [POSITION number] <relation_trigger_sql2003> ::= [ACTIVE | INACTIVE] {BEFORE | AFTER} <mutation_list> [POSITION number] ON {tablename | viewname} <database_trigger> ::= [ACTIVE | INACTIVE] ON db_event [POSITION number] <ddl_trigger> ::= [ACTIVE | INACTIVE] {BEFORE | AFTER} <ddl_events> [POSITION number] <mutation_list> ::= <mutation> [OR <mutation> [OR <mutation>]] <mutation> ::= INSERT | UPDATE | DELETE <db_event> ::= CONNECT | DISCONNECT | TRANSACTION START | TRANSACTION COMMIT | TRANSACTION ROLLBACK <ddl_events> ::= ANY DDL STATEMENT | <ddl_event_item> [{OR <ddl_event_item>} ...] <ddl_event_item> ::= CREATE TABLE | ALTER TABLE | DROP TABLE | CREATE PROCEDURE | ALTER PROCEDURE | DROP PROCEDURE | CREATE FUNCTION | ALTER FUNCTION | DROP FUNCTION | CREATE TRIGGER | ALTER TRIGGER | DROP TRIGGER | CREATE EXCEPTION | ALTER EXCEPTION | DROP EXCEPTION | CREATE VIEW | ALTER VIEW | DROP VIEW | CREATE DOMAIN | ALTER DOMAIN | DROP DOMAIN | CREATE ROLE | ALTER ROLE | DROP ROLE | CREATE SEQUENCE | ALTER SEQUENCE | DROP SEQUENCE | CREATE USER | ALTER USER | DROP USER | CREATE INDEX | ALTER INDEX | DROP INDEX | CREATE COLLATION | DROP COLLATION | ALTER CHARACTER SET | CREATE PACKAGE | ALTER PACKAGE | DROP PACKAGE | CREATE PACKAGE BODY | DROP PACKAGE BODY | CREATE MAPPING | ALTER MAPPING | DROP MAPPING
可以使用ALTER TRIGGER语句更改外部触发器。
语法:
ALTER TRIGGER trigname { [ACTIVE | INACTIVE] [ {BEFORE | AFTER} {<mutation_list> | <ddl_events>} | ON db_event ] [POSITION number] [<external-body>] <external-body> ::= EXTERNAL NAME <extname> ENGINE <engine> [AS <extbody>] <extname> ::= '<module name>!<routine name>[!<misc info>]' <mutation_list> ::= <mutation> [OR <mutation> [OR <mutation>]] <mutation> ::= { INSERT | UPDATE | DELETE }
您可以使用DROP TRIGGER语句删除外部触发器。
语法:
DROP TRIGGER trigname
这是带有解释的声明外部触发器的示例。
create database 'c:\temp\slave.fdb'; create table persons ( id integer not null, name varchar(60) not null, address varchar(60), info blob sub_type text ); commit; create database 'c:\temp\master.fdb'; create table persons ( id integer not null, name varchar(60) not null, address varchar(60), info blob sub_type text ); create table replicate_config ( name varchar(31) not null, data_source varchar(255) not null ); insert into replicate_config (name, data_source) values ('ds1', 'c:\temp\slave.fdb'); create trigger persons_replicate after insert on persons external name 'udrcpp_example!replicate!ds1' engine udr;
触发器实现在udrcpp_example模块中。 在此模块内,触发器以复写名称注册。 外部触发器使用UDR引擎。
在到外部模块的链接中,使用了一个附加参数ds1
,通过该参数,可以从外部触发器的copy_config表中读取用于读取外部数据库的配置。
UDR结构
现在是时候编写第一个UDR了。 我们将在Pascal中描述UDR的结构。 为了说明构建UDR的最小结构,我们将使用来自examples/udr/
转换为Pascal的标准示例。
为新的动态库创建一个新项目,我们将其称为MyUdr。 因此,您应该获得文件MyUdr.dpr
(如果在Delphi中创建了项目)或文件MyUdr.lpr
(如果在Lazarus中创建了项目)。 现在,让我们更改主项目文件,使其看起来像这样:
library MyUdr; {$IFDEF FPC} {$MODE DELPHI}{$H+} {$ENDIF} uses {$IFDEF unix} cthreads,
在这种情况下,您仅需要导出一个firebird_udr_plugin
函数,该函数是外部UDR模块的插件的入口点。 该功能的实现将位于UdrInit模块中。
备注
如果要在Free Pascal中开发UDR,则将需要其他指令。 必须启用{$mode objfpc}
才能启用对象Pascal模式。 相反,可以使用{$mode delphi}
指令来确保与Delphi的兼容性。 由于我的示例应该可以在FPC和Delphi中成功编译,因此我选择{$mode delphi}
。
指令{$H+}
包括对长字符串的支持。 如果您将使用字符串类型,ansistring类型,而不仅仅是以N结尾的字符串PChar,PAnsiChar,PWideChar,则这是必需的。
另外,我们将需要连接单独的模块以支持Linux和其他类似Unix的操作系统上的多线程。
注册过程,函数或触发器
现在添加UdrInit模块,它应该如下所示:
unit UdrInit; {$IFDEF FPC} {$MODE DELPHI}{$H+} {$ENDIF} interface uses Firebird;
在firebird_udr_plugin
函数中, firebird_udr_plugin
必要注册我们的外部过程,函数和触发器的工厂。 对于每个功能,过程或触发器,必须编写自己的工厂。 这是使用IUdrPlugin接口方法完成的:
- registerFunction — ;
- registerProcedure — ;
- registerTrigger — .
, ( ). // SQL. ( ).
. SumArgsFunc. .
SumArgsFunc unit SumArgsFunc; {$IFDEF FPC} {$MODE DELPHI}{$H+} {$ENDIF} interface uses Firebird;
IUdrFunctionFactory. IUdrFunctionFactoryImpl. . , , . .
dispose , . .
setup . , . .
newItem . , . IRoutineMetadata , . PSQL. . TSumArgsFunction
.
IExternalFunction. IExternalFunctionImpl
.
dispose , . .
.
getCharSet , . , .
execute . , , .
. , , BLOB. BLOB, .
, . , . , , , NULL ( Null ). , , IMessageMetadata. , execute.
. 对于
Null Null
, NULL,
UDR . : . , .. EXECUTE PROCEDURE .
UdrInit firebird_udr_plugin
.
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr; AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl; begin
备注
uses SumArgsProc, .
IUdrProcedureFactory. IUdrProcedureFactoryImpl. . , , . .
dispose , . .
setup . , . .
newItem . , . IRoutineMetadata , . PSQL. . TSumArgsProcedure
.
SumArgsProc.
SumArgsProc unit SumArgsProc; {$IFDEF FPC} {$MODE DELPHI}{$H+} {$ENDIF} interface uses Firebird; type
IExternalProcedure. IExternalProcedureImpl
.
dispose , . .
getCharSet . , .
open . , , . , nil, . . TSumArgsFunction.execute.
UDR . firebird_udr_plugin
.
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr; AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl; begin
备注
uses GenRowsProc, .
. , open, .
GenRowsProc unit GenRowsProc; {$IFDEF FPC} {$MODE DELPHI}{$H+} {$ENDIF} interface uses Firebird, SysUtils; type TInput = record start: Integer; startNull: WordBool; finish: Integer; finishNull: WordBool; end; PInput = ^TInput; TOutput = record n: Integer; nNull: WordBool; end; POutput = ^TOutput;
open TGenRowsProcedure
NULL, NULL, NULL, SELECT, nil.
, , . UDR Firebird. UDR Legacy UDF.
, open , IExternalResultSet. IExternalResultSetImpl
.
dispose . .
fetch SELECT. SUSPEND PSQL . , . true, , false
, . , .
备注
Delphi yeild,
while(...) do { ... yield result; }
, open, , fetch. ( SELECT FIRST/ROWS/FETCH FIRST SELECT.)
UDR .
Note
C++ . , . .
UdrInit firebird_udr_plugin
.
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr; AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl; begin
备注
uses TestTrigger, .
IUdrTriggerFactory. IUdrTriggerFactoryImpl.
.
dispose , . .
setup . , . .
newItem . , . IRoutineMetadata , . PSQL. . TMyTrigger
.
TestTrigger.
TestTrigger unit TestTrigger; {$IFDEF FPC} {$MODE DELPHI}{$H+} {$ENDIF} interface uses Firebird, SysUtils; type
IExternalTrigger. IExternalTriggerImpl
.
dispose , . .
getCharSet . , .
execute . , , () . () IExternalTrigger. ACTION_
. , Firebird . , DDL , , , nil. . , , .
备注
, , . IMessageMetadata. , . , , /.
, PSQL
if (:new.B IS NULL) THEN :new.B = :new.A + 1;
UDR , . NEW OLD.
, , .
:
( Delphi , .. record);
IMessageMetadata, / , .
, — , UDR.
. :
TMyStruct = record <var_1>: <type_1>; <nullIndicator_1>: WordBool; <var_2>: <type_1>; <nullIndicator_2>: WordBool; ... <var_N>: <type_1>; <nullIndicator_N>: WordBool; end; PMyStruct = ^TMyStruct;
/ ( ). Null- /, NOT NULL. Null- 2 . -1
/ NULL. NULL- NULL, 2- . SQL :
, .
:
function SUM_ARGS(A SMALLINT, B INTEGER) RETURNS BIGINT ....
:
TInput = record A: Smallint; ANull: WordBool; B: Integer; BNull: WordBool; end; PInput = ^TInput; TOutput = record Value: Int64; Null: WordBool; end; POutput = ^TOutput;
( 3 ):
function SUM_ARGS(A NUMERIC(4, 2), B NUMERIC(9, 3)) RETURNS NUMERIC(18, 6) ....
:
TInput = record A: Smallint; ANull: WordBool; B: Integer; BNull: WordBool; end; PInput = ^TInput; TOutput = record Value: Int64; Null: WordBool; end; POutput = ^TOutput;
:
procedure SOME_PROC(A CHAR(3) CHARACTER SET WIN1251, B VARCHAR(10) CHARACTER SET UTF8) ....
:
TInput = record A: array[0..2] of AnsiChar; ANull: WordBool; B: record Length: Smallint; Value: array[0..39] of AnsiChar; end; BNull: WordBool; end; PInput = ^TInput;
IMessageMetadata. /
:
- /;
- ;
- ;
- BLOB;
- /;
- / NULL;
- ;
- NULL-.
getCount
unsigned getCount(StatusType* status)
/ . , , : 0 <= index < getCount().
getField
const char* getField(StatusType* status, unsigned index)
.
getRelation
const char* getRelation(StatusType* status, unsigned index)
( ).
getOwner
const char* getOwner(StatusType* status, unsigned index)
.
getAlias
const char* getAlias(StatusType* status, unsigned index)
.
getType
unsigned getType(StatusType* status, unsigned index)
SQL .
isNullable
FB_BOOLEAN isNullable(StatusType* status, unsigned index)
true, NULL.
getSubType
int getSubType(StatusType* status, unsigned index)
BLOB (0 — , 1 — . .).
getLength
unsigned getLength(StatusType* status, unsigned index)
.
getScale
int getScale(StatusType* status, unsigned index)
.
getCharSet
unsigned getCharSet(StatusType* status, unsigned index)
BLOB.
getOffset
unsigned getOffset(StatusType* status, unsigned index)
( ).
getNullOffset
unsigned getNullOffset(StatusType* status, unsigned index)
NULL .
getBuilder
IMetadataBuilder* getBuilder(StatusType* status)
IMetadataBuilder, .
getMessageLength
unsigned getMessageLength(StatusType* status)
( ).
IMessageMetadata IRoutineMetadata. , . . 例如:
IMessageMetadata getInputMetadata getOutputMetadata IRoutineMetadata. , , getTriggerMetadata.
, IMessageMetadata . IReferenceCounted. getInputMetadata getOutputMetadata 1 , xInputMetadata
xOutputMetadata
release.
. IMessageMetadata getOffset . .
null , getNullOffset.
. .
, . IUdrProcedureFactory, IUdrFunctionFactory IUdrTriggerFactory UDR. UDR firebird_udr_plugin
.
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr; AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl; begin
TSumArgsFunctionFactory
IUdrFunctionFactory, TGenRowsFactory
IUdrProcedureFactory, TMyTriggerFactory
IUdrTriggerFactory.
, . Firebird. , SuperServer , Classic
.
setup newItem IUdrProcedureFactory, IUdrFunctionFactory IUdrTriggerFactory.
IUdrFunctionFactory = class(IDisposable) const VERSION = 3; procedure setup(status: IStatus; context: IExternalContext; metadata: IRoutineMetadata; inBuilder: IMetadataBuilder; outBuilder: IMetadataBuilder); function newItem(status: IStatus; context: IExternalContext; metadata: IRoutineMetadata): IExternalFunction; end; IUdrProcedureFactory = class(IDisposable) const VERSION = 3; procedure setup(status: IStatus; context: IExternalContext; metadata: IRoutineMetadata; inBuilder: IMetadataBuilder; outBuilder: IMetadataBuilder); function newItem(status: IStatus; context: IExternalContext; metadata: IRoutineMetadata): IExternalProcedure; end; IUdrTriggerFactory = class(IDisposable) const VERSION = 3; procedure setup(status: IStatus; context: IExternalContext; metadata: IRoutineMetadata; fieldsBuilder: IMetadataBuilder); function newItem(status: IStatus; context: IExternalContext; metadata: IRoutineMetadata): IExternalTrigger; end;
, IDisposable, dispose. Firebird , . dispose , , .
IUdrProcedureFactoryImpl
, IUdrFunctionFactoryImpl
, IUdrTriggerFactoryImpl
. .
newItem
newItem , . UDR , .. , . .
. , , , IUdrFunctionFactory
. . .
newItem ,
UDR UDR.
function TSumArgsFunctionFactory.newItem(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalFunction; begin
IRoutineMetadata , UDR . UDR. UDR .
setup
setup . IMetadataBuilder, , .
setup, setup DLL , . .
. , SumArgs.
,
type
, setup , .
SumArgsFunctionFactory procedure TSumArgsFunctionFactory.dispose; begin Destroy; end; function TSumArgsFunctionFactory.newItem(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalFunction; begin Result := TSumArgsFunction.Create(); end; procedure TSumArgsFunctionFactory.setup(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata; AInBuilder, AOutBuilder: IMetadataBuilder); begin
SQL Firebird . , SQL NULL. XSQLDA.
procedure TSumArgsFunction.execute(AStatus: IStatus; AContext: IExternalContext; AInMsg, AOutMsg: Pointer); var xInput: PSumArgsInMsg; xOutput: PSumArgsOutMsg; begin
, , , setup.
create or alter function FN_SUM_ARGS ( N1 varchar(15), N2 varchar(15), N3 varchar(15)) returns varchar(15) EXTERNAL NAME 'MyUdrSetup!sum_args' ENGINE UDR;
select FN_SUM_ARGS('15', '21', '35') from rdb$database
UDR , UDR. . Delphi 2009, Free Pascal FPC 2.2.
备注
Free Pascal
Delphi. FPC 2.6.0 Delphi
.
:
newItem . IUdrFunctionFactoryImpl
, IUdrProcedureFactoryImpl
, IUdrTriggerFactoryImpl
. :
SimpleFactories unit UdrFactories; {$IFDEF FPC} {$MODE DELPHI}{$H+} {$ENDIF} interface uses SysUtils, Firebird; type
setup , , dispose . newItem T
.
implementation 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 end; procedure TFunctionSimpleFactory<T>.dispose; begin Destroy; end; function TFunctionSimpleFactory<T>.newItem(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalFunction; begin Result := T.Create; end; procedure TFunctionSimpleFactory<T>.setup(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata; AInBuilder, AOutBuilder: IMetadataBuilder); begin end; procedure TTriggerSimpleFactory<T>.dispose; begin Destroy; end; function TTriggerSimpleFactory<T>.newItem(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalTrigger; begin Result := T.Create; end; procedure TTriggerSimpleFactory<T>.setup(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata; AFieldsBuilder: IMetadataBuilder); begin end;
1 , . :
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr; AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl; begin
. , . newItem
. UDR IRoutineMetadata
, Firebird, UDR. , , UDR, ,
UDR. , , .
unit UdrFactories; {$IFDEF FPC} {$MODE DELPHI}{$H+} {$ENDIF} interface uses SysUtils, Firebird; type ...
, .
unit UdrFactories; {$IFDEF FPC} {$MODE DELPHI}{$H+} {$ENDIF} interface uses SysUtils, Firebird; type ...
, .
UDR .
unit UdrFactories; {$IFDEF FPC} {$MODE DELPHI}{$H+} {$ENDIF} interface uses SysUtils, Firebird; type ...
newItem ,
, .
implementation ... procedure TFunctionFactory<T>.dispose; begin Destroy; end; function TFunctionFactory<T>.newItem(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalFunction; begin Result := T.Create; (Result as T).Metadata := AMetadata; end; procedure TFunctionFactory<T>.setup(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata; AInBuilder, AOutBuilder: IMetadataBuilder); begin end; procedure TProcedureFactory<T>.dispose; begin Destroy; end; function TProcedureFactory<T>.newItem(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalProcedure; begin Result := T.Create; (Result as T).Metadata := AMetadata; end; procedure TProcedureFactory<T>.setup(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata; AInBuilder, AOutBuilder: IMetadataBuilder); begin end; procedure TTriggerFactory<T>.dispose; begin Destroy; end; function TTriggerFactory<T>.newItem(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata): IExternalTrigger; begin Result := T.Create; (Result as T).Metadata := AMetadata; end; procedure TTriggerFactory<T>.setup(AStatus: IStatus; AContext: IExternalContext; AMetadata: IRoutineMetadata; AFieldsBuilder: IMetadataBuilder); begin end;
https://github.com/sim1984/udr-book/blob/master/examples/Common/UdrFactories.pas .
BLOB
BLOB ( BLOB), . , BLOB , . BLOB . BLOB
IBlob
.
BLOB , BLOB , BLOB , BLOB .
BLOB , BLOB (), 64 . getSegment
IBlob
. putSegment
IBlob
.
BLOB
BLOB
(
LIST).
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;
:
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr; AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl; begin
, . .
. .
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;
BLOB BLOB, ISC_QUAD
.
:
Split 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
SaveBlobToStream
readBlob
BLOB. BLOB , — Delphi. OutputArray Counter.
open BLOB . Split
. .
TSplitProcedure.open function TSplitProcedure.open(AStatus: IStatus; AContext: IExternalContext; AInMsg, AOutMsg: Pointer): IExternalResultSet; var xInput: TInputPtr; xText: string; xDelimiter: string; begin xInput := AInMsg; if xInput.txtNull or xInput.delimiterNull then begin Result := nil; Exit; end; xText := readBlob(AStatus, AContext, @xInput.txt); xDelimiter := TFBCharSet.CS_UTF8.GetString(TBytes(@xInput.delimiter), 0, 4);
备注
TFBCharSet
Firebird.pas.
Firebird.
UTF-8.
FbCharsets.pas
BLOB . BLOB . openBlob IAttachment
. BLOB , . ,
( IExternalContext
).
BLOB (), 64 . getSegment
IBlob
.
TSplitProcedure.SaveBlobToStream 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; blob.close(AStatus); finally if Assigned(att) then att.release; if Assigned(trx) then trx.release; if Assigned(blob) then blob.release; end; end;
备注
, IAttachment
, ITransaction
IBlob
IReferenceCounted
,
.
1.
release.
SaveBlobToStream
BLOB
:
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;
备注
Free Pascal
Delphi TStringStream
. FPC
,
.
fetch
Counter , . . isc_convert_error
.
isc_convert_error 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;
备注
isc_random
, .
:
SELECT ids.ID FROM SPLIT((SELECT LIST(ID) FROM MYTABLE), ',') ids
备注
, BLOB
,
.
,
.
fetch
.
BLOB
BLOB
BLOB .
备注
UDF
BLOB / . UDF
blobsaveload.zip
BLOB /
CREATE PACKAGE BlobFileUtils AS BEGIN PROCEDURE SaveBlobToFile(ABlob BLOB, AFileName VARCHAR(255) CHARACTER SET UTF8); FUNCTION LoadBlobFromFile(AFileName VARCHAR(255) CHARACTER SET UTF8) RETURNS BLOB; END^ CREATE PACKAGE BODY BlobFileUtils AS BEGIN PROCEDURE SaveBlobToFile(ABlob BLOB, AFileName VARCHAR(255) CHARACTER SET UTF8) EXTERNAL NAME 'BlobFileUtils!SaveBlobToFile' ENGINE UDR; FUNCTION LoadBlobFromFile(AFileName VARCHAR(255) CHARACTER SET UTF8) RETURNS BLOB EXTERNAL NAME 'BlobFileUtils!LoadBlobFromFile' ENGINE UDR; END^
:
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr; AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl; begin
BLOB , UDR
06.BlobSaveLoad . LoadBlobFromFile :
interface uses Firebird, Classes, SysUtils; type
execute
TLoadBlobFromFile
, .
execute procedure TLoadBlobFromFileFunc.execute(AStatus: IStatus; AContext: IExternalContext; AInMsg: Pointer; AOutMsg: Pointer); const MaxBufSize = 16384; var xInput: TInputPtr; xOutput: TOutputPtr; xFileName: string; xStream: TFileStream; att: IAttachment; trx: ITransaction; blob: IBlob; buffer: array [0 .. 32767] of Byte; xStreamSize: Integer; xBufferSize: Integer; xReadLength: Integer; begin xInput := AInMsg; xOutput := AOutMsg; if xInput.filenameNull then begin xOutput.blobDataNull := True; Exit; end; xOutput.blobDataNull := False;
BLOB blobId createBlob
IAttachment
. BLOB , . , ( IExternalContext
).
BLOB, putSegment
IBlob
, . close
.
BLOB
BLOB
, BLOB .
BLOB,
.
Delphi Free Pascal
.
IBlob
/ Blob.
FbBlob, .
BlobHelper unit FbBlob; interface uses Classes, SysUtils, Firebird; const MAX_SEGMENT_SIZE = $7FFF; type TFbBlobHelper = class helper for IBlob procedure LoadFromStream(AStatus: IStatus; AStream: TStream); procedure SaveToStream(AStatus: IStatus; AStream: TStream); end; implementation uses Math; procedure TFbBlobHelper.LoadFromStream(AStatus: IStatus; AStream: TStream); var xStreamSize: Integer; xReadLength: Integer; xBuffer: array [0 .. MAX_SEGMENT_SIZE] of Byte; begin xStreamSize := AStream.Size; AStream.Position := 0; while xStreamSize <> 0 do begin xReadLength := Min(xStreamSize, MAX_SEGMENT_SIZE); AStream.ReadBuffer(xBuffer, xReadLength); Self.putSegment(AStatus, xReadLength, @xBuffer[0]); Dec(xStreamSize, xReadLength); end; end; procedure TFbBlobHelper.SaveToStream(AStatus: IStatus; AStream: TStream); var xInfo: TFbBlobInfo; Buffer: array [0 .. MAX_SEGMENT_SIZE] of Byte; xBytesRead: Cardinal; xBufferSize: Cardinal; begin AStream.Position := 0; xBufferSize := Min(SizeOf(Buffer), MAX_SEGMENT_SIZE); while True do begin case Self.getSegment(AStatus, xBufferSize, @Buffer[0], @xBytesRead) of IStatus.RESULT_OK: AStream.WriteBuffer(Buffer, xBytesRead); IStatus.RESULT_SEGMENT: AStream.WriteBuffer(Buffer, xBytesRead); else break; end; end; end; end.
BLOB, BLOB :
TLoadBlobFromFileFunc.execute procedure TLoadBlobFromFileFunc.execute(AStatus: IStatus; AContext: IExternalContext; AInMsg: Pointer; AOutMsg: Pointer); var xInput: TInputPtr; xOutput: TOutputPtr; xFileName: string; xStream: TFileStream; att: IAttachment; trx: ITransaction; blob: IBlob; begin xInput := AInMsg; xOutput := AOutMsg; if xInput.filenameNull then begin xOutput.blobDataNull := True; Exit; end; xOutput.blobDataNull := False;
, , , / . , 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.
备注
JSON .
CHAR, VARCHAR OCTETS NONE BLOB SUB_TYPE BINARY
base64,
JSON.
:
function firebird_udr_plugin(AStatus: IStatus; AUnloadFlagLocal: BooleanPtr; AUdrPlugin: IUdrPlugin): BooleanPtr; cdecl; begin
, :
GetJson unit JsonFunc; {$IFDEF FPC} {$MODE objfpc}{$H+} {$DEFINE DEBUGFPC} {$ENDIF} interface uses Firebird, UdrFactories, FbTypes, FbCharsets, SysUtils, System.NetEncoding, System.Json;
MakeScaleInteger
, writeJson
Json . , execute
.
TJsonFunction.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;
getAttachment
getTransaction
IExternalContext
. BLOB SQL . prepare IAttachment
. SQL . IStatement.PREPARE_PREFETCH_METADATA
, . getOutputMetadata
IStatement
.
备注
getOutputMetadata .
IStatement.PREPARE_PREFETCH_METADATA
.
, .
openCursor ( 2). getMessageLength
IMessageMetadata
. , .
fetchNext
IResultSet
. msg
IStatus.RESULT_OK
, . writeJson
, TJsonObject
TJsonArray
.
, close
, Json , , Blob.
writeJson
. IUtil
, . IMessageMetadata
. TJsonObject
. . NullFlag, null , Json.
writeJson 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; pData: PByte; util: IUtil; metaLength: Integer;
备注
TFbType Firebird.pas
.
,
FbTypes .
TFBCharSet Firebird.pas
.
FbCharsets . ,
,
, , ,
TEncoding
,
Delphi.
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
.
仅此而已。 UDR Firebird, .