直接路径装载的过程其实跟普通插入没有太大的区别,只是设置更复杂,步骤觉得杂乱,其实理解了它的原理,就能自然知道需要哪些步骤。我们先看看插入数据有哪些问题。
-
要插入数据的表名称是什么?表属于哪个schema?
-
要插入的表有哪些字段啊?字段名称是什么?数据类型是什么?最大长度是多少?
-
程序中的数据怎样和字段关联起来啊?
-
怎样把程序数据转换成数据库的数据?
-
怎样把数据库数据存储起来啊?
有了这些问题,我们看看直接路径装载是怎样解决这些问题的。
首先要分配一个句柄来存储表的信息,这个句柄叫做直接路径上下文dpctx,分配句柄后要设置它的属性,其中有表的schema信息,表的名称信息,表的字段个数。然后从dpctx中得到字段列表描述符,针对每个字段设置字段名称,数据类型,数据最大长度这些属性。接着要分配一个句柄来存放表的字段信息,这个句柄叫直接路径字段数组dpca,这个句柄就能把程序中的数据和装载的字段关联起来,使用OCIDirPathColArrayEntrySet()函数设置每行每个字段的数据。最后分配一个代表数据库数据的句柄,这个句柄叫做直接路径流dpstr,这个句柄代表数据库服务器端的数据。dpca代表OCI程序客户端的数据,通过OCIDirPathColArrayToStream()函数就能把客户端的数据转换成服务器端的数据格式,通过OCIDirPathLoadStream()函数就能指示服务器端的数据存储到表的块中。最后通过OCIDirPathFinish()函数提交装载的数据,相当于一般插入后的commit操作。
下面我们把前几节中的代码片段组合成一个完整的例子,看看直接路径装载的完整过程。还是以表test_tab为例,表的属主用户(schema)是scott,字段是ID,NAME和ADDR三个,装载4次数据,每次100行,看看循环设置数据入口和重置状态都在哪些位置来操作。
cpp
OCIEnv *envhp = NULL;
OCIError *errhp = NULL;
OCIServer *svrhp = NULL;
OCISession *usrhp = NULL;
OCISvcCtx *svchp = NULL;
struct dp_columns {
ub4 dtyp; /* 字段类型 */
ub4 clen; /* 字段最大长度 */
char name[32]; /* 字段名称 */
};
int dp_load(void)
{
int i, r;
sword rc, status;
ub4 buf_sz;
ub4 ncol;
ub4 rowcnt;
ub4 rowoff;
ub4 cvtcnt;
OCIDirPathCtx *dpctx;
OCIDirPathColArray *dpca;
OCIDirPathStream *dpstr;
OCIParam *colLst = NULL;
OCIParam *colDsc = NULL;
struct dp_columms col[3];
ub4 id[100];
char name[100][32];
char addr[100][32];
/* 分配直接路径上下文句柄,父句柄是envhp */
rc = OCIHandleAlloc((void *)envhp, (void **)&dpctx,
OCI_HTYPE_DIRPATH_CTX, 0, (void **)NULL);
if (rc != OCI_SUCCESS) {
fprintf(stderr, "Allocate direct path context error !\n");
return (-1);
}
/* 设置表的schema,在上下文句柄中设置 */
if (check_oci_error(errhp,
OCIAttrSet((void *)dpctx, (ub4)OCI_HTYPE_DIRPATH_CTX,
(void *)"scott", strlen("scott"),
(ub4)OCI_ATTR_SCHEMA_NAME, errhp)
) < 0)
return (-1);
/* 设置表名称,在上下文句柄中设置 */
if (check_oci_error(errhp,
OCIAttrSet((void *)dpctx, (ub4)OCI_HTYPE_DIRPATH_CTX,
(void *)"test_tab", strlen("test_tab"),
(ub4)OCI_ATTR_NAME, errhp)
) < 0)
return (-1);
/* 设置转换缓冲区的大小为2M,缓冲区大小要与字段数组相适应,
* 太小的话一次转换缓冲区不够,会返回OCI_CONTINUE,需要再次转换
*/
buf_sz = 2 * 1024 * 1024;
if (check_oci_error(errhp,
OCIAttrSet((void *)dpctx, (ub4)OCI_HTYPE_DIRPATH_CTX,
(void *)&buf_sz, 0,
(ub4)OCI_ATTR_BUF_SIZE, errhp)
) < 0)
return (-1);
/* 设置表的字段个数 */
ncol = 3;
if (check_oci_error(errhp,
OCIAttrSet((void *)dpctx, (ub4)OCI_HTYPE_DIRPATH_CTX,
(void *)&ncol, 0,
(ub4)OCI_ATTR_NUM_COLS, errhp)
) < 0)
return (-1);
/* 获取字段列表描述符,设置字段信息 */
if (check_oci_error(errhp,
OCIAttrGet((void *)dpctx, (ub4)OCI_HTYPE_DIRPATH_CTX,
(void *)&colLst, (ub4)0,
(ub4)OCI_ATTR_LIST_COLUMNS, errhp)
) < 0)
return (-1);
/* 为了方便设置,我们定义一个结构存储字段信息 */
col[0].dtyp = SQLT_INT;
col[0].clen = 8;
strcpy(col[0].name, "ID");
col[1].dtyp = SQLT_CHR;
col[1].clen = 30;
strcpy(col[1].name, "NAME");
col[2].dtyp = SQLT_CHR;
col[2].clen = 200;
strcpy(col[2].name, "ADDR");
for (i=0; i<ncol; i++) {
/* 获取字段描述符,这个描述符是隐式获得,需要释放,否则会造成内存泄露
* 这里字段的位置从1开始编号
*/
if (check_oci_error(errhp,
OCIParamGet((const void *)colLst, (ub4)OCI_DTYPE_PARAM,
errhp, (void **)&colDsc, (ub4)(i+1))
) < 0)
return (-1);
/* 设置字段的属性,字段名称 */
if (check_oci_error(errhp,
OCIAttrSet((void *)colDsc, (ub4)OCI_DTYPE_PARAM,
(void *)col[i].name, (ub4)strlen(col[i].name),
(ub4)OCI_ATTR_NAME, errhp)
) < 0)
return (-1);
/* 设置字段数据类型 */
if (check_oci_error(errhp,
OCIAttrSet((void *)colDsc, (ub4)OCI_DTYPE_PARAM,
(void *)&col[i].dtyp, (ub4)0,
(ub4)OCI_ATTR_DATA_TYPE, errhp)
) < 0)
return (-1);
/* 设置字段的数据最大长度 */
if (check_oci_error(errhp,
OCIAttrSet((void *)colDsc, (ub4)OCI_DTYPE_PARAM,
(void *)&col[i].clen, (ub4)0,
(ub4)OCI_ATTR_DATA_SIZE, errhp)
) < 0)
return (-1);
}
/* 释放掉字段描述符 */
OCIDescriptorFree((void *)colDsc, (ub4)OCI_DTYPE_PARAM);
/* 设置完所有字段后,要把字段列表的描述符也释放掉 */
OCIDescriptorFree((void *)colLst, (ub4)OCI_DTYPE_PARAM);
/* 设置完属性后,准备直接路径装载 */
if (check_oci_error(errhp,
OCIDirPathPrepare(dpctx, svchp, errhp)) < 0)
return (-1);
/* 分配直接路径字段数组句柄,父句柄是dpctx */
rc = OCIHandleAlloc((void *)dpctx, (void **)&dpca,
OCI_HTYPE_DIRPATH_COLUMN_ARRAY, 0, (void **)NULL);
if (rc != OCI_SUCCESS) {
fprintf(stderr, "Allocate direct path column array handle error !\n");
return (-1);
}
/* 分配直接路径流句柄,父句柄是dpctx */
rc = OCIHandleAlloc((void *)dpctx, (void **)&dpstr,
OCI_HTYPE_DIRPATH_STREAM, 0, (void **)NULL);
if (rc != OCI_SUCCESS) {
fprintf(stderr, "Allocate direct path stream handle error !\n");
return (-1);
}
/* 循环4次,每次装载100行数据 */
for (r=0; r<4; r++) {
/* 这里生成100行数据,在真实的环境中数据可能来自文件或其他数据源
* 为了演示方便,直接把name和addr设置成了相同的数据
*/
for (i=0; i<100; i++) {
id = i + 100; /* id从100开始计数 */
strcpy(name[i], "AAAAAAAAAA");
strcpy(addr[i], "BBBBBBBBBBBBBBBB");
}
/* 循环设置每一行每一列的数据入口 */
for (i=0; i<100; i++) {
/* 设置第一列ID的数据入口,列索引是从0开始的 */
if (check_oci_error(errhp,
OCIDirPathColArrayEntrySet(dpca, errhp, (ub4)i, (ub2)0,
(ub1*)&id[i], 4, OCI_DIRPATH_COL_COMPLETE)
) < 0)
return (-1);
/* 设置第二列NAME的数据入口,列索引是1 */
if (check_oci_error(errhp,
OCIDirPathColArrayEntrySet(dpca, errhp, (ub4)i, (ub2)1,
(ub1 *)name[i], (ub4)strlen(name[i]), OCI_DIRPATH_COL_COMPLETE)
) < 0)
return (-1);
/* 设置第三列ADDR的数据入口,列索引是2 */
if (check_oci_error(errhp,
OCIDirPathColArrayEntrySet(dpca, errhp, (ub4)i, (ub2)2,
(ub1 *)addr[i], (ub4)strlen(addr[i]), OCI_DIRPATH_COL_COMPLETE)
) < 0)
return (-1);
}
/* 重置直接路径字段数组的状态 */
if (check_oci_error(errhp,
OCIDirPathColArrayReset(dpca, errhp)) < 0)
return (-1);
/* 重置直接路径流的状态 */
if (check_oci_error(errhp,
OCIDirPathStreamReset(dpstr, errhp)) < 0)
return (-1);
/* 转换字段数组到流数据,然后装载流数据,一共转换100行数据,从第0行开始 */
rowcnt = 100;
rowoff = 0;
while (1) {
status = OCIDirPathColArrayToStream(dpca, dpctx, dpstr,
errhp, (ub4)rowcnt, (ub4)rowoff);
if (status == OCI_CONTINUE) {
/* 转换缓冲区过小,数据没有转换完,得到已经转换的行数 */
if (check_oci_error(errhp,
OCIAttrGet((const void *)dpca,
OCI_HTYPE_DIRPATH_COLUMN_ARRAY,
(void *)(&cvtcnt), (ub4 *)0,
OCI_ATTR_ROW_COUNT, errhp)
) < 0)
return (-1);
rowcnt -= cvtcnt;
rowoff += cvtcnt;
} else {
if (check_oci_error(errhp, status) < 0)
return (-1);
}
/* 装载数据 */
if (check_oci_error(errhp,
OCIDirPathLoadStream(dpctx, dpstr, errhp)) < 0)
return (-1);
/* 如果前面的转换全部完成,现在装载也成功了,说明这100条数据装载完成
* 退出while()循环
*/
if (status == OCI_SUCCESS)
break;
/* 程序到这里,说明还有未转换完的数据,重置直接路径流的状态,进行下一次转换,
* 下次转换从新的rowoff位置开始,转换条数为新的rowcnt条
*/
if (check_oci_error(errhp,
OCIDirPathStreamReset(dpstr, errhp)) < 0)
return (-1);
} /* while()循环的结束边界 */
/* 装载完了100条数据,进入下一循环,装载另外的100条数据,或者全部装载完退出循环 */
} /* for()循环的结束边界 */
/* 数据全部装载完,提交装载的数据 */
if (check_oci_error(errhp,
OCIDirPathFinish(dpctx, errhp)) < 0)
return (-1);
/* 释放前面分配的句柄 */
OCIHandleFree((void *)dpca, OCI_HTYPE_DIRPATH_COLUMN_ARRAY);
OCIHandleFree((void *)dpstr, OCI_HTYPE_DIRPATH_STREAM);
OCIHandleFree((void *)dpctx, OCI_HTYPE_DIRPATH_CTX);
return (0);
}
到这里OCI的直接路径装载就介绍完了,上面的例子演示了一个完整的过程,包括数据转换中遇到的一些问题的解决方法,多看几遍就能对直接路径装载的流程全面掌握。