OCI:如何绑定对象类型的输出参数

时间:2015-01-29 16:36:16

标签: oracle stored-procedures plsql oci

我正在尝试从基于OCI的客户端调用存储过程(在oracle db 11g上)。该过程包含对象类型的单个OUT参数。

问题:我总是得到“ORA-21525:属性号或(索引处的集合元素)%s违反了它的约束”错误。

如果有人能给我一个暗示可能是什么原因,我将非常感激。

注意:有趣的是,在下列情况下一切正常:

  • 我将返回类型从对象类型替换为此类对象的嵌套表。
  • 我将返回类型替换为某种原始类型,例如NUMBER
  • 我将此参数指向IN并以相同方式绑定它。

另外,我发现了以下内容:

  • 同样的错误存在,无论我是从程序返回结果作为OUT参数还是我从FUNCTION返回用户。
  • 如果我尝试通过PLSQL脚本调用存储过程,一切都按预期进行,没有错误。
  • 我还尝试创建由两个OCINumber字段组成的“并行”C ++结构,并使用其对象而不是调用OCIObjectNew(),但得到相同的错误。
  • 我还尝试设置pOutParam = NULL并绑定它,但后来我遇到了“访问冲突,从地址00000读取”。

这是代码。 PLSQL对象类型:

CREATE OR REPLACE TYPE dl_fake_type AS OBJECT
(
    attr_one NUMBER(12,0),
    attr_two NUMBER(12,0)
);
/

程序(为简洁起见,我跳过程序声明)

PROCEDURE dl_fake_fun(out_result OUT dl_fake_type)
IS
    l_result dl_fake_type;
BEGIN
    SELECT dl_fake_type(23, 35) INTO l_result FROM DUAL;
    out_result := l_result;
END dl_fake_fun;

C ++ OCI代码(为简洁起见,没有连接初始化)。注意:我使用MSVS2013和一些C ++ 11特性,比如std :: string :: front()方法。

// ...

typedef basic_string<OraText, char_traits<OraText>, allocator<OraText> > OraTextString;
typedef basic_ostringstream<OraText, char_traits<OraText>, allocator<OraText> > OraOStringStream;

// Check if ociStatus == OCI_SUCCESS. If not, then print error and assert.
void checkOciStatus(const sword ociStatus, OCIError * errorHandle = NULL);


// ...

const OraTextString DL_FAKE_TYPE = (OraText const *)"DL_FAKE_TYPE";
const OraTextString outParamName = (OraText const *)":out_param";

OraOStringStream query(ios::ate);
query << "BEGIN my_dummy_pkg.dl_fake_fun(" << outParamName << "); END;";
const OraTextString & queryString = query.str();
OCIStmt * statement;
const ub4 executionMode = OCI_DEFAULT;
checkOciStatus(OCIHandleAlloc(envhp, (void **)&statement, OCI_HTYPE_STMT, /* xtramemsz */ 0, /* usrmempp */ NULL), errhp);
checkOciStatus(OCIStmtPrepare(statement, errhp, queryString.c_str(), queryString.length(), OCI_NTV_SYNTAX, executionMode), errhp);

const OraTextString schemaName = (OraText const *)"MY_SCHEMA_NAME";
OCIType * typeDescriptor = NULL;
checkOciStatus(
    OCITypeByName(
        envhp, 
        errhp, 
        svchp, 
        schemaName.c_str(), 
        schemaName.size(), 
        DL_FAKE_TYPE.c_str(), 
        DL_FAKE_TYPE.length(), 
        /* version name */ NULL, 
        /* version name length */ 0, 
        OCI_DURATION_SESSION, 
        OCI_TYPEGET_HEADER, 
        &typeDescriptor
        ), 
    errhp
    );

OCIBind* bindHandle = NULL;
checkOciStatus(OCIBindByName(statement, &bindHandle, errhp, outParamName.c_str(), outParamName.length(), NULL, 0, SQLT_NTY, NULL, NULL, NULL, 0, NULL, executionMode), errhp);

void * pOutParam = NULL;
checkOciStatus(OCIObjectNew(envhp, errhp, svchp, OCI_TYPECODE_REF, typeDescriptor, NULL, OCI_DURATION_DEFAULT, /* true = value, false = ref */ FALSE, &pOutParam), errhp);

checkOciStatus(OCIBindObject(bindHandle, errhp, typeDescriptor, &pOutParam, NULL, NULL, 0), errhp);

checkOciStatus(OCIStmtExecute(svchp, statement, errhp, 1, 0, NULL, NULL, executionMode), errhp);

cout << "executed." << endl;

// ...

提前致谢。

1 个答案:

答案 0 :(得分:0)

经过一段时间的挣扎,我找到了适合我的解决方案。

有两点对成功绑定至关重要:

  1. 要绑定的对象必须通过调用OCIObjectNew()来创建,因为它是在问题提供的代码中完成的。注意:我还考虑过直接创建表示相应C ++类型的结构实例的选项。但即使满足第二个条件,这也不起作用。
  2. 创建对象后,必须初始化其所有字段。即类型OCINumber的字段必须设置为某个特定值(例如OCINumberFromInt()),指向OCIString的字段必须设置为NULL
  3. 输出对象绑定的另一个gocha(在绑定输出嵌套表时也会观察到)是传递给OCIBindObject的对象的第二个指针是 second 指针,原因是:看来,OCI将其视为输出对象的指针数组。这意味着,包含第一个指针(指向第二个指针)的变量必须有效才能调用OCIExecuteStatement。这个meens,如果你想提取绑定进程来分离函数,那么你必须传递 second 指向对象的指针,然后将其传递给OCIBindObject()。否则它将无法工作。

    我希望,这些提示可以帮助某人避免在OCI(和Oracle DB)文档的丛林中经历数小时的挫折和痛苦的挣扎。