DM 嵌入式 SQL 高级功能

6.1SSL连接

DM支持SSL加密通信方式,可以通过配置dm_svc.conf文件实现PRO*C程序的SSL通信。dm_svc.conf是DM数据库安装时生成的一个配置文件,在Windows操作平台下此文件位于%SystemRoot%\system32目录,在Linux平台下此文件位于/etc目录。

配置方法如下:

  1. 打开SSL

语法如下:

ENABLE_SSL=(参数)

参数说明:

  • 1:启动SSL配置;0:关闭SSL配置
  1. 配置具体用户的SSL

PRO*C程序连接数据库时,自动将用户名与配置文件中配置了SSL连接的用户名进行匹配,如果能匹配成功,则对其使用SSL连接。

配置具体用户SSL的语法如下:

sSSL_CONFIG=((USER=(用户名1)SSL_PATH=(SSL路径1)SSL_PWD=(SSL key1))(USER=(用户名2)SSL_PATH=(SSL路径2)SSL_PWD=(SSL key2)))

参数说明:

  • USER:需要使用SSL登录的用户名,最大长度128字节
  • SSL_PATH:指定用户的SSL路径,最大长度256字节
  • SSL_PWD:指定用户SSL的key,最大长度128字节
  • 完整的一个用户的SSL配置外面需要用括号扩起来,以分隔多个配置。

例如,配置两个SSL用户,SYSDBA和SYSSSO,配置文件中相关配置项如下设置。

ENABLE_SSL=(1)

SSL_CONFIG=((USER=(SYSDBA)SSL_PATH=(C:\dmdbms\bin\client_ssl\SYSDBA)SSL_PWD=(SYSDBA))(USER=(SYSSSO)SSL_PATH=(C:\dmdbms\bin\client_ssl\SYSSSO)SSL_PWD=(SYSSSO)))
注意

dm_svc.conf配置文件还可以进行其他配置项的配置,这里只介绍了SSL相关配置,其他配置项可参考《DM8系统管理员手册》

6.2PL/SQL块

PRO*C把PL/SQL语句块视作单一的嵌入SQL语句,任何SQL语句能嵌入的地方也能嵌入PL/SQL块。在宿主程序中嵌入PL/SQL块,必须使用关键字EXEC SQL EXECUTE和END-EXEC将PL/SQL语句块括起来。

语法如下:

EXEC SQL [AT [dbname | :dbname]] EXECUTE

BEGIN

plsql_block;

END;

END-EXEC;

例如:

EXEC SQL EXECUTE

	BEGIN

SELECT NAME, EMAIL, PHONE

            INTO :person_name:ind_name, :person_email, :person_phone

            FROM PERSON.PERSON

            WHERE PERSONID = :person_number;

	END;

END-EXEC;

6.3使用大字段句柄处理LOB类型

DM PRO*C中的大字段句柄,对应DM数据库服务器的大字段类型(BLOB、CLOB、LONGVARBINARY、LONGVARCHAR、IMAGE、TEXT)。大字段句柄分为OCIBlobLocator和OCIClobLocator。

在使用大字段句柄前,需要先定义、申请、绑定大字段句柄;使用结束后,需要关闭和释放大字段句柄。

  1. 定义大字段句柄

像定义变量一样定义大字段句柄,例如下面的语句定义了一个大字段句柄blob。

OCIBlobLocator *blob;
  1. 申请大字段句柄

使用ALLOCATE申请大字段句柄,例如下面的语句申请大字段句柄blob。

EXEC SQL [AT [dbname | :dbname]] ALLOCATE :blob;
  1. 绑定大字段句柄

绑定大字段句柄就是通过SQL语句将数据库的大字段列绑定到大字段句柄变量,使本地的大字段变量与数据库的大字段列进行关联。可以通过SELECT INTO语句和RETURNING语句进行绑定。

例如:

EXEC SQL INSERT INTO PRODUCTION.PRODUCT(NAME,AUTHOR,PUBLISHER,PUBLISHTIME,\

PRODUCTNO,PRODUCT_SUBCATEGORYID,SATETYSTOCKLEVEL,ORIGINALPRICE,NOWPRICE,\

DISCOUNT,DESCRIPTION,PHOTO,SELLSTARTTIME) VALUES('噼里啪啦丛书续集',\

'(日)佐佐木洋子','21世纪出版社','1901-01-01','9787539125992',35,'10','58',\

'42','6.1','11','书',empty_blob(),'2006-03-20')RETURNING PHOTO INTO :blob;

EXEC SQL SELECT PHOTO INTO :blob FROM PRODUCTION.PRODUCT where
NAME='噼里啪啦丛书续集';
  1. 使用大字段句柄进行读写

使用大字段句柄读数据到本地缓存区的语法如下:

EXEC SQL [AT [dbname | :dbname]] LOB READ :amt FROM :src [AT :src_offset] INTO :buffer

[WITH LENGTH :buflen] ;

参数说明:

  • dbname:指明lob读操作使用的连接名;
  • amt:输入/输出参数,输入表示将要读取的字节数,输出表示已经读取的字节数
  • src:已绑定的大字段句柄
  • src_offset:大字段读取的偏移量
  • buffer:读取到的本地缓存区
  • buflen:缓存区的长度

使用大字段句柄写数据到大字段中的语法如下:

EXEC SQL [AT [dbname | :dbname]] LOB WRITE [APPEND] [ONE ]:amt FROM :buffer
	[WITH LENGTH :buflen] INTO :dst [AT :dst_offset] ;

参数说明:

  • dbname:指明lob读操作使用的连接名;
  • amt:输入/输出参数,输入表示将要写入的字节数,输出表示已经写入的字节数
  • dst:已绑定的大字段句柄
  • dst_offset:大字段写入的偏移量
  • buffer:本地缓存区地址
  • buflen:缓存区的长度
  • APPEND:表示从大字段的末尾开始写入
  1. 关闭大字段句柄

使用CLOSE关闭大字段句柄,例如下面的语句关闭大字段句柄blob。

EXEC SQL [AT [dbname | :dbname]] LOB CLOSE :blob;
  1. 释放大字段句柄

使用FREE释放大字段句柄,例如下面的语句释放大字段句柄blob。

EXEC SQL [AT [dbname | :dbname]] FREE :blob;
  1. 获取大字段的长度

可以使用如下所示语句将大字段句柄对应的大字段长度写入到变量中。

EXEC SQL [AT [dbname | :dbname]] LOB DESCRIBE :blob GET LENGTH INTO :itotal;

下面给出两个使用大字段句柄处理LOB类型的实例。

例1:读取大字段数据。

##include <stdio.h>

##include "DCI.h"

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;

char username[50];

char password[50];

char servername[50];

int c1=1;

unsigned int iBufLen = 4;

unsigned int offset = 1;//1_based

unsigned int itotal;

unsigned char buf_blob[5];

unsigned char tmp[1024];

OCIBlobLocator *blob;

EXEC SQL END DECLARE SECTION;

/* Declare function to handle unrecoverable errors. */

void sql_error();

main()

