为了能够在创建和使用自定义 DMSQL 程序时,使用其他语言实现的接口,DM8 提供了 C、JAVA 外部函数,这样即使外部函数在执行中出现了任何问题,都不会影响到服务器的正常执行。
无论是 C 外部函数还是 JAVA 外部函数,都需要将动态库或 jar 包上传到服务器端或在服务器端编译生成动态库或 jar 包,系统管理员应对动态库和 jar 包进行严格审查,以防止外部函数中包含病毒或恶意代码,引发安全问题。为了保证数据库的安全性和灵活性,DM 提供了 ini 参数 ENABLE_EXTERNAL_CALL 来开关外部函数功能,默认情况下,数据库会关闭外部函数的创建和执行功能。
需要注意的是,DM 不支持 C 或 JAVA 外部函数存放在 ASM 文件系统上的调用。
10.1 C 外部函数
C 外部函数是使用 C、C++ 语言编写,在数据库外编译并保存在.dll、.so 共享库文件中,被用户通过 DMSQL 程序调用的函数。
C 外部函数的执行都通过代理 dmap 工具进行,为了执行 C 外部函数,需要先启动 dmap 服务。dmap 执行程序在 DM8 安装目录的 bin 子目录下,直接执行即可启动 dmap 服务。
当用户调用 C 外部函数时,服务器操作步骤如下:首先,确定调用的(外部函数使用的)共享库及函数;然后,通知代理进程工作。代理进程装载指定的共享库,并在函数执行后将结果返回给服务器。
10.1.1 生成动态库
DM8 提供两种方案编写 C 外部函数:
- DM 结构化参数
该方案中,用户必须使用 DM8 提供的编写 C 外部函数动态库的接口,严格按照如下格式书写外部函数的 C 代码。
C 外部函数格式
de_data 函数名(de_args *args)
{
C语言函数实现体;
}
参数
- <de_data> 返回值类型。de_data 结构体类型如下:
struct de_data{
int null_flag; //参数是否为空,1表示非空,0表示空
union //只支持int、double(或精度大于24的float)、char类型。其中float类型在系统内部被转化为double类型执行,相关接口请使用double类型的接口
{
int v_int;
double v_double;
char v_str[];
}data;
};
- <de_args> 参数信类型。de_args 结构体类型如下:
struct de_args
{
int n_args; //参数个数
de_data* args; //参数列表
};
- < C 语言函数实现体 > C 语言函数对应的函数实现体。
使用说明
- C 语言函数的参数可通过调用 DM8 提供的一系列 get 函数得到,同时可调用 set 函数重新设置这些参数的值;
- 根据返回值类型,调用不同的 return 函数接口;
- 必须根据参数类型、返回值类型,调用相同类型的 get、set 和 return 函数。当调用 de_get_str 和 de_get_str_with_len 得到字符串后,必须调用 de_str_free 释放空间;
- DM8 提供的编写 C 外部函数动态库的接口如表 10.1 所示。
函数类型 | 函数名 | 功能说明 |
get | int de_get_int(de_args *args, int arg_id); | 第arg_id 参数的数据类型为整型,从参数列表args 中取出第arg_id参数的值 |
double de_get_double(de_args *args, int arg_id); | 第arg_id 参数的数据类型为double 类型,从参数列表args 中取出第arg_id参数的值 | |
char* de_get_str(de_args *args, int arg_id); | 第arg_id 参数的数据类型为字符串类型,从参数列表args 中取出第arg_id参数的值 | |
char* de_get_str_with_len(de_args *args, int arg_id, int* len); | 第arg_id 参数的数据类型为字符串类型,从参数列表args 中取出第arg_id参数的值以及字符串长度 | |
set | void de_set_int(de_args *args, int arg_id, int ret); | 第arg_id 参数的数据类型为整型,设置参数列表args 的第arg_id参数的值为 ret |
void de_set_double(de_args *args, int arg_id, double ret); | 第arg_id 参数的数据类型为double 类型,设置参数列表args 的第arg_id参数的值为 ret | |
void de_set_str(de_args *args, int arg_id, char* ret); | 第arg_id 参数的数据类型为字符串类型,设置第arg_id 参数的值为ret | |
void de_set_str_with_len(de_args *args, int arg_id, char* ret, int len); | 第arg_id 参数的数据类型为字符串类型,将字符串ret 的前len个字符赋值给参数列表 args的第arg_id 参数 | |
void> de_set_null(de_args *args, int arg_id); | 设置参数列表args 的第arg_id个参数为空 | |
return | de_data de_return_int(int ret); | 返回值类型为整型 |
de_data de_return_double(double ret); | 返回值类型为double 型 | |
de_data de_return_str(char* ret); | 返回值为字符串类型 | |
de_data de_return_str_with_len(char* ret, int len); | 返回字符串ret 的前len个字符 | |
de_data de_return_null(); | 返回空值 | |
de_str_free | void de_str_free(char* str); | 调用de_get_str 函数后,需要调用此函数释放字符串空间 |
de_is_null | int de_is_null(de_args *args, int arg_id); | 判断参数列表args 的第arg_id个参数是否为空 |
注:参数个数 arg_id 的起始值为 0。
- 标量类型参数
该方案中,用户不必引用 DM 提供的外部函数接口,可以按照标准的 C 风格编码,使用 C 标量类型作为参数类型。使用该方案编写的 C 函数,只能在使用 X86 CPU 的 64 位非 Windows 系统中,被数据库引用作为外部函数。
C 外部函数格式
返回类型函数名(参数列表)
返回类型函数名(参数列表)
{
C语言函数实现体;
}
使用说明
- 返回类型及参数列表中参数的数据类型只支持 int、double(或精度大于 24 的 float)以及 char*类型。其中 float 的用法和 double 完全一样;
- 参数列表中不支持 out 型参数;
- 如果参数列表中有 char*的参数,不必在函数中对其进行释放;为了安全考虑,最好只对其进行只读操作;
- 如果返回 char*类型,返回值必须使用 malloc 申请空间,且必须有结尾 0,不允许直接返回常量或返回参数列表中传入的字符类型参数。
10.1.2 C 外部函数创建
语法格式
CREATE [OR REPLACE] FUNCTION [<模式名>.]<函数名>[(<参数列表>)]
RETURN <返回值类型>
EXTERNAL '<动态库路径>' [<引用的函数名>] USING < C | CS >;
参数
- < 函数名 > 指明被创建的 C 外部函数的名字;
- < 模式名 > 指明被创建的 C 外部函数所属模式的名字,缺省为当前模式名;
- < 参数列表 > 指明 C 外部函数参数信息,如果是使用 DM 结构化参数编写的 C 函数,参数模式可设置为 IN、OUT 或 IN OUT(OUT IN),缺省为 IN 类型;而使用标量类型参数编写的 C 函数则参数模式只能是 IN 类型。参数类型、个数都应和动态库里定义的一致;
- < 返回值类型 > 必须和动态库里定义的一致;
- < 动态库路径 > 用户按照 DM 规定的 C 语言函数格式编写的 DLL 文件生成的动态库所在的路径;动态库分为 64 位和 32 位两种,使用的时候要和操作系统一一对应。例如,64 位的操作系统要用 64 位的动态库;
- < 引用函数名 > 指明 < 函数名 > 在 < 动态库路径 > 中对应的函数名;
- USING 子句指明函数的类型,如果是 DM 结构化参数的 C 函数,类型为 C;标量类型参数的 C 函数,类型为 CS。
图例
C 外部函数创建
语句功能
创建自定义 C 外部函数。
使用说明
- 仅允许 DBA 创建外部函数;
- 创建或执行外部函数需要设置 INI 参数 ENABLE_EXTERNAL_CALL 为 1;
- < 引用函数名 > 如果为空,则默认与 < 函数名 > 相同;
- < 动态库路径 > 分为.dll 文件(windows)和.so 文件(linux)两种;
- 外部函数定义的返回值类型与对应 C 语言函数的返回值类型必须一致,否则会导致返回值不可预测甚至系统异常。
10.1.3 举例说明
例如,编写(C 语言)外部函数 C_CONCAT,用于将两个字符串连接。下面针对两种编写 C 外部函数的方案,分别进行描述。
一 可以使用 DM 结构化参数方案来完成。
首先 生成动态库。
第一步,使用 Microsoft Visual Studio 2010 新建空项目 newp,位于 d:\xx\tt 文件夹中。新建完毕后,若本机操作系统为 x64,因为 VS 默认的解决方案平台不是 x64,需要在解决方案平台下拉框选择“配置管理器-活动方案解决平台-新建-键入或选择新平台-x64”。在 d:\xx\tt\newp\newp 文件夹中,直接拷入 dmde.lib 动态库和 de_pub.h 头文件。dmde.lib 和 de_pub.h 位于安装文件 x:...\dmdbms\include 中。
第二步,在 newp 项目中,添加头文件。将已有 de_pub.h 头文件添加进来,同时,添加新的 tt.h 头文件。tt.h 文件内容如下:
#include "de_pub.h"
#include "string.h"
#include "stdlib.h"
第三步,在 newp 项目中,添加源文件。名为 tt.c。tt.c 内容如下:
#include "tt.h"
de_data C_CONCAT(de_args *args)
{
de_data de_ret;
char* str1;
char* str2;
char* str3;
int len1;
int len2;
str1 = (char*)de_get_str(args, 0); //从参数列表中取第0个参数
str2 = (char*)de_get_str_with_len(args, 1, (udint4*)&len2); //从参数列表中取第1个参数的值以及长度
len1 = strlen(str1);
str3 = (char*)malloc(len1 + len2);
memcpy(str3, str1, len1);
memcpy(str3 + len1, str2, len2);
de_str_free((sdbyte*)str1); //调用get函数得到字符串之后,需要调用此函数释放字符串空间
de_str_free((sdbyte*)str2);
de_ret = de_return_str_with_len((udbyte*)str3, len1 + len2); //返回字符串
free(str3);
return de_ret;
}
第四步,在 newp 项目的源文件中,添加模块定义文件 tt.def,内容如下:
LIBRARY "tt.dll"
EXPORTS
C_CONCAT
第五步,配置项目属性。
在 Microsoft Visual Studio 2010 界面上,右击 newp 项目-属性,点击打开。
在“配置属性—链接器—输入”中添加附加依赖项 dmde.lib。
在“配置属性—常规”中:输出目录设为 D:\xx\tt\;配置类型选择动态库(.dll);字符集选择使用多字节字符集。
第六步,生成 newp 项目。右击 newp 项目-生成。得到 D:\xx\tt\newp.dll 文件。
至此,外部函数的使用环境准备完毕。
其次,创建并使用外部函数。
第一步,启动数据库服务器 dmserver、启动 DMAP、启动 DIsql。dmserver、DIsql 等工具位于安装目录 x:...\dmdbms\bin 中。
需要先开启系统允许创建外部函数的开关。通过设置 DM.INI 参数 ENABLE_EXTERNAL_CALL=1 开启。设置语句如下:
SF_SET_SYSTEM_PARA_VALUE('ENABLE_EXTERNAL_CALL',1,0,2);
设置完毕后需重启数据库服务器,参数才能生效。
第二步,在 DIsql 中,创建外部函数 MY_CONCAT。语句如下:
CREATE OR REPLACE FUNCTION MY_CONCAT(A VARCHAR, B VARCHAR)
RETURN VARCHAR
EXTERNAL 'd:\xx\tt\newp.dll' C_CONCAT USING C;
第三步,调用 C 外部函数。语句如下:
select MY_CONCAT ('hello ', 'world!');
第四步,查看结果:
hello world!
二 使用 C 标量类型参数方案完成,不过需要注意该方案仅支持使用 X86 CPU 的 64 位非 Windows 系统。
生成动态库,这里改用 LINUX 操作系统作为示例。
第一步,创建 C 文件 test.c,编写 C 函数。
#include <string.h>
#include "stdlib.h"
char* C_CONCAT (char* str1, char* str2)
{
char* str3;
int len1;
int len2;
len1 = strlen(str1);
len2 = strlen(str2);
str3 = (char*)malloc(len1 + len2 + 1); //要多一个字节作为结尾0
memcpy(str3, str1, len1);
memcpy(str3 + len1, str2, len2);
str3[len1 + len2] = 0; //必须有结尾0
return str3;
}
第二步,生成动态库。
gcc -o /mnt/libtest.so -fPIC -shared test.c
获得 libtest.so 文件。
至此,外部函数的使用环境准备完毕。
其次,创建并使用外部函数。
第一步,启动数据库服务器 dmserver,启动 DMAP、启动 DIsql。
第二步,在 DIsql 中,创建外部函数 MY_CONCAT。语句如下:
CREATE OR REPLACE FUNCTION MY_CONCAT(A VARCHAR, B VARCHAR)
RETURN VARCHAR
EXTERNAL '/mnt/libtest.so' C_CONCAT USING CS;
第三步,调用 C 外部函数。语句如下:
select MY_CONCAT ('hello ', 'world!');
第四步,查看结果:
hello world!
10.2 JAVA 外部函数
JAVA 外部函数是使用 JAVA 语言编写,在数据库外编译生成的 jar 包,被用户通过 DMSQL 程序调用的函数。
JAVA 外部函数的执行都通过代理 dmagent 工具进行,为了执行 JAVA 外部函数,需要先启动 dmagent 服务。dmagent 执行程序在 DM8 安装目录的 tool/dmagent 子目录下,其使用说明文档可参看该目录下的《readme》文档。
当用户调用 JAVA 外部函数时,服务器操作步骤如下:首先,确定调用(外部函数使用的)jar 包及函数;然后,通知代理进程工作。代理进程装载指定的 jar 包,并在函数执行后将结果返回给服务器。
需要注意的是,进行 JAVA 外部函数调用应保证当前用户可以运行 JAVA 命令,否则会导致调用失败。
10.2.1 生成 jar 包
用户必须严格按照 JAVA 语言的格式书写代码,书写完成后生成 jar 包。
10.2.2 JAVA 外部函数创建
语法格式
CREATE [OR REPLACE] FUNCTION [<模式名>.]<函数名>[(<参数列表>)]
RETURN <返回值类型>
EXTERNAL '<jar包路径>' <引用函数名> USING JAVA;
参数
- < 函数名 > 被创建的 JAVA 外部函数的名字;
- < 模式名 > 被创建的 JAVA 外部函数所属模式的名字,缺省为当前模式名;
- < 参数列表 > JAVA 外部函数参数信息,参数模式可设置为 IN、OUT 或 IN OUT (OUT IN),缺省为 IN 类型。参数类型、个数都应和 jar 包里的一致。目前支持的函数参数类型:int、字符串(char、varchar、varchar2)、bigint、double、binary/varbinary,分别对应 java 类型:Int、string、 long、double、byte[];
- < 返回值类型 > 必须 jar 包里定义的一致;
- <jar 包路径 > 用户按照 java 语言格式编写的源码生成的 jar 包,及其依赖的 jar 包所在的相对路径或绝对路径;
- < 引用函数名 > 指明 < 函数名 > 在 <jar 包路径 > 中对应的函数名,函数调用通过 JAVA 反射实现,建议调用函数访问限定符为 public,另外,所属类需不含构造函数或包含无参的构造函数。若 < 函数名 > 包含包名,则包名(或.包名)与类名的分隔符要用.或/。而类与方法的分隔符要用.。若不指定 < 引用函数名 >,则后续使用 JAVA 外部函数时会报错。
图例
JAVA 外部函数创建
语句功能
创建自定义 JAVA 外部函数。
使用说明
- 仅允许 DBA 创建外部函数;
- 创建或执行外部函数需要设置 INI 参数 ENABLE_EXTERNAL_CALL 为 1;
- < 引用函数名 > 书写格式:包名、类名与方法名之间用.进行分隔。
10.2.3 举例说明
例如,写(JAVA 语言)外部函数:testAdd 用于求两个数之和,testStr 用于在一个字符串后面加上 hello。
首先,生成 jar 包。
第一步,使用 eclipse 创建新项目 newp,位于 F:\workspace 文件夹中。
第二步,在 newp 项目中,添加包。右击 newp,新建(new)一个 package,命名(name)为 com.test.package1。
第三步,在 package1 包中,添加类文件。右击 src,新建(new)一个 class,命名(name)为 test。Modifiers 选择 public。class 文件内容如下:
package com.test.package1;
public class test {
public static int testAdd(int a, int b) {
return a + b;
}
public static String testStr(String str) {
return str + " hello";
}
}
第四步,生成 jar 包。在 newp 项目中,右击,选中 EXPORT,选择 Java\JAR file,取消.classpath 和.project 的勾选。目标路径 JAR file 设置为:E:\test.jar,然后 finish。
第五步,查看 E 盘中 test.jar。已经存在。
第六步,在安装目录的..\dmdbms\bin 中创建一个 external_jar 文件夹,将 test.jar 拷入其中。
至此,外部函数的使用环境准备完毕。
其次,创建并使用外部函数。
第一步,启动数据库服务器 dmserver,启动 DM 管理工具。
第二步,在 DM 管理工具中,创建外部函数 MY_INT 和 MY_chr,语句如下:
CREATE OR REPLACE FUNCTION MY_INT(a int, b int)
RETURN int
EXTERNAL '..\dmdbms\bin\external_jar\test.jar' "com.test.package1.test.testAdd" USING java;
CREATE OR REPLACE FUNCTION MY_chr(s varchar)
RETURN varchar
EXTERNAL '..\dmdbms\bin\external_jar\test.jar' "com.test.package1.test.testStr" USING java;
第三步,调用 JAVA 外部函数,语句如下:
select MY_INT(1,2);
select MY_chr('abc');
第四步,查看结果,分别为:
3
abc hello
10.3 DMAP 使用说明
DMAP(DM Assit progress)作为数据库管理系统的辅助进程,提供 C 外部函数、备份还原等功能的执行。
10.3.1 启动 DMAP
安装 DM 数据库以后,DMAP 服务会自动启动。如果需要手动启动,有两种途径:一是启动 DM 服务查看器中的 DmAPService;二是通过手动启动 DMAP 执行码实现,DMAP 执行码位于 DM 安装目录的 bin 子目录下。除此之外,LINUX 下,还可以调用 bin 目录下的 DmAPService 脚本启动 DMAP 服务。
默认不带参数启动 DMAP 时,DMAP 监听端口号为 4236,与 DM 服务器 INI 参数 EXTERNAL_AP_PORT 的默认值一致。
也可以使用参数 dmap_ini 指定 DMAP.INI 配置文件启动 DMAP,如下例所示:
dmap dmap_ini=d:\dmap.ini
其中,DMAP.INI 可配置 dmap 的端口号,如下所示:
AP_PORT=4236
需要注意的是,当使用 DMAP.INI 对 AP_PORT 进行配置时,DM 服务器 INI 参数 EXTERNAL_AP_PORT 的配置值应与 AP_PORT 一致。
10.3.2 使用 DMAP 执行外部函数
例 创建并使用 C 外部函数 MY_CONCAT。
CREATE OR REPLACE FUNCTION MY_CONCAT(A VARCHAR, B VARCHAR)
RETURN VARCHAR
EXTERNAL 'D:\testroot\for_dmserver\smoketest_data\dameng\detest64.dll' C_CAT USING C;
/
select MY_CONCAT ('hello ', 'a');
查询结果如下:
行号 MY_CONCAT('hello','a')
--------- ----------------------
1 hello a
10.3.3 DMAP 日志
DMAP 日志文件记录了 DMAP 工具产生的相关日志。DMAP 日志文件命名规则为:dm_dmap_年月.log。
DMAP 日志记录的信息格式为:
时间 + 日志类型(INFO/WARN/ERROR/FATAL)+ 进程(dmap)+ 进程 ID+ 线程名(线程名为空则使用 T 开头的线程 ID)+ 日志内容。
例 展示一段 dm_dmap_202211.log 日志信息。
2022-11-30 09:00:58.357 [INFO] dmap P0000015192 T0000000000000032076 dmap V8
2022-11-30 09:00:58.372 [INFO] dmap P0000015192 T0000000000000032076 dmap is ready
2022-11-30 09:00:58.372 [INFO] dmap P0000015192 T0000000000000032076 [for dem]SYSTEM IS READY.