外部函数

为了能够在创建和使用自定义 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语言函数实现体;
}

参数

  1. <de_data> 返回值类型。de_data 结构体类型如下:
struct de_data{
	int  null_flag;   //参数是否为空,1表示非空,0表示空
	union	       //只能为int、double或char类型
    {
		int    v_int;
		double  v_double;
		char   v_str[];
	}data;
};
  1. <de_args> 参数信类型。de_args 结构体类型如下:
struct de_args
{
	int        n_args;      //参数个数
    de_data*      args;        //参数列表
};
  1. < C 语言函数实现体 > C 语言函数对应的函数实现体。

使用说明

  1. C 语言函数的参数可通过调用 DM8 提供的一系列 get 函数得到,同时可调用 set 函数重新设置这些参数的值;
  2. 根据返回值类型,调用不同的 return 函数接口;
  3. 必须根据参数类型、返回值类型,调用相同类型的 get、set 和 return 函数。当调用 de_get_str 和 de_get_str_with_len 得到字符串后,必须调用 de_str_free 释放空间;
  4. DM8 提供的编写 C 外部函数动态库的接口如表 10.1 所示。
表10.1 DM8支持的编写C外部函数动态库的接口
函数类型 函数名 功能说明
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语言函数实现体;
}

使用说明

  1. 返回类型及参数列表中参数的数据类型只支持 int、double 以及 char*类型;
  2. 参数列表中不支持 out 型参数;
  3. 如果参数列表中有 char*的参数,不必在函数中对其进行释放;为了安全考虑,最好只对其进行只读操作;
  4. 如果返回 char*类型,返回值必须使用 malloc 申请空间,且必须有结尾 0,不允许直接返回常量或返回参数列表中传入的字符类型参数。

10.1.2 C 外部函数创建

语法格式

CREATE OR REPLACE FUNCTION [<模式名>.]<函数名>[(<参数列表>)]
RETURN <返回值类型>
EXTERNAL '<动态库路径>' [<引用的函数名>] USING < C | CS >;

参数

  1. < 函数名 > 指明被创建的 C 外部函数的名字;
  2. < 模式名 > 指明被创建的 C 外部函数所属模式的名字,缺省为当前模式名;
  3. < 参数列表 > 指明 C 外部函数参数信息,如果是使用 DM 结构化参数编写的 C 函数,参数模式可设置为 IN、OUT 或 IN OUT(OUT IN),缺省为 IN 类型;而使用标量类型参数编写的 C 函数则参数模式只能是 IN 类型。参数类型、个数都应和动态库里定义的一致;
  4. < 返回值类型 > 必须和动态库里定义的一致;
  5. < 动态库路径 > 用户按照 DM 规定的 C 语言函数格式编写的 DLL 文件生成的动态库所在的路径;动态库分为 64 位和 32 位两种,使用的时候要和操作系统一一对应。例如,64 位的操作系统要用 64 位的动态库;
  6. < 引用函数名 > 指明 < 函数名 > 在 < 动态库路径 > 中对应的函数名;
  7. USING 子句指明函数的类型,如果是 DM 结构化参数的 C 函数,类型为 C;标量类型参数的 C 函数,类型为 CS。

图例

C 外部函数创建

C 外部函数创建

语句功能

创建自定义 C 外部函数。

使用说明

  1. 仅允许 DBA 创建 C 外部函数;
  2. < 引用函数名 > 如果为空,则默认与 < 函数名 > 相同;
  3. < 动态库路径 > 分为.dll 文件(windows)和.so 文件(linux)两种;
  4. 外部函数定义的返回值类型与对应 C 语言函数的返回值类型必须一致,否则会导致返回值不可预测甚至系统异常。

权限

使用该语句的用户必须是 DBA 或该存储过程的拥有者且具有 CREATE FUNCTION 数据库权限的用户。

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;

参数

  1. < 函数名 > 被创建的 JAVA 外部函数的名字;
  2. < 模式名 > 被创建的 JAVA 外部函数所属模式的名字,缺省为当前模式名;
  3. < 参数列表 > JAVA 外部函数参数信息,参数模式可设置为 IN、OUT 或 IN OUT (OUT IN),缺省为 IN 类型。参数类型、个数都应和 jar 包里的一致。目前支持的函数参数类型:Int、字符串(char、varchar、varchar2)、bigint、double。分别对应 java 类型:Int、string、 long、double;
  4. < 返回值类型 > 必须 jar 包里定义的一致;
  5. <jar 包路径 > 用户按照 java 语言格式编写的源码生成的 jar 包,及其所依赖的 jar 包所在的绝对路径。dmagent 方式 jar 包要使用绝对路径;ap 方式 jar 包名字是不起作用的;
  6. < 引用函数名 > 指明 < 函数名 > 在 <jar 包路径 > 中对应的函数名,jar 包中的函数应为 static 类型。若 < 函数名 > 包含包名,则包名(或.包名)与类名的分隔符要用.或/。而类与方法的分隔符要用.。

图例

JAVA 外部函数创建

JAVA 外部函数创建

语句功能

创建自定义 JAVA 外部函数。

使用说明

  1. 仅允许 DBA 创建 JAVA 外部函数;
  2. < 引用函数名 > 书写格式:包名、类名与方法名之间用.进行分隔。

权限

使用该语句的用户必须是 DBA 或该存储过程的拥有者且具有 CREATE FUNCTION 数据库权限的用户。

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

可在 log 目录下查看 DMAP 日志文件,其中记录了执行 C 外部函数时消息收发的日志。

10.3.3 DMAP 日志

DMAP 日志文件记录了 DMAP 工具产生的相关日志,包括 C 外部函数和备份还原等。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.
微信扫码
分享文档
扫一扫
联系客服