{

/* Connect to DM. */

    strcpy(username, "SYSDBA");

    strcpy(password, "SYSDBA");

    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);

    printf("\n\n BEGIN BLOB select into \n");

    EXEC SQL update PRODUCTION.PRODUCT set PHOTO='abcdabcdabcd';

    EXEC SQL commit;

    EXEC SQL ALLOCATE :blob;

    EXEC SQL SELECT PHOTO into :blob FROM PRODUCTION.PRODUCT where NAME='红楼梦';

    EXEC SQL LOB DESCRIBE :blob GET LENGTH INTO :itotal;

    printf("itotal %d\n",itotal);

    while (1)

    {

        printf("offset %d\n",offset);

        EXEC SQL LOB READ :iBufLen FROM :blob AT :offset INTO :buf_blob;

        printf("%s\n",buf_blob);

        memcpy(tmp + offset - 1, buf_blob, iBufLen);

        offset = offset + iBufLen;

        if (offset >= itotal)

        	break;

    }

    EXEC SQL FREE :blob;

    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);

}

例2:写入大字段

EXEC SQL BEGIN DECLARE SECTION;

    OCIBlobLocator *blob;

    unsigned int offset = 1;

    unsigned int itotal = 2;

    unsigned char buf_blob[10]="AF";

EXEC SQL END DECLARE SECTION;

	EXEC SQL ALLOCATE :blob;

	EXECSQL INSERT INTO PRODUCTION.PRODUCT(NAME,AUTHOR,PUBLISHER,PUBLISHTIME,\

PRODUCTNO,PRODUCT_SUBCATEGORYID,SATETYSTOCKLEVEL,ORIGINALPRICE,NOWPRICE,\

DISCOUNT,DESCRIPTION,PHOTO,SELLSTARTTIME) VALUES('噼里啪啦丛书续集',\

'(日)佐佐木洋子','21世纪出版社','1901-01-01','9787539125992',35,'10','58',\

'42','6.1','11','书',empty_blob(),'2006-03-20')RETURNING PHOTO INTO :blob;

    EXEC SQL LOB WRITE :itotal FROM :buf_blob INTO :blob AT :offset;

    EXEC SQL FREE :blob;

    EXEC SQL COMMIT RELEASE;

6.4游标变量

除了3.3.4节中介绍的游标语句,DM嵌入式SQL还支持游标变量,将游标作为一种对象变量使用。使用时基本的步骤为:定义、申请、打开、获取数据、关闭、释放游标。

  1. 定义游标变量,变量类型为sql_cursor。

例如:

sql_cursor person_cv;

sql_cursor person_cursor;

sql_cursor *person;/*游标变量指针*/
  1. 申请游标变量。

例如:

EXEC SQL ALLOCATE :person_cv;
  1. 打开游标变量
  • 可以使用PL/SQL语句块打开,如:
EXEC SQL EXECUTE

    BEGIN

    	OPEN :person_cv FOR SELECT * FROM person.person;

    END;

END-EXEC;
  • 也使用包打开游标,如:

在服务器端定义包体:

CREATE PACKAGE demo_cur_pkg AS

    TYPE personName IS RECORD (name VARCHAR2(51));

    TYPE cur_type IS REF CURSOR RETURN personName;

	PROCEDURE open_person_cur (

				curs IN OUT cur_type,

personid IN NUMBER);

END;

CREATE PACKAGE BODY demo_cur_pkg AS

	CREATE PROCEDURE open_person_cur (

				curs IN OUT cur_type,

personid IN NUMBER) IS

	BEGIN

    OPEN curs FOR

        SELECT name FROM person.person

            WHERE personid = personid

            ORDER BY name ASC;

    END;

END;

在客户端使用此包:

EXEC SQL EXECUTE

    begin

    	demo_cur_pkg.open_person_cur(:person_cursor, :personid);

    end;

END-EXEC;
  1. 获取数据。

例如:

EXEC SQL FETCH :person_cv INTO :output_person_rec;
  1. 释放游标变量。

例如:

EXEC SQL FREE :person_cv;

使用游标进行查询时,需要注意:

  • 当返回值的数据类型与宿主变量的数据类型不一致时,DM将返回值转换成宿主变量的类型。这种转换只局限于数值转换。不论数据类型如何,如果返回给宿主变量的值是NULL,那么相应指示符变量被置为-1。如果没有与之相应的指示符变量,那么SQLCODE被设置为-5000。数值型数据转换,包括short、int、double、float四种数值型数据的转换,若发生溢出错误,将给出警告信息;
  • 如果当前游标已经指向查询的最后一条记录,使用FETCH语句将会导致返回错误代码(SQLCODE=100)。

例:下面是一个使用游标变量的例子

##include <stdio.h>

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;

char username[50];

char password[50];

char servername[50];

EXEC SQL END DECLARE SECTION;

typedef struct _EMP_CV

{

    int personid;

    char sex[2] ;

    char name[51];

    char email[51];

    char phone[26];} V_EMP_CV;

EXEC SQL BEGIN DECLARE SECTION;

    SQL_CURSOR emp_cv;

    V_EMP_CV output_emp_rec;

EXEC SQL END DECLARE SECTION;

/* Declare function to handle unrecoverable errors. */

void sql_error();

main()

{

/* Connect to DM. */

    strcpy(username, "SYSDBA");

    strcpy(password, "SYSDBA");

    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);

EXEC SQL ALLOCATE :emp_cv;

EXEC SQL EXECUTE

    BEGIN

    	OPEN :emp_cv FOR SELECT * FROM person.person;

    END;

END-EXEC;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

for (;;)

{

    EXEC SQL FETCH :emp_cv INTO :output_emp_rec;

    printf("personid=%d; sex=%s; name=%s; email=%s;
phone=%s;\n",output_emp_rec.personid,output_emp_rec.sex,\

    output_emp_rec.name,output_emp_rec.email,output_emp_rec.phone);

}

EXEC SQL CLOSE :emp_cv;

EXEC SQL FREE :emp_cv;

    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);

}

6.5批量执行

DM的PRO*C支持批量操作,可以批量查询、打开、获取和执行,批量操作描述信息的相关信息需要增加语法FOR:size,其中size用于指定批量的行数,可以是常量,也可以是变量。

6.5.1SELECT批量操作

  1. 使用数组宿主变量

当知道查询会返回的行数时,可以使用简单的SELECT INTO语句,你可以使用大于等于行数的数组宿主变量。

例:下例中从PERSON.PERSON表中查询3条记录放入数组宿主变量,而数组宿主变量可以存放4个元素。

int cc1[4];

VARCHAR cc2[4][200];

VARCHAR cc3[4][200];

EXEC SQL SELECT top 3 personid,name,phone into :cc1,:cc2,:cc3 FROM
person.person;
  1. 使用数组宿主变量+游标FETCH

如果不知道查询结果的数据行数时,可以使用数组宿主变量+游标FETCH,每次FETCH的行数等于宿主数组的大小。

例如,下例中每次fetch 10行。最后一次fetch是余下的数据可能小于10行。

int personid[10];

char name[10][51];

EXEC SQL DECLARE person_cursor CURSOR FOR

	SELECT personid, name FROM person.person;

EXEC SQL OPEN person_cursor;

EXEC SQL WHENEVER NOT FOUND do break;

for (;;)

{

    EXEC SQL FETCH person_cursor

    INTO :personid, :name;

	/* process batch of rows */

	...

}

...
  1. 使用sqlca.sqlerrd[2]

对于INSERT、UPDATE、DELETE和SELECT INTO语句,sqlca.sqlerrd[2]记录了操作处理的数据行数。对于游标FETCH操作sqlca.sqlerrd[2]记录的是当前总共FETCH的行数。你可以利用sqlca.sqlerrd[2]的值进行批量操作的循环控制。

例如,下面代码片段中sqlca.sqlerrd[2]用来记录已经FETCH的行数。

int personid[50];

char name[50][51];

int rows_to_fetch, rows_before, rows_this_time;

