Giter Club home page Giter Club logo

nodejs-idb-pconnector's Introduction

npm ryver-chat ryver-signup docs

idb-pconnector - Promise-based DB2 Connector for IBM i

Project Status: (production ready as a "technology preview")

Objective: provide a promise-based database connector for DB2 on IBM i.

This project is a promise-based wrapper over the idb-connector project that enables the use of modern JavaScript's async/await syntax.

Connection Pooling is supported by using the DBPool class.

The DBPool class includes integrated aggregates (runSql and prepareExecute) to simplify your code.

Table of Contents

Install

npm install idb-pconnector

NOTE

This package only installs on IBM i systems.

Examples

exec

Using exec method to run a select statement and return the result set:

const { Connection, Statement, } = require('idb-pconnector');

async function execExample() {
  const connection = new Connection({ url: '*LOCAL' });
  const statement = new Statement(connection);

  const results = await statement.exec('SELECT * FROM QIWS.QCUSTCDT');

  console.log(`results:\n ${JSON.stringify(results)}`);
}

execExample().catch((error) => {
  console.error(error);
});

prepare bind execute

Using prepare, bind, and execute methods to insert data:

insert example

const {
  Connection, Statement, IN, NUMERIC, CHAR,
} = require('idb-pconnector');

async function pbeExample() {
  const connection = new Connection({ url: '*LOCAL' });
  const statement = new Statement(connection);
  const sql = 'INSERT INTO US_STATES(id, name, abbr, region) VALUES (?,?,?,?)';

  await statement.prepare(sql);
  await statement.bindParameters([1, 'Alabama', 'AL' ,'south']);
  await statement.execute();
}

pbeExample().catch((error) => {
  console.error(error);
});

select example

const {
  Connection, Statement, IN, CHAR,
} = require('idb-pconnector');

async function pbeExample() {
  const connection = new Connection({ url: '*LOCAL' });
  const statement = new Statement(connection);
  const sql = 'SELECT * FROM QIWS.QCUSTCDT WHERE CITY = ? AND STATE = ?';

  await statement.prepare(sql);
  await statement.bindParameters(['Dallas','TX']);
  await statement.execute();
  
  let resultSet = await statement.fetchAll();
  
  console.log(resultSet) // array with response
}

pbeExample().catch((error) => {
  console.error(error);
});

DBPool

Using DBPool to return a connection then call a stored procedure:

const { DBPool } = require('idb-pconnector');

async function poolExample() {
  const pool = new DBPool();

  const connection = pool.attach();

  const statement = connection.getStatement();

  const sql = `CALL QSYS2.SET_PASE_SHELL_INFO('*CURRENT', '/QOpenSys/pkgs/bin/bash')`

  await statement.prepare(sql);
  await statement.execute();

  if (results) {
    console.log(`results:\n ${JSON.stringify(results)}`);
  }
  await pool.detach(connection);
}

poolExample().catch((error) => {
  console.error(error);
});

prepareExecute

Using prepareExecute method to insert data:

const { DBPool } = require('idb-pconnector');

async function prepareExecuteExample() {
  /*
   * Prepare and execute an SQL statement.
   * Params are passed as an array values.
   * The order of the params indexed in the array
   * should map to the order of the parameter markers
   */
  const pool = new DBPool();

  const sql = 'INSERT INTO QIWS.QCUSTCDT VALUES (?,?,?,?,?,?,?,?,?,?,?) with NONE';

  const params = [4949, 'Johnson', 'T J', '452 Broadway', 'MSP', 'MN',
    9810, 2000, 1, 250, 0.00];

  await pool.prepareExecute(sql, params);
}

prepareExecuteExample().catch((error) => {
  console.error(error);
});

runSql

Using runSql method to directly execute a select statement:

NOTE

This method will not work with stored procedures use prepareExecute() instead.

const { DBPool } = require('idb-pconnector');

async function runSqlExample() {
  /*
   * Directly execute a statement by providing the SQL to the runSql() function.
   */
  const pool = new DBPool();

  const results = await pool.runSql('SELECT * FROM QIWS.QCUSTCDT');

  if (results) {
    console.log(`results:\n ${JSON.stringify(results)}`);
  }
}

runSqlExample().catch((error) => {
  console.error(error);
});

setLibraryList

Change to system naming and set the library list (using CHGLIBL) of the connection.

const { Connection } = require('idb-pconnector');

async function setLibListExample() {
  const connection = new Connection({ url: '*LOCAL' });

  await connection.setLibraryList(['QIWS', 'QXMLSERV']);

  const statement = connection.getStatement();
  const results = await statement.exec('SELECT * FROM QCUSTCDT');
  console.log(`results:\n ${JSON.stringify(results)}`);
  await statement.close();
}

setLibListExample().catch((error) => {
  console.error(error);
});

Documentation

Please read the docs.

License

MIT

Contributing

Please read the contribution guidelines.

Release

For information on making a release, read RELEASE.md.

nodejs-idb-pconnector's People

Contributors

aaronbartell avatar abmusse avatar brianmjerome avatar danny-hcs avatar dependabot[bot] avatar i3uckwheat avatar jhonnattan123 avatar jorgecolonconsulting avatar kadler avatar patrickhrastnik avatar theprez avatar worksofliam avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nodejs-idb-pconnector's Issues

Need to patch how idb-connector dbstmt, dbconn are deleted in idb-connector.js

When you have an application that uses both idb-connector and idb-pconnector packages you can run into dbstmt and dbconn not being available when requiring idb-connector in your app.

I noticed this issue while running one of my sample web apps with latest release of idb-pconnector 1.0.3.

This caused by the way dbstmt and dbconn are deleted in idb-pconnector.js

Before Patch

const idb = require('idb-connector');
const Connection = require('./connection');
const Statement = require('./statement');
const DBPool = require('./dbPool');

module.exports.Connection = Connection;
module.exports.Statement = Statement;
module.exports.DBPool = DBPool;

// do not export dbstmt and dbconn from idb-connector
delete idb.dbstmt;
delete idb.dbconn;
// export constants from idb-connector
module.exports = { ...module.exports, ...idb };

These properties were removed from the required idb-connector object to prevent re-exporting those function in idb-pconnector.

Instead the propreties should have been removed from module.exports object which gets a clone of the properties with the spread operator.

After Patch

const idb = require('idb-connector');
const Connection = require('./connection');
const Statement = require('./statement');
const DBPool = require('./dbPool');

module.exports.Connection = Connection;
module.exports.Statement = Statement;
module.exports.DBPool = DBPool;

// export constants from idb-connector
module.exports = { ...module.exports, ...idb };

// do not export dbstmt and dbconn from idb-connector
delete module.exports.dbstmt;
delete module.exports.dbconn;

I knew that require() function caches modules to speed things up.

What I did not know is that native addons do not get reloaded when object in the require cache changes. See this

In any case, by requiring idb-connector in idb-pconnector.js it gets cached then dbstmt and dbconn are deleted from the object.

if you require idb-connector again somewhere else the cached version without dbstmt and dbconn is returned.

This problem can be easily shown in the node REPL

Before Patch

โŒ idb-connector cannot reference dbconn dbstmt

