嵌入式SQL

转自:http://blog.csdn.net/angelseyes/article/details/3962133

C语言程序代码中直接嵌入SQL语句,使数据库编程变得非常简单明了,而且嵌入式SQL是一种标准,代码不需要很多的修改就能移植到支持嵌入式SQL的数据库系统上去,但这同时也是一个缺点,许多数据库系统不提供嵌入式SQL的预编译器。

1.1        编译

编译过程分为两步,第一步,对带有嵌入式SQLC代码程序(通常此程序以.pc结尾,简称PC代码)使用proc做一次预编译,将里面的嵌入式SQL转化为代表数据库功能调用的C代码。第二步,使用C编译器将C代码编译连接成可执行文件。

配置文件

proc首先读取$ORACLE_HOME/precomp/pcscfg.cfg,里面的选项与命令行选项作用完全相同,这样可以让proc使用变得简洁。但从减少系统配置步骤、保持应用迁移的灵活性的角度出发,建议对此文件不作修改,将各种选项写在程序的编译文件中。

命令行选项:

sys_include: 系统头文件目录,如/usr/include,/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include, /usr/lib/gcc-lib/i386-redhat-linux/2.96/include ,$ORACLE_HOME/precomp/public,$(ORACLE_HOME)/rdbms/public -I$(ORACLE_HOME)/rdbms/demo,前3个通常在配置文件中已经写入

include:自定义的应用头文件目录

sqlcheck:SQL语句检查方式,语法(SYNTAX)和语义(SEMANTICS)。

语法:仅检查SQL语句的语法结构

语义:除语法检查外,还检验数据库对象和宿主变量的有效性

userid:访问数据库途径,在sqlcheck为语义检查时必须指定,如dbuser/oracle@oradb

iname:输入pc文件名

oname:输出c文件名

如果省略iname、oname,只需输入pc文件名,缺省转化成同名后缀为.cC文件。

threads:线程支持,yes或no

例子

proc sqlcheck=semantic userid=dbuser/oracle threads=yes sys_include=$(ORACLE_HOME)/precomp/public sys_include=$(ORACLE_HOME)/rdbms/public sys_include=$(ORACLE_HOME)/rdbms/demo include=$IN threads=yes iname=emp.pc oname=emp.c  //$IN是自定义的应用文件目录

gcc -I$(ORACLE_HOME)/precomp/public -I$(ORACLE_HOME)/rdbms/public -I$(ORACLE_HOME)/rdbms/demo –g –c –o emp.o emp.c

参见proc/single/Makefile。

1.2        SQL语句

建议设立一个与表结构一一对应的结构定义,作为宿主结构,下面的所有例子几乎都会用到关于表emp的宿主结构(emp_t emp)。

参见proc/single/db.h中emp_t和emp建表脚本proc/single/emp.sql

1.2.1          内部类型与宿主类型对应

下面是最常用的内部数据类型和宿主变量类型的对应关系:

内部数据类型

宿主变量类型

char(n)

char[n+1]

varchar2(n)

char[n+1]

number(n<=6)

int

number(n>6)

double

number(m,n)

double

date

char[15]

 

1.2.2    连接和断开

最简单的形式

这是最为常用的形式:应用程序不指明任何数据源,Oracle库从环境变量ORACLE_SID中得到数据库实例名,在本机连接此实例。应用程序只要输入数据库用户名和口令即可。

strcpy(dbuser,”dbuser”);  strcpy(password,”oracle”);

EXEC SQL connect :dbuser identified by :password;  //dbuser和password是char[n]的宿主变量。

也可以将用户名和口令写在一起。

strcpy(dbuser_password,”dbuser/oracle”);

EXEC SQL connect :dbuser_password;

跨机连接

首先在$ORACLE_HOME/network/admin/tnsnames.ora中添加对方数据库信息,取得一个连接名。

假定已经在tnsnames.ora中配好了名为oradb2的连接。

strcpy(dbstring, “oradb2”);

EXEC SQL connect :dbuser identified by :password using :dbstring;

多个连接

可以在一个应用程序中同时使用两个以上的数据库连接,为区分不同的连接,要为每个连接起一个名字。

strcpy(dbuser,”dbuser1”);  strcpy(password,”oracle1”);

strcpy(dbconnect,”dbconnect1”);

EXEC SQL connect :dbuser identified by :password at :dbconnect;

strcpy(dbuser,”dbuser2”);  strcpy(password,”oracle2”);

strcpy(dbconnect,”dbconnect2”);

EXEC SQL connect :dbuser identified by :password at :dbconnect;

在多连接的情况下,每句sql都要指明连接名。

EXEC SQL at :dbconnect select …;  //下面不再讨论多连接,仅仅说明其使用方法

断开

应用程序无论正常还是非正常退出,数据库连接自动中断,也可以显式关闭。