EXEC SQL DECLARE person_cursor CURSOR FOR

	SELECT personid, name

    FROM person.person

    WHERE personid = 2;

EXEC SQL OPEN person_cursor;

EXEC SQL WHENEVER NOT FOUND CONTINUE;

/* initialize loop variables */

rows_to_fetch = 10; /* number of rows in each "batch" */

rows_before = 0; /* previous value of sqlerrd[2] */

rows_this_time = 10;

while (rows_this_time == rows_to_fetch)

{

    EXEC SQL FOR :rows_to_fetch

    FETCH person_cursor

    	INTO :personid, :name;

    rows_this_time = sqlca.sqlerrd[2] - rows_before;

    rows_before = sqlca.sqlerrd[2];

}

...
  1. 批量获取描述信息

通过描述符可以获取动态SQL语句的输出列类型与长度,关于描述符的具体说明见6.6节。DM支持批量获取描述信息。

例如:

char sqlstr[500];

int array_size=5;

int col_cnt;

int i;

char c_name[50];

int c1_data[5];

char c2_data[5][21];

char c3_data[5][50];

char c4_data[5][50];

char c5_data[5][50];

int c1_type,c2_type,c3_type,c4_type,c5_type;

int c1_length,c2_length,c3_length,c4_length,c5_length;

EXEC SQL drop table testdes;

EXEC SQL create table testdes(c1 int,c2 varchar(20),c3 varbinary(20),c4 clob,c5
blob);

EXEC SQL insert into testdes values(1, 'dm', 0x12ab, 'dameng', 0x34ef);

EXEC SQL insert into testdes values(2, 'dm', 0x12ab, 'dameng', 0x34ef);

EXEC SQL insert into testdes values(3, 'dm', 0x12ab, 'dameng', 0x34ef);

EXEC SQL insert into testdes values(4, 'dm', 0x12ab, 'dameng', 0x34ef);

EXEC SQL insert into testdes values(5, 'dm', 0x12ab, 'dameng', 0x34ef);

strcpy(sqlstr, "select c1,c2,c3,c4,c5 from testdes");

EXEC SQL PREPARE stmt FROM :sqlstr;

EXEC SQL DECLARE cur_stmt CURSOR FOR stmt;

EXEC SQL OPEN cur_stmt;

/**************

* 绑定输出变量

***************/

EXEC SQL FOR :array_size ALLOCATE DESCRIPTOR 'out';

EXEC SQL DESCRIBE OUTPUT stmt USING DESCRIPTOR 'out';

c1_type=4;

c1_length=4;

EXEC SQL SET DESCRIPTOR 'out' VALUE 1 TYPE=:c1_type, LENGTH=:c1_length;

c2_type=1;

c2_length=10;

EXEC SQL SET DESCRIPTOR 'out' VALUE 2 TYPE=:c2_type, LENGTH=:c2_length;

c3_type=1;

c3_length=4;

EXEC SQL SET DESCRIPTOR 'out' VALUE 3 TYPE=:c3_type, LENGTH=:c3_length;

c4_type=1;

c4_length=10;

EXEC SQL SET DESCRIPTOR 'out' VALUE 4 TYPE=:c4_type, LENGTH=:c4_length;

c5_type=1;

c5_length=4;

EXEC SQL SET DESCRIPTOR 'out' VALUE 5 TYPE=:c5_type, LENGTH=:c5_length;

EXEC SQL GET DESCRIPTOR 'out' :col_cnt=COUNT;

printf("col_cnt:%d\n",col_cnt);

for(i=1;i<=col_cnt;i++)

{

    memset(c_name, 0, 10);

    EXEC SQL GET DESCRIPTOR 'out' VALUE :i :c_name=NAME;

    printf("%s ",c_name);

}

printf("\n------------------------\n");

EXEC SQL FOR :array_size FETCH cur_stmt INTO DESCRIPTOR 'out';

EXEC SQL FOR :array_size GET DESCRIPTOR 'out' VALUE 1 :c1_data=DATA;

EXEC SQL FOR :array_size GET DESCRIPTOR 'out' VALUE 2 :c2_data=DATA;

EXEC SQL FOR :array_size GET DESCRIPTOR 'out' VALUE 3 :c3_data=DATA;

EXEC SQL FOR :array_size GET DESCRIPTOR 'out' VALUE 4 :c4_data=DATA;

EXEC SQL FOR :array_size GET DESCRIPTOR 'out' VALUE 5 :c5_data=DATA;

for(i=0;i<array_size;i++)

	printf("%d %s %s %s %s\n",c1_data[i],c2_data[i],c3_data[i],c4_data[i],c5_data[i]);

EXEC SQL CLOSE cur_stmt;

EXEC SQL DEALLOCATE DESCRIPTOR 'out';

EXEC SQL DEALLOCATE DESCRIPTOR 'in';

EXEC SQL COMMIT WORK;

return 0;

6.5.2INSERT批量操作

同样地,可以使用宿主数组作为INSERT语句的输入变量,只需要在执行插入操作前给数组赋值即可。如果数组数据不足也可以使用FOR语句来指定批量插入的行数,未指定FOR则默认插入行数等于数组大小。

例如,下面代码片段使用数组进行批量插入。

char name[50][51];

char phone[50][21];

char email[50][51];

/* 为数组宿主变量赋值*/

...

EXEC SQL INSERT INTO PERSON.PERSON (NAME, PHONE, EMAIL)

VALUES (:name, :phone, :email);

6.5.3UPDATE批量操作

也可以使用数组作为UPDATE语句的宿主变量,与INSERT批量操作类似。

例如,下面代码片段使用数组进行批量更新。

int person_number[50];

char phone[50][51];

/* 为数组宿主变量赋值*/

...

EXEC SQL UPDATE PERSON.PERSON SET PHONE = :phone

WHERE PERSONID = :person_number;

在上面例子中PERSONID是表PERSON.PERSON的主键,每个PERSONID对应一行数据。批量更新也可以是条件数组中每条数据对应多行。

例如:

char sex [2][51];

char email[2][51];

/* 为数组宿主变量赋值 */

...

EXEC SQL UPDATE person.person SET email = :email

WHERE sex = :sex;

6.5.4DELETE批量操作

同样可以使用数组作为DELETE语句的宿主变量,与INSERT批量操作类似。

例如,下面代码片段使用数组进行批量删除。

int person_number[50];

char phone[50];

/* 为数组宿主变量赋值*/

...

EXEC SQL DELETE FROM PERSON.PERSON WHERE PERSONID = :person_number;

在上面例子中PERSONID是表PERSON.PERSON的主键,每个PERSONID对应一行数据。批量删除也可以是条件数组中每条数据对应多行。

例如:

char sex [2][51];

...

EXEC SQL DELETE FROM person.person WHERE sex = :sex;

6.5.5FOR语法

前面介绍的的SELECT、UPDATE、INSERT和DELETE批量操作都可以使用FOR语法指定具体批量操作的行数。

例如,下面的代码片段使用FOR语法指定INSERT批量操作处理的行数。

char name[100][51];

char email[100][51];

int rows_to_insert;

/* 为数组宿主变量赋值*/

...

rows_to_insert = 25; /* 设置FOR子句变量*/

EXEC SQL FOR :rows_to_insert /* 将插入25条记录*/

INSERT INTO PERSON.PERSON (NAME, EMAIL)

VALUES (:name, :email);

6.5.6使用结构数组

