上一节我们介绍了LOB定位符的绑定和定义操作,这里重点强调一下定义操作,这个行为一般用于LOB SELECT操作,LOB SELECT操作是所有LOB操作的基础,所有的关于LOB操作的OCI函数都是针对LOB定位符的,LOB SELECT的目的就是为了得到一个LOB定位符,一个LOB表的每行数据的每个LOB字段都对应一个LOB定位符,所以处理一行数据的多个LOB字段也要得到多个LOB定位符。下面看看LOB写操作的步骤。
1. 准备LOB SELECT语句,一般SQL语句文本为SELECT lob_column FROM lob_table WHERE XXXX=XXXX FOR UPDATE。
2. 定义输出的LOB定位符。这个定位符在之前要分配好,不用设置为空LOB。
3. 执行LOB SELECT语句。
4. 执行OCIStmtFetch()操作,把LOB定位符和LOB字段关联起来。
5. 打开LOB定位符,使用OCILobOpen()函数。
6. 写入LOB数据,可以循环写入多次LOB数据,使用OCILobWrite2()函数。
7. 关闭LOB定位符,使用OCILobClose()函数。
8. 提交数据库改变。
先看看用到的OCI函数原型和参数。
打开LOB函数。
sword OCILobOpen ( OCISvcCtx *svchp,
OCIError *errhp,
OCILobLocator *locp,
ub1 mode );
svchp是一个输入参数,是OCI服务上下文句柄。
errhp是一个输入/输出参数,错误句柄,用于返回错误码和错误信息文本。
locp是一个输入/输出参数,需要打开的LOB定位符。
mode是一个输入参数,打开的方式,取值OCI_LOB_READONLY或OCI_LOB_READWRITE。
关闭LOB函数。
sword OCILobClose ( OCISvcCtx *svchp,
OCIError *errhp,
OCILobLocator *locp );
svchp是一个输入参数,是OCI服务上下文句柄。
errhp是一个输入/输出参数,错误句柄,用于返回错误码和错误信息文本。
locp是一个输入/输出参数,需要关闭的LOB定位符。
写LOB函数。
sword OCILobWrite2 ( OCISvcCtx *svchp,
OCIError *errhp,
OCILobLocator *locp,
oraub8 *byte_amtp,
oraub8 *char_amtp,
oraub8 offset,
void *bufp,
oraub8 buflen,
ub1 piece,
void *ctxp,
OCICallbackLobWrite2 (cbfp)
(
void *ctxp,
void *bufp,
oraub8 *lenp,
ub1 *piecep
void **changed_bufpp,
oraub8 *changed_lenp
)
ub2 csid,
ub1 csfrm );
svchp是一个输入/输出参数,是OCI服务上下文句柄。
errhp是一个输入/输出参数,错误句柄,用于返回错误码和错误信息文本。
locp是一个输入/输出参数,需要操作的LOB定位符,唯一引用一个LOB。
byte_amtp是一个输入/输出参数,写入LOB的字节数,在BLOB写入时使用。
char_amtp是一个输入/输出参数,写入LOB的字符个数,在CLOB写入时使用,BLOB写入时忽略这个参数。
offset是一个输入参数,写入LOB的绝对偏移量,从LOB头开始计算。对CLOB计算单位是字符,对BLOB计算单位是字节。offset的位置从1开始计算。如果使用流写入方式,只需要在第一次调用写函数时设置offset值,后续的写函数可以忽略offset,函数会自动判断。
bufp是一个输入参数,是写入的LOB数据的缓冲区指针。
buflen是一个输入参数,表示LOB数据缓冲区中数据的大小,以字节计算。
piece是一个输入参数,表示写入的是哪个数据片。取值为OCI_ONE_PIECE,流模式中为OCI_FIRST_PIECE,OCI_NEXT_PIECE和OCI_LAST_PIECE。
ctxp是一个输入参数,是回调函数的上下文指针,可以设置为NULL。
cbfp是一个输入参数,是回调函数的函数指针,不使用回调函数设置为NULL。
csid是一个输入参数,是缓冲区中数据的字符集ID,只对CLOB起作用。
csfrm是一个输入参数,是缓冲区数据的字符集形式,取值SQLCS_IMPLICIT表示与数据库字符集一致,取值SQLCS_NCHAR表示使用国际字符集。
我们使用上一节中创建的表test_clob_tab,里面已经插入一条空LOB数据,现在看看怎样写入LOB数据,我们向LOB中写入20次,每次写入4000字符,使用流模式写入。代码如下。
OCIEnv *envhp = NULL;
OCIError *errhp = NULL;
OCIServer *svrhp = NULL;
OCISession *usrhp = NULL;
OCISvcCtx *svchp = NULL;
OCIStmt *smthp = NULL;
int write_to_lob(void)
{
int i;
sword rc;
sb4 ec;
ub1 piece;
int slen;
oraub8 amt;
OCIDefine *defp;
OCILobLocator *locp;
char sqltxt[1024];
text errbuf[512];
char buf[4096];
/* 分配LOB定位符 */
rc = OCIDescriptorAlloc((const void *)envhp, (void **)&locp,
OCI_DTYPE_LOB, 0, (void **)NULL);
if (rc != OCI_SUCCESS) {
OCIErrorGet(envhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);
fprintf(stderr, "OCIDescriptorAlloc() - [%d] %s\n", ec, errbuf);
return (-1);
}
/* 下面执行LOB SELECT操作 */
strcpy(sqltxt, "SELECT MESSAGE FROM test_clob_tab WHERE ID=1 FOR UPDATE");
slen = strlen(sqltxt);
rc = OCIStmtPrepare(smthp, errhp, (const OraText *)sqltxt, slen,
OCI_NTV_SYNTAX, OCI_DEFAULT);
if (rc != OCI_SUCCESS) {
OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);
fprintf(stderr, "OCIStmtPrepare() - [%d] %s\n", ec, errbuf);
return (-1);
}
/* 定义LOB定位符输出 */
rc = OCIDefineByPos((OCIStmt *)smthp,
(OCIDefine **)&defp,
(OCIError *)errhp,
(ub4)1,
(void *)&locp,
(sb4)sizeof(OCILobLocator *),
(ub2)SQLT_CLOB,
(void *)NULL,
(ub2 *)NULL,
(ub2 *)NULL,
(ub4)OCI_DEFAULT);
if (rc != OCI_SUCCESS) {
OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);
fprintf(stderr, "OCIDefineByPos() - [%d] %s\n", ec, errbuf);
return (-1);
}
/* 执行语句 */
rc = OCIStmtExecute(svchp,
smthp, /* stmthp */
errhp, /* errhp */
0, /* iters */
0, /* rowoff */
NULL, /* snap_in */
NULL, /* snap_out */
OCI_DEFAULT); /* mode */
if (rc != OCI_SUCCESS) {
OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);
fprintf(stderr, "OCIExecute() - [%d] %s\n", ec, errbuf);
return (-1);
}
/* 执行fetch操作 */
rc = OCIStmtFetch(smthp, errhp, 1, OCI_FETCH_NEXT, OCI_DEFAULT);
if (rc != OCI_SUCCESS) {
OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);
fprintf(stderr, "OCIStmtFetch() - [%d] %s\n", ec, errbuf);
return (-1);
}
/* 打开LOB */
rc = OCILobOpen(svchp, errhp, locp, OCI_LOB_READWRITE);
if (rc != OCI_SUCCESS) {
OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);
fprintf(stderr, "OCILobOpen() - [%d] %s\n", ec, errbuf);
return (-1);
}
for (i=0; i<20; i++) {
if (i == 0)
piece = OCI_FIRST_PIECE;
else if (i == 19)
piece = OCI_LAST_PIECE;
else
piece = OCI_NEXT_PIECE;
sprintf(buf, "%02d", i);
memset(&buf[2], 3998, 'A');
amt = 4000;
rc = OCILobWrite2(svchp, errhp, locp, NULL, &amt, 1,
buf, 4000, piece, 0, SQLCS_IMPLICIT);
if (rc != OCI_SUCCESS) {
OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);
fprintf(stderr, "OCILobWrite2() - [%d] %s\n", ec, errbuf);
return (-1);
}
}
/* 关闭LOB */
rc = OCILobClose(svchp, errhp, locp);
if (rc != OCI_SUCCESS) {
OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);
fprintf(stderr, "OCILobClose() - [%d] %s\n", ec, errbuf);
return (-1);
}
/* 提交改变 */
rc = OCITransCommit(svchp, errhp, OCI_DEFAULT);
if (rc != OCI_SUCCESS) {
OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);
fprintf(stderr, "OCITransCommit() - [%d] %s\n", ec, errbuf);
return (-1);
}
/* 释放LOB定位符 */
rc = OCIDescriptorFree((void *)locp, OCI_DTYPE_LOB);
if (rc != OCI_SUCCESS) {
OCIErrorGet(errhp, 1, NULL, &ec, errbuf, 512, OCI_HTYPE_ERROR);
fprintf(stderr, "OCIDescriptorFree() - [%d] %s\n", ec, errbuf);
return (-1);
}
return (0);
}
在上面的写入中使用了流模式,在第一次写入时使用OCI_FIRST_PIECE,最后一次写入使用OCI_LAST_PIECE,其他的写入使用OCI_NEXT_PIECE,只需要在第一次写入时指定写入偏移量1,就可以从头写入数据,后续写操作的偏移量都被忽略了。写入函数的csid设置为0,数据使用环境变量NLS_LANG定义的字符集。
如果不使用流模式写入,那么写入操作使用OCI_ONE_PIECE,每次写入一片数据,每次要自己计算偏移量的位置,好处是可以随机向任意位置写入数据。
在写数据前要打开LOB,写完后要关闭LOB,跟写一个文件相似。如果不打开LOB也可以直接调用写函数,不过每次写操作Oracle还是会隐式的打开LOB,写完一次后隐式关闭LOB,这样在大量多次写入时会影响效率,所以在写之前打开LOB是一个好习惯。