背景
ODBC (Open Database Connectivity),即开放数据库连接,是由 Microsoft 公司基于 X/OPEN CLI 提出的用于访问数据库的应用程序编程接口,主要完成应用程序和数据库系统之间的中间件功能。基于 ODBC 的应用程序通过 ODBC 提供的 API 与数据库进行交互,在避免了应用程序直接操作数据库系统的同时,极大的增强了应用程序的可移植性、扩展性和可维护性。
DB2 ODBC 基于 ODBC 标准,并引入了针对 DB2 数据库的特有扩展,是 IBM DB2 家族的主要产品之一。它提供了关系型数据库的 C/C++ 编程接口,应用程序可以在不改变源代码的情况下,在不同的关系型数据库上运行,是嵌入式 SQL 之外的另一种有效的编程接口。
本文将主要描述如何在 DB2 for z 平台上使用 DB2 ODBC 进行程序开发。
区别
1. 与传统 ODBC 的异同
首先,DB2 ODBC 基于 ISO CLI 标准,能够很好的完成数据库操作的基本功能,绝大部分 API 与 ODBC3.0 相同。但同时也舍弃了一些不必要的 API,如 SQLCopyDesc() , SQLGetDescField() 等,因此应用程序在不同 ODBC 版本之间进行转换时,要注意当前版本对 API 的兼容性。
其次,DB2 ODBC 针对 DB2 的特性提供了功能强大的扩展,以便能够充分发挥 DB2 的优势。主要体现在 SQLCA 对 DB2 诊断信息的支持、LOB 及 LOB-locator 的处理等方面。
2. 与 Embedded SQL 的异同
首先,与 Embedded SQL 相比,ODBC 程序具有更好的可移植性。Embedded SQL 程序在运行前需要指定与数据库对应的预编译器将 Embedded SQL 进行预编译。但由于预编译器与数据库之间的对应关系,使得 Embedded SQL 程序在某种程度上依赖于特定的数据库,因此限制了程序的可移植性。而 ODBC 程序本身不需要预编译器,在运行时动态地执行程序本身的内容,独立于数据库,因此具有更好的可移植性。
其次,ODBC 具有更好的易用性。在 ODBC 程序中,用户不需要直接对 cursor 进行定义及操作,可以使用通配符进行数据库操作的准备工作,不需要直接触发 commit 或 roll back 语句完成事务的提交或回滚。DB2 ODBC 的这些封装操作,使得用户可以更加关注程序逻辑而非语言本身,因此具有更好的易用性。
优点
1. 良好的可移植性。
ODBC 程序独立于数据库,使得程序在不同的数据库上运行时,不需要由于数据库产品的不同而重新编译和绑定。
2. 良好的易用性。
ODBC 提供的简单易懂的编程接口,将大量繁琐的底层操作封装到 API 的同时,使用户可以更加聚焦于业务逻辑。
3. 强大的编程接口。
ODBC 提供了强大的编程接口。用户可以通过通配符、数组来动态输入输出数据,通过指定的 API 获得程序运行时的详细信息,同时连接多个不同的数据库进行对应的操作等。
ODBC 应用的结构
概念模型
图 2.1 为一个简单的 ODBC 应用的概念模型:
图 2.1 ODBC 概念模型
一个 ODBC 程序通常可以分为三个模块:初始化、数据库操作和资源释放。初始化模块主要完成数据库句柄的创建,环境参数的设置及连接到数据库等操作;数据库操作部分是整个程序的主体,主要完成与业务逻辑相关的数据库对象及数据的增、删、改、查等操作;资源释放模块主要包括数据库连接及资源的释放等。
在更加详细地介绍三个模块之前,首先要介绍一个重要概念:Handle。它是 ODBC 程序中指向某种数据对象的句柄,用来进行程序控制。具体可分为 Environment handle,Connection handle 和 Statement handle( 以下简称 Stmt handle)。
1. Environment handle:环境句柄。是指向包含全局信息数据对象的句柄,用来设置当前程序的整体属性,如 ODBC 的版本,最大连接数等。 每个 ODBC 程序中只能有一个 Environment handle。通过 Environment handle 进行设置的属性会作用于当前整个程序。此外,还可以通过 Environment handle 创建数据库连接所对应的 Connection handle。
2. Connection Handle:连接句柄。是指向包含数据源连接的数据对象的句柄,用来控制当前连接的属性,如事务自动提交、连接类型、隔离级别等,作用范围为当前连接。在程序中可以通过创建多个 Connection handle 连接到多个数据源,一个 Connection handle 同时只能连接到一个数据源。此外,还可以通过 connection handle 创建数据库操作所对应的 Stmt handle。
3. Stmt handle:操作句柄。是用来指向包含数据库操作信息的数据对象的句柄,用来完成数据库对象及数据的操作,作用域为当前操作。
初始化
初始化模块主要进行分配和初始化系统资源,为稍后的数据库操作做准备。具体包括 Environment handle 和 Connection handle 的创建和初始化、连接到指定的数据源、全局及连接属性的设置等。图 2.2 所示为初始化部分的示意图,图中虚线表示的是可选项,即可以自定义全局或连接属性,也可以不进行设置,使用系统的默认值。 下面将分别描述。
为了便于描述,首先将后续章节例子程序中将会出现的全局变量声明如例 2.1 所示:其中 henv,hdbc,hstmt 分别代表 Environment handle,Connection handle 及 Stmt handle,rc 为程序执行操作后的返回结果,sqlstmt 为用来存储具体数据库操作所的 SQL 语句变量。
图 2.2 初始化模块
例 2.1 全局变量声明
SQLHENV henv = SQL_NULL_HENV;
SQLHDBC hdbc = SQL_NULL_HDBC;
SQLHSTMT hstmt = SQL_NULL_HSTMT;
SQLRETURN rc = SQL_SUCCESS;
SQLCHAR sqlstmt[200];
1. 分配 handle:DB2 ODBC 推荐使用 ODBC3.0 API SQLAllocHandle() 来统一分配 handle,一个典型的分配 Environment handle 和 Connection handle 的例子如例 2.2 所示:
例 2.2 分配 Environment 和 Connection handle
henv=0;
rc = 0;
printf("/nSQLAllocHandle - allocate ENV handle ");
rc=SQLAllocHandle( (SQLSMALLINT)SQL_HANDLE_ENV,
(SQLHANDLE)SQL_NULL_HANDLE,(SQLHANDLE *)&henv );
printf("/nDMLDDLM-henv=%i/n",henv);
if( rc != SQL_SUCCESS ){
printf("/nAllocate environment handle error/n");
goto dberror;
}
hdbc=0;
printf("/nSQLAllocHandle - allocate connection handle ");
rc=SQLAllocHandle((SQLSMALLINT)SQL_HANDLE_DBC,(SQLHANDLE)henv,(SQLHANDLE *)&hdbc );
printf("/nDMLDDLM-hdbc=%i/n",hdbc);
if( rc != SQL_SUCCESS ){
printf("/nAllocate connection handle error/n");
goto dberror;
}
例 2.2 中的 SQLAllocHandle() 的参数描述如表 2.1 所示:
表 2.1 SQLAllocHandle() 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | HandleType | SQLSMALLINT | 要分配的 handle 类型 , 包括三种: SQL_HANDLE_ENV : Environment handle ; SQL_HANDLE_DBC : Connection handle ; SQL_HANDLE_STMT: Stmt handle 。 |
2 | Inputhandle | SQLHANDLE | 与当前要分配的 handle 所对应的上一级 handle : 分配 Environment handle 时 : 指定 SQL_NULL_HANDLE ; 分配 Connection handle 时 : 指定对应的 Environment handle 变量; 分配 Stmt handle 时,指定对应的 Connection handle 变量。 |
3 | OutputHandlePtr | SQLHANDLE * | 作为分配结果的 handle 指针,指向新分配完毕的 handle 的变量。 |
注意:每个程序中只能有一个 Environment handle ,但是却可以分配多个 Connection handle 。 除了使用 SQLAllocHandle() ,DB2 ODBC 还支持使用 ODBC 1.0 API :SQLAllocEnv() 、SQLAllocConnect() 和 SQLAllocStmt() 来分配 Environment ,Connection 及 Stmt handle 。为了程序的可维护性和可移植性,推荐用户使用 SQLAllocHandle() 来统一分配 handle 。
2.设置和查询全局属性:DB2 ODBC 使用 SQLSetEnvAttr() 和 SQLGetEnvAttr() 来设置和查询全局属性。一个设置并查询 Environment handle 属性的例子如例 2.3 所示:
例 2.3 设置并查询 Environment handle 属性
SQLINTEGER output_nts;
rc = SQLSetEnvAttr( henv, SQL_ATTR_OUTPUT_NTS, ( SQLPOINTER ) SQL_TRUE, 0 );
if( rc != SQL_SUCCESS ){
printf("/nSet environment handle attribute error/n");
goto dberror;
}
rc = SQLGetEnvAttr(henv, SQL_ATTR_OUTPUT_NTS, &output_nts, 0, 0);
if( rc != SQL_SUCCESS ){
printf("/nGet environment handle attribute error/n");
goto dberror;
}
printf("/nNull Termination of Output strings is: ");
if (output_nts == SQL_TRUE)
printf("True/n");
else
printf("False/n");
例 2.3 使用 SQLSetEnvAttr() 设置 DB2 ODBC 在数据库操作时以 NULL 作为输出字符串的终结符,并通过 SQLGetEnvAttr() 查询设置的结果。SQLSetEnvAttr() 的参数描述如表 2.2 所示,SQLGetEnvAttr() 的参数描述如表 2.3 所示:
表 2.2 SQLSetEnvAttr() 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | EnvironmentHandle | SQLHENV | 当前程序的 Environment handle 变量 |
2 | Attribute | SQLINTEGER | 属性名称,具体包括: 当前 ODBC 版本 SQL_ATTR_ODBC_VERSION , 输出字符串终结符属性 SQL_ATTR_OUTPUT_NTS , 连接类型 SQL_ATTR_CONNECTTYPE 等。 更多的属性名称和详细用法请参看 《 DB2 Version 9.1 for z/OS ODBC Guide and Reference 》 |
3 | ValuePtr | SQLPOINTER | 指向属性值变量的指针 |
4 | StringLength | SQLINTEGER | 当 value 为字符类型时,value 的字节长度 当 value 不为字符类型时,则忽略此参数 |
表 2.3 SQLGetEnvAttr() 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | EnvironmentHandle | SQLHENV | 当前程序的 Environment handle 变量 |
2 | Attribute | SQLINTEGER | 属性名称,具体包括: 当前 ODBC 版本 SQL_ATTR_ODBC_VERSION , 输出字符串终结符属性 SQL_ATTR_OUTPUT_NTS , 连接类型 SQL_ATTR_CONNECTTYPE 等。 更多的属性名称和详细用法请参看 《 DB2 Version 9.1 for z/OS ODBC Guide and Reference 》 |
3 | ValuePtr | SQLPOINTER | 指向返回属性值变量的指针 |
4 | BufferLength | SQLINTEGER | ValuePtr 指针指向的 buffer 的最大长度 |
5 | StringLengthPtr | SQLINTEGER * | 指向包含 ValuePtr 长度变量的指针 |
3. 设置和查询连接属性:DB2 ODBC 推荐使用 ODBC3.0 API SQLSetConnectAttr() 和 SQLGetConnectAttr() 来设置和获得连接属性。一个设置和查询连接属性的例子如例 2.4 所示:
例 2.4 设置和查询连接属性
SQLINTEGER autocommit;
rc=SQLSetConnectAttr(hdbc,SQL_ATTR_AUTOCOMMIT,(void*)SQL_AUTOCOMMIT_OFF,SQL_NTS);
if( rc != SQL_SUCCESS ){
printf("/nset connection handle attribute error/n");
goto dberror;
}
rc = SQLGetConnectAttr( hdbc, SQL_AUTOCOMMIT, &autocommit, 0, NULL ) ;
if( rc != SQL_SUCCESS ){
printf("/nget connection handle attribute error/n");
goto dberror;
}
printf( "/nAutocommit is: " ) ;
if ( autocommit == SQL_AUTOCOMMIT_ON )
printf( "ON/n" ) ;
else
printf( "OFF/n" ) ;
例 2.4 使用 SQLSetConnectAttr() 将 Connection handle 设置为手动提交事务,作用范围为当前连接,并使用 SQLGetConnectAttr 查询设置结果。此外,DB2 ODBC 还支持使用 ODBC 1.0 API SQLSetConnectOption() 和 SQLGetConnectOption() 进行 Connection 属性的设置和查询,但出于可移植性和扩展性的考虑,同样不做推荐。建议使用 SQLSetConnectAttr() 和 SQLGetConnectAttr() 进行相关的操作。例 2.4 中 SQLSetConnectAttr() 的参数描述如表 2.4 所示,SQLGetConnectAttr() 的参数说明如表 2.5 所示:
表 2.4 SQLSetConnectAttr() 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | EnvironmentHandle | SQLHDBC | 当前程序的 Connection handle 变量 |
2 | Attribute | SQLINTEGER | 属性名称,具体包括: 连接类型 SQL_ATTR_CONNECTTYPE , 事务自动提交 SQL_ATTR_AUTOCOMMIT 等。 更多的属性名称和详细用法请参看 《 DB2 Version 9.1 for z/OS ODBC Guide and Reference 》 |
3 | ValuePtr | SQLPOINTER | 指向属性值变量的指针 |
4 | StringLength | SQLINTEGER | 当 value 为 Integer 类型时,则忽略此参数 当 value 为字符类型时: i. 如果 Attribute 是 ODBC 定义的属性,应为 value 的长度; ii. 如果是 IBM 扩展所定义的属性,并且 value 是以 nul-terminated 的字符串,则此处可以为 value 的长度或者 SQL_NTS |
表 2.5 SQLGetConnectAttr() 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | EnvironmentHandle | SQLHDBC | 当前程序的 Connection handle 变量 |
2 | Attribute | SQLINTEGER | 属性名称,具体包括: 连接类型 SQL_ATTR_CONNECTTYPE , 事务自动提交 SQL_ATTR_AUTOCOMMIT 等。 更多的属性名称和详细用法请参看 《 DB2 Version 9.1 for z/OS ODBC Guide and Reference 》 |
3 | ValuePtr | SQLPOINTER | 指向返回的属性值变量的指针 |
4 | BufferLength | SQLINTEGER | ValuePtr 指向的 buffer 的字节长度,且取决于以下条件: 当 value 为 Integer 类型时,则忽略此参数; 当 value 为字符类型时: i. 如果 Attribute 是 ODBC 定义的属性,应为 value 的长度; ii. 如果是 ibm 扩展所定义的属性,并且 value 是以 nul-terminated 的字符串,则此处可以为 value 的长度或者 SQL_NTS |
5 | StringLengthPtr | SQLINTEGER * | 指向包含 ValuePtr 长度变量的指针 |
4. 连接到数据源:DB2 ODBC 使用 SQLConnect() 或者 SQLDriverConnect() 连接到指定的数据源。SQLConnect 和 SQLDriverConnect 都可以完成连接到数据库的功能,但是 SQLDriverConnect 能够在连接时通过指定连接字符串进行更加详细的设置。下面将分别详细描述。
使用 SQLConnect 连接数据库的代码如例 2.5 所示。其中,SQL_NTS 表示输入参数是 null 为终结符的字符串。
例 2.5 使用 SQLConnect() 数据库连接实例
SQLCHAR
server[18];
SQLCHAR
uid[30];
SQLCHAR pwd[30];
strcpy((char *)server,"stlec1");
strcpy((char *)uid,"user");
strcpy((char *)pwd,"password");
rc=SQLConnect(hdbc, server,SQL_NTS,uid,SQL_NTS,pwd,SQL_NTS);
if( rc != SQL_SUCCESS ){
printf("/nSQLConnect error/n");
goto dberror;
}
SQLConnect 的各个参数如表 2.6 所示:
表 2.6 SQLConnect () 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | hdbc | SQLHDBC | 连接数据源的 Connection handle 变量 |
2 | szDSN | SQLCHAR * | 要连接的数据源的名称 |
3 | cbDSN | SQLSMALLINT | szDSN 的字节数 |
4 | szUID | SQLCHAR * | 连接的用户名 |
5 | cbUID | SQLSMALLINT | cbUID 的字节数 |
6 | szAuthStr | SQLCHAR * | 连接密码 |
7 | cbAuthStr | SQLSMALLINT | szAuthStr 的字节数 |
· 使用 SQLDriverConnect() 连接到数据库的例子如例 2.6 所示。在连接字符串中 , 需要指定数据库名称或者别名 DSN ,登陆数据库的用户名 UID 及密码 PWD 。
例 2.6 使用 SQLDriverConnect() 连接数据库实例
SQLCHAR driverconnect[100];
sprintf((char*)driverconnect, (char*)"DSN=STLEC1; UID=user; PWD=password;");
rc=SQLDriverConnect(hdbc, NULL, driverconnect,SQL_NTS,NULL,0,NULL,SQL_DRIVER_NOPROMPT);
if( rc != SQL_SUCCESS ){
printf("/ndriver connection error/n");
goto dberror;
}
SQLDriverConnect() 参数如表 2.7 所示 :
表 2.7 SQLDriverConnect() 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | hdbc | SQLHDBC | 连接数据源的 connection handle 变量 |
2 | hwnd | SQLHWND | 此参数未使用,通常指定为 NULL |
3 | szConnStrIn | SQLCHAR * | 连接字符串,可以在其中指定连接的详细信息 |
4 | cbConnStrIn | SQLSMALLINT | szConnStrIn 的字节数 |
5 | szConnStrOut | SQLCHAR * | 指向包含连接字符串的 buffer 的指针变量 |
6 | cbConnStrOutMax | SQLSMALLINT | szConnStrOut 所指向的 buffer 的最大字节数 |
7 | pcbConnStrOut | SQLSMALLINT * | 指向包含连接字符串中有效字节数量的 buffer 的指针变量 |
8 | fDriverCompletion | SQLUSMALLINT | 运行时是否提示用户需要更多信息的变量,DB2 ODBC 只支持不提示信息即 SQL_DRIVER_NOPROMPT |
数据库操作
数据库操作模块包括是整个程序的主体 , 根据业务需求 , 完成对数据库对象的创建、修改、删除及数据的增、删、改、查等。一个基本的 ODBC 数据库操作由以下五步组成,如图 2.3 所示,下面将分别描述。
图 2.3 数据库操作示意图
1. 分配 Stmt handle:包括 Stmt handle 的创建,并根据需要对其属性进行设置,例如设置绑定参数类型,游标是否可滚动等。Stmt handle 是执行数据库操作的句柄,所有涉及数据库数据的操作都由 Stmt handle 来实现,同时也可以针对需求对某些数据库操作进行特殊的属性设置,如游标是否可滚动,隔离级别的设置等。Stmt handle 由 Connection handle 分配,一个 Connection handle 可以分配同时多个 Stmt handle 来进行不同的操作。一个典型的分配 Stmt handle 并进行 Stmt 属性设置的代码如例 2.7 所示:
例 2.7 分配并设置 Stmt handle 属性实例
printf("/nSQLAllocStmt /n");
hstmt=0;
rc=SQLAllocHandle((SQLSMALLINT)SQL_HANDLE_STMT,(SQLHANDLE)hdbc, (SQLHANDLE *)&hstmt);
if( rc != SQL_SUCCESS ) goto dberror;
printf("/nhstmt=%i/n",hstmt);
printf("/nSet Stmt handle attributes ");
rc=SQLSetStmtAttr(hstmt,SQL_ATTR_TXN_ISOLATION,
(void *)SQL_TXN_READ_COMMITTED,(SQLINTEGER)0);
if( rc != SQL_SUCCESS ) goto dberror;
例 2.7 完成分配 Stmt handle 的操作,并将 stmt handle 的隔离级别设置为 SQL_TXN_READ_COMMITTED,SQLSetStmtAttr() 的参数说明如表 2.8 所示:
表 2.8 SQLSetStmtAttr() 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | StatementHandle | SQLHSTMT | Stmt handle 变量 |
2 | Attribute | SQLINTEGER | 属性名称,具体包括: 隔离级别 SQL_ATTR_TXN_ISOLATION , 参数绑定类型 SQL_ATTR_BIND_TYPE , 使用的游标类型 SQL_ATTR_CURSOR_TYPE , 最大返回数据数 SQL_ATTR_MAX_ROWS 等。 更多的属性名称和详细用法请参看 《 DB2 Version 9.1 for z/OS ODBC Guide and Reference 》 |
3 | ValuePtr | SQLPOINTER | 指向属性值变量的指针 |
4 | StringLength | SQLINTEGER | 当 value 为 Integer 类型时,则忽略此参数; 当 value 为字符类型时: i. 如果 Attribute 是 ODBC 定义的属性,应为 value 的长度; ii. 如果是 ibm 扩展所定义的属性,并且 value 是以 nul-terminated 的字符串,则此处可以为 value 的长度或者 SQL_NTS 。 |
2. 执行数据库操作。其中数据库操作主要包括数据库对象 ( 表、视图、存储过程、触发器等 ) 的操作 ( 创建、修改、删除 ) 和数据的操作 ( 增、删、改、查 ) 两部分。执行数据库操作有两种方法:
对于只执行一次的操作,使用直接执行的方法即可。DB2 ODB 使用 SQLExecDirect() 完成直接执行的操作。通过直接执行的方式创建表并插入数据的例子如例 2.8 所示,SQLExecDirect() 的参数描述如表 2.9 所示:
例 2.8 直接执行的方式创建表并插入一条数据
strcpy((char *)sqlstmt, "CREATE TABLE TABLE2A (INT4 INTEGER)");
printf("/nDMLDDLM sqlstmt=%s",sqlstmt);
rc=SQLExecDirect(hstmt,sqlstmt,SQL_NTS);
if( rc != SQL_SUCCESS ) goto dberror;
strcpy((char *)sqlstmt, "INSERT INTO TABLE2A VALUES(1)");
printf("/nDMLDDLM sqlstmt=%s",sqlstmt);
rc=SQLExecDirect(hstmt,sqlstmt,SQL_NTS);
if( rc != SQL_SUCCESS ) goto dberror;
表 2.9 SQLExecDirect() 参数说明
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | hstmt | SQLHSTMT | Stmt handle 变量 |
2 | sqlstmt | SQLCHAR * | 指向数据库操作变量的指针 |
3 | StringLength | SQLINTEGER | 指定 sqlstmt 的字节长度 , 或者当 sqlstmt 以 null 为终结符时 , 指定其为 SQL_NTS |
对于在程序中可能重复执行的操作,通常使用先准备后执行的方法,这样只需一次准备多次执行即可,有效地提高了程序的运行效率。DB2 ODBC 使用 SQLPrepare() 准备操作,使用 SQLExecute() 来执行对应的操作。SQLPrepare() 的参数描述与 SQLExecDirect() 相同,SQLExecute() 只有一个参数,即准备完毕的 Stmt handle。例 2.8 中,用户在插入数据时直接把数据写死在 sqlstmt 中,导致了该 sqlstmt 只能使用一次,如果需要插入多条数据的情况,建立多个对应的 sqlstmt 显然不是一个很好的解决办法。在这种情况下,用户可以通过参数绑定的方式将要插入的数据绑定到指定 sqlstmt 中,此时,在 sqlstmt 中用通配符"?"来代替所对应的数据,如"INSERT INTO TABLE2A VALUES (?)",这样通过一次 prepare 多次 bind 就可以完成对应的操作,使用起来更加简便。DB2 ODBC 使用 SQLBindParameter() 完成参数绑定的过程,一个使用 SQLBindParameter() 绑定参数且准备后执行插入数据的例子如例 2.9 所示:
例 2.9 通过绑定参数 , 先准备后执行的方式插入数据
long H1INT4;
long LNH1INT4;
printf("/nSQLPrepare ");
strcpy((char *)sqlstmt, "INSERT INTO TABLE2A VALUES(?)");
printf("/nsqlstmt=%s",sqlstmt);
rc=SQLPrepare(hstmt,sqlstmt,SQL_NTS);
if( rc != SQL_SUCCESS ) goto dberror;
printf("/nSQLBindParameter /n");
H1INT4=1;
LNH1INT4=sizeof(H1INT4);
rc=SQLBindParameter(hstmt,1,SQL_PARAM_INPUT,SQL_C_LONG,
SQL_INTEGER,0,0,&H1INT4,0,(SQLINTEGER *)&LNH1INT4);
if( rc != SQL_SUCCESS) goto dberror;
printf("/n SQLExecute ");
rc=SQLExecute(hstmt);
if( rc != SQL_SUCCESS ) goto dberror;
如例 2.9 所示,DB2 ODBC 通过 SQLBindParameter() 将变量 H1INT4 绑定到 sqlstmt 中的"?"中去,然后通过准备后执行完成插入的功能。当需要继续插入数据时,只需要重新将对应的变量绑定到 hstmt 然后运行 SQLExecute 即可,不需要重新准备。SQLBindParameter() 的参数描述如表 2.10 所示:
表 2.10 SQLBindParameter 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | hstmt | SQLHSTMT | 进行绑定的 stmt handle 变量; |
2 | ipar | SQLUSMALLINT | 参数通配符的序号,从 1 开始计数; |
3 | fParamType | SQLSMALLINT | 参数类型: SQL_PARAM_INPUT :输入参数; SQL_PARAM_ONPUT :输出参数,主要用于 store procedure ; SQL_PARAM_INPUT_OUTPUT :输入输出参数,主要用于 stored procedure ; |
4 | fCType | SQLSMALLINT | 指定参数的 C 的类型,包括 SQL_C_FLOAT, ,SQL_C_LONG, ,SQL_C_SHORT ,SQL_C_TYPE_DATE ,SQL_C_TYPE_TIME 等。 更多的类型请参看 《 DB2 Version 9.1 for z/OS ODBC Guide and Reference 》 |
5 | fSqlType | SQLSMALLINT | 指定参数的 SQL 类型,包括 SQL_DOUBLE , SQL_FLOAT ,SQL_GRAPHIC ,SQL_INTEGER 等。 更多的类型请参看 《 DB2 Version 9.1 for z/OS ODBC Guide and Reference 》 |
6 | cbColDef | SQLUINTEGER | 参数的精度; |
7 | ibScale | SQLSMALLINT | 参数的 scale ; |
8 | rgbValue | SQLPOINTER | 指向绑定数据的指针; |
9 | cbValueMax | SQLINTEGER | 当绑定参数为 binary 或者 character 时,指定其字节长度; |
10 | pcbValue | SQLINTEGER * | 指向包含绑定数据长度变量的指针; |
3. 处理操作结果:当数据库操作不涉及到数据的返回时,只需要检查操作的返回码 rc 即可。当数据库操作为数据查询操作时,通常会返回符合查询条件的结果集,此时需要对查询结果进行处理。DB2 ODBC 可以使用 SQLFetch () 、SQLExtendedFetch() 和 SQLFetchScroll() 来得到返回结果。这三个 API 中,SQLFetch () 是最常用的 API ,每次调用返回一条数据,后两个可以通过设置返回结果集的大小和接收数组的大小来得到多条数据。一个使用 SQLFetch () 获取数据的例子如例 2.10 所示:
例 2.10 使用 SQLFetch() 处理返回结果
SQLINTEGER H1INT4;
SQLINTEGER LNH1INT4;
printf("/nSQLBindCol /n");
rc=SQLBindCol(hstmt,1,SQL_C_DEFAULT,(SQLPOINTER) &H1INT4,
(SQLINTEGER)sizeof(H1INT4),(SQLINTEGER *) &LNH1INT4 );
if( rc != SQL_SUCCESS ) goto dberror;
printf("/n SQLFetch /n");
rc=SQLFetch(hstmt);
printf("/nH1INT4 = %i", H1INT4);
例 2.10 中,我们使用了 SQLBindCol() 建立返回结果和接收变量的对应关系,然后通过调用 SQLFetch() 将返回结果取到接收变量中。SQLBindCol() 的参数描述如表 2.11 所示:
表 2.11 SQLBindCol() 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | hstmt | SQLHSTMT | 数据库操作对应的 stmt handle 变量; |
2 | 1 | SQLUSMALLINT | 列序号 , 从 1 开始算起; |
3 | SQL_C_DEFAULT | SQLSMALLINT | 与结果集中的列所对应的 C 的数据类型; |
4 | &H1INT4 | SQLPOINTER | 接收变量的缓存地址; |
5 | sizeof(H1INT4) | SQLINTEGER | 接收变量的最大有效长度; |
6 | &LNH1INT4 | SQLSMALLINT * | 指向包含接受变量实际长度的指针变量; |
4. 事务提交或回滚:DB2 ODBC 使用 SQLEndTran() 或 SQLTransact() 来完成事物的提交和回滚操作。推荐使用 ODBC3.0 标准 API SQLEndTran() 。在默认状态下,DB2 ODBC 会自动提交每一次数据库操作,即每一次数据库操作为一个事务。当需要改变事务提交模式时,可以通过设置 Connection handle 属性 ( 见节 2.1.1) 或者改变 ODBC INI 文件来实现 ( 下面将会具体描述 ) 。一个提交事务的例子如例 2.11 所示:
例 2.11 提交事务
strcpy((char *)sqlstmt, "INSERT INTO TABLE2A VALUES(2)");
printf("/nDsqlstmt=%s",sqlstmt);
rc=SQLExecDirect(hstmt,sqlstmt,SQL_NTS);
if( rc != SQL_SUCCESS ) goto dberror;
printf("/nSQLEndTran SQL_COMMIT ");
rc=SQLEndTran(SQL_HANDLE_DBC,hdbc,SQL_COMMIT);
if( rc != SQL_SUCCESS ) goto dberror;
例 2.11 中 SQLEndTran() 的参数描述如表 2.12 所示:
表 2.12 SQLEndTran() 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | HandleType | SQLSMALLINT | 事务的 handle 类型 , 可以为 SQL_HANDLE_DBC: 提交或回滚 Connection 范围内的事务 SQL_HANDLE_ENV: 提交或回滚 Environment 范围内所有事务 |
2 | Handle | SQLHANDLE | 与参数 1 相对应的 handle 变量 |
3 | CompletionType | SQLSMALLINT | 指定要完成的事务操作类型 , 可以为 SQL_COMMIT 或 SQL_ROLLBACK |
5. 释放 Stmt handle:在成功执行数据库操作后,需要进行释放 Stmt handle。如果还有后续的数据库操作,可以返回到步骤 1 重新分配 Stmt handle 后进行。因为重用 Stmt handle 进行 catalog,参数和返回结果不同的数据库操作时需要进行额外的设置,因此不推荐重用 Stmt handle 进行下一次操作。DB2 ODBC 使用 SQLFreeHandle() 或 SQLFreeStmt() 来释放 Stmt handle,推荐使用 ODBC3.0 API SQLFreeHandle()。一个使用 SQLFreeHandle() 例子如例 2.12 所示:
例 2.12 释放 stmt handle
printf("/nSQLFreeHandle() statement handle ");
rc=SQLFreeHandle(SQL_HANDLE_STMT, hstmtIns );
if( rc != SQL_SUCCESS ) goto dberror;
例 2.12 中 SQLFreeHandle () 的参数描述如表 2.13 所示:
表 2.13 SQLFreeHandle() 参数描述
参数序号 | 参数名称 | 参数类型 | 参数描述 |
1 | HandleType | SQLSMALLINT | 指定需要释放的 handle 类型,可以为 SQL_HANDLE_ENV :释放 Environment handle SQL_HANDLE_DBC :释放 Connection handle SQL_HANDLE_STMT :释放 Stmt handle |
2 | Handle | SQLHANDLE | 与参数 1 相对应的 handle 变量 |
资源释放
主要包括断开数据库连接,释放 Connection 及 Environment handle 等操作。DB2 ODBC 使用 SQLDisconnect() 来断开数据库连接。该 API 只有一个参数,即要释放的 Connection handle 变量,在释放数据库连接之前,需要保证当前连接下所有的事务操作已经提交或回滚。释放后的 Connection handle 可以被重用进行下一次数据库连接。释放 Connection 和 Environment handle 的过程与例 2.12 相似,一个释放资源的例子如例 2.13 所示:
例 2.13 资源释放
printf("/n SQLDisconnect connection handle ");
rc=SQLDisconnect(hdbc);
if( rc != SQL_SUCCESS ) goto dberror; ;
printf("/nSQLFreeHandle() connection handle ");
rc=SQLFreeHandle( SQL_HANDLE_DBC, hdbc );
if( rc != SQL_SUCCESS ) goto dberror;
printf("/nSQLFreeHandle() environment handle ");
rc=SQLFreeHandle( SQL_HANDLE_ENV, henv );
if( rc != SQL_SUCCESS ) goto dberror;
DB2 ODBC 常用技巧
环境参数的设置
在 ODBC 程序中,通常需要对环境参数进行特定的设置从而更好地完成程序的功能。DB2 ODBC 中设置环境参数有两种方法:通过设置初始化文件和动态设置 handle 属性来完成。动态设置 handle 属性在第二章中已经略有提及,本节将详细介绍使用初始化文件进行参数设置。
1. 初始化文件 (INI file) 的参数设置
DB2 ODBC 使用初始化文件来保存 ODBC 程序的一些基本设置,用户可以将程序需要的环境参数保存在初始化文件中,当程序运行时,DB2 ODBC 会自动读取初始化文件中参数的配置并进行对应的设置,从而达到设置环境参数的目的。通常情况下,初始化文件会定义在 DSNAOINI 数据定义语句中,如例 3.1 中的 JCL 代码所示:
例 3.1 使用 JCL 定义初始化文件
//DSNAOINI DD DSN=USER.SRCLIB.DATA(DSNAOINI),DISP=SHR
下面将详细介绍初始化文件参数的设置。
初始化文件由三种不同类型的 section 组成:Common section ,Subsystem section 和 Data source section ,具体描述如下:
Common section :由全局参数组成,这些参数作用于使用此初始化文件的全部程序。使用 [COMMON] 作为 common section 开始的标识符;
Subsystem section :由适用于当前 Subsystem 的参数组成。使用 [Subsystem name] 作为 Subsystem section 开始的标识符。此处的 Subsystem name 应为实际子系统的名称;
Data source section :由适用于当前数据源的参数组成,当程序连接到此数据源时,参数发生作用。一个初始化文件中可以包含 0 个或多个 Data source sections 。使用 [Data source name] 作为此 data source section 开始的标识符。同样,此处的 Data source name 同样应为实际数据源的名称。
一个典型的初始化文件如例 3.2 所示:
例 3.2 初始化文件实例
; common section begin
[COMMON]
MVSDEFAULTSSID=VA1A
CONNECTTYPE=2
; subsystem section begin for V81A
[V81A]
MVSATTACHTYPE=CAF
; DATA SOURCE for STLEC1 begin
[STLEC1]
AUTOCOMMIT=0
如例 3.2 所示,分号表示当前行为注释,Subsystem 名称为 V81A ,Data source name 为 STLEC1 。
常用的 DB2 ODBC 初始化参数如表 3.1 所示,感兴趣的读者可以从《 DB2 Version 9.1 for z/OS ODBC Guide and Reference 》一书中找到更多的参数描述。
表 3.1 常用的 DB2 ODBC 初始化参数
参数名称 | Section 名称 | 描述 | 可取值 | |
CONNECTTYPE | Common | 指定连接数据库的默认连接类型 | 1: 默认值,多个并发连接,每个连接拥有自己独立的提交范围; 2: 多个连接共享同一个分布式提交范围; |
|
MULTICONTEXT | Common | 指定每个数据库连接是否是否创建独立的上下文环境 | 0: 默认值,对于每一个数据库连接并不创建独立的上下文环境; 1: 对于没一个数据库连接创建独立的上下文环境 |
|
MVSDEFAULTSSID | Common | 指定默认连接的的 DB2 subsystem | 默认值为 DSN | |
MVSATTACHTYPE | Subsystem | 指定 DB2 ODBC 连接到 DB2 for z/OS 时使用的 attachment type | CAF: 默认值,call attachment facility (CAF) ; RRSAF: Resource Recovery Services attachment facility (RRSAF) ; |
|
AUTOCOMMIT | Data source | 指定是否自动提交事务 | 1: 自动提交 ( 默认值 ) ; 0: 手动提交; |
|
CURSORHOLD | Data source | 指定是否保留游标 | 1: 默认值,事务提交后游标继续保留下来; 0: 事务提交后游标被释放; |
|
TXNISOLATION | Data source | 设置隔离级别 |
|
|
UNDERSCORE | Data source | 指定下划线是否作为通配符 | 1: 作为通配符使用; 0: 不作为通配符; |
2. 动态设置 Handle 属性
正如在第二章提及的一样,通过设置 handle 属性也可以完成设置环境参数的目的。当初始化文件和 API 同时设置一个参数的属性时,以 API 设置的属性为准,因为 API 设置的属性为运行时生效,会覆盖初始化文件中的设置。常用的设置 handle 属性的 API 如表 3.2 所示:
表 3.2 设置 handle 属性的 API
API 名称 | Handle 类型 | 作用范围 |
SQLSetEnvAttr() | Environment handle | 当前 ODBC 程序本身 |
SQLSetEnvAttr() | Connection handle | 当前 Connection |
SQLSetStmtAttr() | Stmt handle | 当前 Stmt |
数据的批量处理
1. 批量插入数据
DB2 ODBC 提供了使用数组来进行传递参数从而完成数据的批量插入的功能。DB2 ODBC 使用 SQLParamOptions() 来确定批量插入数据的条数。常见的批量插入的流程如下:
a) 准备 Statement;
b) 使用 SQLBindParameter() 绑定参数数组到 Statement;
c) 使用 SQLParamOptions() 设置批量插入的数据条数;
d) 运行 SQLExecute()。
一个完整的批量插入数据的例子如例 3.3 所示:
例 3.3 批量插入数据
#define TC 12
long ix;
SQLINTEGER H1INT4[TC];
SQLINTEGER LNH1INT4[TC];
SQLCHAR INSERT[250];
for (ix=0; ix<=TC-1; ix++) {
H1INT4[ix] = ix;
LNH1INT4[ix] = sizeof(SQLINTEGER);
}
strcpy((char *)INSERT, (char *)"INSERT INTO TABLE2A VALUES(?)");
/* there should be allocate environment, connection, stmt handle here*/
printf("/nSQLPrepare ");
printf("/nsqlstmt=%ls",INSERT);
rc=SQLPrepare(hstmt,INSERT,SQL_NTS);
if( rc != SQL_SUCCESS) goto dberror;
printf("/nSQLBindParameter ");
rc=SQLBindParameter(hstmt,1,SQL_PARAM_INPUT,SQL_C_LONG,
SQL_INTEGER,0,0,H1INT4,0,(SQLLEN *)LNH1INT4);
if( rc != SQL_SUCCESS) goto dberror;
printf("/nSQLParamOptions ");
rc = SQLParamOptions(hstmt, TC, NULL);
if( rc != SQL_SUCCESS) goto dberror;
printf("/nSQLExecute ");
rc=SQLExecute(hstmt);
if ( rc != SQL_SUCCESS ) goto dberror;
例 3.3 将一次性插入 12 条数据,其中 SQLParamOptions() 的参数描述如表 3.3 所示:
表 3.3 SQLParamOptions() 参数描述
参数序号 | 参数 | 参数类型 | 参数描述 |
1 | hstmt | SQLHSTMT | stmt handle 变量 |
2 | crow | SQLUINTEGER | 指定一次性插入的数据条数 |
3 | pirow | SQLUINTEGER * | 指向当前参数数组的指针 |
2. 批量获取查询结果
DB2 ODBC 使用 SQLExtendedFetch() 或 SQLFetchScroll() 来批量获取查询结果,其流程如下:
a)调用 SQLSetStmtAttr() 进行结果集大小的设置。
若使用 SQLExtendedFetch() ,需设置 SQL_ATTR_ROWSET_SIZE 属性;
若使用 SQLFetchScroll() ,需设置 SQL_ATTR_ROW_ARRAY_SIZE 属性。
b)调用 SQLBindCol() 将结果集中的数据和接收数组绑定起来。DB2 ODBC 支持两种不同的绑定方式:列绑定和行绑定。
列绑定是将接收数组与结果集中某一列的返回结果绑定起来,该数组中的元素都是此列的数值,这些数值分属不同的行。
行绑定通常是定义一个 struct 数组,每个 struct 与结果集中一行数据相对应,绑定时将 struct 数组与结果集中的每行数据建立联系,当调用获取 API 时每个 struct 就得到一行返回的数据。此外,DB2 ODBC 默认的绑定方式是列绑定,如果需要进行行绑定,则需要设置 stmt handle 的 SQL_ATTR_BIND_TYPE 属性为行绑定。
c)使用 SQLExtendedFetch() 或 SQLFetchScroll() 来批量获取数据。
使用 SQLExtendedFetch() 并通过列绑定来批量获取查询结果的例子如例 3.4 所示:
例 3.4 SQLExtendedFetch() 批量获取查询结果
/*variable declaration*/
SQLCHAR SELECT[250];
SQLINTEGER H1INT4[TC];
SQLINTEGER LNH1INT4[TC];
long ix;
SQLUINTEGER pcrow;
SQLUSMALLINT Row_Stat[TC];
printf("/n SQLSetStmtattr ");
rc=SQLSetStmtAttr(hstmt, SQL_ATTR_ROWSET_SIZE,(SQLPOINTER)TC,NULL);
if( rc != SQL_SUCCESS) goto dberror;
printf("/n SQLPrepare
");
strcpy((char *)SELECT,(char *)"SELECT INT4 FROM TABLE2A ");
printf("/n sqlstmt=%ls",SELECT);
rc=SQLPrepare(hstmt,SELECT,SQL_NTS);
if( rc != SQL_SUCCESS) goto dberror;
printf("/n SQLBindCol ");
rc=SQLBindCol(hstmt,1,SQL_C_LONG,(SQLPOINTER) H1INT4,
(SQLLEN) sizeof(H1INT4)/TC,(SQLLEN *) LNH1INT4);
if( rc != SQL_SUCCESS) goto dberror;
printf("/n SQLExecute ");
rc=SQLExecute(hstmt);
if( rc != SQL_SUCCESS ) goto dberror;
pcrow = 0;
printf("/n SQLExtendedFetch ");
rc=SQLExtendedFetch(hstmt, SQL_FETCH_NEXT,0,&pcrow,Row_Stat);
if( rc != SQL_SUCCESS ) goto dberror;
/* print the data */
for ( ix=0; ix<TC; ix++ ){
printf("/n %d ROW= %d", ix, H1INT4[ix]);
}
SQLExtendedFetch() 的参数描述如表 3.4 所示:
表 3.4 SQLExtendedFetch() 参数描述
参数序号 | 参数 | 参数类型 | 参数描述 |
1 | hstmt | SQLHSTMT | stmt handle 变量; |
2 | fFetchType | SQLUSMALLINT | 指定获取数据方向,DB2 ODBC 只支持 SQL_FETCH_NEXT ; |
3 | irow | SQLINTEGER | 为以后扩展预留的参数,可用任何整数替代; |
4 | pcrow | SQLUINTEGER * | 返回实际获取的数据数量; |
5 | rgfRowStatus | SQLUSMALLINT * | 返回包含获取数据状态的数组,如果某条数据获取成功,则在数组中对应的元素为 SQL_ROW_SUCCESS ; |
使用 SQLFetchScroll 和行绑定批量获取数据的例子如例 3.5 所示:
例 3.5 使用 SQLFetchScroll 和行绑定批量获取数据
/*variable declaration*/
SQLCHAR SELECT[250];
long ix;
SQLUINTEGER pcrow;
SQLUSMALLINT Row_Stat[TC];
struct{
SQLINTEGER H1INT4;
SQLINTEGER LNH1INT4;
} rows[TC]
printf("/n SQLSetStmtattr ");
rc=SQLSetStmtAttr(hstmt, SQL_ATTR_BIND_TYPE,(SQLPOINTER)(sizeof(rows)/TC), NULL);
if( rc != SQL_SUCCESS) goto dberror;
rc=SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE,(SQLPOINTER)TC, NULL);
if( rc != SQL_SUCCESS) goto dberror;
strcpy((char *)SELECT, (char *)"SELECT INT4 FROM TABLE2A ");
printf("/n SQLPrepare ");
printf("/n sqlstmt=%ls",SELECT);
rc=SQLPrepare(hstmt,SELECT,SQL_NTS);
if( rc != SQL_SUCCESS) goto dberror;
printf("/n SQLBindCol ");
rc=SQLBindCol(hstmt,1,SQL_C_CHAR,(SQLPOINTER) &rows[0].H1INT4,
(SQLLEN) sizeof(rows[0].H1INT4),(SQLLEN *) &rows[0].LNH1INT4);
if( rc != SQL_SUCCESS) goto dberror;
printf("/n SQLExecute ");
rc=SQLExecute(hstmt);
if( rc != SQL_SUCCESS ) goto dberror;
memset(rows,L'/0',sizeof(rows));
memset(Row_Stat,L'/0',sizeof(Row_Stat));
pcrow = 0;
printf("/n SQLFetchScroll ");
rc=SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0);
if( rc != SQL_SUCCESS ) goto dberror;
for( ix=0; ix<TC; ix++ ){
printf("/n %d ROW= %d", ix, rows[ix].H1INT4);
}
例 3.5 使用行绑定时,需要将 stmt handle 的 SQL_ATTR_BIND_TYPE 属性设置为每个 struct 的字节数。SQLFetchScroll() 的参数描述如表 3.5 所示:
例 3.5 SQLFetchScroll() 参数说明
参数序号 | 参数 | 参数类型 | 参数描述 |
1 | hstmt | SQLHSTMT | stmt handle 变量 |
2 | FetchOrientation | SQLUSMALLINT | 获取类型 , 可选值为: SQL_FETCH_NEXT, SQL_FETCH_PRIOR, SQL_FETCH_FIRST, SQL_FETCH_LAST, SQL_FETCH_ABSOLUTE, SQL_FETCH_RELATIVE DB2 ODBC 不支持 SQL_FETCH_BOOKMARK 。 |
3 | FetchOffset | SQLINTEGER | 获取数据的偏移量,当类型为 SQL_FETCH_ABSOLUTE 时,从第 FetchOffset 开始返回结果集,当类型为 SQL_FETCH_RELATIVE 时,从距当前结果集 FetchOffset 的位置开始返回结果集。 |
catalog 的查询
DB2 ODBC 提供了丰富的 API 来查询 DB2 catalog 表,具体的 API 与功能如表 3.5 所示:
表 3.5 DB2 ODBC Catalog API
API 名称 | 描述 |
SQLTables() | 返回系统 catalog 表中表的信息 |
SQLDataSources() | 返回系统可用的数据库信息 |
SQLColumns() | 返回指定表中的列的详细信息 |
SQLColumnPrivileges() | 返回指定表中列及其权限信息 |
SQLTablePrivileges() | 返回系统 catalog 表中表及其权限信息 |
SQLPrimaryKeys() | 返回指定表中主键的详细信息 |
SQLStatistics() | 返回指定表的 index ,cardinality 及 page 等统计信息 |
SQLForeignKeys() | 返回指定表的外键信息 |
SQLSpecialColumns() | 返回指定表的主键和 unique index 的详细信息 |
SQLProcedures() | 返回系统中注册的 stored procedure 的信息 |
一个简单的使用 SQLTables() 返回表信息的例子如例 3.6 所示:
例 3.6 调用 SQLTables() 返回并打印表的信息
struct {
SQLINTEGER ind;
SQLCHAR str[29];
} tabcat, tabschem, tabname, tabtype;
strcpy((char *)tabschem.str, "ADMF001");
printf("/n SQLTables ");
rc=SQLTables(hstmt,NULL,0,tabschem.str,SQL_NTS,NULL, 0, NULL,0);
if (rc != SQL_SUCCESS) goto dberror;
printf("/n SQLBindCol table name ");
rc=SQLBindCol(hstmt, 3, SQL_C_CHAR, (SQLPOINTER)tabname.str,128, &tabname.ind);
if (rc != SQL_SUCCESS){
printf("/nSQLBindCol bind of table NAME failed");
goto dberror;
}
rc=SQLBindCol(hstmt,4,SQL_C_CHAR,(SQLPOINTER)tabtype.str,128, &tabtype.ind);
if (rc != SQL_SUCCESS){
printf("/n SQLBindCol bind of table type failed");
goto dberror;
}
rc=SQLBindCol(hstmt, 2, SQL_C_CHAR, (SQLPOINTER)tabschem.str,128, &tabschem.ind);
if (rc != SQL_SUCCESS){
printf("/n SQLBindCol bind of table schem failed");
goto dberror;
}
rc=SQLFetch(hstmt);
while (rc == SQL_SUCCESS){
printf("/nDMLSEL1M SQLFetch tabschem=%s,tabname=%s,tabtype=%s",
tabschem.str, tabname.str,tabtype.str);
rc=SQLFetch(hstmt);
}
由于篇幅所限,本文将不再对每个 API 的参数说明及用法做详细的介绍,感兴趣的读者可以从《 DB2 Version 9.1 for z/OS ODBC Guide and Reference》中得到详细的描述。
错误处理
1. Return Code
在调用 DB2 ODBC API 时,可以通过 return code 来检验当前操作是否执行成功,常见的 return code 有以下几种:
SQL_SUCCESS:执行成功,没有任何其他信息返回;
SQL_SUCCESS_WITH_INFO:执行成功,同时返回 Warning 或者其他信息。可以通过调用 SQLGetDiagRec() 来获得 SQLSTATE 或其他信息来确定当前状态。常见的 SQL_SUCCESS_WITH_INFO 包括数据库返回的数据出现字符串截断等问题;
SQL_ERROR:执行失败,可以通过 SQLGetDiagRec() 来获得 SQLSTATE 或其它错误信息;
SQL_INVALID_HANDLE:执行失败,且是由于使用了无效的 handle 变量 (Environment,Connection 或 Stmt handle);
SQL_NEED_DATA :运行时缺少必要的参数;
SQL_NO_DATA_FOUND:执行成功,但是没有返回任何数据;
2. SQLCODE 和 SQLSTATE
SQLSTATE 和 SQLCODE 都是通过返回码来确定数据库操作执行时的信息,用来进行错误定位。但是其不同之处在于 SQLSTATE 基于 SQL 标准,因此对于不同的数据库都具有相同的意义。SQLSTATE 是一个五字符的数组。五个字符包含数值或者大写字母,代表各种错误或者警告条件的代码。SQLSTATE 具有层次化的模式:前两个字符通常用来标识错误条件的类别,后三个字符表示在该通用类中的子类。成功的状态是由 00000 标识的。而 SQLCODE 则是根据数据库本身个性化的,会因为数据库产品的不同而有所区别。通常情况下,SQLCODE 返回一个简单的整数。 0 标识成功,正数标识带着额外信息的成功,负数表示一个错误。每个 SQLCODE 代表的信息可以参看《 DB2 Version 9.1 for z/OS Codes 》。在程序开发过程中,可以根据实际情况使用 SQLSTATE 和 SQLCODE 来确定出错的原因。
3. ODBC 错误处理
当 ODBC API 执行完毕时,需要对执行结果进行验证以便确定操作是否执行成功。可以通过检查 return code 的方法来确定执行结果,如果 return code 不为 SQL_SUCCESS,则需要进行相应错误处理。首先根据返回的 return code 确定当前错误类型,然后通过调用 SQLGetSQLCA() 得到 SQLCODE 和 SQLSTATE 等更加详尽的信息,以便精确定位错误。一个完整的错误处理的例子如例 3.7 所示:
例 3.7 错误处理实例
dberror:
printf("/n entry dberror label");
printf("/nrc = %d", rc);
/*make sure the rc type*/
switch (rc) {
case SQL_SUCCESS:
break;
case SQL_INVALID_HANDLE:
printf("/n check_error> SQL_INVALID HANDLE ");
break;
case SQL_ERROR:
printf("/n check_error> SQL_ERROR ");
break;
case SQL_SUCCESS_WITH_INFO:
printf("/n check_error> SQL_SUCCESS_WITH_INFO");
break;
case SQL_NO_DATA_FOUND:
printf("/n check_error> SQL_NO_DATA_FOUND ");
break;
default:
printf("/n check_error> Invalid rc from api rc = %i",rc );
break;
}
/*get the sqlcode and sqlstate*/
printf("/ncall SQLGetSqlca ");
rc = SQLGetSQLCA(henv, hdbc, hstmt, &sqlca);
if( rc == SQL_SUCCESS ) {
int i;
printf("/n*** Printing the SQLCA:/r");
printf("/nSQLCAID .... %s", sqlca.sqlcaid);
printf("/nSQLCABC .... %d", sqlca.sqlcabc);
printf("/nSQLCODE .... %d", sqlca.sqlcode);
printf("/nSQLERRML ... %d", sqlca.sqlerrml);
printf("/nSQLERRMC ... %s", sqlca.sqlerrmc);
printf("/nSQLERRP ... %s", sqlca.sqlerrp);
for (i = 0; i < 6; i++)
printf("/nSQLERRD%d ... %d",i+1, sqlca.sqlerrd[i]);
for (i = 0; i < 10; i++)
printf("/nSQLWARN%d ... %c", i, sqlca.sqlwarn[i]);
printf("/nSQLSTATE ... %s", sqlca.sqlstate);
}
else
printf("/nSQLGetSQLCA failed rc = %i", rc);
如例 3.7 所示,dberror 标识着此处进入错误处理模块。在每步的 ODBC API 调用结束时 , 进行 rc 检验,如果不为 SQL_SUCCESS,则进入此模块以确定更加详细的错误信息。函数 SQLGetSQLCA() 调用时接收四个参数 , 分别为运行时的 Environment handle,Connection handle,Stmt handle 和指向结构 sqlca 的指针。
总结
本文在简单介绍了 DB2 ODBC 的背景,与传统的 ODBC 的区别及优点的基础上,详细描述了 DB2 ODBC 程序的概念模型和程序开发流程,对于常用编程接口的使用方法给出了详细的实例,并且对于环境参数的设置、数据批量处理、catalog 查询、错误处理等 ODBC 比较重要的模块给出了多种处理方法。本文在由浅入深的讲解的基础上,辅以大量生动的实例,使得读者在了解基本原理的同时,能够快速掌握开发 DB2 ODBC 应用的方法,对于相关人员具有很大的参考价值。但由于笔者水平有限和篇幅限制,疏漏之处在所难免,欢迎大家批评指证。此外,感兴趣的读者可以从《 DB2 Version 9.1 for z/OS ODBC Guide and Reference》一书中得到关于 DB2 ODBC 程序开发更加详细的描述。