当批量操作同时涉及到多个列时,可以将这些列集成到一个结构体中,然后定义结构数组,在嵌入式SQL中使用结构数组进行查询或插入更新,这样也能实现批量的效果。

在嵌入式SQL中使用结构数组有以下限制:

  • 不能在PL\SQL语句块中使用结构数组
  • 不能在where 条件中使用结构数组
  • 不能在update语句中使用结构数组

例如:下例中批量查询同时查询PERSON.PERSON的三个列,因此定义了一个对应这三个列的结构数组person_rec,同时还使用了对应这个结构数组的指示变量数组。

##include <stdio.h>

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;

struct

{

    int personid;

    char name[51];

    char email[51];

} person_rec[5];

struct

{

    short ind_personid;

    short ind_name;

    short ind_email;

} ind_person_rec[5];

char username[50];

char password[50];

char servername[50];

int num_ret;

EXEC SQL END DECLARE SECTION;

/* Declare function to handle unrecoverable errors. */

void sql_error();

void print_rows();

main()

{

/* Connect to DM. */

    strcpy(username, "SYSDBA");

    strcpy(password, "SYSDBA");

    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 a cursor for the FETCH. */

    EXEC SQL DECLARE c1 CURSOR FOR

    	SELECT personid, name, email FROM person.person where personid\<15;

    EXEC SQL OPEN c1;

    num_ret = 0;

/* Array fetch loop - ends when NOT FOUND becomes true. */

    EXEC SQL WHENEVER NOT FOUND DO break;

    for (;;)

    {

        EXEC SQL FETCH c1 INTO :person_rec :ind_person_rec;

        print_rows(sqlca.sqlerrd[2] - num_ret);

        num_ret = sqlca.sqlerrd[2]; /* Reset the number. */

    }

/* Print remaining rows from last fetch, if any. */

    if ((sqlca.sqlerrd[2] - num_ret) > 0)

        print_rows(sqlca.sqlerrd[2] - num_ret);

        EXEC SQL CLOSE c1;

        printf("\nAu revoir.\n\n\n");

        EXEC SQL COMMIT WORK RELEASE;

        exit(0);

    }

void

print_rows(n)

int n;

{

    int i;

    printf("\npersonid name email");

    printf("\n------ -------- ------\n");

    for (i = 0; i < n; i++)

        printf("%d;%s; %s\n", person_rec[i].personid,

        	person_rec[i].name, person_rec[i].email);

}

void

sql_error(msg)

char *msg;

{

    char err_msg[512];

    size_t buf_len, msg_len;

	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);

}

6.6动态SQL语句

动态SQL技术是一种先进的程序设计技术。

前面介绍的嵌入式SQL语句为编程提供了一定的灵活性,使用户可以在程序运行过程中根据实际需要输入WHERE子句或HAVING子句中的某些变量的值。这些SQL语句的共同特点是,语句中主变量的个数与数据类型在预编译时都是确定的,只是主变量的值是程序运行过程中动态输入的,我们称这类嵌入式SQL语句为静态SQL语句。

静态SQL语句提供的编程灵活性在许多情况下仍显得不足,有时候需要编写更为通用的程序。例如查询人员信息表,经理想查询所有人员姓名、性别,员工想查询联系电话,也就是说查询条件是不确定的,要查询的属性列也是不确定的,这就无法用一条静态SQL语句实现了。因为在这些情况下,SQL语句不能完整地写出来,而且这类语句在每次执行时都还有可能变化,只有在程序执行时才能构造完整。象这种在程序执行过程中临时生成的SQL语句叫动态SQL语句。

实际上,如果在预编译时下列信息不能确定,就必须使用动态SQL技术:

  • SQL语句正文;
  • 主变量个数;
  • 主变量的数据类型;
  • SQL语句中引用的数据库对象(例如,列、索引、基本表、视图等)。

动态SQL方法允许在程序运行过程中临时“组装”SQL语句,主要有三种形式:

  1. 语句可变;
  2. 条件可变;
  3. 数据对象、查询条件均可变。

这几种动态形式几乎覆盖所有的可变要求。为了实现上述三种可变形式,SQL提供了相应的语句,例如,EXECUTE IMMEDIATE、PREPARE、EXECUTE、DESCRIBE等。

6.6.1DM动态SQL语句

6.6.1.1动态SQL语句的表示方法

一般来说,应该使用一个字符串变量来表示一个动态SQL语句的文本,但该文本部分包括EXEC SQL子句。

注意

下列语句不能作动态SQL语句:CLOSE、 DECLARE、DESCRIBE、 EXECUTE、FETCH、INCLUDE、OPEN、WHENEVER、PREPARE。

在大多数情况下,动态SQL语句的文本中可能包含虚拟宿主变量,这些变量只为实宿主变量保留位置。

例如,下列例子中的问号就是只为实宿主变量保留位置:

DELETE FROM EMP WHERE NO=? AND NAME=?;

6.6.1.2生成动态 SQL语句的方法

在DM嵌入式SQL中,生成动态SQL语句的方法有四种,下面分别介绍。

  1. 方法一

用立即执行语句执行动态SQL语句。

语法格式如下:

EXEC SQL [AT [dbname | :dbname]] EXECUTE IMMEDIATE <SQL动态语句文本>;

功能:动态地准备和执行一条语句。该语句首先分析动态语句文本,检查是否有错误,如果有错误则不执行它,并在SQLCODE中返回错误码;如果没发现错误则执行它。

使用说明:用该方法处理的SQL语句一定不是SELECT语句,而且不包含任何虚拟的输入宿主变量。

例如,下列SQL动态语句文本串是合法的:

CREATE TABLE person.person

(

"personid" integer identity(1,1) not null,/*人员编号*/

"sex" char(1) not null,/*人员性别*/

"name" varchar(50) not null,/*人员姓名*/

"email" varchar(50),/*邮箱地址*/

"phone" varchar(25),/*联系电话*/

cluster primary key("personid")

)

INSERT INTO person.person VALUES('F' , '李丽', 'lily@sina.com', '02788548562');

如果有语句EXEC SQL EXECUTE IMMEDIATE :string,则该语句将分析和执行宿主变量string中的SQL语句。

例如,有语句EXEC SQL EXECUTE IMMEDIATE INSERT INTO person.person VALUES('F' , '李丽', 'lily@sina. com', '02788548562')。该语句直接分析和执行 INSERT INTO person.person VALUES('F' , '李丽', 'lily@sina. com', '02788548562')。

对于仅执行一次的动态语句,用立即执行语句最合适。而对于重复多次执行的动态SQL语句,用立即执行语句则会降低效率,应选用方法二。

  1. 方法二

方法二首先用准备语句,准备将要执行的动态SQL语句,然后用执行语句EXECUTE执行已准备好的动态SQL语句。在该方法中,动态SQL语句的处理分三步:

  1. 构造一个动态SQL语句;
  2. 用PREPARE语句来分析和命名该动态SQL语句;
  3. 用EXECUTE语句来执行它。

下面分别介绍 PREPARE、EXECUTE语句。

  • 准备语句

语法格式如下:

EXEC SQL [AT [dbname | :dbname]] PREPARE <SQL 语句名> FROM

<SQL动态语句文本>;
  • dbname:指明语句声明使用的连接名,动态SQL操作完成前不支持进行连接切换;
  • <SQL语句名>标识被分析的动态SQL语句,它是供预编译程序使用的标识符,而不是宿主变量;<SQL动态语句文本>包含动态SQL语句。

功能:准备一个语句执行。

