本章概要介绍 PRO*C 与嵌入式 SQL 的基本概念,以及 PRO*C 程序的工作机制即 DM 的预编译系统。
1.1 功能简介
SQL 语言作为结构化的查询语言,可以完成对数据库的定义、查询、更新、控制、维护、恢复、安全管理等一系列操作,充分体现了关系数据库的特征。但 SQL 语言是非过程性语言,本身没有过程性结构,大多数语句都是独立执行,与上下文无关,而绝大多数完整的应用都是过程性的,需要根据不同的条件来执行不同的任务。因此,单纯用 SQL 语言很难实现这样的应用。为此,DM 数据库提供了 SQL 的两种使用方式:一种是交互方式,另一种是嵌入方式。
嵌入方式是将 SQL 语言嵌入到高级语言中,这样一来,既发挥了高级语言数据类型丰富、处理方便灵活的优势,又以 SQL 语言弥补了高级语言难以描述数据库操作的不足,从而为用户提供了建立大型管理信息系统和处理复杂事务所需要的工作环境。在这种方式下使用的 SQL 语言称为嵌入式 SQL,而嵌入 SQL 的高级语言称为主语言或宿主语言。DM 数据库允许 C 作为嵌入方式的主语言。在 DM 系统中,我们将嵌有 SQL 语句的 C 语言程序称为 PRO*C 程序。
嵌入在主语言程序中的 SQL 语句并不能直接被主语言编译程序识别,必须对这些 SQL 语句进行预处理,将其翻译成主语言语句,生成由主语言语句组成的目标文件,然后再由编译程序编译成可执行文件,执行该文件,方可得到用户所需要的结果。
DM 的 PRO*C 嵌入工作方式支持的功能如下:
- 支持国家和军用标准关系数据库语言 SQL;
- 支持嵌入 SQL 语言的多模块程序设计;
- 提供对用户透明的查询优化功能;
- 支持对远程数据库的访问。
1.2 预编译系统的结构与功能
1.2.1 预编译系统的结构
预编译系统的结构如下图所示。
1.2.2 预编译系统的功能
预编译系统主要包括词法分析、语法分析、消息生成等几个子系统。
- 词法分析子系统
在嵌入工作方式下,词法分析子系统从指定的主语言源程序中逐段找出嵌入的 SQL 声明节或语句段,将它们进行分解,得到一个个单词,顺序填入单词表,供语法分析时使用;而对非 SQL 嵌入段的主语言正文则直接写入目标文件。在交互工作方式下,词法分析子系统只需要接受用户输入的单个 SQL 语句或 SQL 语句块,将它们分解成单词串并顺序填入单词表。
- 语法分析子系统
语法分析子系统在词法分析的基础上,对单词表中所存入的 SQL 单词串按 SQL 语言文本进行初步的合法性检查,并对各种单词进行分类,识别语句中的嵌入变量并进行相应的语法检查。
- 消息生成模块
在 DM 中,客户端对数据库的操作都是以消息方式传送给数据库服务器,因此预编译系统应具有将 SQL 语句转换为消息的功能,这就是消息生成。消息生成的任务是将 SQL 语句翻译成主语言消息生成语句和消息发送及回收语句,并将它们写入目标文件。需要说明的是,预编译系统并不直接生成消息,而是利用对 DPI 接口的函数调用实现,这样可以进行必要的封装,模块性也更强。
1.2.3 预编译系统的处理流程
在嵌入工作方式下,预编译系统的功能是:对嵌入的 SQL 语句段和 SQL 声明节进行全面的词法、语法检查,然后将 SQL 语句翻译成主语言语句(即 DPI 函数调用)写入目标文件中,使目标文件成为一个纯由主语言组成的程序。整个预编译的处理流程如图所示:
1.3 预编译系统配置
1.3.1 预编译系统包含的程序和文件
预编译系统提供的软件有:
- 预编译命令行运行程序 dpc_new.exe;
- 编译时要使用的文件 dpc_dll.h、DPI.h、DPItypes.h、sqlca.h;若兼容 ORACLE,则需另外添加 sqlca_ora.h、sqlda_ora.h、dpc_ora_dll.h;若兼容 DB2,则需另外添加 sqlca_db2.h、sqlda_db2.h;
- 连接时需要的库文件 dmdpc.lib(Windows 操作系统)或者 libdmdpc.a(Linux);
- 执行时需要的动态库文件 dmdpc.dll(Windows 操作系统)或者 libdmdpc.so(Linux)。
1.3.2 预编译命令的使用方法
预编译时,在命令提示符窗口中输入带参数的 dpc_new 命令,语法如下:
dpc_new parameter=value {parameter=value}
dpc_new 支持的参数含义如下表所示。
参数 | 介绍 |
---|---|
FILE | .pc 文件的绝对路径。例如:FILE=/home/pc/test.pc。如未指定完整路径,则使用 dpc_new.exe 所在的路径。例如:FILE=test.pc。必选参数 |
TYPE | 指定生成文件类型。C 代表 C 源文件,CPP 代表 C++ 源文件;默认为 C |
MODE | 编译模式。STD 表示标准编译模式;DM 表示达梦编译模式;ORACLE 表示兼容部分 ORACLE 语法的编译模式;DB2 表示兼容部分 DB2 语法的编译模式。缺省为 STD 编译模式 |
MACRO | 解析宏,如果源 pc 文件中有#define 定义的宏,在文件中使用了宏,若不加此命令,文件中的宏将不能识别 |
DEFINE | 条件编译宏,如果源 pc 文件中有#defWIN32 等条件编译宏,可以根据需要在 DEFINE 命令中设置对应的条件编译宏,来解析对应的内容。如果要使用多个宏用分号分隔,例如:define=WIN32;DM64 |
INCLUDE_DIR/INCLUDE | 包含的头文件目录,指定 pc 文件中 include 的头文件所在的目录。指定多个目录时用分号或冒号隔开,不能混合使用,例如:include_dir=d:\test1;d:\test2 |
WRITE_DIRECT | 解析的文件是否立即可见,缺省为 N。当为 N 时,dpc_new 工具解析 pc 文件时解析完部分就会立即写入 C 文件;当为 Y 时,整个 pc 文件解析完,才会一起写入生成的 C 文件 |
OCI_CONNECT | 是否使用 OCI 连接(Y/N),缺省为 N |
INCLUDE_SQLCA | 是否包含 sqlca(Y/N),缺省为 N |
CHAR_MAP | 字符串的处理方式,目前支持 STRING、CHARZ 和 CHARF,缺省为 CHARZ |
TYPE_MODE | 数据类型兼容模式。STD 表示标准数据类型;DM 表示达梦数据类型;ORACLE 表示兼容部分 ORACLE 数据类型;DB2 表示兼容 DB2 数据类型。缺省为 STD(目前仅用来兼容 oracle 的 count(*) sqlda 返回类型与长度) |
DEL_TMP_FILE | 预编译报错时是否删除临时文件,缺省为 Y,删除临时文件。使用了 INCLUDE_DIR、MACRO 参数进行预编译会生成临时文件,报错信息里的行数信息基于临时文件,保留临时文件有利于定位问题 |
XA_CONNECT | 是否使用 XA 连接,缺省为 N |
UNSAFE_NULL | 允许不使用指示符表列的 NULL 提取,缺省为 N,表示不允许不绑定指示符获取 NULL 值,执行会报错。置为 Y 表示允许不绑定指示符获取 NULL 值,执行不会报错 |
DYN_STMT_WITHOUT_INTO | 是否去掉动态绑定的 SELECT INTO 语句中 INTO 内容。缺省为 N,表示 不去掉 INTO。例如: EXEC SQL PREPARE S FROM SELECT C1 INTO :a FROM TEST; 本参数置为 Y 时会将“INTO :a”去掉 |
USE_GLOBAL_CTX | 是否使用全局变量登记 USE 上下文(Y/N)。缺省为 N,若设置为 Y,EXEC SQL CONTEXT USE :ctx 操作不生成 C 接口,将 ctx 登记到全局变量,在具体数据库操作时再生成 USE ctx 的 C 接口 |
PARSE | 控制对哪一处非 SQL 代码进行语法分析。可选值 FULL/NONE/PARTIAL,暂时实际仅支持 FULL,缺省为 FULL |
VARCHAR_RTRIM | 动态绑定插入字符串数据是否按指定长度插入(Y/N)。缺省为 N,表示不去掉结尾 0,按指定长度插入;Y 表示截断字符串结尾 0,不按指定长度插入 |
BACKSLASH_IGNORE | 解析单引号''字符时,是否忽略单字符中的转义符反斜杠(Y/N),缺省为 N,是则将其当转义符处理,即将''当成 c 代码字符解析 例如:使用场景变量声明 char a='\'';,置为 N 由于会当成嵌入 sql 里面的字符串分析,dpc_new 解析不过 |
LINES | 预编译生成目标文件是否生成#line 指令(Y/N),缺省为 N。加入#line 指令调试代码可以调试源 pc 文件 |
CURSOR_TYPE | 设置游标类型,值为 FORWARD_ONLY、KEYSET_DRIVEN、DYNAMIC、STATIC。缺省为 DYNAMIC |
USE_CURSOR_BIND | 游标 Fetch 是否使用游标上的全局绑定句柄(Y/N),默认 N 不使用。设为 Y 使用游标上全局绑定句柄,防止循环 fetch 时重复申请释放绑定句柄,影响性能,在游标 close 时释放游标上全局绑定句柄。 |
DEST | 目标文件的目录,和 ONAME 指定的名称搭配使用。如果 DEST 、ONAME 同时指定了路径,则以 ONAME 中指定的路径为准 |
ONAME | 目标文件的名称或绝对路径。如果此处指定的是名称,例如:ONAME=aa.c,需和 DEST 指定的目录搭配使用。如果此处指定的是绝对路径,例如:ONAME=/home/pc/aa.c,则以 ONAME 中指定的路径为准 |
ORACA | 是否启用 ORACA(YES/NO),缺省为 NO |
SQLCHECK | 预编译时 SQL 脚本的检查类型。可取值 SYNTAX、SEMANTICS、FULL,目前不同取值的含义没有区别,都是检查 SQL 的语法和语义。 指定 SQLCHECK 时必须和参数 USERID 同时使用。 缺省时,表示预编译时不检查 SQL 脚本的语法和语义 |
USERID | 连接服务器的连接字符串。username/password[@server[:port]],其中 server 缺省为 localhost,port 缺省为 5236。 指定 USERID 时必须和命令参数 SQLCHECK 同时使用 |
HELP | 打印帮助信息 |
例 1 假设已有文件为 test.pc,则编译命令如下。
./dpc_new FILE=test.pc
如果编译成功将会生成 test.c 文件,否则 dpc_new 工具会报错。
例 2 本例说明如何根据设置的 define 命令,来解析对应的内容。
假设原始文件 test.pc 内容如下:
##include <stdio.h>
##include <time.h>
##ifdef WIN32
##include <Windows.h>
##include <PROCESS.H>
typedef HANDLE os_thread_t;
typedef LPTHREAD_START_ROUTINE os_thread_fun_t;
##else
##include <pthread.h>
##endif
使用如下命令进行编译:
./dpc_new FILE=test.pc DEFINE=WIN32
则生成的 test.c 的内容为如下所示。可以看到,因为 DEFINE 参数指明了使用 WIN32 条件编译宏,因此编译结果中正确解析了源文件中“ifdef WIN32”部分。
##include <stdio.h>
##include <time.h>
##include <Windows.h>
##include <PROCESS.H>
typedef HANDLE os_thread_t;
typedef LPTHREAD_START_ROUTINE os_thread_fun_t;
若没有使用 DEFINE=WIN32 参数,使用如下命令进行编译
./dpc_new FILE=test.pc
则生成的 test.c 的内容为:
##include <stdio.h>
##include <time.h>
##include <pthread.h>
例 3 本例说明 MACRO 参数的作用。
假设原始文件 test.pc 内容如下
#define BUFFERMAX 20971520 //缓存 1024*1024*20
unsigned char buffer[BUFFERMAX];
如果编译命令中不加 MACRO=Y,那么在嵌入式 SQL 语句中使用 buffer 变量就无法识别 BUFFERMAX。使用 MACRO=Y 后,生成的 C 文件将所有使用 BUFFERMAX 地方用 20971520 替代。上面的 pc 将解析为下面内容:
##include "dpc_dll.h"
/* Thread Safety */
typedef void * sql_context;
typedef void * SQL_CONTEXT;
unsigned char buffer [ 20971520 ] ;
注意如果指定了INCLUDE_DIR目录,则默认MACRO=Y
1.3.3 编译目标代码文件时的编译选项
_OciConnect:目标文件需要使用 dci.dll 时,包含头文件 dci.h,在编译时应该加上该选项。
DM64:如果在 64 位机器上编译,需要加上该选项。由于 dpc_new 在编译时需要使用 DPItypes.h,而在 DPItypes.h 中定义了 slength 和 ulength 类型,如下所示:
##ifdef DM64
typedef sdint8 slength;
typedef udint8 ulength;
##define SLENGTH_MAX SDINT8_MAX
##define ULENGTH_MAX UDINT8_MAX
##else
typedef sdint4 slength;
typedef udint4 ulength;
##define SLENGTH_MAX SDINT4_MAX
##define ULENGTH_MAX UDINT4_MAX
##endif
因此,如果 64 位环境下编译应用时没加 DM64 宏,会导致 DPI 接口调用异常。