用户定义函数
SmartESQL 支持“存储过程”,允许开发人员创建自己的 Lua 用户自定义函数(UDF)。SmartESQL 的 UDF 也可以用 Python 或 C/C++ 编写。这些方法在下面的章节中进行了描述。SmartESQL 服务器提供了一项服务(一个内部函数),通过一个名为 Metafunction 的元表列出所有 UDF,该表可以像任何 SQL 表一样进行查询:
select * from Metafunction;
Name Profile ReturnType nArguments Language
-------------------------------------------
format String(...) 15 2 C++
choice String(...) 15 3 C++
trim String(...) 15 1 C++
...
请注意,Metafunction 虚拟表会返回有关静态注册的函数(用 C++ 编写)以及当前连接通过 CREATE FUNCTION 语句动态创建的函数的信息。所有用户定义函数的完整列表可以在 Functions 表中找到(如果该表存在)。
如果将 SqlOptimizerParameters 的 preloadUDF 参数设置为 true,则所有这些函数都会在数据库打开时加载,元函数表将包含所有函数(动态加载和静态链接)的完整列表。
另外请注意,如果参数数量被列为 -1,则表示参数数量可变。
Lua 用户自定义函数
Lua 是一种非常优雅、流行且易于采用的脚本语言,其语法丰富,支持运算符重载、封装、继承、多态等特性。Lua 高级且速度极快的动态即时编译器 LuaJIT 以及小巧的内存占用,使其成为为 SmartESQL 服务器开发复杂数据库用户自定义函数和存储过程的绝佳过程式语言。基于 Lua 的用户自定义函数在 SQL 服务器的上下文中运行,消除了固有的客户端 - 服务器进程间通信和网络开销,并充分利用了现代硬件的多核特性。 (详情请参阅《使用 Lua 与 SmartESQL》。)
Python 用户自定义函数
如果使用 Python 类 SqlEngine 创建 SmartEDB 数据库,也可以用 Python 语言创建用户定义函数(UDF)。涉及以下步骤:
支持的 Python 版本为 2.7,且机器上需要有共享库。(对于 Linux 系统,此库为 libpython2.7.so。) 默认情况下,此库会在标准系统库加载路径中进行搜索。可以使用环境变量 PYTHONLIB 进行更改;例如:
export PYTHONLIB=/opt/build/my/python/libpython2.7.so
声明执行所需操作的 Python 函数。此函数的参数将是用户定义函数(UDF)的参数,其返回值将被转换为 SQL 值。例如;
def gen_quotes(Bid, BidSize): ... Bid.append(some_bids) BidSize.append(some_bid_sizes) return len(some_bids)
省略号(...)所表示的代码会使用序列迭代器从字段“Bid”和“BidSz”生成出一个出价值和大小的列表,并将其分别存入变量 some_bids 和 some_bid_sizes 中。
使用为本地连接创建(默认情况下)的 SqlEngine 对象在 SQL 引擎中注册用户定义函数 (UDF):
conn.engine.registerFunction(int, "gen_quotes", gen_quotes, -1)
在 SQL SELECT 语句中调用用户定义函数(UDF)。假设我们有一个包含“Bid”和“BidSz”列的表,并且该表已填充了一些记录。然后,我们可以生成出价并返回生成的出价总数,如下所示:
SELECT sum(gen_quotes(Bid, BidSz)) FROM Bids FOR UPDATE;
请注意,此示例中的 FOR UPDATE 子句是必需的,因为用户定义函数(UDF)将使用序列迭代器向序列 Bid 和 BidSz 追加值。
C/C++ 用户自定义函数
C/C++ 用户定义函数(UDF)必须定义为返回类型为 static String*,并且可以接受类型为 Value* 的参数。或者用户定义的聚合函数必须定义为返回类型为 static Value*,并且可以接受类型为 Value* 的参数。(有关在 UDF 中处理查询的详细信息,请参阅 SQL 中的动态内存分配页面。)
用户定义函数(UDF)可通过使用 C/C++ API 与 SmartESQL 运行时环境进行“注册”来实现静态链接。在 SmartESQL 应用程序中,通过声明类型为 static SqlFunctionDeclaration udf() 的函数来完成此操作,该函数带有指定返回类型、用于调用的名称、指向函数的指针以及所需参数数量的参数。(请注意,SmartEDB 对索引有另一种用户定义函数的概念,该概念在 SmartEDB 用户指南中有描述。本节仅描述 SQL UDF。)例如,考虑以下定义:
extern "C"
{
McoSql::Value* toggleTrace(McoSql::SqlEngine* engine,
McoSql::Vector<McoSql::Value>* args)
{
McoSql::Value *enable = args->getAt(0);
return BoolValue::create(func->engine->trace(enable->isTrue()));
}
}
(请注意,extern "C" 声明是必要的,这样才能给函数一个未经过 C++ 名称修饰的名称。)
UDF 还可以通过使用具有以下形式的 SQL CREATE FUNCTION 语句动态加载:
create function NAME ( param1_name param1_type, ...,
paramN_name paramN_type)
returns result_type as 'LIBRARY-NAME' [, 'C_FUNCTION-NAME'];
例如:
create function toggleTrace(enable bit) returns bit as 'mysharedlib';
(请注意,如果该函数所属库的名称(假设为“xxx”)不包含后缀,则在 UNIX 系统中会将其转换为 libxxx.so,而在 Windows 系统中则称为 xxx.dll。)
现在可以通过以下 SELECT 语句调用此用户定义函数(UDF):
select toggleTrace(true);
与 C 语言中静态声明的函数不同,内部动态声明的函数会保留其参数类型的有关信息。这些类型由 SQL 编译器进行检查。SQL 引擎在内部将所有整数类型表示为 64 位整数(通过 int8 类型),将浮点类型表示为双精度浮点数(通过 real8 类型)。当函数参数声明为除 int8 或 real8 标量类型之外的任何类型时,会应用隐式转换。因此,函数可以声明为接受 float 类型的参数,但实际上会接收到 double 类型的值(使用 RealValue 对象表示),并且也应返回 RealValue 类型的值。(在 SQL API 中无法返回 float 类型的值)。
使用动态加载的用户定义函数修改数据
使用用户定义函数(UDF),可以更改数据库中的数据、执行 SQL 语句和/或查询、处理序列迭代器等。例如,考虑以下 UDF 定义:
McoSql::Value *load_cme_trades(McoSql::SqlEngine *engine,
McoSql::Vector<McoSql::Value> *args)
{
McoSql::DataSource *res = engine->executeQuery(
engine->getCurrentTransaction(), "SELECT TradeDateTime,
SeqNum, GroupCode, ProductCode, InstrumentName,
EntryPrice, EntryQuantity, AggressorSide, TickDirection
FROM TOBSeq
WHERE Book = %s AND
SecurityID = %i AND
EntryType = %s",
Book.c_str(), SecurityID,
EntryType.c_str() );
...
}
后续操作是在调用此用户定义函数(UDF)的事务上下文中执行的,因此如果要在 UDF 内更改数据库中的数据,则此事务必须为读写事务。对于 INSERT 和 UPDATE 语句,事务将自动升级,但如果需要使用序列迭代器更改序列数据,则必须在 UDF 代码中显式升级事务。例如,以下可能是前面代码片段的延续:
...
McoSql::Cursor *cursor = res->records();
McoSql::Record *rec = cursor->next();
McoSequence<int8> *fldEntryPrice = dynamic_cast
<McoSequence<int8>*>(rec->get(5));
engine->getCurrentTransaction()->upgrade();
EntryPrice->append(values, 1000);
...
其中 EntryPrice 是数据库对象 TOBSeq 中的一个序列字段。
关于在用户定义函数中使用序列的注意事项
如上所述,UDF 代码可以访问序列迭代器并对其执行操作。序列迭代器表示为 McoGenericSequence 类或其派生类的对象。在 UDF 中使用序列时,请注意以下几点:
向序列追加元素
仅可使用 McoGenericSequence::append() 方法向已实例化的序列追加值。已实例化的序列由类型为 McoSequence<T> 的对象表示,该对象具有以下方法:
virtual void append(T const* items, mco_size_t nItems);
这里 T 是实际的序列数据类型。同样,无法向未实例化的序列添加项。请注意,添加元素时必须以 READ_WRITE 模式打开事务。如果使用自动事务和 SELECT 语句访问序列字段,则应采用如上例所示的 SELECT...FOR UPDATE 形式。
遍历序列
序列可以被迭代:
使用通过方法 McoGenericSequence::getIterator() 获得的低级迭代器
使用方法
McoSql::Value* McoGenericSequence::next();
。此方法将返回一个IntValue
、RealValue
或String
,表示序列元素的值,如果已无更多元素则返回NULL
。
请注意,在不同的场景中,迭代器可能未被初始化,所以在调用 next() 之前,最好先调用 getIterator() 来执行初始化操作。例如:
{
McoGenericSequence *time_it = dynamic_cast
<McoGenericSequence *>(args->get(0));
if (time_it == NULL)
{
process_error();
}
time_it->getIterator();
for (McoSql::Value *vt; vt != NULL; vt = time_it->next())
{
...
}
}
序列类
如上文“向序列追加”部分所述,序列可以表示为 McoGenericSequence 类或 McoSequence<T> 类,它们在不同的场景下被继承。最佳实践是在用户定义函数(UDF)中仅在需要时使用 McoSequence<T>,即用于更新或向序列追加。
用户定义函数(UDF)中的空参数
可以将 NULL 作为参数值传递给用户定义的函数。例如:
create function sf(s string) returns string in 'lua' as 'return s end';
create table t(s string);
insert into t values(['hello', null]);
select sf(s) from t;
select sf(null);
#1
------------------------------------------------------------------------------
hello
null
Selected records: 2