使用说明:用该方法处理的SQL语句,不能是SELECT语句。该语句可能包含虚拟输入宿主变量(用问号表示),而且变量的类型是已知的。

例如:

INSERT INTO person.person VALUES(?, ?, ?, ?);

DELETE FROM person.person WHERE personid =?;

例如,用stmt标识被分析的 SQL语句,”?”表示一个虚拟输入宿主变量。

strcpy(sql_stmt, "DELETE FROM person.person WHERE personid =?");

EXEC SQL PREPARE stmt FROM :sql_stmt;
  • 执行语句

语法格式如下:

EXEC SQL EXECUTE <SQL 语句名> [<结果使用子句>];

功能:执行一个由PREPARE准备好的动态SQL语句。

使用说明:<SQL 语句名>标识被分析的动态SQL语句。<结果使用子句>指出一个实宿主变量表,用于替换虚宿主变量,且<结果使用子句>中的实宿主变量要与被分析的动态SQL语句中的虚宿主变量在类型、次序上相对应,个数相匹配。

例如,执行上例准备的SQL语句。

EXEC SQL EXECUTE stmt USING :personid;

其中,personid 是一个实输入宿主变量,它替换前面所述的动态SQL语句中的“?”。

  1. 方法三

用方法三处理的SQL语句一定是SELECT语句,而且它包含的选择表项或虚拟输入宿主变量的个数或类型在预编译时都已知。

例如:

SELECT NAME, PHONE FROM PERSON.PERSON WHERE PERSONID=?

在方法三中,动态SQL语句的处理过程如下:

  1. 构造一个动态SQL语句;
  2. 用PREPARE语句来分析和命名该动态SQL语句,其描述同法二;
  3. 用游标语句来执行它。

执行动态SQL查询的游标语句包括动态声明游标、动态打开游标、动态拨动游标、动态关闭游标,其中除了动态打开游标,其余的使用方法与3.3.4节中介绍的相同。

下面介绍一下动态打开游标语句,语法格式为:

EXEC SQL OPEN <游标名> [USING : host_variable [,.]];

功能:打开动态游标。

使用说明:<游标名>是要打开的游标的名字,host_variable是实际宿主变量,用于替换动态SQL语句中的虚拟宿主变量。

6.6.1.3动态 SQL实例

下面的例子示范了三种生成动态SQL的方法。在运行本程序之前,应该先确定登录的用户名下是否已经存在表PERSON,如果这个表已经存在,那么程序将在执行一开始的建表语句时发生异常而退出登录。

##include <stdio.h>

##include <stdlib.h>

##include <string.h>

EXEC SQL BEGIN DECLARE SECTION;

long SQLCODE;

char SqlStr[200];

char username[19];

char password[19];

char servername[20];

varchar person_no[10],person_name[50],person_phone[25];

EXEC SQL END DECLARE SECTION;

void main()