EXEC SQL commit work release;

1.2.3    事务

提交

EXEC SQL commit;

回滚

EXEC SQL rollback;

1.2.4    标准SQL语句

select

EXEC SQL * into :emp from emp where no=:emp->no;

EXEC SQL select name,to_char(upd_ts,’yyyymmddhh24miss’) into :emp->name,:emp->upd_ts from emp where no=:emp->no;

lock

EXEC SQL select * into :emp from emp where no=:emp->no for update;

update

EXEC SQL update emp set duty=:emp->duty,upd_ts=sysdate where no=:emp->no;

delete

EXEC SQL delete from emp where no=:emp->no;

insert

EXEC SQL insert into emp values (:emp);

EXEC SQL insert into emp values (:emp->name,:emp-> name,:emp->age,:emp->duty,:emp->salary,:emp->to_date(upd_ts,’yyyymmddhh24miss’));

cursor

EXEC SQL declare emp_cur cursor for select * from emp where age>25;

EXEC SQL open emp_cur;

EXEC SQL fetch emp_cur into :emp;

EXEC SQL close emp_cur;

1.2.5    动态SQL语句

在输出域或输入查询条件,甚至表名,不能事先确定的时候,可以引入动态sql语句的方法。

非查询语句

专指insert,update,delete等无结果集的sql语句。

不带宿主变量:

最直接的做法,处理一条纯粹的sql语句,不需要任何变化,每次都完整地从sql解析走到执行。

strcpy(sql,”delete from emp where no=0”);

EXEC SQL execute immediate :sql;

查询条件使用宿主变量:

目的是为了只进行一次解析,以后只需绑定不同输入就能直接执行。

strcpy(sql,”delete from emp where no=:no”);

EXEC SQL prepare sql_stmt from :sql;

emp.no=0;

EXEC SQL execute sql_stmt using :emp.no;

emp.no=1;

EXEC SQL execute sql_stmt using :emp.no;

查询语句

查询必须使用游标方式。

strcpy(sql,”select * from emp where duty=:duty and age>:age”);

strcpy(emp.duty,”1”);  emp.age=20;

EXEC SQL prepare sql_stmt from :sql;

EXEC SQL declare sql_cur cursor for sql_stmt;

EXEC SQL open sql_cur using :emp.duty,:emp.age;

EXEC SQL fetch sql_cur into :emp;

EXEC SQL close sql_cur;

1.2.6          数组操作

利用宿主数组可以用一次sql操作处理成批记录,降低了应用程序与oracle服务进程之间的通讯开销,也减少了sql语句解析次数,这样能提高数据库应用程序的效率,对update,delete和insert尤为有效。

宿主数组

假定要操作表emp,定义宿主数组:

double a_no[50]

char a_name[50][21];

CURSOR

目的在于:试图在一次fetch中选出批量数据以减少fetch的次数。但fetch过程本身消耗资源不大,所以使用宿主数组对效率提高有限。

int rec_count;  //本次条数

int accu_count;  //累计条数

EXEC SQL declare emp_cur FOR select no,name from emp where age>25;

EXEC SQL open emp_cur;

EXEC SQL fetch emp_cur into :a_no,:a_name;  //rec_count保存当前fetch的记录条数,最多为a_no,a_name中较少的结构个数

rec_count=sqlca.sqlerr[2]-accu_account;  //sqlca.sqlerr[2]记录cursor累积读取的记录条数,这样与上次累积数之差就是本次读取的条数

accu_count=sqlca.sqlerr[2];  // accu_count保存累积fetch的记录条数

当最后一次取完时,sqlca.sqlcode为1403,即记录不存在,注意count可能会大于0,这里的“记录不存在”只是表明“没有填满宿主结构”。

SELECT

EXEC SQL select no,name into :a_no,:a_name from emp where age>25;

不能用for :rec_count子句进行操作记录条数控制,即不可指定选出若干条记录,因为有“执行此语句若干次”和“一次选出若干条记录”的歧义,需要条数控制应使用CURSOR。

DELETE

在a_no中准备N条待删除记录的no,也就是主键。

a_no[0]=0;  a_no[1]=1;  a_no[2]=2; …

rec_count=N;

EXEC SQL for :rec_count delete from emp where no=:a_no;

UPDATE

假定要更新一部分记录的name域。

在a_no中准备N条待更新记录的no,在a_name中准备N个新的name,两者必须一一对应。

a_no[0]=0;  strcpy(a_name[0],”职员0”);

a_no[1]=1;  strcpy(a_name[1],”职员1”);

a_no[N-1]=N-1;  strcpy(a_name[N-1],”职员N-1”);

rec_count=N;

EXEC SQL for :rec_count update emp set name=:a_name where no=:a_no;

INSERT

为了说明问题,这里对emp表作了一些改变,假定除no和name域外都有缺省值。

