执行存储过程 NJS-012 时出现 node-oracledb 错误

Posted

技术标签:

【中文标题】执行存储过程 NJS-012 时出现 node-oracledb 错误【英文标题】:node-oracledb error when executing stored procedure NJS-012 【发布时间】:2019-01-13 19:15:45 【问题描述】:

我正在运行 Node 8.9.4、Hapi 17.4 和 Oracledb 2.2。

尝试调用存储过程时,我收到错误“NJS-012:在参数 2 中遇到无效的绑定数据类型”。我所做的一切似乎都无法解决这个问题。调用该过程的代码是:

async function getSavedViews(req, h, server) 
    let connection = await server.app.db.getConnection();

    let bindVars = 
        P_USER_NAME:   req.payload.user_name,
        P_CONTENT_TYPE: req.payload.content_type,
        P_PROJECT_NUMBER: req.payload.project_number,
        OP_GRID_TAB_TYP:  dir: server.app.db.BIND_OUT, type: server.app.db.ARRAY  
    

    let res = server.methods.response();

    try 
        res.error = false;
        res.msg = "Retrieved saved views.";
        res.data = await connection.execute(
            `BEGIN APPS.XXETA_GRID_USER_CONTEXT_PKG.EXTRACT_GRID_DETAILS(:P_USER_NAME, :P_CONTENT_TYPE, :P_PROJECT_NUMBER, :OP_GRID_TAB_TYP); END;`,
            bindVars
        );
     catch (err) 
        server.app.logger.error(err.message);
        res.error = true;
        res.msg = err.message,
        res.data = [];
    

    return res;

存储过程描述为:

我从记录器中得到的错误是:2018-08-06 15:02:20 ERROR NJS-012:在参数 2 中遇到无效的绑定数据类型

任何帮助将不胜感激。

更新:

作为绑定变量的复杂类型如下所示...

CREATE OR REPLACE TYPE XXETA_GRID_CONTEXT_REC_TYP AS OBJECT
   (
        GRID_VIEW_ID NUMBER (15),
        GRID_VIEW_NAME VARCHAR2 (240),
        USER_NAME VARCHAR2 (30),
        PROJECT_NUMBER  VARCHAR2 (5)
   )

【问题讨论】:

node-oracledb 4 现在支持用户定义类型的绑定,请参阅oracle.github.io/node-oracledb/doc/api.html#objects 【参考方案1】:

2019 年 8 月 28 日更新:

Node-oracledb 在 v4(2019 年 7 月 25 日发布)中增加了对 SQL 对象类型和 PL/SQL 记录类型的支持。有关详细信息,请参阅文档的此部分: https://oracle.github.io/node-oracledb/doc/api.html#objects

鉴于与之前列出的完全相同的对象,现在可以使用以下 javascript 来完成这项工作,而代码行数比以前少得多:

const oracledb = require('oracledb');
const config = require('./db-config.js');

async function runTest() 
  let conn;

  try   
    const sql = 
     `call xxeta_grid_user_context_pkg.extract_grid_details(
        p_user_name      => :P_USER_NAME,
        p_content_type   => :P_CONTENT_TYPE,
        p_project_number => :P_PROJECT_NUMBER,
        op_grid_tab_typ  => :OP_GRID_TAB_TYP
      )`;

    const binds = 
      P_USER_NAME: 'Jane Doe',
      P_CONTENT_TYPE: 'Some Content Type',
      P_PROJECT_NUMBER: '123',
      OP_GRID_TAB_TYP: 
        dir: oracledb.BIND_OUT,
        type: 'HR.XXETA_GRID_CONTEXT_TAB_TYP'
       
    

    conn = await oracledb.getConnection(config);

    const result = await conn.execute(
      sql,
      binds
    );

    const gridContexts = [];

    for (let x = 0; x < result.outBinds.OP_GRID_TAB_TYP.length; x += 1) 
      gridContexts.push(
        gridViewId: result.outBinds.OP_GRID_TAB_TYP[x].GRID_VIEW_ID,
        gridViewName: result.outBinds.OP_GRID_TAB_TYP[x].GRID_VIEW_NAME,
        userName: result.outBinds.OP_GRID_TAB_TYP[x].USER_NAME,
        projectNumber: result.outBinds.OP_GRID_TAB_TYP[x].PROJECT_NUMBER
      );
    

    console.log(gridContexts);
   catch (err) 
    console.error(err);
   finally 
    if (conn) 
      try 
        await conn.close();
       catch (err) 
        console.error(err);
      
    
  


runTest();

上一个答案:

目前不支持复杂类型。您指定的外绑定属于此类别。在直接支持此类类型之前,您需要添加一些包装代码以将复杂类型分解为一个或多个简单类型。我在这里展示了一个例子: https://jsao.io/2017/01/plsql-record-types-and-the-node-js-driver/

那篇文章的目标是调用一个接受自定义记录类型数组的存储过程。要调用它,我首先必须声明一些要绑定的简单数组类型。然后我可以使用这些数组来创建更复杂的数组并调用该过程。

在你的情况下,你需要做相反的事情。在 PL/SQL 块中,声明一个类型为 APPS.XXETA_GRID_CONTEXT_TAB_TYP 的局部变量。然后,在调用过程之后,遍历数组并使用它来填充一些简单的数组(VARCHAR2、NUMBER 或 DATE)并将它们用作您的输出绑定。

更新:

只要你有以下物品:

create or replace type xxeta_grid_context_rec_typ as object (
  grid_view_id   number(15),
  grid_view_name varchar2(240),
  user_name      varchar2(30),
  project_number varchar2(5)
)
/