{

int i;

char person_id[3][6]={"1","2","3"};

char person_name[3][11]={"李丽","王刚","李勇",};

char person_phone[3][12]={"02788548562","13055173012","15955173024"};

sprintf(servername,"localhost");

sprintf(username,"SYSDBA");

sprintf(password,"SYSDBA");

EXEC SQL LOGIN :username PASSWORD :password SERVER :servername;

EXEC SQL WHENEVER SQLERROR GOTO sql_err;

/* 用方法一建表 */

strcpy(SqlStr,"CREATE TABLE person (\"personid\" integer identity(1,1) not
null,\"name\" varchar(50) not null,\"phone\" varchar(25))");

EXEC SQL EXECUTE IMMEDIATE :SqlStr;

/* 用方法二插入一组值 */

strcpy(SqlStr,"INSERT INTO person (\"name\", \"phone\") VALUES(?,?)");

EXEC SQL PREPARE INS1 FROM :SqlStr;

for(i=0;i<3;i++)

{

strcpy(person_name,person_name[i]);

strcpy(person_phone,person_phone[i]);

EXEC SQL EXECUTE INS1 USING :person_name,:person_phone;

EXEC SQL COMMIT WORK;

}

/* 用方法三查询并显示结果 */

strcpy(SqlStr,"SELECT \"name\", \"phone\" FROM person WHERE \"personid\"
=?");

strcpy(person_no,"1");

EXEC SQL PREPARE S1 FROM :SqlStr;

EXEC SQL DECLARE C1 CURSOR FOR S1;

EXEC SQL OPEN C1 USING :person_no;

printf("The resultis:%s\n",person_no);

EXEC SQL FETCH C1 INTO :person_name,:person_phone;

printf("NO=%s,NAME=%s\n\n",person_phone, person_name);

EXEC SQL CLOSE C1;

EXEC SQL DROP TABLE PERSON CASCADE;

sql_err:

printf("Error Code:%d\n",SQLCODE);

EXEC SQL ROLLBACK;

EXEC SQL LOGOUT;

exit(1);

}

6.6.2ANSI动态SQL语句

DM支持ANSI标准的动态SQL语法,以下面的SQL为例:

SELECT PERSONID ,NAME FROM PERSON.PERSON WHERE EMAIL=:email_data;

使用ANSI动态SQL的步骤是:

  1. 声明变量,包含一个字符串变量存放待执行的SQL语句;
  2. 为输入输出变量分配描述符;
  3. 准备(PREPARE)SQL语句;
  4. 为输入描述符描述输入;
  5. 设置输入描述符(上述SQL中有一个输入宿主变量email_data);
  6. 声明打开一个动态游标;
  7. 设置输出描述符(上述SQL中有两个输出变量:personid和name)
  8. 反复FETCH数据,使用GET DESCRIPTOR检索personid和name对应输出描述符的data域;
  9. 关闭游标,释放描述符。

ANSI动态SQL语句中描述符起着重要的作用,下面主要介绍描述符相关操作,其他的声明SQL语句变量、准备语句等在前面都已介绍过了。

  1. 分配描述符

在动态SQL语句中使用描述符前,必须先分配描述符。

语法格式为:

EXEC SQL ALLOCATE DESCRIPTOR [GLOBAL | LOCAL] <:desc_nam | string_literal>

[WITH MAX <:occurrences | numeric_literal>];

参数说明:

  • GLOBAL描述符能够在其他模块中使用,LOCAL描述符只能在当前模块中使用;
  • 描述符的名字可以是一个宿主变量,也可以是字符串;
  • occurrences是描述符最多可以绑定的变量数,可以使用宿主变量或者直接在SQL中使用数字表示。
  1. 获取语句描述信息

描述符分为输入变量的描述符和输出变量的描述符。DESCRIBE输入描述符可描述语句的绑定变量,DESCRIBE输出描述符能描述输出列的类型跟长度。

语法格式为:

EXEC SQL DESCRIBE [INPUT | OUTPUT] <sql_statement> USING [SQL] DESCRIPTOR [GLOBAL | LOCAL] <:desc_nam | string_literal>;
  1. 设置描述符

使用SET DESCRIPTOR语句设置输入描述符属性。

语法格式为:

EXEC SQL [FOR <array_size>] SET DESCRIPTOR <:desc_nam | string_literal> < <COUNT = :hv0> | <VALUE item_number> >

< [REF] item_name1 = :hv1 > {, < [REF] item_nameN = :hvN > } ;

参数说明:

  • FOR <array_size>:数组大小,oracle兼容模式下支持设置DATA,INDICATOR为数组宿主变量。
  • :desc_nam:前面ALLOCATE DESCRIPTOR分配描述符名称,以宿主变量形式书写。例如,char c1="out_lobInsert";……SET DESCRIPTOR :c1……。
  • string_literal:前面ALLOCATE DESCRIPTOR分配描述符名称,以字符串形式书写。例如,……SET DESCRIPTOR 'out_lobInsert'……。
  • COUNT:输入或输出绑定变量个数,可以是一个宿主变量,也可以直接使用数字。
  • VALUE item_number:动态SQL绑定变量位置。
  • hv1 .. hvN: 设置的用来绑定宿主变量。
  • item_nameI:描述符属性名,详细参见表6.1。
  • REF :仅语法上支持,不起任何作用。且只能在设置DATA, INDICATOR属性时使用。

SET DESCRIPTOR可以设置的描述符属性如下表所示。

表6.1 SET DESCRIPTOR可设置的描述符属性名
属性名 介绍
TYPE ANSI列类型码。详细参见表6.2
LENGTH 列数据的最大长度
INDICATOR 对应列的指示符
DATA 对应要设置存放列数据的宿主变量

其中,ANSI的列类型码见下表。

表6.2 ANSI数据类型编码
数据类型 列类型码
CHARACTER 1
CHARACTER VARYING 12
DATE 9
DECIMAL 3
DOUBLE PRECISION 8
FLOAT 6
INTEGER 4
NUMERIC 2
REAL 7
SMALLINT 5
CLOB (oracle模式下兼容) 112
BLOB (oracle模式下兼容) 113
  1. 提取描述符

在执行FETCH操作后,可以通过GET DESCRIPTOR获取返回的查询结果。

语法格式如下:

EXEC SQL GET DESCRIPTOR [GLOBAL | LOCAL] <:desc_nam | string_literal>

VALUE<item_number> :hv1 = DATA, :hv2 = INDICATOR, :hv3 = RETURNED_LENGTH ;

参数说明:

  • desc_nam可以是字符串也可以是宿主变量;
  • item_number可以是数字也可以是宿主变量;
  • hv1、hv2、hv3 必须是宿主变量;
  • 可以只获取DATA、INDICATOR、RETURNED_LENGTH其中一个或多个。

GET DESCRIPTOR可以查询的描述符相关属性如下表所示。

表6.3 GET DESCRIPTOR可以查询的描述符属性
属性 介绍
TYPE 类型码
LENGTH 列长度
OCTET_LENGTH 列按字符串输出最大字节数
RETURNED_LENGTH 实际数据长度
RETURNED_OCTET_LENGTH 列按字符串输出实际字节数
PRECISION 精度
SCALE 标度
NULLABLE 列是否可以为空
INDICATOR 指示数据是否为NULL
DATA 指定数据存放宿主变量
NAME 列名
CHARACTER_SET_NAME 列的字符集

例如,下面的代码片段示例如何使用ANSI动态SQL,分配了输入描述符‘in’与输出描述符‘out’,准备并执行一个查询语句,用SET DESCRIPTOR设置描述符,用GET DESCRIPTOR获取描述符的相关属性值。

##include <stdio.h>

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;

char* dyn_statement = "SELECT name, personid FROM person.person WHERE personid
= :personid" ;

int personid_type_in = 3, personid_len_in = 4, personid_data_in = 10 ;

int name_type = 97, name_len = 50 ;

char name_data[51];

int personid_type = 3, personid_len = 4 ;

int personid_data ;

long SQLCODE = 0 ;

char username[50];

char password[50];

char servername[50];

EXEC SQL END DECLARE SECTION;

void sql_error();

main()

{

/* Connect to DM. */

    strcpy(username, "SYSDBA");

    strcpy(password, "SYSDBA");

    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);

EXEC SQL ALLOCATE DESCRIPTOR 'in' ;

EXEC SQL ALLOCATE DESCRIPTOR 'out' ;

EXEC SQL PREPARE s FROM :dyn_statement ;

EXEC SQL DESCRIBE INPUT s USING DESCRIPTOR 'in' ;

EXEC SQL SET DESCRIPTOR 'in' VALUE 1 TYPE = :personid_type_in,

	LENGTH = :personid_len_in, DATA = :personid_data_in ;

EXEC SQL DECLARE c CURSOR FOR s ;

EXEC SQL OPEN c USING DESCRIPTOR 'in';

EXEC SQL DESCRIBE OUTPUT s USING DESCRIPTOR 'out' ;

EXEC SQL SET DESCRIPTOR 'out' VALUE 1 TYPE = :name_type,

	LENGTH = :name_len, DATA = :name_data ;

EXEC SQL SET DESCRIPTOR 'out' VALUE 2 TYPE = :personid_type,

	LENGTH = :personid_len, DATA = :personid_data ;

EXEC SQL WHENEVER NOT FOUND DO BREAK ;

while (SQLCODE == 0)

{

    EXEC SQL FETCH c INTO DESCRIPTOR 'out' ;

    EXEC SQL GET DESCRIPTOR 'out' VALUE 1 :name_data = DATA ;

    EXEC SQL GET DESCRIPTOR 'out' VALUE 2 :personid_data = DATA ;

    printf("\nname = %s personid = %d", name_data, personid_data) ;

}

EXEC SQL CLOSE c ;

EXEC SQL DEALLOCATE DESCRIPTOR 'in' ;

EXEC SQL DEALLOCATE DESCRIPTOR 'out' ;

    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);

}

6.7多线程支持

PRO*C支持使用嵌入式SQL语句与指令开发多线程应用。

6.7.1多线程应用的运行上下文环境

上下文环境资源包括一个或多个连接到一个或多个服务,一个或多个游标对应一个连接,以及它们的状态信息。不仅仅可以每个线程与连接使用多个上下文,也可以将上下文从一个线程传给另一个线程使用。

例如,一个多线程应用中定义一个线程T1,执行了一个查询,返回前10行数据给应用,T1结束。又定义一个线程T2,将T1中使用的上下文CONTEXT传给T2,在T2中使用同样的游标处理方式就能获取接下来的10行数据了。

6.7.2上下文的两种使用方式

CONTEXT在多线程应用中有两种使用方式:

  1. 多个线程共享一个CONTEXT;
  2. 多个线程各自单独使用一个CONTEXT。
注意

不论使用哪种方式,在同一时刻多个线程不能使用同一个CONTEXT。多个线程使用同一个CONTEXT,必须互斥访问,多个线程各自使用一个CONTEXT时,可以各自自由使用。

6.7.3多线程嵌入式SQL与指令

多线程应用中需要使用下面这些SQL与指令:

  • EXEC SQL ENABLE THREADS;
  • EXEC SQL CONTEXT ALLOCATE :context_var;
  • EXEC SQL CONTEXT USE <:context_var | DEFAULT>;
  • EXEC SQL CONTEXT FREE :context_var;

这些EXEC SQL语句中的context_var是上下文句柄,必须声明为sql_context。语法格式为:

sql_context <context_variable>;

USE DEFAULT 表示使用默认的全局的上下文,直到下一个CONTEXT USE使用后才会切换上下文。

  1. EXEC SQL ENABLE THREADS

该语句用来初始化多线程环境,必须是应用的第一条可执行的嵌入SQL语句。

  1. EXEC SQL CONTEXT ALLOCATE

该语句用来初始化CONTEXT变量内存

  1. EXEC SQL CONTEXT USE

该语句使用指定的运行上下文,使用之前必须先ALLOCATE。EXEC SQL CONTEXT USE指令与C规则一样有自己的范围,全局的上下文能在多个模块使用,局部的只能在当前模块使用。

例如,在下面的代码片段中,function2()中的UPDATE语句使用的是全局上下文ctx1。