> require('idb-pconnector')
{ Connection: [Function: Connection],
  Statement: [Function: Statement],
  DBPool: [Function: DBPool],
  SQL_FALSE: 0,
  SQL_TRUE: 1,
  SQL_NTS: -3,
  SQL_SQLSTATE_SIZE: 5,
  ...
}
> require('idb-connector')
{ SQL_FALSE: 0,
  SQL_TRUE: 1,
  SQL_NTS: -3,
  SQL_SQLSTATE_SIZE: 5,
  ....
}

After Patch

โœ”๏ธ idb-connector can reference dbstmt dbconn

{ Connection: [Function: Connection],
  Statement: [Function: Statement],
  DBPool: [Function: DBPool],
  SQL_FALSE: 0,
  SQL_TRUE: 1,
  SQL_NTS: -3,
  SQL_SQLSTATE_SIZE: 5,
  ...
}
> require('idb-connector')
{ dbconn: [Function: dbconn],
  dbstmt: [Function: dbstmt],
  SQL_FALSE: 0,
  SQL_TRUE: 1,
  SQL_NTS: -3,
  SQL_SQLSTATE_SIZE: 5,
}

Will open a PR with this patched

Code Simplification

Original report by Brian Jerome (Bitbucket: bjerome, GitHub: bjerome).


I don't have access to make changes to this repo, but I'd like to suggest some code improvements for idb-pconnector.js

Before

#!js

function makeBool(data) {
	if (data == 1)
		return true;
	else if (data == 0)
		return false;
	else
		return data;
}

After

#!js

function makeBool(data) {
	return data === 0 || data === 1 ? !!data : data;
}

Before

#!js

function isInt(data) {
	if (typeof data === 'number')
		return true;
	else
		return false;
}

After

#!js

function isInt(data) {
	return typeof data === 'number';
}

Add CI setup

We can add a Jenkins setup from our internal Jenkins, but non-IBMers wouldn't be able to access it. Alternatively we could use TravisCI and mock up nodejs-idb-connector.

Example code does not work

Hi

I have tried using the examples out of pconnector and the regular connector, but both return with the same error.

const {dbconn, dbstmt} = require('idb-connector');
const sSql = 'SELECT STATE FROM QIWS.QCUSTCDT';
const connection = new dbconn();
connection.conn("*LOCAL");
const statement = new dbstmt(connection);

statement.exec(sSql, (x) => {
  console.log("%s", JSON.stringify(x));
  statement.close();
  connection.disconn();
  connection.close();
});

Gives me:

ERROR: SQLALLOCENV(1)/home/ALAN3/prod_demo/test.js:5
const statement = new dbstmt(connection);
                  ^

Error: SQLSTATE=PAERR SQLCODE=8013 The Dbconn Object is not connected
    at Object.<anonymous> (/home/ALAN3/prod_demo/test.js:5:19)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:743:3)

The pconnector gives me:

ERROR: SQLALLOCENV(1)Example app listening on port 9123!

Any initial ideas what the issue could be? If these examples are supposed to be working, what can I check on my system to determine the issue. I am using Node.js 10.15.0 and I am on IBM i 7.3.

Detect when to bind a parameter as a CLOB within formatParams()

Original report by me.


When using the DBPool method prepareExecute(sql, params)

formatParams() will set the bind Indicator for you by checking the type of the variable that is being bound. Well this works ok execpt if you want to bind a CLOB.

@aaronbartell @ThePrez @brianmjerome Any thoughts on how to detect the parameter should be bound as a CLOB?

#!js

formatParams(params) {
    let boundParams = [],
      me = this,
      parameter,
      SQL_PARAM_INPUT_OUTPUT = idbp.SQL_PARAM_INPUT_OUTPUT;

    for ( parameter of params  ){
      if (typeof parameter === 'string'){ //String
        boundParams.push([parameter, SQL_PARAM_INPUT_OUTPUT, idbp.SQL_BIND_CHAR]);
      } else if (typeof parameter === 'number') { //Number
        let indicator;
        Number.isInteger(parameter) ? indicator = idbp.SQL_BIND_INT : indicator = idbp.SQL_BIND_NUMERIC;
        boundParams.push( [parameter, SQL_PARAM_INPUT_OUTPUT, indicator] );
      } else if (typeof parameter === null) { //Null
        boundParams.push([parameter, SQL_PARAM_INPUT_OUTPUT, idbp.SQL_BIND_NULL_DATA]);
      } else if (Buffer.isBuffer(parameter)){ //Binary/blob
        boundParams.push([parameter, SQL_PARAM_INPUT_OUTPUT, idbp.SQL_BIND_BINARY]);
      } else if (typeof parameter === 'boolean'){ //Boolean
        boundParams.push([parameter, SQL_PARAM_INPUT_OUTPUT, idbp.SQL_BIND_BOOLEAN]);
      } else {
        me.log(`Parameter that caused error was ${JSON.stringify(parameter)}`);
        throw TypeError('Parameters to bind should be String, Number, null, or Buffer');
      }
    }
    me.log(`Size of BoundParams: ${boundParams.length} \n ${JSON.stringify(boundParams)}`);
    return boundParams;
  }

DBPool creates 16 connections when incrementsSize = 10

const pool = new DBPool({ url: "*LOCAL" }, { debug: true , incrementSize: 10});

The above statement creates 16 connections. Is this expected?

The following shows in the log file.

Creating Connection 0...
Connection 0 created
Creating Connection 1...
Connection 1 created
Creating Connection 2...
Connection 2 created
Creating Connection 3...
Connection 3 created
Creating Connection 4...
Connection 4 created
Creating Connection 5...
Connection 5 created
Creating Connection 6...
Connection 6 created
Connection 7 created
Creating Connection 8...
Connection 8 created
Creating Connection 9...
Connection 9 created
Creating Connection 0...
Connection 0 created
Creating Connection 1...
Connection 1 created
Creating Connection 2...
Connection 2 created
Creating Connection 3...
Connection 3 created
Creating Connection 4...
Connection 4 created
Creating Connection 5...
Connection 5 created
Creating Connection 6...
Connection 6 created
Creating Connection 7...
Connection 7 created

Cannot pass null as part of parameter array to prepareExecute()

Version 1.0.4

When trying to pass following params array to prepareExecute()

[ 'Nicholas Runolfsdottir V',
  'Maxime_Nienow',
  '[email protected]',
  'Ellsworth Summit',
  'Suite 729',
  'Aliyaview',
  '45169',
  { value: null },
  { value: null },
  '586.493.6943 x140',
  { value: null } ]

one gets the following error:

Error: The parameter object must define a value property
    at params.forEach (/home/GUENEY/code/obi/node_modules/idb-pconnector/lib/dbPool.js:332:17)
    at Array.forEach (<anonymous>)
    at DBPool.setupParams (/home/GUENEY/code/obi/node_modules/idb-pconnector/lib/dbPool.js:327:12)
    at DBPool.prepareExecute (/home/GUENEY/code/obi/node_modules/idb-pconnector/lib/dbPool.js:260:38)

The problem lies in the dbPool.js' setupParams function:

if (!value) {
    throw new Error('The parameter object must define a value property');
}

