3.嵌入式程序的组成
3.1 一个简单的嵌入式程序结构分析
首先,从一个简单的程序入手来认识嵌入式程序的组成和基本语法。
例如,下面是一个名为 test.pc 的简单的嵌入式程序。这个程序查询并输出“人员信息”表中人员编号为“1”的人员姓名和联系电话。
//test.pc
#include <stdio.h>
//宿主变量的定义
EXEC SQL BEGIN DECLARE SECTION;
char username[20],password[20],servername[20];
varchar person_name[50];
varchar person_phone[25];
EXEC SQL END DECLARE SECTION;
void main(void)
{
printf(" Please input username:");
scanf("%s",username);
printf(" Please input password :");
scanf("%s",password);
printf(" Please input servername :");
scanf("%s",servername);
//登录数据库
EXEC SQL LOGIN :username PASSWORD :password SERVER :servername;
//对数据库操作
EXEC SQL SELECT name, phone
INTO :person_name,:person_phone FROM person.person
WHERE personid = '1';
printf("\n 对应的人员姓名为: %s\n", person_name);
printf("\n 对应的联系电话为: %s\n", person_phone);
//退出数据库
EXEC SQL LOGOUT;
}
一般说来,pc 文件由以下几个部分构成:
- 宿主变量定义
在上面的例子中,宿主变量定义为嵌入在如下语句中的部分:
EXEC SQL BEGIN DECLARE SECTION;
EXEC SQL END DECLARE SECTION;
- 登录数据库
EXEC SQL LOGIN :username PASSWORD :password SERVER :servername;
- 数据库操作
登录成功之后,就可以对数据库进行各种操作。
- 退出登录
EXEC SQL LOGOUT;
下面将对以上各部分加以详细说明。
3.2 宿主变量的定义
嵌入的 SQL 语句可以使用主语言的变量来输入/输出数据。例如在 SELECT 语句中,需要将查找到的数据通过 INTO 子句送到某些变量中,或者 WHERE 子句需要用某些变量的值作条件。我们称 SQL 语句中使用到的变量为宿主变量。在 SQL 中所使用的宿主变量均必须在嵌入的 SQL 声明节中加以说明。
3.2.1 声明节语句
- 声明节开始语句
语法:
EXEC SQL BEGIN DECLARE SECTION;
功能:
表示声明节的开始。
使用说明:
后面必须有一个声明节结束语句与之相对应。
- 声明节结束语句
语法:
EXEC SQL END DECLARE SECTION;
功能:
表示声明节的结束。
使用说明:
前面必须有一个声明节开始语句与之相对应。
- 使用声明节应注意的问题
在声明节开始语句与结束语句中间,采用 C 语言定义变量的方式定义宿主变量。在声明节中定义的变量可以被 C 语句使用,而不必在声明节之外重新定义这些变量,否则会引起变量重复定义的错误。
一个程序模块中可以出现多个声明节,各个声明节可以出现在 C 程序的说明语句可出现的位置上。PRO*C 规定,一个模块中所有的声明节中的宿主变量不得同名,而不论其实质是全局变量还是局部变量。预编译时,相应声明节的 C 语句是去掉了“EXECSQL BEGIN DECLARE SECTION”和“EXEC SQL END DECLARE SECTION”两个语句后的变量定义语句,其位置顺序均保持不变。
3.2.2 常规数据类型变量的定义
在声明节中,可以使用的常规数据类型包括:char、short、int、long、float、double 等 C 数据类型。定义这些类型变量的方法与 C 语言定义相应变量的方法基本上是相同的。由于 DM 采用的是一次一个元组的处理方式,因而限制了数组的定义和使用,即不能使用数组的某个元素。作为一种特例,在声明节中定义字符数组,作为字符串指针使用。如:char name[20]等价于 char *name ,其中 name 是指向长度为 20 个字节空间的字符指针(等价于指针变量)。
在定义常规类型的变量时,与 C 语言一样,可以加上存储类型修饰符,包括 extern、static 和 auto。
语法如下:
<宿主变量定义>::=<常规数据类型变量定义>
<常规数据类型变量定义>::=[<存储类型>] <C 类型说明><变量声明>[{,<变量声明>}...]
<存储类型>::=extern|static|auto
<C类型说明>::= char | short | int| long | float | double 等
<变量声明>::= <变量名说明> [= <常量表达式>]
<变量名说明>::=<变量名> | *<变量名> | <变量名>[无符号整数]
例 常规数据类型变量定义。
......
EXEC SQL BEGIN DECLARE SECTION;
char shop_no[20],shop_name[20];
short flag;
EXEC SQL END DECLARE SECTION;
......
预编译后,产生的结果如下:
char shop_no[20],shop_name[20];
short flag;
......
3.2.3 宿主变量的使用
3.2.3.1 常规使用方法
在 SQL 语句中引用的宿主变量都应在声明节中定义,而且定义应先于引用。
在 SQL 语句中使用宿主变量时,在引用的宿主变量名前必须加上并且只能加上冒号“:”,而不论说明该变量时变量名前有无“*”,或者是否采用数组形式。在 C 语句中使用宿主变量时,不能够加上“:”。
例 查询人员编号为“1”的人员姓名及联系电话。
EXEC SQL BEGIN DECLARE SECTION;
varchar person_name[50];
varchar person_phone[25];
varchar person_id[10];
EXEC SQL END DECLARE SECTION;
......
strcpy(person_id,"1");
EXEC SQL SELECT name, phone
INTO :person_name, :person_phone FROM person.person
WHERE personid =:person_id;
printf("%s %s ", person_name, person_phone);
执行结果如下:
李丽02788548562
3.2.3.2 指示符的使用
在 SQL 语句中,在一个宿主变量之后,可以再跟一个数据类型为整型 (short,long,int)的被称为指示变量的宿主变量,其作用是指明该变量之前的一个宿主变量的取值情况。具体地说,它有如下两个作用,分别举例说明:
- 指示 SQL 语句中输入变量的值是否为空值。
例 使用指示变量插入空值。利用预置指示变量的值为-1,表明插入一个空值。
EXEC SQL BEGIN DECLARE SECTION;
varchar person_name[50];
varchar person_phone[25];
varchar person_id[10];
short value_indicator;
EXEC SQL END DECLARE SECTION;
......
strcpy(person_name,"李丽");
person_phone ='02788548562';
value_indicator=-1;
EXEC SQL INSERT INTO person.person (name, phone)
VALUES(:person_name,:person_phone INDICATOR :value_indicator);
//该语句等价于:EXEC SQL INSERT INTO person.person (name, phone) VALUES('李丽', NULL);
在前一种插入语句中,person_phone 带指示变量 value_indicator,由于 value_indicator 的值为-1,尽管 person_phone 的值为 02788548562,系统仍不使用该值作插入值,而认为 person_phone 的插入值为空。如果希望 person_phone 的插入值为 02788548562,只需改变 value_indicator 的值为 0 即可。
在后一个插入语句中,person_phone 未使用指示变量,它的插入值在插入语句中固定为 NULL,如果希望 person_phone 的插入值为 02788548562,则要修改该插入语句。因此,前一种使用指示变量的方法比后一种方法灵活,同时也给用户编程带来方便。
- 指示执行 SQL 语句后,返回结果是否为空值或有截取等情况。
在 INTO 子句中,使用指示变量表明该 SQL 语句执行时,对被指示的宿主变量的赋值情况。
一个指示变量的取值有三种:取值为 0,说明返回值非空且赋给宿主变量时不发生截舍;取值为-1,说明返回的值为空值;取值>0,说明返回的值为非空值,但在赋给宿主变量时,字符串的值被截舍,这时指示变量的值为截断之前的长度。
3.2.3.3 使用宿主变量应注意的问题
宿主变量的命名是有大小写区别的。字符序列相同,大小写不同的变量名代表不同的变量。
宿主变量的命名不应是 C 语句的关键字,以免产生误用或错误。使用宿主变量时应注意:SQL 语句引用一个宿主变量时,宿主变量名前加一个冒号;C 语句中使用宿主变量时,不加冒号;引用宿主变量时,可以使用一个相关的指示变量。
另外,由于 C 语言所允许的数据类型与 SQL 语言所采用的数据类型有一定的差别,因而在输入输出数据时,在两种数据类型之间要做一定的转换工作。DM 支持 BYTE、NUMERIC 与 short、int、float、double 之间的转换,但应注意数据转换时产生的溢出问题。
3.2.4VARCHAR 宿主变量的使用
可以使用 VARCHAR 类型声明可变长度的字符串,如果需要处理 VARCHAR 类型列的数据的输入输出,使用 VARCHAR 类型变量比使用 C 字符串类型会更加方便。“VARCHAR”可以是大写也可以是小写,但是不能大小写混用。
VARCHAR 类型经过预编译被转换为 C 的结构。
例
VARCHAR username[20];
经过预编译后转换成:
struct
{
unsigned short len;
unsigned char arr[20];
} username;
其中 len 是数据的实际长度,arr 存放的是字符串的数据。在 SQL 语句中使用 VARCHAR 宿主变量与使用 C 类型宿主变量一样。
例
EXEC SQL SELECT NAME INTO :username FROM PERSON.PERSON;
3.2.5 游标变量的使用
游标在 PRO*C 中作为一种对象变量使用,具体的使用方法见第 6.4 节介绍。
3.2.6CONTEXT 变量
运行上下文 CONTEXT 变量实际上是一个句柄,指向客户端内存的一段区域,对应一个 DM8 数据库连接,包含 0 个或多个游标的状态与信息等。
使用 CONTEXT 变量一般包括以下几个步骤:
- 使用 sql_context 定义上下文宿主变量
例
sql_context my_context ;
- 使用 ALLOCATE 语句初始化 context 变量
例
EXEC SQL CONTEXT ALLOCATE :my_context ;
- 使用 context 变量,context 使用后直到下一个 CONTEXT USE 才会切换到新的上下文。
例
EXEC SQL CONTEXT USE :my_context ;
- 使用 FREE 语句释放 context 变量
例
EXEC SQL CONTEXT FREE :my_context ;
CONTEXT 变量的具体使用可参看后续“多线程支持”章节。
3.2.7 结构宿主变量
可以在嵌入式程序中定义 C 结构宿主变量,然后在 SQL 语句中引用结构变量,需要注意结构中的成员必须与查询或插入 SQL 语句中表达式的顺序一致。
例 1 使用结构进行数据插入。
typedef struct
{
charsex[2];
char name[51];
char email[51];
char phone[26];
} person_record;
person_record new_person;
//为new_person赋值
......
EXEC SQL INSERT INTO PERSON.PERSON(SEX, NAME, EMAIL, PHONE)
VALUES (:new_person);
结构中的成员也可以是数组,这样用来批量执行,可同时操作多行。
例 2 一次向 PERSON 表插入三行数据。
typedef struct
{
char sex[3][2];
char name[3][51];
char email[3][51];
char phone[3][26];
} person_record;
person_record new_person;
//为new_person赋值
......
EXEC SQL INSERT INTO PERSON.PERSON(SEX, NAME, EMAIL, PHONE)
VALUES (:new_person);
例 3 当需要使用指示符变量时,由于宿主变量都包含在结构里,所以必须再定义一个结构包含各个宿主变量对应的指示符变量,并且成员顺序必须与宿主变量的结构一致。本例中宿主变量使用例 2 中声明的 new_person。
struct
{
short sex_ind;
short name_ind;
short email_ind;
short phone_ind;
} person_record_ind;
person_record_ind new_person_ind;
//为new_person_ind赋值
......
EXEC SQL INSERT INTO PERSON.PERSON(SEX, NAME, EMAIL, PHONE)
VALUES (:new_person :new_person_ind);
例 4 一个完整的使用结构的示例。
/*
* This program connects to DM, declares and opens a cursor,
* fetches the name, email, and phone of all
* people, displays the results, then closes the cursor.
*/
##include <stdio.h>
##define UNAME_LEN 20
##define PWD_LEN 40
EXEC SQL INCLUDE SQLCA;
typedef struct
{
char name[50];
char email[50];
char phone[50];
}person_info;
person_info person_rec_ptr;
EXEC SQL BEGIN DECLARE SECTION;
char username[50];
char password[50];
char servername[50];
EXEC SQL END DECLARE SECTION;
/* Declare function to handle unrecoverable errors. */
void sql_error();
main()
{
/* Connect to DM. */
strcpy(username, "SYSDBA");
strcpy(password, "Dmsys_123");
strcpy(servername, "192.168.0.89:5289");
EXEC SQL WHENEVER SQLERROR DO sql_error("DM error--");
EXEC SQL CONNECT :username IDENTIFIED BY :password USING :servername;
printf("\nConnected to dm as user: %s\n", username);
/* Declare the cursor. All static SQL explicit cursors
* contain SELECT commands. 'salespeople' is a SQL identifier,
* not a (C) host variable.
*/
EXEC SQL DECLARE salespeople CURSOR FOR
SELECT NAME, EMAIL, PHONE FROM PERSON.PERSON;
/* Open the cursor. */
EXEC SQL OPEN salespeople;
/* Get ready to print results. */
printf("\n\nThe company's salespeople are--\n\n");
printf("Salesperson EMAIL PHONE\n");
printf("----------- ------ ----------\n");
/* Loop, fetching all salesperson's statistics.
* Cause the program to break the loop when no more
* data can be retrieved on the cursor.
*/
EXEC SQL WHENEVER NOT FOUND DO break;
for (;;)
{
EXEC SQL FETCH salespeople INTO :person_rec_ptr;
printf("%s %s %s\n", person_rec_ptr.name,
person_rec_ptr.email, person_rec_ptr.phone);
}
/* Close the cursor. */
EXEC SQL CLOSE salespeople;
printf("\nArrivederci.\n\n");
EXEC SQL COMMIT WORK RELEASE;
exit(0);
}
void
sql_error(msg)
char *msg;
{
char err_msg[512];
size_t buf_len, msg_len;
EXEC SQL WHENEVER SQLERROR CONTINUE;
printf("\n%s\n", msg);
/* Call sqlglm() to get the complete text of the error message.*/
buf_len = sizeof (err_msg);
sqlglm(err_msg, &buf_len, &msg_len);
printf("%.*s\n", msg_len, err_msg);
EXEC SQL ROLLBACK RELEASE;
exit(1);
}
3.2.8 指针变量
也可以在嵌入式 SQL 中使用指针类型的变量。
例
typedef unsigned char *MY_RAW1;
EXEC SQL TYPE MY_RAW1 IS STRING(500) REFERENCE;
MY_RAW1 C21;
C21=malloc(504);
EXEC SQL SELECT '123456' INTO :C21 FROM DUAL;
3.3 可执行的 SQL 语句
在 C 程序中嵌入的可执行 SQL 语句包括普通的 SQL 语句和数据库登录/退出语句,其中数据库登录/退出不是 SQL 标准语句,为嵌入式程序扩展的可执行 SQL 语句。
3.3.1 数据库登录语句
语法如下:
EXEC SQL LOGIN :username PASSWORD :pwd [AT [dbname | :dbname]]
SERVER:servername;
或
EXEC SQL CONNECT :username IDENTIFIED BY :pwd [AT [dbname | :dbname]] [USING :servername];
或
EXEC SQL CONNECT :conninfo [AT [dbname | :dbname]];
功能:
用于数据库的登录。
使用说明:
- 语句中 username 是长度最多为 128 个字符的指针,其值为注册用户的名字;
- pwd 表示相应的用户口令;
- DM PROC 支持多连接,dbname 用于指定连接名,为长度最大为 128 个字符的标识符或宿主变量。在使用多连接时给每个连接命名(不命名时系统会自动给一个默认连接名),在执行其他嵌入 SQL 时通过指定连接名可以切换到不同连接进行操作;
- servername 用于指定服务名,名字的长度最多为 128 个字符。servername 指定的名字可以是 dm_svc.conf 中配置的服务名,也可以是直接书写的连接串(包含用户名密码以及 IP 和端口号),例如:SYSDBA/Dmsys_123@192.168.0.89:5236 或 192.168.0.89:5236,其中用户名为 SYSDBA,密码以“Dmsys_123”为例,实际运行中需要用户自行替换为数据库初始化时设定的密码。
注意在用户程序中,登录语句的逻辑位置必须先于所有可执行的 SQL语句,只有执行登录语句之后,才能执行其它可执行的SQL语句。如果登录失败,则终止程序的运行。
3.3.2 数据库退出语句
语法如下:
EXEC SQL [AT [dbname | :dbname]] LOGOUT;
或
EXEC SQL [AT [dbname | :dbname]] COMMIT RELEASE;
功能:
断开与数据库的连接。
使用说明:
在一个程序运行结束后,最好主动断开客户程序与 DM 数据库服务器之间的联系,有利于服务器释放相应的空间。LOGOUT 语句直接断开与服务器的连接,COMMIT RELEASE 语句则会先自动执行一个提交操作再断开与服务器的连接。
3.3.3 普通 SQL 语句
能在 C 程序中嵌入的可执行普通 SQL 语句包括建表、建索引等 DDL 语句;SELECT、INSERT、DELETE、UPDATE 等 DML 语句;RETURNING 语句和游标操作语句等。
- SELECT 语句
查询是最常用的 SQL 操作之一,可以将从数据库中检索到的值赋给相应的目标变量。
详细的 SELECT 语句语法介绍请参考《DM8_SQL 语言使用手册》。常用的语法如下:
EXEC SQL [AT [dbname | :dbname]] SELECT [ALL|DISTINCT]<选择清单>INTO<选择目标清单> FROM <表引用> [,<表引用>...] WHERE<搜索条件>[<HAVING子句>];
例 下面的语句对 PERSON.PERSON 表进行查询。
EXEC SQL SELECT NAME,PHONE INTO :name,:phone FROM PERSON.PERSON WHERE
PERSONID=:personid;
查询语句的 INTO、WHERE、CONNECT BY、START WITH、GROUP BY、HAVING、ORDER BY、FOR UPDATE 等语法在嵌入 SQL 中也同样支持。
- INSERT 语句
使用 INSERT 语句向数据库插入数据,例如下面的语句通过宿主变量向 PERSON.PERSON 表插入数据。
EXEC SQL INSERT INTO PERSON.PERSON(NAME, PHONE) VALUES(:name, :phone);
- DELETE 语句
使用 DELETE 语句删除数据库表中的数据,例如下面的语句通过宿主变量删除 PERSON.PERSON 表中的特定数据。
EXEC SQL DELETE FROM PERSON.PERSON WHERE PERSONID=:personid;
- UPDATE 语句
使用 UPDATE 语句更新数据库表中指定列的值,例如下面的语句通过宿主变量更新 PERSON.PERSON 表中指定行的 PHONE 与 EMAIL 列的值。
EXEC SQL UPDATE PERSON.PERSON SET PHONE=:phone,EMAIL=:email WHERE
PERSONID=:PERSONID;
- DML RETURNING 语句
INSERT、DELETE、UPDATE 语句后面可以跟 RETURNING 子句。
RETURNING 子句的语法如下:
<RETURNING | RETURN><expr {,expr}>
INTO <:hv [[INDICATOR]:iv] {, :hv [[INDICATOR]:iv]}>
其中 hv 表示宿主变量,iv 表示指示符变量。
注意RETURN后的表达式与INTO后的宿主变量数必须一致
- DDL 语句
可使用 DDL 语句进行数据定义,包括创建用户、创建表、修改表、创建索引等等。
例 下面的语句创建表 T。
EXEC SQL CREATE TABLE T(C1 INT, C2 INT);
3.3.4 游标语句
使用游标一般包括四个步骤:
- 声明游标:DECLARE CURSOR
- 打开游标:OPEN CURSOR
- 使用游标获取数据:FETCH
- 关闭游标:CLOSE CURSOR
使用游标必须先声明,声明游标实际上是定义了一个游标工作区,并给该工作区分配了一个指定名字的指针(即游标)。游标工作区用以存放满足查询条件行的集合,因此它是一说明性语句,是不可执行的。在打开游标时,就可从指定的基表中取出所有满足查询条件的行送入游标工作区并根据需要分组排序,同时将游标置于第一行的前面以备读出该工作区中的数据。当对行集合操作结束后,应关闭游标,释放与游标有关的资源。
3.3.4.1 声明游标语句
声明一个游标,给它一个名称,同时也可定义与之相联系的 SELECT 语句。
语法如下:
[AT [dbname | :dbname]] DECLARE 游标名 CURSOR [WITH HOLD] [FAST | NOFAST] FOR {<SELECT语句> | <SQL 语句名>};
参数说明:
- dbname:指明游标声明使用的连接名,游标其他操作只是语法上支持 AT,所以游标操作完成前不支持再进行连接切换;
- 游标名:指明声明的游标的名称;
- <SELECT 语句>:被称为查询说明,指明对应于被定义的游标的 SELECT 语句,不能包括 INTO 子句;
- <SQL 语句名>:PREPARE 语句的语句名。
使用说明:
- 用户必须在其他的嵌入式 SQL 语句引用该游标之前定义它。游标说明的范围在整个预编译单元内,并且每一个游标的名字必须在此范围内唯一;
- Oracle 兼容模式下默认提交时不关闭游标,非 Oracle 兼容模式下默认提交时关闭游标。WITH HOLD 游标与兼容模式无关,提交时都不关闭;
- FAST 属性指定游标是否为快速游标。缺省时默认为 NO FAST 对应的普通游标。若定义游标时设置 FAST 属性将游标定义为快速游标,该游标在执行过程中会提前返回结果集,速度上提升明显,但是存在以下的使用约束:
- 使用快速游标的 DMSQL 程序块中不能修改快速游标所涉及的表;
- 禁止将快速游标赋值给其它游标对象;
- 快速游标上不能创建引用游标;
- 不支持快速游标更新和删除;
- 快速游标不支持 next 以外的 fetch 方向。
例 声明一个游标 cheap_book。
EXEC SQL DECLARE cheap_book CURSOR FOR
SELECT NAME, AUTHOR, PUBLISHER, NOWPRICE
FROM PRODUCTION.PRODUCT
WHERE NOWPRICE < 15.0000;
3.3.4.2 打开游标语句
打开一个已经声明过的游标。
语法如下:
格式一:OPEN <游标名>
格式二:OPEN <游标变量> FOR <查询表达式> | <表达式子句>;
<表达式子句>::=<表达式> [USING <绑定参数> {, <绑定参数>}]
参数说明:
- 游标名:指明被打开的游标的名称
例 打开游标 cheap_book。
EXEC SQL OPEN cheap_book;
3.3.4.3 拨动游标语句
使游标移动到指定的一行,若游标名后跟随有 INTO 子句,则将游标当前指示行的内容取出分别送入 INTO 后的各变量中。
语法如下:
FETCH [[NEXT|PRIOR|FIRST|LAST|ABSOLUTE n|RELATIVE n] [FROM] ] <游标名>
[INTO <赋值对象>{,<赋值对象>>}];
参数说明:
- NEXT:游标下移一行;
- PRIOR:游标前移一行;
- FIRST:游标移动到第一行;
- LAST:游标移动到最后一行;
- ABSOLUTE n:游标移动到第 n 行;
- RELATIVE n:游标移动到当前指示行后的第 n 行。
使用说明:
- 当游标被打开后,若不指定游标的移动位置,第一次执行 FETCH 语句时,游标下移,指向工作区中的第一行,以后每执行一次 FETCH 语句,游标均顺序下移一行,使这一行成为当前行;
- INTO 后的变量个数、类型必须与定义游标语句中、SELECT 后各值表达式的个数、类型一一对应。
例 1 对于上一节中打开的游标,若连续执行以下语句两次:
EXEC SQL FETCH cheap_book INTO :a, :b, :c, :d;
由于是两次连续执行 FETCH 语句且结果均放在变量:a、:b、:c、:d 中,这样,第一次的结果已被第二次的结果冲掉,最后的结果如下:
a='老人与海';b='海明威';c='上海出版社';d=6.1000
该例说明:当需要连续取出工作区的多行数据时,应将 FETCH 语句置入高级语言的循环结构中。
例 2 当设置 ABSOLUTE 属性时,n 值是从 1 开始的。如执行如下语句:
EXEC SQL FETCH ABSOLUTE 3 cheap_book INTO :a, :b, :c, :d;
结果如下:
a='突破英文基础词汇'; b='刘毅'; c='外语教学与研究出版社';d=11.1000
3.3.4.4 关闭游标语句
关闭指定的游标,收回它所占的资源。
语法如下:
CLOSE <游标名>;
参数说明:
- 游标名:指明被关闭的游标的名称
例 关闭之前打开的 cheap_book 游标。
EXEC SQL CLOSE cheap_book;
3.3.4.5 游标定位删除更新语句
- 可更新游标
在嵌入式 SQL 中,通过游标对基表进行修改和删除时要求该游标表必须是可更新的。可更新游标的条件是:游标定义中给出的查询说明必须是可更新的。DM 对查询说明是可更新的有这样的规定:
- 查询说明的 FROM 后只带一个表名,且该表必须是基表或者是可更新视图;
- 查询说明是单个基表或单个可更新视图的行列子集,SELECT 后的每个值表达式只能是单纯的列名,如果基表上有聚集索引键,则必须包含所有聚集索引键;
- 查询说明不能带 GROUP BY 子句、HAVING 子句、ORDER BY 子句;
- 查询说明不能嵌套子查询。
不满足以上条件的游标表是不可更新的。3.3.4.1 节例子中声明的游标 cheap_book 就是一个可更新游标。
- 游标定位删除语句
语法如下:
[AT [dbname | :dbname]] DELETE FROM <表引用> [WHERE CURRENT OF <游标名>];
<表引用>::= [<模式名>.] <基表或视图名> | <外部连接表>
<基表或视图名>::=<基表名> | <视图名>
例 使用之前声明的游标 cheap_book,下面的语句将游标拨动到指定的位置,并进行游标定位删除。
EXEC SQL OPEN cheap_book;
EXEC SQL FETCH ABSOLUTE 2 cheap_book;
EXEC SQL DELETE FROM PRODUCTION.PRODUCT WHERE CURRENT OF cheap_book;
- 游标定位更新语句
语法如下:
[AT [dbname | :dbname]] UPDATE <表引用>
SET <列名>=<值表达式>{,<列名>=<值表达式>}
[WHERE CURRENT OF <游标名>];
<表引用>::= [<模式名>.] <基表或视图名> | <外部连接表>
<基表或视图名>::=<基表名> | <视图名>
例 使用之前声明的游标 cheap_book,下面的语句将游标拨动到指定的位置,并进行游标定位更新。
EXEC SQL OPEN cheap_book;
EXEC SQL FETCH ABSOLUTE 3 cheap_book;
EXEC SQL UPDATE PRODUCTION.PRODUCT SET NOWPIRCE=13.0000 WHERE CURRENT OF cheap_book;
3.3.5 嵌入式程序中的异常处理
3.3.5.1 嵌入的异常声明语句
在 C 程序中,任何可出现说明语句的位置,皆可嵌入异常声明处理语句,其作用是指明在该异常声明处理语句的作用域内每个 SQL 操纵语句在发生操作异常时的处理方法。下面介绍异常声明语句的格式、作用域、异常动作的执行。
- 语法格式
<嵌入的异常声明>::= EXEC SQL WHENEVER <条件><异常动作>;
<条件>::= SQLERROR │ NOT FOUND
<异常动作>::= STOP│ CONTINUE│GOTO <标号> │GO TO <标号> │DO<用户代码>
其中:<标号>为标准的 C 语言的标号。
- WHENEVER 语句的作用域
一个 WHENEVER 语句的作用域是从该语句出现的位置开始,到下一个指明相同条件的 WHENEVER 语句出现(若没有下一个相同条件的 WHENEVER 语句,则到文件结束)之前的所有 SQL 语句。这种起始、终止位置是指源程序的物理位置,与该程序逻辑执行顺序无关。
- 异常动作的说明
如果<异常动作>为 STOP,则停止操作;
如果<异常动作>为 CONTINUE,即使产生异常,也不需要作异常处理。使用 CONTINUE 的主要作用是取消前面与之具有相同条件的异常声明处理语句的作用;
如果动作为 GOTO<标号>,则程序转移到标号处执行;
如果动作为 DO<用户代码>,则程序继续执行用户的 C 代码。
- 动作的触发条件
当一个 SQL 语句执行后返回的 SQLCODE < 0 时,SQLERROR 为真;当一个 SQL 语句执行后返回的 SQLCODE = 100,NOT FOUND 为真。
当嵌入的异常声明的条件得到满足时,则触发异常动作的执行。
- 预编译的处理及举例
在对含有异常声明处理语句的程序进行预编译时,所有的异常声明处理语句都被删除。根据其作用域,在相应的 SQL 语句的调用函数之后,生成根据 SQLCODE 作相应处理的语句。
例 按人员编号查询人员姓名、联系电话。设该文件名为 test.pc。
EXEC SQL BEGIN DECLARE SECTION;
long SQLCODE;
char SQLSTATE[6];
varchar person_name[50];
varchar person_phone[25];
varchar person_id[10];
EXEC SQL END DECLARE SECTION;
//①
若打开游标出错,则转至标号 err_proc 处执行。同样在取值过程中出错,也要转至 err_proc 处执行。若游标 C1 移至游标表的最后一行之后,则返回的 SQLCODE 为 100,程序将跳转到标号 aaa 处的语句处执行。
在对该程序段预编译后,生成对应 C 文件 test.c。程序 test.c 内容为:
#include "dpc_dll.h"
/* Thread Safety */
typedef void * sql_context;
typedef void * SQL_CONTEXT;
int SQLCODE;
char SQLSTATE[6];
char person_name[50];
char person_phone[25];
char person_id[10];
//①定义游标
/*EXEC SQL DECLARE C1 CURSOR FOR
SELECT name, phone
FROM person.person WHERE personid=:person_id;*/
{
void* handle = NULL;
char curname[128];
char* sqlstmt = NULL;
sqlstmt = "SELECT name, phone FROM person.person WHERE personid=\n\?;";
dpc_alloc_bind_items(1, &handle);
dpc_bind_item(handle, 1, "person_id", (void*)person_id, DPC_C_VARCHAR, 10, 10,10, NULL, 0, -1);
strcpy(curname, "C1");
dpc_declare_cursor_ex(curname, sqlstmt, handle);
dpc_free_bind_items(handle);
dpc_get_rt_info(&SQLCODE, SQLSTATE);
}
strcpy(person_id,"1");
// ②打开游标
/*EXEC SQL OPEN C1;*/
{
dpc_open_cursor_ex("C1",NULL, 1);
dpc_get_rt_info(&SQLCODE, SQLSTATE);
if (dpc_get_rt_info(&SQLCODE, SQLSTATE) < 0)
goto err_proc;
}
for(;;)
{//③拨动游标
/*EXEC SQL FETCH C1 INTO :person_name,:person_phone;*/
{
void* handle_using = NULL;
void* handle_into = NULL;
dpc_alloc_bind_items(2, &handle_into);
dpc_bind_item(handle_into, 1, "person_name", (void*)person_name, DPC_C_VARCHAR,50, 50, 50, NULL, 0, -1);
dpc_bind_item(handle_into, 2, "person_phone", (void*)person_phone,
DPC_C_VARCHAR, 25, 25, 25, NULL, 0, -1);
dpc_fetch("C1", handle_into, handle_using, "", "", 1, DPC_FETCH_NEXT, DPC_C_INT,NULL, 4, 1);
dpc_free_bind_items(handle_using);
dpc_free_bind_items(handle_into);
dpc_get_rt_info(&SQLCODE, SQLSTATE);
if (dpc_get_rt_info(&SQLCODE, SQLSTATE) < 0)
goto err_proc;
if (dpc_get_rt_info(&SQLCODE, SQLSTATE) == dpc_get_data_not_found())
goto aaa;
}
printf("%s,%s ", person_name, person_phone);
}
......
aaa:
//④关闭游标
/*EXEC SQL CLOSE C1;*/
{
dpc_close_cursor_by_name("C1");
dpc_get_rt_info(&SQLCODE, SQLSTATE);
if (dpc_get_rt_info(&SQLCODE, SQLSTATE) < 0)
goto err_proc;
}
exit(0);
err_proc:
printf("error = %d",SQLCODE);
exit(1);
如果预编译出错,则给出错误信息说明。
3.3.5.2 通过 SQLCODE 和 SQLSTATE 获取 SQL 语句执行的信息
在 C 文件中可以定义两个特殊的变量 SQLSTATE 和 SQLCODE:
long SQLCODE;
char SQLSTATE[6];
这两个变量记录了当前 SQL 语句执行情况的信息。也就是说执行了一个 SQL 语句之后,可以通过返回的 SQLCODE 和 SQLSTATE 来判断语句执行是否发生异常;如果发生了异常,那么具体是哪一种异常。这些信息对于了解程序运行情况和程序流程的控制是很有用的。
在 DM 嵌入式程序中 SQLCODE 是一个 long 型变量,可能的值有:
- SQLCODE < 0,SQL 语句执行发生异常;
- SQLCODE = 0,SQL 语句执行成功;
- SQLCODE > 0,SQL 语句执行中产生警告。如 100 表示找不到数据(如删除一个空表中的记录等)。
DM 嵌入式程序中,SQLSTATE 是一个 char[6]型的变量。其前两个字符表示类别代码,随后的三个字符表示子类的代码。例如当 SQLSTATE = "22012"时,22 是类别代码,表示 SQL 语句发生了数据异常;012 是该类别下属的子类代码,表示发生的是被零除的数据异常。同样是数据异常,当 SQLSTATE = "22008"时表示的则是发生了日期时间数据溢出。DM 遵循 SQL92 标准,所以 SQLSTATE 中代码所表示的具体含义可以参考 SQL92 标准中的 SQLSTATE 部分。
3.3.6 数据类型支持
DM 嵌入式编程支持的数据类型见下表。
数据类型 | DM_TYPE |
---|---|
DSQL_CHAR | 1 |
DSQL_VARCHAR | 2 |
DSQL_BIT | 3 |
DSQL_TINYINT | 5 |
DSQL_SMALLINT | 6 |
DSQL_INT | 7 |
DSQL_BIGINT | 8 |
DSQL_DEC | 9 |
DSQL_FLOAT | 10 |
DSQL_DOUBLE | 11 |
DSQL_BLOB | 12 |
DSQL_DATE | 14 |
DSQL_TIME | 15 |
DSQL_TIMESTAMP | 16 |
DSQL_BINARY | 17 |
DSQL_VARBINARY | 18 |
DSQL_CLOB | 19 |
DSQL_TIME_TZ | 22 |
DSQL_TIMESTAMP_TZ | 23 |
DSQL_INTERVAL_YEAR | 100 |
DSQL_INTERVAL_MONTH | 101 |
DSQL_INTERVAL_DAY | 102 |
DSQL_INTERVAL_HOUR | 103 |
DSQL_INTERVAL_MINUTE | 104 |
DSQL_INTERVAL_SECOND | 105 |
DSQL_INTERVAL_YEAR_TO_MONTH | 106 |
DSQL_INTERVAL_DAY_TO_HOUR | 107 |
DSQL_INTERVAL_DAY_TO_MINUTE | 108 |
DSQL_INTERVAL_DAY_TO_SECOND | 109 |
DSQL_INTERVAL_HOUR_TO_MINUTE | 110 |
DSQL_INTERVAL_HOUR_TO_SECOND | 111 |
DSQL_INTERVAL_MINUTE_TO_SECOND | 112 |
关于数据类型的长度、精度与刻度,需要注意以下几点:
- DSQL_CHAR、DSQL_VARCHAR 的长度为其定义的长度。char、varchar2 类型会被创建为 DSQL_CHAR 类型;
- DSQL_BIT、DSQL_TINYINT、DSQL_SMALLINT、DSQL_INT、DSQL_BIGINT 的精度分别为 3、3、5、10、19,刻度为 0;
- DSQL_DEC 的精度与刻度根据定义来确定。dec 的精度与刻度都为 0;当 dm_svc.conf 中的 DEC2DOUB 参数为 TRUE 时,转换为 double 类型,精度为 53;
- DSQL_CLOB、DSQL_BLOB 类型的长度并不限制,缺省为返回-1;
- 创建 DSQL_FLOAT 需要使用 real 关键字,精度为 24;float 与 double 类型都会被创建为 DSQL_DOUBLE 类型,精度为 53。
3.4 编写嵌入式程序的注意事项
PRO*C 程序和 C 程序相类似,其编写也遵守标准 C 程序的规定。除此之外,还应注意下列问题:
- 编程者应该在程序中定义一个类型为长整型的全局变量 SQLCODE。该变量可以在嵌入的变量声明节中定义,也可以在声明节之外定义。定义该变量的物理位置应先于所有可执行的 SQL 语句。SQLCODE 将保存一条 SQL 语句执行后所产生的返回码。
- 应广泛使用嵌入的异常声明处理语句,以便及时根据返回结果进行相应的处理。异常声明语句可出现在程序中的任何语句可出现的位置上。
- 宿主变量分全局变量与局部变量,在程序开始处定义的为全局变量,在子函数内定义的为局部变量,其作用域范围与相应位置定义的主语言变量相同,检查作用域的工作由主语言编译程序承担,预编译系统未作此项检查。
- 最好将宿主变量定义为全局变量。
- 应广泛使用指示变量。当查询的结果为空值时,若相应的目标变量之后无指示变量,SQLCODE 将被置为-5000。
- 游标声明是一种特殊的说明性语句。我们规定该语句只能出现在可执行的 C 语句出现的位置上。在一个程序中,不能够定义同名游标,游标也不能跨模块使用。游标声明语句的物理位置应先于使用该游标的打开、拨动与关闭语句。在一个程序中,可以多次打开、关闭同一个游标。
- 在一个程序中,第一条可执行的 SQL 语句必须是“LOGIN”语句。
- 在程序运行结束前,最好有一条语句“LOGOUT”,注销对数据库的使用。
- 最好能够根据 SQL 语句的执行结果进行错误处理。
- 在对一个 PRO*C 程序进行预编译时(例如原文件为 test.pc),在生成的 C 文件中会自动生成如下语句:
##include "dpc_dll.h"
##include "sqlca.h"
static sqlca_t sqlca;
- 一个 PRO*C 程序可以含有多个事务。在 DM 中,一个事务是被当作单个实体处理的 SQL 语句序列。在进行嵌入式程序设计时,应该注意正确及时的使用数据库的提交语句或回滚语句,一个模块中的事务最好安排在本模块内提交或回滚。在一个事务中,要么每个 SQL 语句的操作结果都被提交使之变为长久生效,要么同时被回滚使其改变被取消。
- 如果多个用户进程因为争夺资源而发生死锁,DM 服务器将取消某一个用户进程的工作,该进程的当前事务被终止。