sql_context ctx1; /* declare global context ctx1 */

function1()

{

    sql_context :ctx1; /* declare local context ctx1 */

    EXEC SQL CONTEXT ALLOCATE :ctx1;

    EXEC SQL CONTEXT USE :ctx1;

    EXEC SQL INSERT INTO ... /* local ctx1 used for this stmt */

    ...

}

function2()

{

	EXEC SQL UPDATE ... /* global ctx1 used for this stmt */

}
  1. EXEC SQL CONTEXT FREE

该语句用来释放指定的CONTEXT。

下面的两个代码片段分别示例使用CONTEXT的两种方式。

例1,示例使用多个CONTEXT,每个线程使用单独的CONTEXT。

main()

{

	sql_context ctx1,ctx2;

EXEC SQL ENABLE THREADS;

    EXEC SQL CONTEXT ALLOCATE :ctx1;

    EXEC SQL CONTEXT ALLOCATE :ctx2;

    ...

    thread_create(..., function1, ctx1);

    thread_create(..., function2, ctx2);

    ...

    EXEC SQL CONTEXT FREE :ctx1;

    EXEC SQL CONTEXT FREE :ctx2;

    ...

}

void function1(sql_context ctx)

{

	EXEC SQL CONTEXT USE :ctx;

	/* 使用ctx执行可执行SQL语句*/

	...

}

void function2(sql_context ctx)

{

    EXEC SQL CONTEXT USE :ctx;

    /* 使用ctx执行可执行SQL语句*/

    ...

}

例2,示例多个线程使用同一个context。因为function1()和function2()有可能同时执行,在使用EXEC SQL时应该使用互斥量。

mutex_t mutex;

main()

{

    sql_context ctx;

    EXEC SQL CONTEXT ALLOCATE :ctx;

    ...

    /* spawn thread, execute function1 (in the thread) passing ctx */

    thread_create(..., function1, ctx);

    /* spawn thread, execute function2 (in the thread) passing ctx */

    thread_create(..., function2, ctx);

    ...

}

void function1(sql_context ctx)

{

    EXEC SQL CONTEXT USE :ctx;

    mutex_lock(&mutex);

    /* Execute SQL statements on runtime context ctx. */

    ...

    mutex_unlock(&mutex);

}

void function2(sql_context ctx)

{

    EXEC SQL CONTEXT USE :ctx;

    mutex_lock(&mutex);

    /* Execute SQL statements on runtime context ctx. */

    ...

    mutex_unlock(&mutex);

}

6.7.4多线程PRO*C程序注意事项

DM提供的PRO*C是线程安全的,同时需要保证PRO*C应用编写也线程安全,如全局变量的使用等。除此之外需要注意以下两点:

  • 对于静态或全局宿主变量要注意线程安全
  • 避免在多个线程里同时使用同一个CONTEXT

6.8PRO*C与OCI环境关联

DM兼容Oracle的SQLEnvGet和SQLSvcCtxGet接口,可以用来把OCI环境句柄与PRO*C的CONTEXT上下文环境关联起来,进而可以在PRO*C应用中使用OCI句柄进行操作。

6.8.1SQLEnvGet

SQLEnvGet返回与PRO*C的CONTEXT关联的OCI环境句柄指针。

函数定义:

sword SQLEnvGet(

dvoid *rctx,

OCIEnv **oeh

);

功能说明:返回与rctx对应的oeh OCI环境句柄指针

参数说明:

  • rctx:输入参数,上下文变量CONTEXT,SQL_CONTEXT类型
  • oeh:输出参数,要返回的OCI环境句柄

返回值:成功返回0,失败返回-1

6.8.2SQLSvcCtxGet

SQLSvcCtxGet用来获取与PRO*C上下文变量sql_context相关的OCI的上下文句柄,在OCI接口中能直接使用返回的OCI上下文句柄。

函数定义:

sword SQLSvcCtxGet(

dvoid *rctx,

text *dbname,

sb4 dbnamelen,

OCISvcCtx **svc

);

功能说明:返回与rctx关联的svc给OCISvcCtx句柄

参数说明:

  • rctx:输入参数,sql_context类型指针变量
  • dbname:输入参数,逻辑上的连接名
  • dbnamelen:输入参数,连接名长度
  • svc:输出参数,OCISvcCtx指针的地址

返回值:成功返回0,失败返回-1

6.8.3编写与OCI关联的PRO*C程序

编写与OCI关联的PRO*C程序的步骤主要如下:

  1. include OCI接口的头文件DCI.h
  2. 声明OCI环境句柄OCIEnv *oeh
  3. 声明OCI上下文句柄OCISvcCtx *svc
  4. 声明OCI的错误句柄OCIError *err
  5. 使用PRO*C连接数据库
  6. 使用SQLEnvGet获取OCI的环境句柄,与PRO*C的上下文关联

在这一步中,单线程应用与多线程应用获取OCI环境句柄的方式有一些不同:

  • 单线程应用:
retcode = SQLEnvGet(SQL_SINGLE_RCTX, &oeh);
  • 多线程应用:
sql_context ctx1;

...

EXEC SQL CONTEXT ALLOCATE :ctx1;

EXEC SQL CONTEXT USE :ctx1;

...

EXEC SQL CONNECT :uid IDENTIFIED BY :pwd;

...

retcode = SQLEnvGet(ctx1, &oeh);
  1. 使用获取到的环境句柄分配OCI错误句柄
retcode = OCIHandleAlloc((dvoid *)oeh, (dvoid **)&err,

(ub4)OCI_HTYPE_ERROR, (ub4)0, (dvoid **)0);
  1. 使用SQLSvcCtxGet获取OCI要使用的上下文句柄

在这一步中,单线程应用与多线程应用获取上下文句柄的方式有一些不同:

  • 单线程应用:
retcode = SQLSvcCtxGet(SQL_SINGLE_RCTX, (text *)dbname, (ub4)dbnlen, &svc);
  • 多线程应用:
sql_context ctx1;

...

EXEC SQL ALLOCATE :ctx1;

EXEC SQL CONTEXT USE :ctx1;

...

EXEC SQL CONNECT :uid IDENTIFIED BY :pwd AT :dbname USING :hst;

...

retcode = SQLSvcCtxGet(ctx1, (text *)dbname, (ub4)strlen(dbname), &svc);

6.8.4PRO*C中使用OCI实例

下面给出了一个在PRO*C中使用OCI的程序实例。

##include <stdio.h>

##include <stdlib.h>

##include <string.h>

##include <DCI.h>

EXEC SQL INCLUDE SQLCA;

typedef struct _tagORAConnection

{

    OCIEnv *envhp;

    OCIServer *srvhp;

    OCIError *errhp;

    OCISession *usrhp;

    OCISvcCtx *svchp;

    OCITrans* txnhp; /* 事务句柄 */

    void * ctx; /* pro*c连接上下文*/

    FILE* fpTracefile; /*trace文件的句柄*/

} ORAConnection;

EXEC SQL BEGIN DECLARE SECTION;

char username1[20],password1[20],servername1[50];

int * nrc;

##define BUFFERMAX 20971520 /* 缓存 1024*1024*20 */

unsigned char buffer[BUFFERMAX];

int nrsize;

OCIClobLocator *clob1;

OCIClobLocator *clob2;

int offset =1;

int nlength;

int errcnt=0;

EXEC SQL END DECLARE SECTION;

void

CHECKOK()