Although necessary, this change isn't enough. During my tests, I noticed that dbstmt in idb-connector can't actually handle a null parameter.

Error: PE: Failed to bindParam()
    at statement.bindParam.catch (/home/GUENEY/code/obi/node_modules/idb-pconnector/lib/dbPool.js:262:20)
Caused By:
 Error: SQLBindParmeter FAILED
SQLSTATE=HY009 SQLCODE=-99999 Fehler bei SQL Call Level Interface aufgetreten.
    at stmt.bindParam (/home/GUENEY/code/obi/node_modules/idb-pconnector/lib/statement.js:31:18)

This means that parameter arrays such as:

[ 'Clementina DuBuque',
  'Moriah.Stanton',
  '[email protected]',
  'Kattie Turnpike',
  'Suite 198',
  'Lebsackbury',
  '31428-2261',
  '-38.2386',
  '57.2232',
  '024-648-3804',
  null ]

also don't work and throw the same error as above.

Field * Functions Return Promise Rejections "Index must be an Integer"

Original report by me.


@brianmjerome

When I try to use any of field functions for example:

#!javascript
async function example(){
  const dba2 = require('idb-pconnector');
  let sql = 'SELECT * FROM QIWS.QCUSTCDT';
  let dbStmt = new dba2.Connection().connect().getStatement();
  await dbStmt.prepare(sql)
  await dbStmt.execute()
  let col_name = dbStmt.fieldName(0);
  console.log(col_name);
}
example();

I get back

#!js

constructed, dbconn={}
Promise { undefined }
(node:371169) UnhandledPromiseRejectionWarning: Index must be an Integer.
(node:371169) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:371169) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

I did pass an integer to the method. Not sure why its rejecting.

Async Connection assertion error

Original report by Brian Jerome (Bitbucket: bjerome, GitHub: brianmjerome).


For Node v8, using this package, i'm trying to create an async connection via a connection pool designed in https://bitbucket.org/litmis/nodejs-idb-pconnector/issues/3/async-connection-pool#comment-45004910

#!javascript

const {Connection, Statement} = require('/QOpenSys/pkgs/lib/node_modules/idb-pconnector');
...
async newConnection() {
    let me = this;

    me.conn = new Connection();
    me.conn.connect(me.database);

    console.log(`Connection #${me.poolIndex} created`);
  }

The error i'm getting is the following:

#!bash

-bash-4.4$ node ptest
Creating Connection #0...
constructed, dbconn={}
Connection #0 created
Assertion failed: handle->InternalFieldCount() > 0, file  /home/XUMENG/.node-gyp/8.10.0/include/node/node_object_wrap.h, line 50
IOT/Abort trap (core dumped)

Update the Connection() Constructor

Original report by me.


I propose that the Connection constructor should be updated from Connection() to Connection(url, username, password). The url, username, and password parameters would be optional. If the parameters are not provided than the same current workflow would remain (not breaking compatibility). If the parameters are provide the constructor would run connect(url, username, password) implicitly thereby by passing the intermediate step we currently have to always run Connection.connect().

#!js

class Connection {
  constructor(url = '', username = '', password = '') {
    let me = this,
      isLocal = url === '*LOCAL';

    me.dbconn = new idb.dbconn();

    if (isLocal && !username &&!password){
      me.connect();
    } else if (url && username && password){
      me.connect(url, username, password);
    }
  }

  /**
     * Establishes a Connection to the database.
     * @param {string} [url] - The url of the database to connect to, which Defaults to '*LOCAL'.
     * @param {string} [username] - The user name for the database user.
     * @param {string} [password] - The password for the database user.
     * @returns {object} - The dbConn Object with an established connection.
     * @memberof Connection
     */
  connect(url = '*LOCAL', username = '', password = '') {
    let me = this;

    //connecting to '*LOCAL' and username, password was not provided.
    if (url && !username && !password){
      me.dbconn.conn(url);
    } else {
      //connecting to remote url or username, password was provided.
      me.dbconn.conn(url, username, password);
    }
    return me;
  }

Any Thoughts?

@ThePrez @aaronbartell @brianmjerome

refactor: Statement tests

After #42, many statement tests now print deprecation warnings. We should should refactor these test cases.

Also want to implement following:

