使用OCI,长SQL语句将非常大的int数组插入到Oracle DB中

时间:2014-07-01 23:00:56

标签: sql c oracle oci

我正在接管一个OCI应用程序,我需要在表中插入一个1414个整数序列。本应用程序的前一个编写者基本上是以

形式动态构建SQL插入语句

INSERT INTO <TABLE_NAME> (<COLUMNS...>) VALUES (<VALUES...>) 通过一系列strcpys,sprintfs,strcats等。 并存储在静态分配的缓冲区char insertStatement[BUFLEN]中,其中BUFLEN采用任何必要的大小。

然后使用

准备和执行该陈述
OCIStmtPrepare(...);
OCIStmtExecute(...);

到目前为止,此工作正常;但是现在 我有一个表格,其中SDATA_ARRAY类型的列定义为

create or replace TYPE     SDATA_ARRAY 
AS VARRAY(1414) OF integer;

需要以某种方式通过OCI将1414个短整数插入此表。尝试使用构建文字SQL语句的就地策略在此失败,因为结果字符串的长度大约为6000个字符,并且尝试执行它会产生错误

Error msg: ORA-01704: string literal too long

所以我显然需要在这里改变策略,并且无论如何以这种方式使用strcats实际上是注入漏洞。所以我能说的解决方案是使用绑定。我一直在http://docs.oracle.com/cd/E11882_01/appdev.112/e10646/oci05bnd.htm#LNOCI050阅读Oracle的OCI文档,并了解如何绑定到原始数​​据类型。但是,我无法弄清楚如何使用具有用户定义的OCIBindByName()类型的VARRAY之类的函数。我想尝试以下内容:

unsigned short sdata[1414];
...
strcpy(insertStatement, "INSERT INTO TABLE(SDATA) VALUES (SDATA_ARRAY(:sdata));");
OCIStmtPrepare(stmthp, errhp, (text *) insertStatement, (ub4) strlen(insertStatement), 
               (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT);
// OCIBindByName(stmthp, &bindp, errhp, (text *) ":sdata", (sb4) strlen(":sdata"), 
//               sizeof(sdata), ???, ...);
OCIStmtExecute(svchp, stmthp, errhp, (ub4) 1, (ub4) 0, (OCISnapshot *) NULL, 
               (OCISnapshot *) NULL, OCI_DEFAULT);

显然确保检查每次通话的错误。

  • 我对OCIBindByName()中的ub2 dty参数有什么用,用于指定绑定变量的类型? (与SQLT_STR,SQLT_INT等相反)我是否需要进行额外的OCI调用?

  • 有没有更好的方法来使这个插入语句(使用严格的OCI)不会导致我得到长度错误?也许是递增的?

  • 有没有比VARRAY类型更好的方法来存储这些1414个整数? (除了与DBA交谈之外,并没有真正改变我的权力,但是......)

  • 最后,使用绑定调用甚至允许我超过长度错误?

在这个项目之前我没有使用SQL,Oracle或OCI的经验,这是给我的唯一原因是因为我对C最熟悉。因此,非常感谢任何与我正在尝试做的相关的建议/批评/替代想法!

编辑:此页面http://docs.oracle.com/cd/B14117_01/appdev.101/b10779/oci03typ.htm似乎暗示VARRAY的类型常量是“命名数据类型”的SQLT_NTY,将其与对象和嵌套表放在同一类别中。但是,SQLT_NTY似乎映射到C代码中的结构,而我的数据存储在short数组中。我认为这意味着我必须将数组包装在一个结构中才能使其工作,可能使用Oracle的Object Type Translator。但是,仍然不确定绑定语句是什么样的,以及我是否需要添加其他步骤,例如OCIBindObject()。

1 个答案:

答案 0 :(得分:2)

因此,为了使用这样的用户定义数据类型进行处理,必须在对象模式下初始化OCI:OCIInitialize(OCI_OBJECT, ...)

将数组绑定到语句需要一些额外的工作。我最终拼凑在一起的解决方案(通过大量搜索和回溯)具有一般形式:

OCIArray *array = NULL;
OCIType *tdo = NULL;
OCIBind *bndp = NULL;

char *typeOwner = "OWNER"; // Whichever user owns the type definition
char *typeName = "SDATA_ARRAY";

OCITypeByName(envhp, errhp, svchp, (text *) typeOwner, strlen(typeOwner),
              (text *) typeName, strlen(typeName), NULL, 0, 
              OCI_DURATION_SESSION, OCI_TYPEGET_HEADER, &tdo);

OCIObjectNew(envhp, errhp, svchp, OCI_TYPECODE_VARRAY, tdo, NULL, 
             OCI_DURATION_SESSION, TRUE, &array);

for ( int i = 0; i < 1414; i++ ) {
    OCINumber num_val;
    OCINumberFromInt(errhp, sdata[i], sizeof(unsigned short), 
                     OCI_NUMBER_UNSIGNED, &num_val);
    OCICollAppend(envhp, errhp, &num_val, NULL, array);
}
OCIStmtPrepare(stmthp, errhp, statement, strlen(statement),
               OCI_NTV_SYNTAX, OCI_DEFAULT);
OCIBindByName(stmthp, &bnpd, errhp, ":sdata", strlen(":sdata"), 
              NULL, 0, SQLT_NTY, NULL, 0, 0, 0, 0, OCI_DEFAULT);

OCIBindObject(bndp, errhp, tdo, &array, NULL, NULL, NULL);

OCIStmtExecute(svchp, stmthp, errhp, 1, 0, NULL, NULL, OCI_COMMIT_ON_SUCCESS);

OCIObjectFree(envhp, errhp, array, OCI_OBJECTFREE_FORCE);

(为了简洁起见,我没有包含错误检查/处理,但它已经存在。)

此解决方案首先获取自定义数据类型(OCITypeByName(...))并创建它的实例。接下来,Oracle提供了一组函数来操作泛型集合(例如VARRAY或嵌套表),因此我使用一个(OCICollAppend(...))迭代地将我的数据加载到Oracle的数据类型中以表示VARRAY,{ {1}}。

接下来,我按照惯例准备语句,并使用OCIArray作为类型代码调用OCIBindByName(...)。根据我收集的内容,这是用于任何自定义数据类型的代码。这之后必须调用SQLT_NTY,它负责用户定义对象所需的一些额外工作。

使用这些步骤,我能够成功插入数据。它可能不是最佳解决方案,但我认为如果有人遇到同样的问题我会学到什么(因为OCI文档是一个巨大的纠结混乱),或者如果有人可以建议的话改进。