为了能够在创建和使用自定义 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 服务。
同时,DM 结构化的 C 外部函数支持结合 INI 参数 EFC_USE_AP 进行性能优化,当指定参数值为 0 时,结构化的 C 外部函数会在 dmserver 内部调用,不再和 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参数的值。 二者区别: de_get_str使用服务器自动申请的空间,使用完毕后需要de_str_free释放空间。 de_get_str_with_space使用第三方申请的空间(通过v_str_in和v_str_in_len传入),使用完毕后不需要de_str_free释放空间。 v_str_in为已申请内存的字符串空间,由调用者分配,其大小不能少于v_str_in_len。若已分配长度等于0,v_str_in和返回值均返回NULL。若分配空间不够,则只返回部分字符串。 v_str_in_len为空间长度,包含结尾0。如果v_str_in_len小于实际参数加结尾0长度,则截断输出 |
|
char* de_get_str_with_space ( de_args* args, int arg_id, byte* v_str_in, int v_str_in_len ); |
||
char* de_get_str_with_len( de_args *args, int arg_id, int* len ); |
第arg_id参数的数据类型为字符串类型,从参数列表args中取出第arg_id参数的值以及字符串长度。 二者区别: de_get_str_with_len使用服务器自动申请的空间,需要使用de_str_free释放空间。 de_get_str_with_len_and_space使用第三方申请的空间(通过v_str_in和v_str_in_len传入),不需要使用de_str_free释放空间。 v_str_in为已申请内存的字符串空间,由调用者分配,其大小不能少于v_str_in_len。若已分配长度等于0,v_str_in和返回值均返回NULL。若分配空间不够,则只返回部分字符串。 v_str_in_len为空间长度,包含结尾0。如果v_str_in_len小于实际参数加结尾0长度,则截断输出。 len为实际返回长度,不包含结尾0 |
|
char* de_get_str_with_len_and_space ( de_args* args, int arg_id, byte* v_str_in, int v_str_in_len, int * len ); |
||
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 [IF NOT EXISTS] [<模式名>.]<函数名>[(<参数列表>)]
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 位的动态库;
- < 引用的函数名 > 指明 < 函数名 > 在 < 动态库路径 > 中对应的函数名。如果不使用双引号,则系统会自动将函数名转换成大写字母,如果使用双引号则不会进行大小写转换。建议使用双引号将函数名括起来,避免由于大小写问题而导致函数名不匹配。示例如下:……EXTERNAL '/mnt/mylibtest.so' "myy_add" USING CS;
- USING 子句指明函数的类型,如果是 DM 结构化参数的 C 函数,类型为 C;标量类型参数的 C 函数,类型为 CS。
图例
C 外部函数创建
语句功能
创建自定义 C 外部函数。
使用说明
- 三权分立下,仅允许拥有 CREATE PROCEDURE 权限的用户创建外部函数;四权分立下,仅允许拥有 CREATE PROCEDURE 权限的用户创建外部函数;
- 创建或执行外部函数需要设置 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 = NULL;
char* str2 = NULL;
char* str3 = NULL;
int len1 = 0;
int len2 = 0;
if(!de_is_null(args, 0))
{
str1 = (char*)de_get_str(args, 0); //从参数列表中取第0个参数
len1 = strlen(str1);
}
if(!de_is_null(args, 1))
{
//从参数列表中取第1个参数的值以及长度
str2 = (char*)de_get_str_with_len(args, 1, (udint4*)&len2);
}
str3 = (char*)malloc(len1 + len2);
memcpy(str3, str1, len1);
memcpy(str3 + len1, str2, len2);
//调用get函数得到字符串之后,需要调用此函数释放字符串空间
if(!de_is_null(args, 0))
{
de_str_free((sdbyte*)str1);
}
if(!de_is_null(args, 1))
{
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 = NULL;
int len1 = 0;
int len2 = 0;
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 [IF NOT EXISTS] [<模式名>.]<函数名>[(<外部函数参数列表>)]
RETURN <返回值类型>
EXTERNAL '<jar包路径>' [AND] <引用的JAVA函数名>[(<JAVA函数参数列表>)] USING JAVA;
<外部函数参数列表>::=<外部函数参数声明> {,<外部函数参数声明>}
<外部函数参数声明>::=<外部函数参数名> [<外部函数参数模式>]<外部函数参数类型>
<JAVA函数参数列表>::=<JAVA函数参数声明> {,<JAVA函数参数声明>}
<JAVA函数参数声明>::=<JAVA函数参数类型>
参数
- < 函数名 > 被创建的 JAVA 外部函数的名字;
- < 模式名 > 被创建的 JAVA 外部函数所属模式的名字,缺省为当前模式名;
- < 外部函数参数列表 > JAVA 外部函数参数信息;
- < 外部函数参数模式 > 可设置为 IN、OUT 或 IN OUT(OUT IN),缺省为 IN 类型。参数类型、个数都应和 jar 包里的一致;
- < 外部函数参数类型 > 目前支持的函数参数类型:int、字符串(char、varchar、varchar2)、bigint、double/float/binary_float、binary/varbinary、clob、blob,分别对应 java 类型:int、string、 long、double、byte[]、string、byte[];
- < 返回值类型 > 必须 jar 包里定义的一致;
- <jar 包路径 > 用户按照 JAVA 语言格式编写的源码生成的 jar 包,及其依赖的 jar 包所在的相对路径或绝对路径;
- < 引用的 JAVA 函数名 > 指明 < 函数名 > 在 <jar 包路径 > 中对应的 JAVA 函数名,函数调用通过 JAVA 反射实现,建议调用函数访问限定符为 public,另外,所属类须不含构造函数或包含无参的构造函数。若 < 函数名 > 包含包名,则包名(或.包名)与类名的分隔符要用.或/。而类与方法的分隔符要用.。若不指定 < 引用函数名 >,则后续使用 JAVA 外部函数时会报错。一个正确的 JAVA 函数名示例如下:……EXTERNAL '/data/sda/test.jar' "com.test.package1.test.pass(java.lang.String,java.lang.String[])" USING JAVA;;
- <JAVA 函数参数列表 > 用于指定 JAVA 函数的参数列表。缺省情况下,系统自动定位参数。只有未指定 < 外部函数参数模式 > 或 < 外部函数参数模式 > 中所有参数模式均为 IN 时,<JAVA 函数参数列表 > 才可缺省;
- <JAVA 函数参数声明 > 由一个或多个 <JAVA 函数参数类型 > 组成。<JAVA 函数参数类型 > 书写时必须包括 JAVA 类的完整名称。例如:……EXTERNAL '/data/sda/test.jar' "com.test.package1.test.pass(java.lang.String,java.lang.String[])" USING JAVA;。
图例
JAVA 外部函数创建
语句功能
创建自定义 JAVA 外部函数。
使用说明
- 当 < 引用的 JAVA 函数名 >[(<JAVA 函数参数列表 >)]的长度超过 128 个字节并且不超过 32767 个字节时,不能使用双引号,需要改用单引号括起来,并且需要指定 AND 关键字。例如:EXTERNAL '/data/sda/test.jar' AND 'com.test.package1.test.pass(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)' USING JAVA;
- 三权分立下,仅允许拥有 CREATE PROCEDURE 权限的用户创建外部函数;四权分立下,仅允许拥有 CREATE PROCEDURE 权限的用户创建外部函数;
- 创建或执行外部函数需要设置 INI 参数 ENABLE_EXTERNAL_CALL 为 1;
- 只能执行 agent_home/resources/ap 目录下的 jar 包。
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。已经存在。
第六步,在外部函数对应的 dmagent 的 resources 目录下,创建一个 ap 文件夹(若 resources 目录下已有 ap 文件夹则不用创建),将 test.jar 拷入其中。
至此,外部函数的使用环境准备完毕。
其次,创建并使用外部函数。
第一步,启动数据库服务器 dmserver,启动 DM 管理工具。
第二步,在 DM 管理工具中,创建外部函数 MY_INT 和 MY_chr,语句如下:
CREATE OR REPLACE FUNCTION MY_INT(a int, b int)
RETURN int
EXTERNAL 'E:\develop\tool\agent\resources\ap\test.jar' "com.test.package1.test.testAdd" USING java;
CREATE OR REPLACE FUNCTION MY_chr(s varchar)
RETURN varchar
EXTERNAL 'E:\develop\tool\agent\resources\ap\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 外部函数
例 创建并使用 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.