  • No longer use QIWS.QCUSTCDT from insert tests
  • Add after/afterEach hooks to delete data inserted by test cases
  • Close out open statement and connection handles in each test

npm package outdated?

Original report by Brian Jerome (Bitbucket: bjerome, GitHub: brianmjerome).


Is the npm package outdated after the pull request was merged? I'm getting errors after trying to run the packaged version from npm. Looking at the new source and the error I'm getting clearly doesn't match:

#!bash

/QOpenSys/pkgs/lib/node_modules/idb-pconnector/lib/idb-pconnector.js:23
        this.stmt = new dba.dbstmt(dbConn.wrp);
                                          ^

TypeError: Cannot read property 'wrp' of undefined
    at new Statement (/QOpenSys/pkgs/lib/node_modules/idb-pconnector/lib/idb-pconnector.js:23:43)
    at Object.<anonymous> (/home/BJEROME/web-node/debug/ptest.js:3:14)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Function.Module.runMain (module.js:693:10)
    at startup (bootstrap_node.js:188:16)
    at bootstrap_node.js:609:3

Also assuming it's outdated because there is no test script included.

npm install fails

Any thoughts on this install issue?

This is a V7R3 machine.

npm i idb-pconnector fails with the following errors.

verbose cli [ '/QOpenSys/pkgs/lib/nodejs8/bin/node',
verbose cli '/QOpenSys/usr/bin/npm',
verbose cli 'i',
verbose cli 'idb-pconnector' ]
info using [email protected]

[37;40mnode-pre-gyp [0m [0m [30;43mWARN [0m [0m [35mUsing needle for node-
pre-gyp https download [0m
[0m [37;40mnode-pre-gyp [0m [0m [30;43mWARN [0m [0m [35mPre-built binaries
not installable for [email protected] and [email protected] (node-v57 ABI, unkno
wn) (falling back to source compile with node-gyp) [0m

[0m [37;40mgyp [0m [0m [31;40mERR! [0m [0m [35mstack [0m Error: not found:
gmake

[0m [37;40mnode-pre-gyp [0m [0m [31;40mERR! [0m [0m [35mstack [0m Error: F
ailed to execute '/QOpenSys/pkgs/lib/nodejs10/bin/node /QOpenSys/pkgs/lib/nod
ejs10/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js build --fall
back-to-build --module=/QOpenSys/pkgs/lib/nodejs10/lib/node_modules/idb-pconn
ector/node_modules/idb-connector/lib/binding/Release/node-v64-ibmi-ppc64/db2i
a.node --module_name=db2ia --module_path=/QOpenSys/pkgs/lib/nodejs10/lib/node
_modules/idb-pconnector/node_modules/idb-connector/lib/binding/Release/node-v

[0m [37;40mnpm [0m [0m [31;40mERR! [0m [35m [0m Exit status 1
[0m [37;40mnpm [0m [0m [31;40mERR! [0m [35m [0m
[0m [37;40mnpm [0m [0m [31;40mERR! [0m [35m [0m Failed at the idb-connector
@1.1.8 install script.

INPUT_OUTPUT isn't valid

Original report by Aaron Bartell (Bitbucket: aaronbartell, GitHub: aaronbartell).


The docs say either SQL_PARAM_INPUT_OUTPUT or INPUT_OUTPUT can be used but it appears only SQL_PARAM_INPUT_OUTPUT works. See below for how to reproduce the error.

$]โ€บ node
> const db = require('idb-pconnector');
undefined
> console.log(db.SQL_PARAM_INPUT_OUTPUT);
3
undefined
> console.log(db.INPUT_OUTPUT);
undefined
undefined

Prepare Execute Parameter Array

When working with:
pool.prepareExecute(sql,params)

I am receiving the following Error:

Error: The parameter object must define a value property

I believe this is due to an issue inside of routine setupParms()

My code looks as follows:
const sql =CALL . (?, ?, ?, ?, ?, ?, ?)`

const params = [
{value: 'PARM1', io: 'in', asClob: false},
{value: 'PARM2', io: 'in', asClob: false},
{value: 'PARM3', io: 'in', asClob: false},
{value: 'PARM4', io: 'in', asClob: false},
{value: 'PARM5', io: 'out', asClob: true},
{value: 0, io: 'out', asClob: false},
{value: 'PARM6', io: 'out', asClob: false}
];

await pool.prepareExecute(sql, params);`

Looking at the IDB-pconnector Code I see this which I am thinking should be equal too null "not" not equal too null:

Line : if (!value && value !== null)

` * Internal helper function to format params and set Param Indicator & Bind Type

  • @param {array} params - an array of values to bind. type of values should be:
  • (string , number , or null)
  • @param {object} options - an object with config options to set for all parameters.
  • Currently, the input/output indicator is the only available option to set:
  • {io: in | out | both}.
  • This will override the default which is to bind as 'both'.
  • @returns {array} - an array of bounded params properly formated to use.
    */
    setupParams(params, options) {
    const boundParams = [];
    let { io } = options;
io = io || 'both';

params.forEach((parameter) => {
  if (parameter instanceof Object) {
    const { value, asClob = false } = parameter;

    if (!value && value !== null) {
      throw new Error('The parameter object must define a value property');
    }`

Or there could be something wrong with my Code. Any feedback would be appreciated. Thank You...
M0rwhsky

Deprecate implicitly creating a connection within Statement Constructor

Currently there is a way to Create a Statement from a Connection through getStatement()

Connection -> Statement

There is also an unorthodox way of creating a Connection implicitly when one is not provided to the Statement constructor.

image

Statement -> Connection

This causes a circular dependency issue.

Currently we workaround this circular dependency by tucking away the requirement of statement within the getStatement() function.

I propose to add a function createConnection(), this function will return a connection object connected to "*LOCAL" same behavior as before. The requirement of Connection would be tucked away within this method.

When a connection object is not passed to the Statement constrcutor createConnection would be called.

Once we are ready to deprecate implicitly creating a connection we can simply just remove this function.

Project status, bring out of pre-alpha?

Original report by Aaron Bartell (Bitbucket: aaronbartell, GitHub: aaronbartell).


This project declares (pre-alpha, NOT PRODUCTION READY) status in the README.md.

**Is that still the opinion? **

Hapi converted entirely to async/await and now it is awkward to use existing non-Promise stuff because it needs an extra/external Promise wrapper:

  get_customers: function(request, h) {
    return new Promise(resolve => {   <------ This is what I have to add.  
      var sql = `SELECT * FROM QIWS.QCUSTCDT`;
      pool.easy(sql, function(result) {
        resolve(result)
      })
    })
  }

Would much prefer to use this project and have the following syntax:

  get_customers: function(request, h) {
      var sql = `SELECT * FROM QIWS.QCUSTCDT`;
      return await statement.exec(sql);
  }

[Feature] Coerce DB2 type to equivalent JSON type

Since fetch() doesn't coerce the values in the result set it just returns string values, I worked on a subroutine that would do that. Not sure how you guys would want to implement this, if you'd like to. Here's the essence of the code:

const {
  SQL_CHAR,
  SQL_NUMERIC,
  SQL_DECIMAL,
  SQL_INTEGER,
  SQL_SMALLINT,
  SQL_FLOAT,
  SQL_REAL,
  SQL_DOUBLE,
  SQL_DATETIME,
  SQL_VARCHAR,
  SQL_BLOB,
  SQL_CLOB,
  SQL_DBCLOB,
  SQL_DATALINK,
  SQL_WCHAR,
  SQL_WVARCHAR,
  SQL_BIGINT,
  SQL_BLOB_LOCATOR,
  SQL_CLOB_LOCATOR,
  SQL_DBCLOB_LOCATOR,
  SQL_UTF8_CHAR,
  SQL_WLONGVARCHAR,
  SQL_LONGVARCHAR,
  SQL_GRAPHIC,
  SQL_VARGRAPHIC,
  SQL_LONGVARGRAPHIC,
  SQL_BINARY,
  SQL_VARBINARY,
  SQL_LONGVARBINARY,
  SQL_DATE,
  SQL_TYPE_DATE,
  SQL_TIME,
  SQL_TYPE_TIME,
  SQL_TIMESTAMP,
  SQL_TYPE_TIMESTAMP,
  SQL_DECFLOAT,
  SQL_XML
} = require("idb-connector/lib/db2a");

/**
 *
 * @param {string} value
 * @return {number}
 */
function toNumber (value) {
  return Number(value);
}

/**
 *
 * @param {string} value
 * @return {string}
 */
function toString (value) {
  return String(value);
}

/**
 * matches DB2 Type to equivalent JSON type and returns a function that'll coerce the value
 *
 * @param {number} typeConstant a DB2 CLI constant
 * @return {(function(string): string)|(function(string): number)}
 */
function coerceDb2Type (typeConstant) {
  switch (typeConstant) {
    case SQL_CHAR:
    case SQL_VARCHAR:
    case SQL_LONGVARCHAR:
    case SQL_WCHAR:
    case SQL_WVARCHAR:
    case SQL_WLONGVARCHAR:
    case SQL_VARGRAPHIC:
    case SQL_LONGVARGRAPHIC:
    case SQL_VARBINARY:
    case SQL_LONGVARBINARY:
    case SQL_UTF8_CHAR:
    case SQL_XML:
      return toString;

    case SQL_NUMERIC:
    case SQL_DECIMAL:
    case SQL_INTEGER:
    case SQL_SMALLINT:
    case SQL_FLOAT:
    case SQL_REAL:
    case SQL_DOUBLE:
    case SQL_DECFLOAT:
      return toNumber;

    default:
      return toString;
  }
}

/**
 * Builds a map of with the key as the field name and value as a callback that will coerce the value into the equivalent JSON data type
 *
 * @param {Statement} statement
 * @return {Promise<Object<string, (function(string): string)|(function(string): number)>>}
 */
async function getFieldTypeMap(statement) {
  let map =  {};
  let numFields;

  try {
    numFields = await statement.numFields();
  } catch(e) {
    return map;
  }

  for (let i = 0; i < numFields; i++) {
    map[await statement.fieldName(i)] = coerceDb2Type(await statement.fieldType(i));
  }

  return map;
}

module.exports = {coerceDb2Type, getFieldTypeMap};
let fieldTypeMap = await getFieldTypeMap(statement);
let row = await statement.fetch();

for (let fieldName in row) {
  row[fieldName] = fieldTypeMap[fieldName](row[fieldName]); // coerce value
}

fetch() kills program at end of data

Original report by Jason (Bitbucket: sccadevops, GitHub: Unknown).


Processing a dataset with fetch() dies at the end of the set... Maybe I am doing it wrong?
Would you mind adding an example using the fetch() function which includes how to detect the end of the dataset?

To get our example to work: I had to change the fetch() function to catch the return code from fetchSync() and reject the promise if <> 0...

#!javascript
async fetch() {
    let stmt = this.stmt;
    return new Promise((resolve, reject) => {
     
        let rc = stmt.fetchSync((result, error) => {
          if (error) {
            reject(error);
          } else {
           resolve(result);
          }
        });
        if (rc !== 0) {
          reject(rc);
        }
    });
  }

Code Simplification

Original report by Brian Jerome (Bitbucket: bjerome, GitHub: bjerome).


I don't have access to make changes to this repo, but I'd like to suggest some code improvements for idb-pconnector.js

Before

#!js

function makeBool(data) {
	if (data == 1)
		return true;
	else if (data == 0)
		return false;
	else
		return data;
}

After

#!js

function makeBool(data) {
	return data === 0 || data === 1 ? !!data : data;
}

Before

#!js

function isInt(data) {
	if (typeof data === 'number')
		return true;
	else
		return false;
}

After

#!js

function isInt(data) {
	return typeof data === 'number';
}

DBPool should be able to limit the number of connections it will create

Original report by Kerim Gรผney (Bitbucket: TaskForce_Kerim, GitHub: KerimG).


First of all, great work on this lib! Really appreciate what you all are doing.

I was going through the docs and trying out a few things. I don't think I completely understand DBPool.attach()

As I understand, when attach() is called, it returns an available connection from the pool but if all connections are in use, the connection pool size is increased. Wouldn't that lead to the same issues as in idb-connector issue 23t if attach() is called faster than connections freeing up?

Wouldn't it be better to queue further attach() attempts after the pool is exhausted and wait for connections to free up again?

Async Connection Pool

Original report by Brian Jerome (Bitbucket: bjerome, GitHub: brianmjerome).


@aaronbartell I know you have an article on a db2 connection pool based on the node bears project.

I wanted to discuss possible implementations with this async/await functionality. I have my own implementation for my node project using the idb-connector package.

Does it make sense for devs to implement this independently of this pconnector, or could a pool be created and shipped with this as a standard? Is it worth making a pool of connections still?

Code Simplification

Original report by Brian Jerome (Bitbucket: bjerome, GitHub: brianmjerome).


I don't have access to make changes to this repo, but I'd like to suggest some code improvements for idb-pconnector.js

Before

#!js

function makeBool(data) {
	if (data == 1)
		return true;
	else if (data == 0)
		return false;
	else
		return data;
}

After

#!js

function makeBool(data) {
	return data === 0 || data === 1 ? !!data : data;
}

Before

#!js

function isInt(data) {
	if (typeof data === 'number')
		return true;
	else
		return false;
}

After

#!js

function isInt(data) {
	return typeof data === 'number';
}

Add Statement.bindParameters() that accepts an array of values

I purpose to allow Statement.bindParam() to accept an array of values instead of array of arrays like here.

This should not be difficult we already implement similar behavior for DBPool.prepareExecute().

That way you can use this:

  await statement.bindParam([9997, 'Johnson', 'A J', 453 Example','Fort']);

rather than this:

statement.bindParam([
    [9997, IN, NUMERIC],
    ['Johnson', IN, CHAR],
    ['A J', IN, CHAR],
    ['453 Example', IN, CHAR],
    ['Fort', IN, CHAR],
  ]);

connection.setConnAttr fails

would like to switch the connection to system naming mode and set the library list. How to do that?

when I run connection.setConnAttr( 3017, 1 ) the statement fails with error code 9 - argument value not valid.
3017 is the constant for SQL_ATTR_DBC_SYS_NAMING. and 1 is the constant for SQL_TRUE..

const { Connection } = require('idb-pconnector');
async function execExample()
{
  try
  {
    const connection = new Connection({ url: '*LOCAL' });

    // constants from here: https://github.com/php/pecl-database-ibm_db2/blob/master/php_ibm_db2.h
    const sql_sys_naming = 3017; // #define SQL_ATTR_DBC_SYS_NAMING 3017
    const sql_true = 1;
    const sql_false = 0;

    // trying to set the connection to system naming mode.
    await connection.setConnAttr(sql_sys_naming, sql_true);

  } catch (error)
  {
    console.error(`Error was: \n${error.stack}`);
  }
}

execExample();

running the code from QP2TERM:

node steve7d.js
Error was:
Error: SQLSTATE=HY009 SQLCODE=-99999 Error occurred in SQL Call Level Interface
at Promise (/home/njs/node_modules/idb-pconnector/lib/idb-pconnector.js:135:24)
at new Promise ()
at Connection.setConnAttr (/home/njs/node_modules/idb-pconnector/lib/idb-pconnector.js:133:12)
at execExample (/home/njs/steve7d.js:19:22)
at Object. (/home/njs/steve7d.js:27:1)
at Module._compile (module.js:653:30)
at Object.Module._extensions..js (module.js:664:10)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
at Function.Module._load (module.js:498:3)
$

30 02/25/19 05:24:26.174043 QSQCLI QSYS *STMT QP2USER2 QSYS
From module . . . . . . . . : SQLSCA
From procedure . . . . . . : SQLSetConnectAttr
Statement . . . . . . . . . : 17088
To module . . . . . . . . . : QP2API
To procedure . . . . . . . : runpase_common__FiPvT2
Statement . . . . . . . . . : 4
Message . . . . : Error occurred in SQL Call Level Interface
Cause . . . . . : A procedure call encountered an error. The error code is
9. Error codes are: 1 -- Memory allocation failure. 2 -- Column number not
valid. 3 -- Program type out of range. 4 -- SQL data type out of range. 8 --
Operation canceled. 9 -- Argument value not valid. 10 -- Function sequence
error. 12 -- Transaction operation state not valid. 13 -- Memory management
problem. 14 -- Maximum number of handles allocated. 15 -- No cursor name

DBPool.prepareExecute() and Statement.prepare() + Statement.bind() + Statement.execute() behave differently

Original report by Kerim Gรผney (Bitbucket: TaskForce_Kerim, GitHub: KerimG).


IBM i OS            v7.2
Node                v8.11.3
idb-pconnector      v1.0.0
idb-connector       v1.1.3

Attached you will find 2 JavaScript files. They're slightly modified versions of the examples for "prepareExecute" and "prepare bind execute".

The "prepare bind execute" example throws the error

Error: Failed To Bind Parameters

Can Statement.prepare/bind/execute only be used for inserts?

DBPool.prepareExecute() fails if no params array is provided.

Original report by Kerim Gรผney (Bitbucket: TaskForce_Kerim, GitHub: KerimG).


IBM i OS            v7.2
Node                v8.11.3
idb-pconnector      v1.0.0
idb-connector       v1.1.3

According to the docs, the params array is optional but leaving it out causes a TypeError dbPool.js line 420

#!javascript

// at this point outParams is the return value of statement.execute() 
// but without parameter binding it returns null
if (outParams.length){ // TypeError: Cannot read property 'length' of null
      data.outputParams = outParams;
}

oh and same issue in line 414

#!javascript

// if resultSet is empty, right side of && throws error
if (!resultSet.length && !outParams.length){

Set library list

Hi

I realise that we may need to update the setLibraryList API to work with all the connections in a pool.

What do you think?

Liam

npm not updated to 1.1 still on 1.0.8

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Update whats returned from prepareExecute()

Original report by me.


I suggest that the prepareExecute() with dbPool.js should return an object containg:

{outputParams: [], resultSet: []} if either outputParams or resultSet are available. If neither are available prepareExecute() would return null.

Currently prepareExecute() will only return an resultSet [] if available otherwise will return null.

Changing the return type to the proposed object would allow output parameters to be returned which is currently not a feature. Also output parameters are needed when returning data from the xml service stored procedure.

#!js

  /**
   * Shortcut to prepare ,bind, and execute. Just provide the sql and the params as an array.
   * @param {string} sql - the sql to prepare , include parameter markers (?, ?, ...)
   * @param {array} [params] - an optional array of values to bind. order of the values in the array must match the
   * order of the desired parameter marker in the sql string.
   * @return {object} - In the Format: {resultSet: [], outputParams: []}
   * if the Prepared SQL returns result set it is returned as an array of objects.
   * if the Prepared SQL returns output parameters it is returned as an array of objects/
   * else null will be returned indicating that there is no result set or outputParams.
   */
  async prepareExecute(sql = '', params = [], options = {}) {
    let me = this,
      connection = me.attach(),
      statement = connection.getStatement(),
      resultSet = [],
      outParams = [],
      data = {};

    try {
      me.log(`Preparing Statement...`);
      await statement.prepare(sql);
      if (params.length > 0){
        me.log(`Binding Parameters...`);
        await statement.bindParam(me.setupParams(params, options));
      }
      outParams = await statement.execute();
    } catch (err) {
      me.log(` SQL Error: ${err}`);
      me.retire(connection);
    }
    try {
      me.log(`Fetching Result Set...`);
      resultSet = await statement.fetchAll();
      me.log(`Fetched Result Set`);
    } catch (err){
      //tried to get the results no were results were available.
      //this is normal behavior for some queries for example "INSERT" or "UPDATE"
      me.log('SQL did not return a result set.');
    }
    try {
      await connection.detach();
    } catch (err){
      me.log(`Connection Err:\n ${err}`);
      await me.retire(connection);
    }
    if (!resultSet.length && !outParams.length){
      return null;
    }
    if (resultSet.length){
      data.resultSet = resultSet;
    }
    if (outParams.length){
      data.outputParams = outParams;
    }
    return data;
  }

Any thoughts?

@ThePrez @aaronbartell @brianmjerome

Error Handling

Original report by me.


Noticed a few Issues regarding error handling.

  1. Wth the aggregate functions I noticed that errors can be masked / swallowed while working within the async function.

  2. error.stack information missing.

Problem 1

For instance the runSql() example:

const {DBPool} = require('idb-pconnector');
const pool = new DBPool({url: '*LOCAL'}, {debug: true});
async function runSqlExample(){
  /*
  * Directly execute a statement by providing the SQL to the runSql() function.
  * NOTE: Stored Procedures must use the prepareExecute() method instead.
  */
 try {

   let result = await pool.runSql('blank');

    if (result !== null) {
     console.log(`\n${JSON.stringify(result)}`);
   } else{
      console.log('Nothing to show');
   }

 } catch (error){
   console.log(`Error was: ${error.stack}`);
 }
}

runSqlExample();

if debug is turned off the log will be:

Nothing to show

Even though an error occurred and should have been caught in the catch block!

If debug is turned on the log is:

Creating Connection 0...
Connection 0 created
Creating Connection 1...
Connection 1 created
Creating Connection 2...
Connection 2 created
Creating Connection 3...
Connection 3 created
Creating Connection 4...
Connection 4 created
Creating Connection 5...
Connection 5 created
Creating Connection 6...
Connection 6 created
Creating Connection 7...
Connection 7 created
Finding available Connection...
Connection 0 found
Executing SQL...
SQL Statement: 
(node:143412) Warning: N-API is an experimental feature and could change at any time.
SQLExecDirect(-1): 

 **** ERROR *****
SQLSTATE: 42617
Native Error Code: -198
SQL statement empty or blank. 
SQL Error: Error: SQLSTATE=42617 SQLCODE=-198 SQL statement empty or blank.
Retiring Connection 0...
SQLDisconnect: conn obj [180311650] handler [2]
SQLFreeConnect: conn obj [180311650] handler [2]
SQLFreeConnect[0]
Connection 0 retired
Detaching Connection 0...
SQLFreeStmt: stmth 3 [SQL_DROP]
SQLFreeStmt(-2)
Connection 0 detached
SQL did not return a result set.
Nothing to show

An error occurs within the runSql() but it is not get sent up to runSqlExample() because it caught within runSql() and an error message is printed only if debug is turned on. Otherwise you would be returned null but not know an error occurred.

 async runSql(sql = '') {
    let me = this,
      connection = me.attach(),
      statement = connection.getStatement(),
      resultSet = [];

    try {
      me.log(`Executing SQL...\nSQL Statement: ${sql}`);
      resultSet = await statement.exec(sql);
      me.log(`Executed SQL`);
    } catch (err) {
      me.log(`SQL Error: ${err}`);
      await me.retire(connection);
    }
    try {
      await me.detach(connection);
    } catch (error) {
      await me.retire(connection);
      console.log(`Connection Error: ${error}`);
    }
    if (!resultSet.length){
      me.log('SQL did not return a result set.');
      return null;
    }
    me.log(`Fetched Result Set`);
    return resultSet;
  }

Errors should be thrown when detected within runSql() instead of caught and swallowed, so that callers can be handle it accordingly.

I think a better solution would be:

 async runSql(sql = '') {
    let me = this,
      connection = me.attach(),
      statement = connection.getStatement(),
      resultSet = [];

    try {
      me.log(`Executing SQL...\nSQL Statement: ${sql}`);
      resultSet = await statement.exec(sql);
      me.log(`Executed SQL`);
    } catch (err) {
      me.log(`SQL Error: ${err}`);
      await me.retire(connection);
      throw err;
    }
    try {
      await me.detach(connection);
    } catch (error) {
      await me.retire(connection);
      console.log(`Connection Error: ${error}`);
      throw error;
    }
    if (!resultSet.length){
      me.log('SQL did not return a result set.');
      return null;
    }
    me.log(`Fetched Result Set`);
    return resultSet;
  }

Now running the same example from before with debug off

log

Error was: Error: SQLSTATE=42617 SQLCODE=-198 SQL statement empty or blank.

Notice how the error was caught now but the stack the information was not provided?

Problem 2

Within idb-pconnecor.js the error variable passed to the reject() is a Javascipt Error returned from idb-connector but it does not contain any stack info!

So to account for this I found two soultions either pass the reject() a new Error() which would create the stack at that moment or use Error.captureStackTrace. I would prefer to just pass reject a new Error() with the message returned from the error variable.

  async exec(sqlString) {
    let stmt = this.stmt;

    return new Promise((resolve, reject) => {
      stmt.exec(sqlString, function (result, error){
        if (error) {
          reject(new Error(error.message));
        } else {
          resolve(result);
        }
      });
    });
  }

So now lets run our example again:

log:

Error was: Error: SQLSTATE=42617 SQLCODE=-198 SQL statement empty or blank.
    at Object.<anonymous> (idb-pconnector.js:323:18)

So now the stack was printed and pointed back to where the rejection occurred!

We can actually improve the stack trace by appending to it within the runSql():

 async runSql(sql = '') {
    let me = this,
      connection = me.attach(),
      statement = connection.getStatement(),
      resultSet = [];

    try {
      me.log(`Executing SQL...\nSQL Statement: ${sql}`);
      resultSet = await statement.exec(sql);
      me.log(`Executed SQL`);
    } catch (err) {
      me.log(`SQL Error: ${err}`);
      await me.retire(connection);
      const reason = Error('runSql(): Failed to exec() ');
      reason.stack += `\nCaused By:\n ${err.stack}`;
      throw reason;
    }
    try {
      await me.detach(connection);
    } catch (error) {
      await me.retire(connection);
      console.log(`Connection Error: ${error}`); 
      const reason = Error('runSql(): Failed to detach() the connection');
      reason.stack += `\nCaused By:\n ${error.stack}`;
      throw reason;
    }
    if (!resultSet.length){
      me.log('SQL did not return a result set.');
      return null;
    }
    me.log(`Fetched Result Set`);
    return resultSet;
  }

Now running the example the log is:

Error was: Error: runSql: Failed to exec()
    at DBPool.runSql (dbPool.js:354:22)
    at <anonymous>
Caused By:
 Error: SQLSTATE=42617 SQLCODE=-198 SQL statement empty or blank.
    at Object.<anonymous> (idb-pconnector.js:323:18)

Which is more detailed and easier to trace what went wrong.

We can also use .catch() within the aggregates instead of try catch blocks.

 async runSql(sql = '') {
    let me = this,
      connection = me.attach(),
      statement = connection.getStatement(),
      resultSet = [];

      me.log(`Executing SQL...\nSQL Statement: ${sql}`);
      resultSet = await statement.exec(sql).catch(async (err)  =>{
        me.log(`SQL Error: ${err}`);
        await me.detach(connection);
        const reason = Error('runSql(): Failed to exec() ');
        reason.stack += `\nCaused By:\n ${err.stack}`;
        throw reason;
    });

      await me.detach(connection).catch(async (error) => {
        await me.retire(connection);
        console.log(`Connection Error: ${error}`); 
        const reason = Error('runSql(): Failed to detach() the connection');
        reason.stack += `\nCaused By:\n ${error.stack}`;
        throw reason;
    });

    if (!resultSet.length){
      me.log('SQL did not return a result set.');
      return null;
    }
    me.log(`Fetched Result Set`);
    return resultSet;
  }

Solution 1

we use reject( new Error(error.message) ) to obtain the stack info, also to update runSql & prepareExecute() to throw errors when they occur & use .catch() blocks.

Alternative Solution

Instead of rejecting the promise (causes an error to be thrown) when we determine a problem , we could always resolve our promises with an object that contains: error & result property.That way we could always check the returned object if the error is set and deal with it accordingly. This might be a nicer approach than Solution 1.

Thoughts @ThePrez @aaronbartell @brianmjerome

Remove SqlCall Class

This is a new class that was added in 1.0 development, but we haven't shipped 1.0 yet, so it doesn't make much sense to deprecate it. Instead we should just remove it and move the code back in to iSql.

All current users will be using iSql (unless they're using the alpha). Users would then see two deprecation warnings: "iSql is deprecated, use SqlCall" and then "SqlCall is deprecated and will be removed". This is confusing and would be better changed to just having iSql say that it will be removed and not having SqlCall at all.

Originally posted by @kadler in IBM/nodejs-itoolkit#117 (review)

Table of Contents [TOC]

Original report by me.


Table of Contents renders fine within bitbucket but on NPM it shows up as [TOC] I think this macro is bitbucket specific.

Before reading about [TOC] I tried creating a list of links using standard markdown links [Go to Examples](#examples) . I found that this does not work on Bitbucket because Bitbucket appends the prefix#markdown-header to all headers when rendering the page! ๐Ÿค”

This can be seen by looking source html of our repo:

<h1 id="markdown-header-examples"><strong>Examples</strong></h1>

@aaronbartell Do you know of a better way to create a portable Table of Contents?

Program connects and when there is sql error, it leaves the jobs hanging out there

We have a client which has many QSQSRVR jobs when there is a program encountering an SQL error...

Connection:

const {
  DBPool, Statement, SQL_ATTR_DBC_SYS_NAMING, SQL_TRUE
} = require('idb-pconnector');

this.pool = new DBPool({
      url: '*LOCAL'
    }, {incrementSize: 1});
    this.connection = this.pool.attach();
  },

Sql executed via:

 statement = new Statement(conn.connection);
    await statement.prepare(sqlStatement);
    await statement.execute();

SQL gets error in job:
Value for column or variable too long.
Job is in CNDW status.

When program reconnects, new job is spawned. The client ends up having many many jobs.

  1. Why are jobs not reused?
  2. Is there a way we can prevent this?

hr2

hr1

pool.runSQL doesn't work with non-resultset

Original report by Aaron Bartell (Bitbucket: aaronbartell, GitHub: aaronbartell).


Function pool.runSQL doesn't work with SQL statements that don't produce a result set (i.e. INSERT).

Recreate issue:

app.js:

async function doSQL(){
  try {
    const {DBPool} = require('idb-pconnector');
    const pool = new DBPool();
    await pool.runSql(
      `INSERT INTO MYLIB.MYTABLE VALUES (123,'Smith')`
    );
  }catch(e){
    console.log(e);    
  }
}

doSQL();
$ node app.js
constructed, dbconn={}
constructed, dbconn={}
constructed, dbconn={}
constructed, dbconn={}
constructed, dbconn={}
constructed, dbconn={}
constructed, dbconn={}
constructed, dbconn={}
TypeError: Cannot read property 'length' of null
    at DBPool.runSql (/home/aaron/git/hapi_base/node_modules/idb-pconnector/lib/dbPool.js:351:20)

The row is successfully inserted but line 361 in dbPool.js is checking the length when the resultSet object is null.

'undefined' showing up in SQL Stored Proc results

Original report by Aaron Bartell (Bitbucket: aaronbartell, GitHub: aaronbartell).


The word undefined is showing up in INPUT_OUTPUT parameters when calling an External SQL Stored Procedure.

Reproduce the issue.

PGM1:

**free
dcl-pr PGM1 extpgm;
  controlnumber char(50);
  custno char(6);
  cusshp char(3);
  deldat char(8);
  servloc char(2);
  ordtype char(3);
  prcwght char(7);
  field char(50);
  errflg char(1);
end-pr;
dcl-pi PGM1;
  controlnumber char(50);
  custno char(6);
  cusshp char(3);
  deldat char(8);
  servloc char(2);
  ordtype char(3);
  prcwght char(7);
  field char(50);
  errflg char(1);
end-pi;
controlnumber = '1134';
ordtype = '1';
field = 'it worked';
errflg = 'N';
return;
CREATE OR REPLACE PROCEDURE MYLIB.PGM1
  (  
  INOUT controlnumber char(50),
  INOUT custno char(6),
  INOUT cusshp char(3),
  INOUT deldat char(8),
  INOUT servloc char(2),
  INOUT ordtype char(3),
  INOUT prcwght char(7),
  INOUT field char(50),
  INOUT errflg char(1)
  )
  EXTERNAL NAME  MYLIB.PGM1
  LANGUAGE  RPGLE
  PARAMETER STYLE GENERAL WITH NULLS;

Sample program

const db = require('idb-pconnector');
const pool = new db.DBPool({}, {debug: true });

async function invoke(){
  let dbconn = pool.attach();
  let stmt = dbconn.getStatement();
  try {
    await stmt.prepare('CALL MYLIB.PGM1(?,?,?,?,?,?,?,?,?)');
    let parms = [];
    await stmt.bind([
      [parms[0], db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
      [parms[1], db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR],
      [parms[2], db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
      [parms[3], db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
      [parms[4], db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
      [parms[5], db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
      [parms[6], db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
      [parms[7], db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
      [parms[8], db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR]
    ]);
    parms = await stmt.execute();
    parms.forEach(parm => console.log(parm));    
    await pool.detach(dbconn);
  } catch (error){
    console.log(`Error was: \n\n${error}`);
    pool.retire(dbconn);
  }
}

invoke();

Below are results. Notice where I placed the arrows and how it has a portion of the word 'undefined' as part of the resulting field.

$ node call_rpg.js

Creating Connection 0...
Connection 0 created
Creating Connection 1...
Connection 1 created
Creating Connection 2...
Connection 2 created
Creating Connection 3...
Connection 3 created
Creating Connection 4...
Connection 4 created
Creating Connection 5...
Connection 5 created
Creating Connection 6...
Connection 6 created
Creating Connection 7...
Connection 7 created
Finding available Connection...
Connection 0 found
Prepare().
SQLPrepare(0): CALL MYLIB.PGM1(?,?,?,?,?,?,?,?,?)
BindParamAsync().
SQLBindParameter(0) TYPE[ 1] SIZE[ 50] DIGI[0] IO[3] IND[ -3] INDEX[0]
SQLBindParameter(0) TYPE[ 1] SIZE[  6] DIGI[0] IO[3] IND[ -3] INDEX[1]
SQLBindParameter(0) TYPE[ 1] SIZE[  3] DIGI[0] IO[3] IND[ -3] INDEX[2]
SQLBindParameter(0) TYPE[ 1] SIZE[  8] DIGI[0] IO[3] IND[ -3] INDEX[3]
SQLBindParameter(0) TYPE[ 1] SIZE[  2] DIGI[0] IO[3] IND[ -3] INDEX[4]
SQLBindParameter(0) TYPE[ 1] SIZE[  3] DIGI[0] IO[3] IND[ -3] INDEX[5]
SQLBindParameter(0) TYPE[ 1] SIZE[  7] DIGI[0] IO[3] IND[ -3] INDEX[6]
SQLBindParameter(0) TYPE[ 1] SIZE[ 50] DIGI[0] IO[3] IND[ -3] INDEX[7]
SQLBindParameter(0) TYPE[ 1] SIZE[  1] DIGI[0] IO[3] IND[ -3] INDEX[8]
Execute().
SQLExecuteAsync(1):
SQLNUMRESULTSCOLS(0) Column Count = 0
NO RESULTS :SQLExecuteAsync()
1134
undefined
undefined
undefined
undefined
1  efined          <-------------------------
undefined
it worked
Nndefined      <-------------------------
Detaching Connection 0...
SQLFreeStmt: stmth 3 [SQL_DROP]
SQLFreeStmt(0)
Connection 0 detached

Calling the same from idb-connector works as expected. Here's an example:

let db = require('idb-connector');
let sql = "CALL MYLIB.PGM1(?,?,?,?,?,?,?,?,?)";
let dbconn = new db.dbconn();
dbconn.debug(true);
dbconn.conn("*LOCAL");
let stmt = new db.dbstmt(dbconn);
stmt.prepare(sql, () => {
  stmt.bindParam([
    ['', db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
    ['', db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR],
    ['', db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
    ['', db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
    ['', db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
    ['', db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
    ['', db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
    ['', db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR], 
    ['', db.SQL_PARAM_INPUT_OUTPUT, db.SQL_BIND_CHAR],
  ], function(){
    stmt.execute((out) => { 
      console.log(JSON.stringify(out));
      stmt.close();
      dbconn.disconn();
      dbconn.close();
    });
  });
});

Log:

$]โ€บ node call_rpg.js
SQLConnect(0): conn obj [1802f5e90] handler [2]
Prepare().
SQLPrepare(0): CALL MYLIB.PGM1(?,?,?,?,?,?,?,?,?)
BindParamAsync().
SQLBindParameter(0) TYPE[ 1] SIZE[ 50] DIGI[0] IO[3] IND[  0] INDEX[0]
SQLBindParameter(0) TYPE[ 1] SIZE[  6] DIGI[0] IO[3] IND[  0] INDEX[1]
SQLBindParameter(0) TYPE[ 1] SIZE[  3] DIGI[0] IO[3] IND[  0] INDEX[2]
SQLBindParameter(0) TYPE[ 1] SIZE[  8] DIGI[0] IO[3] IND[  0] INDEX[3]
SQLBindParameter(0) TYPE[ 1] SIZE[  2] DIGI[0] IO[3] IND[  0] INDEX[4]
SQLBindParameter(0) TYPE[ 1] SIZE[  3] DIGI[0] IO[3] IND[  0] INDEX[5]
SQLBindParameter(0) TYPE[ 1] SIZE[  7] DIGI[0] IO[3] IND[  0] INDEX[6]
SQLBindParameter(0) TYPE[ 1] SIZE[ 50] DIGI[0] IO[3] IND[  0] INDEX[7]
SQLBindParameter(0) TYPE[ 1] SIZE[  1] DIGI[0] IO[3] IND[  0] INDEX[8]
Execute().
SQLExecuteAsync(0):
SQLNUMRESULTSCOLS(0) Column Count = 0
NO RESULTS :SQLExecuteAsync()
["1134                                              ","","","","","1  ","","it worked                                         ","N"]
SQLFreeStmt: stmth 3 [SQL_DROP]
SQLFreeStmt(0)
SQLDisconnect: conn obj [1802f5e90] handler [2]
SQLFreeConnect: conn obj [1802f5e90] handler [2]
SQLFreeConnect[0]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.