{

    if(sqlca.sqlcode!=0)

    {

        printf("fail! [%d] %s\n",sqlca.sqlcode,sqlca.sqlerrm.sqlerrmc);

        errcnt = errcnt +1;

    }

}

int CLIAppInit_EC(char dbAlias[],

    char user[],

    char pswd[],

    ORAConnection *conn)

{

    sword rc;

    rc = OCIHandleAlloc((dvoid *) conn->envhp, (dvoid **) & conn->srvhp,

    					OCI_HTYPE_SERVER, 0, (dvoid **) 0);

    if (rc != 0)

    	return -1;

    rc = OCIHandleAlloc((dvoid *) conn->envhp, (dvoid **) & conn->errhp,

    					OCI_HTYPE_ERROR, 0, (dvoid **) 0);

    if (rc != 0)

    	return -1;

    rc = OCIServerAttach(conn->srvhp, conn->errhp, (text *) dbAlias,

    					strlen(dbAlias), OCI_DEFAULT);

    if (rc != 0)

    {

        OCIHandleFree((dvoid *) conn->envhp, OCI_HTYPE_ENV);

        return -1;

    }

    OCIHandleAlloc((dvoid *) conn->envhp, (dvoid **) &conn->svchp,

    	OCI_HTYPE_SVCCTX, 0, (dvoid **) 0);

    OCIAttrSet((dvoid *) conn->svchp, OCI_HTYPE_SVCCTX, (dvoid *) conn->srvhp,

    	0, OCI_ATTR_SERVER, conn->errhp);

    OCIHandleAlloc((dvoid *)conn->envhp, (dvoid **) &conn->usrhp,

    	OCI_HTYPE_SESSION, (size_t)0, (dvoid **) 0);

    OCIAttrSet((dvoid *) conn->usrhp, OCI_HTYPE_SESSION, (dvoid *) user,

    	(ub4)strlen(user), OCI_ATTR_USERNAME, conn->errhp);

    OCIAttrSet((dvoid *) conn->usrhp, OCI_HTYPE_SESSION, (dvoid *) pswd,

    	(ub4)strlen(pswd), OCI_ATTR_PASSWORD, conn->errhp);

	rc = OCISessionBegin(conn->svchp, conn->errhp, conn->usrhp,

						OCI_CRED_RDBMS, OCI_DEFAULT);

    if (rc != 0)

    {

        OCIHandleFree((dvoid *) conn->envhp, OCI_HTYPE_ENV);

        return -2;

    }

	OCIAttrSet((dvoid *)conn->svchp, OCI_HTYPE_SVCCTX,

		(dvoid *)conn->usrhp, 0, OCI_ATTR_SESSION, conn->errhp);

	OCIHandleAlloc((dvoid *) conn->envhp, (dvoid **) &conn->txnhp,

		OCI_HTYPE_TRANS, 0, 0);

	OCIAttrSet((dvoid *) conn->svchp, OCI_HTYPE_SVCCTX,

		(dvoid *) conn->txnhp, 0, OCI_ATTR_TRANS, conn->errhp);

	printf(" Connected to %s.\n", dbAlias);

	return 0;

} /* CLIAppInit */

int main()

{

    FILE *fp;

    OCIEnv *oeh;

    OCISvcCtx *svc;

    OCIError *err;

    sword rc;

    ORAConnection *_conn;

    EXEC SQL BEGIN DECLARE SECTION;

    sql_context ctx;

    EXEC SQL END DECLARE SECTION;

    EXEC SQL ENABLE THREADS;

    EXEC SQL CONTEXT ALLOCATE :ctx;

    EXEC SQL CONTEXT USE :ctx;

    printf("\n\n BEGIN sqlenvget_main sqlenvget \n");

    _conn = calloc(1, sizeof(ORAConnection));

    if (_conn == NULL)

    {

        printf("calloc failed");

        return -1;

    }

    printf("%s\n","STEP11");

    memset(servername1, 0, sizeof(servername1));

    memset(username1, 0, sizeof(username1));

    memset(password1, 0, sizeof(password1));

    strcpy(username1,"SYSDBA");

    strcpy(password1,"SYSDBA");

    strcpy(servername1,"192.168.0.157:5290");

    EXEC SQL CONNECT :username1 IDENTIFIED BY :password1 USING :servername1;

    printf("%s\n","STEP12");

    rc=SQLEnvGet(ctx, &_conn->envhp);

    rc = CLIAppInit_EC(servername1,

    username1,

    password1,

    _conn);

    if (rc != 0)

    {

    	goto END_FLAG;

    }

	rc=SQLSvcCtxGet(ctx, (text *)0, (ub4)0, &_conn->svchp);

    fp = NULL;

    fp = fopen("data.txt","rb");

    if( fp == NULL )

    {

        printf("%s\n","open file failed!");

        *nrc = 2;

        goto END_FLAG;

    }

    EXEC SQL DROP TABLE DTMODULE;

    EXEC SQL WHENEVER SQLERROR DO CHECKOK();

    EXEC SQL ALLOCATE :clob1;

    EXEC SQL ALLOCATE :clob2;

    EXEC SQL CREATE TABLE DTMODULE(c1 int,c2 clob);

    EXEC SQL INSERT INTO DTMODULE (c1,c2)VALUES(0,empty_clob());

    EXEC SQL COMMIT;

    EXEC SQL SELECT c2 INTO :clob1 FROM DTMODULE WHERE c1=0 FOR UPDATE;

    offset = 1;

    while((nlength =fread(buffer,1,BUFFERMAX,fp)) !=0 )

    {

        EXEC SQL LOB WRITE :nlength FROM :buffer INTO :clob1 at :offset;

        offset = offset + nlength;

    }

    nlength=0;

    EXEC SQL LOB DESCRIBE :clob1 GET LENGTH INTO :nlength;

    fclose(fp);

    EXEC SQL COMMIT;

    EXEC SQL SELECT C2 INTO :clob2 from DTMODULE WHERE c1=0;

    nlength=0;

    EXEC SQL LOB DESCRIBE :clob2 GET LENGTH INTO :nlength;

    printf("nlength=%d",nlength);

    EXEC SQL COMMIT RELEASE;

END_FLAG:

    if(_conn->txnhp)

    OCIHandleFree((dvoid *) _conn->srvhp, (ub4) OCI_HTYPE_SERVER);

    if(_conn->srvhp)

    OCIHandleFree((dvoid *) _conn->srvhp, (ub4) OCI_HTYPE_SERVER);

    if(_conn->svchp)

    OCIHandleFree((dvoid *) _conn->svchp, (ub4) OCI_HTYPE_SVCCTX);

    if(_conn->errhp)

    OCIHandleFree((dvoid *) _conn->errhp, (ub4) OCI_HTYPE_ERROR);

    if(_conn->usrhp)

    OCIHandleFree((dvoid *) _conn->usrhp, (ub4) OCI_HTYPE_SESSION);

    if(_conn->envhp)

    OCIHandleFree((dvoid *) _conn->envhp, (ub4) OCI_HTYPE_ENV);

    free(_conn);

    _conn = NULL;

    printf("\n\n END sqlenvget_main sqlenvget \n");

    return(0);

}

6.9修改当前连接的自动提交属性

DM PRO*C提供dpc_autocommit()和dpc_autocommit_off()接口,用于修改当前连接的自动提交属性。

函数定义:

void dpc_autocommit()

功能说明:修改当前连接为自动提交

函数定义:

void dpc_autocommit_off()

功能说明:修改当前连接为非自动提交

微信扫码
分享文档
扫一扫
联系客服