在a_no,a_name中准备N条记录的no和name,两者必须一一对应。

a_no[0]=0;  strcpy(a_name[0],”职员0”);

a_no[1]=1;  strcpy(a_name[1],”职员1”);

a_no[N-1]=N-1;  strcpy(a_name[N-1],”职员N-1”);

rec_count=N;

EXEC SQL for :rec_count insert into emp(no,name) values(:a_no,:a_name);

宿主结构数组

宿主结构数组其实是宿主变量组合起来的数组,由于update set语句的特殊性,所以宿主结构数组仅能用于SELECT,FETCH和INSERT

定义部分域结构组合emp_part_def a_emp_part[50],和a_no[50]形成混合模式。

double a_no[50];

typedef struct

{

char name [21];

int age;

} emp_part_def;

emp_part_def a_emp_part[50];

SELECT

EXEC SQL select no,name,age into :a_no,:a_emp_part from emp where age>25;

CURSOR

iAccuCount=0;

EXEC SQL DECLARE emp_part_cur FOR select name,age from emp where age>25;

EXEC SQL OPEN emp_part_cur;

EXEC SQL FETCH emp_part_cur into :a_emp_part;

rec_count=sqlca.sqlerr[2]-accu_count;

accu_count=sqlca.sqlerr[2];

INSERT

emp_def a_emp[50];

在a_emp中准备N条记录。

a_emp[0].no=0;  strcpy(a_emp[0].upd_ts,”20020101000000”);  …

a_emp[1].no=1;  strcpy(a_emp[1].upd_ts,”20020101000000”);  …

a_emp[N-1].no=N-1;  strcpy(a_emp[N-1].upd_ts,”20020101000000”);  …

rec_count=N;

EXEC SQL for :rec_count insert into emp values(:a_emp);

1.3        编程框架

1.3.1    总体原则

为了使程序模块划分更加简洁清晰,提高应用程序的可移植性,所以采取将主程序模块和数据库功能模块分离的方法,即不在主程序逻辑中出现嵌入式sql语句,而把这些语句归并到数据库功能模块中去,对主程序而言,展现的是某种功能。同时在数据库模块上再进一步把通用功能和定制功能分割开来。大致框架如下:

定制功能PC模块

通用功能头文件

定制功能头文件

通用功能PC模块

主程序

可执行文件

应用部分

数据库部分

连接

定制功能C模块

通用功能C模块

 

 

 

 

 

 

 

 

 

 

 

 

 

 

通用功能头文件和通用功能模块参见proc/single /dbcom.h和proc/single/dbcom.pc,定制功能头文件和定制功能模块参见proc/single/dbfunc.h和proc /single/dbfunc.pc,编译连接方法参见proc/single/Makefile。

1.3.2    单线程和多线程

由于嵌入式SQL语句被预编译成全局性的C数据结构和函数调用,所以在单线程条件下不会有任何执行问题,程序编写十分简单,参见proc/single/。

而多线程条件就复杂很多,为了保证多个线程能够安全互斥地使用全局数据结构,必须给每个线程一个表明身份的上下文标志(context),在进行sql操作时彼此能区别开来。

每个线程使用一个数据库连接,凭借自己的id使用专有的数据结构,包括上下文标志(context)等,此数据结构是数据库连接数组(a_thrd_conn)的一个成员,参见dbcom.h中thrd_conn_t和dbcom.c中a_thrd_conn。这样做的目的在于:统一存放连接有关数据,使线程引用变得简单。如下所示:

 

数据库连接数组(thrd_conn_t a_thrd_conn)

上下文标志

index=1

index=2

线程1

线程2

数据库连接1

数据库连接2

引用专有数据

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

加入多线程支持

在proc的命令行或配置文件pcscfg.cfg中写入threads=yes,表明为待编译的程序提供thread-safe功能,参见proc/multi/Makefile。

proc thread=yes …

应用程序中最前面必须指明本程序支持多线程,参见proc/multi/multi.c和proc/multi/dbcom.pc中DbsEnableThread。

EXEC SQL enable threads;

代码大致流程

每个线程使用线程编号(thrd_index)引用数据库连接数组。

在建立数据库连接的时候,分配上下文标志,参见proc/multi/dbcom.pc中DbsConnectThread。

EXEC SQL context allocate :a_thrd_conn[thrd_index].context;

断开时,释放上下文标志,参见DbsDisconnThread。

EXEC SQL context free :a_thrd_conn[thrd_index].context;

在每次sql操作前使用上下文标志(由于proc的一些原因,threads=yes条件下的sql语句在被proc转换时使用上面离其最近的use所指明的context,所以保险做法是每句sql语句前使用use)。

EXEC SQL context use : a_thrd_conn[thrd_index].context

EXEC SQL update emp set …;

猜你喜欢

转载自blog.csdn.net/witto_sdy/article/details/38554659