create or replace type xxeta_grid_context_tab_typ as table of xxeta_grid_context_rec_typ
/

create or replace package xxeta_grid_user_context_pkg
as

procedure extract_grid_details(
  p_user_name      in varchar2,
  p_content_type   in varchar2,
  p_project_number in varchar2,
  op_grid_tab_typ  out xxeta_grid_context_tab_typ
);

end;
/

create or replace package body xxeta_grid_user_context_pkg
as

procedure extract_grid_details(
  p_user_name      in varchar2,
  p_content_type   in varchar2,
  p_project_number in varchar2,
  op_grid_tab_typ  out xxeta_grid_context_tab_typ
)

is

  l_xxeta_grid_context_rec xxeta_grid_context_rec_typ;

begin

  op_grid_tab_typ := xxeta_grid_context_tab_typ();

  for x in 1 .. 3
  loop
    l_xxeta_grid_context_rec := xxeta_grid_context_rec_typ(
      grid_view_id   => x,
      grid_view_name => 'Some Grid View',
      user_name      => p_user_name,
      project_number => p_project_number
    );

    op_grid_tab_typ.extend();

    op_grid_tab_typ(x) := l_xxeta_grid_context_rec;
  end loop;

end;

end;
/

以下 Node.js 代码可以调用存储过程并从复杂的 out 参数中获取值。

const oracledb = require('oracledb');
const config = require('./dbConfig.js');

async function runTest() 
  let conn;

  try 
    const userName = 'Jane Doe';
    const contentType = 'Some Content Type';
    const projectNumber = '123';

    // This is what we want to populate with records/objects that come out
    // of the procedure.
    const gridContexts = [];

    // We start by declaring some other arrays, one for each field in the
    // xxeta_grid_context_rec_typ type.
    const gridViewIds = [];
    const gridViewNames = [];
    const userNames = [];
    const projectNumbers = []; 

    conn = await oracledb.getConnection(config);

    // Then we execute the procedure with a little wrapper code to populate
    // the individual arrays.
    let result = await conn.execute(
     `declare

        -- This is a local variable that you'll use to get the out data from
        -- the procedure.
        l_xxeta_grid_context_tab xxeta_grid_context_tab_typ;

      begin

        xxeta_grid_user_context_pkg.extract_grid_details(
          p_user_name      => :user_name,
          p_content_type   => :content_type,
          p_project_number => :project_number,
          op_grid_tab_typ  => l_xxeta_grid_context_tab
        );

        -- Now that the local variable is populated, iterate over it to
        -- populate the individual out binds.
        for x in 1 .. l_xxeta_grid_context_tab.count
        loop
          :grid_view_ids(x) := l_xxeta_grid_context_tab(x).grid_view_id;
          :grid_view_names(x) := l_xxeta_grid_context_tab(x).grid_view_name;
          :user_names(x) := l_xxeta_grid_context_tab(x).user_name;
          :project_numbers(x) := l_xxeta_grid_context_tab(x).project_number;
        end loop;

      end;`,
      
        user_name: userName,
        content_type: contentType,
        project_number: projectNumber,
        grid_view_ids: 
          dir: oracledb.BIND_OUT,
          type: oracledb.NUMBER,
          maxArraySize: 200
        ,
        grid_view_names: 
          dir: oracledb.BIND_OUT,
          type: oracledb.STRING,
          maxArraySize: 200
        ,
        user_names: 
          dir: oracledb.BIND_OUT,
          type: oracledb.STRING,
          maxArraySize: 200
        ,
        project_numbers: 
          dir: oracledb.BIND_OUT,
          type: oracledb.STRING,
          maxArraySize: 200
        
      
    );

    // At this point you can access the individual arrays to populate the 
    // original target array with objects. This is optional, you can work
    // with the individual arrays directly as well.
    for (let x = 0; x < result.outBinds.grid_view_ids.length; x += 1) 
      gridContexts.push(
        gridViewId: result.outBinds.grid_view_ids[x],
        gridViewName: result.outBinds.grid_view_names[x],
        userName: result.outBinds.user_names[x],
        projectNumber: result.outBinds.project_numbers[x]
      );
    

    console.log(gridContexts);
   catch (err) 
    console.error(err);
   finally 
    if (conn) 
      try 
        await conn.close();
       catch (err) 
        console.error(err);
      
    
  


runTest();

希望对您有所帮助!对复杂类型的直接支持在增强列表中,只是不能说什么时候会落地。

【讨论】:

我是否必须更改数据库上的存储过程?如果是这样,那是行不通的。我根本没有能力改变数据库。如果这个问题听起来很愚蠢,请原谅。我对 PLSQL 几乎一无所知。 不,您不必更改存储过程。请告诉我 APPS.XXETA_GRID_CONTEXT_TAB_TYP 的样子,我会用一个例子更新我的答案。 谢谢丹。我一发现就会这样做。我对数据库的权限只让我看到签名。我看不到那个自定义类型是什么样子的。 DBA 必须告诉我。 丹,我已经用复杂类型更新了原帖。

以上是关于执行存储过程 NJS-012 时出现 node-oracledb 错误的主要内容,如果未能解决你的问题,请参考以下文章

执行存储过程“PLS-00103”时出现此错误

使用 MySqlCommand 执行存储过程时出现 SqlNullValueException

示例数据 - “执行包含更新和插入语句的存储过程时出现问题”

使用存储过程执行 PL/SQL 代码时出现问题

oracle 执行存储过程时出现卡死

在oracle中执行alter存储过程时出现无效表名错误