概述
接着上一篇文章《Oracle调用接口(OCI)源码剖析(1):创建数据库连接》,我们继续对OCI中执行sql语句并获取结果的源码进行剖析。该操作主要是由两个函数完成的:CDbExecsql和CDbFetch,其中CDbExecsql函数用于执行普通sql语句,CDbFetch函数用于获取数据库的返回结果。
下面对这两个函数的源码进行分析。
1.执行普通sql语句的操作
在OCI中,执行普通sql语句的操作是由CDbExecsql函数实现的,其代码如下:
INT32 CDbExecsql(void *pHandle,INT8 *psql)
{
CDb *hDb = (CDb *)pHandle;
CDbRecordset *pRec = NULL;
OCIHDBC hdbc;
sword rc = (sword)0;
INT32 row_num = 0;
if ((NULL == hDb) || (NULL == hDb->hdbc) || (NULL == hDb->hRec))
{
return OCI_ERROR;
}
hdbc = hDb->hdbc;
pRec = hDb->hRec;
/* 初始化结果集行数据Buf */
memset((void *)pRec,0,sizeof(CDbRecordset));
/* 申请语句句柄 */
rc = OCIHandleAlloc((dvoid *)hdbc->envhp,(dvoid**)&hdbc->stmthp,(ub4)OCI_HTYPE_STMT,(size_t)0,(dvoid **)0);
if (RETCODE_IS_FAILURE(rc))
{
DoDbErrProc(hdbc->errhp,rc,"OCIHandleAlloc[OCI_HTYPE_STMT]");
return OCI_ERROR;
}
/* 准备执行数据库操作 */
rc = DoDbsqlExecute(hDb,(text *)psql);
if (RETCODE_IS_FAILURE(rc))
{
/************************************************ * 如果语句是事务,则以下语句可保证不锁表; * * 如果不是事务,也无关大局 * ************************************************/
if (OCI_STMT_SELECT != pRec->sqltype)
{
OCITransRollback(hdbc->svchp,hdbc->errhp,(ub4)0);
}
/* 需要释放stmt句柄 */
DoStmtFree(hdbc->stmthp);
return OCI_ERROR;
}
/* 对于非Select操作,直接返回 */
if (OCI_STMT_SELECT != pRec->sqltype)
{
/* 获取受delete insert update所影响的列数 */
rc = OCIAttrGet((dvoid *)hdbc->stmthp,(dvoid *)&row_num,(ub4 *)0,OCI_ATTR_ROW_COUNT,hdbc->errhp);
if (RETCODE_IS_FAILURE(rc))
{
row_num = OCI_ERROR;
}
/* 需要释放stmt句柄 */
DoStmtFree(hdbc->stmthp);
if ( row_num == OCI_ERROR )
{
return C_OS_FAIL;
}
return C_OS_SUCCESS;
}
/* 定义结果集 */
rc = DoDbDefine(hDb);
if ((OCI_ERROR == rc) || (0 == rc))
{
rc = DoDbErrProc(hdbc->errhp,"DoDbDefine");
/* 需要释放stmt句柄 */
DoStmtFree(hdbc->stmthp);
return rc;
}
return OCI_SUCCESS;
}
从该函数的代码实现中,我们可以看到:
1)执行普通sql语句包括这几步主要操作:第一步,申请语句句柄;第二步,执行数据库操作;第三步,对于非Select操作直接返回,对于Select操作定义结果集。
2)申请语句句柄操作是由OCIHandleAlloc函数实现的,它是OCI底层自带的函数。
3)执行数据库操作是由DoDbsqlExecute函数实现的,该函数主要执行这几步操作:第一步,执行OCIStmtPrepare函数准备sql语句;第二步,执行OCIAttrGet函数获取数据库操作类型;第三步,执行OCIStmtExecute函数进行具体的数据库操作。
4)定义结果集操作是由DoDbDefine函数实现的,该函数主要执行这几步操作:第一步,执行OCIAttrGet函数提取数据库操作返回列的列数;第二步,执行OCIParamGet函数在语句句柄中指定参数描述符;第三步,执行OCIAttrGet函数获得返回结果集列数据类型;第四步,执行OCIAttrGet函数获得返回结果集列名称;第五步,执行OCIDefineByPos函数定义输出变量。
2.获取数据库的返回结果的操作
在OCI中,获取数据库的返回结果的操作是由CDbFetch函数实现的,其代码如下:
INT32 CDbFetch(void *pHandle,INT8 *pData,INT32 maxlen)
{
CDb *hDb = (CDb*)pHandle;
CDbRecordset *pRec = NULL;
OCIHDBC hdbc;
sword rc =(sword)0;
INT32 row_num = 0,iLoop,pos = 0;
if ((NULL == hDb) || (NULL == hDb->hdbc) || (NULL == hDb->hRec))
{
return OCI_ERROR;
}
hdbc = hDb->hdbc;
pRec = hDb->hRec;
if ((NULL == hdbc->stmthp) || (NULL == hdbc->errhp))
{
return OCI_ERROR;
}
/* 获取结果数据 */
memset((void *)pData,maxlen);
memset(pRec->pRecordBuf,CDB_MAX_COL_NUM * CDB_MAX_COL_WIDTH);
rc = OCIStmtFetch(hdbc->stmthp,hdbc->errhp,1,OCI_FETCH_NEXT,OCI_DEFAULT);
if (OCI_NO_DATA == rc)
{
DoStmtFree(hdbc->stmthp);
return CDB_FETCH_NO_DATA;
}
rc = DoDbErrProc(hdbc->errhp,"OCIStmtFetch");
if ((OCI_SUCCESS != rc) && (OCI_SUCCESS_WITH_INFO != rc))
{
/* 需要释放stmt句柄 */
DoStmtFree(hdbc->stmthp);
return OCI_ERROR;
}
/* 向pData填充结果集数据 */
for (iLoop = 0; iLoop < pRec->colCount; iLoop++)
{
int len;
/* 清除尾部空格 */
len = pRec->pColWidth[iLoop];
switch (pRec->pColType[iLoop])
{
case sqlT_AFC:
case sqlT_AVC:
case sqlT_CHR:
case sqlT_STR:
len = DoDbrTrim(pRec->pRecordBuf[iLoop],sizeof(pRec->pRecordBuf[iLoop]));
break;
default:
break;
}
/* 注意:如果长度不够,返回值也是成功的 */
if (pos + pRec->pColWidth[iLoop] > maxlen)
{
break;
}
/* 拷贝实际长度数据 */
memcpy((void *)(pData + pos),(const void *)pRec->pRecordBuf[iLoop],len);
/* 偏转定义长度 */
pos += pRec->pColWidth[iLoop];
}
return OCI_SUCCESS;
}
从该函数的代码实现中,我们可以看到:
1)该函数主要完成将CDbExecsql函数执行sql语句之后的结果拷贝到输出缓存(pData)的操作。
2)在获取结果数据之前,先用OCIStmtFetch函数来提取固定数目的记录,同时也可判断数据库中是否有结果数据返回,如果没有,则不执行后续操作。
3)注意函数中的for循环,它将数据库返回的结果按照列的顺序依次拷贝到输出缓存中。其中,数据库返回数据的列数是pRec->colCount,每列宽度存放在pRec->pColWidth[CDB_MAX_COL_NUM]数组中,每列的具体内容存放在pRec->pRecordBuf[CDB_MAX_COL_NUM][CDB_MAX_COL_WIDTH]二维数组中。
执行sql语句并获取结果的CDbExecsql和CDbFetch函数调用
CDbExecsql和CDbFetch函数调用要在获取了数据库的连接之后,也就是说,要连上数据库之后才能获取结果。
CDbExecsql和CDbFetch函数调用的示例代码如下:
INT32 main(void)
{
INT8 szDBServerName[50] = {0};
INT8 szDBName[50] = {0};
INT8 szDBUser[50] = {0};
INT8 szDBPwd[50] = {0};
INT8 szsqlBuf[100] = {0};
INT8 szRcvBuf[100] = {0};
INT32 iRetVal = 0;
void *pDBHandle = NULL;
// 获取数据库各参数的值
memcpy(szDBServerName,"db192_1_8_13",strlen("db192_1_8_13"));
memcpy(szDBName,"dbp_166",strlen("dbp_166"));
memcpy(szDBUser,strlen("dbp_166"));
memcpy(szDBPwd,strlen("dbp_166"));
// 连接数据库
pDBHandle = CDbCreateDb("Oracle",szDBServerName,szDBName,szDBUser,szDBPwd);
if (pDBHandle == NULL) // 连接失败
{
printf("ConnectDB Failed! ServiceName:%s,DBName:%s,User:%s,Pwd:%s\n",szDBPwd);
return -1;
}
printf("ConnectDB success! ServiceName:%s,szDBPwd);
// 执行sql语句并获取结果
// 获取sql语句
memcpy(szsqlBuf,"select Boxnumber from tb_test where id=1",strlen("select Boxnumber from tb_test where id=1"));
// 调用CDbExecsql函数执行sql语句
iRetVal = CDbExecsql(pDBHandle,szsqlBuf);
if (iRetVal != 0) // 执行失败
{
printf("CDbExecsql Failed! RetVal=%d (ServiceName:%s,Pwd:%s)\n",iRetVal,szDBPwd);
return -1;
}
// 调用CDbFetch函数获取数据库返回的结果
iRetVal = CDbFetch(pDBHandle,szRcvBuf,100);
if (iRetVal != 0) // 执行失败
{
printf("CDbFetch Failed! RetVal=%d (ServiceName:%s,szDBPwd);
return -1;
}
// 打印从数据库中获取到的结果
printf("RcvBuf=%s\n",szRcvBuf);
return 0;
}
说明:
1)CDbExecsql函数的两个输入参数分别是:数据库句柄和sql语句缓存,CDbFetch函数的三个输入参数分别是:数据库句柄、结果集缓存及缓存大小。其中,数据库句柄是由CDbCreateDb函数执行之后获得的。