ifx_db
Version:
IBM Informix bindings for node
961 lines (772 loc) • 24.8 kB
JavaScript
/*
Copyright (c) 2015, Sathyanesh Krishnan<msatyan@gmail.com>
Copyright (c) 2013, Dan VerWeire <dverweire@gmail.com>
Copyright (c) 2010, Lee Smith <notwink@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Setting CSDK_HOME bin path to the path env before load for windows
var os = require('os'),
path = require('path');
if (os.platform() == 'win32')
{
//CSDK_HOME
process.env.PATH = process.env.PATH + ';' +
path.resolve(__dirname, process.env.CSDK_HOME + '/bin');
}
var odbc = require("bindings")("ifx_node_bind")
, SimpleQueue = require("./simple-queue")
, util = require("util")
;
// Call of odbc.ODBC() loads odbc library and allocate environment handle.
// All calls of new Database() should use this same odbc unless passed as
// options.odbc. ENV will keep value of this odbc after first call of Database.
var ENV;
module.exports = function (options)
{
return new Database(options);
}
module.exports.debug = false;
module.exports.Database = Database;
module.exports.ODBC = odbc.ODBC;
module.exports.ODBCConnection = odbc.ODBCConnection;
module.exports.ODBCStatement = odbc.ODBCStatement;
module.exports.ODBCResult = odbc.ODBCResult;
module.exports.loadODBCLibrary = odbc.loadODBCLibrary;
module.exports.open = function (connStr, options, cb)
{
var db;
if (typeof options === 'function')
{
cb = options;
options = null;
}
db = new Database(options);
db.open(connStr, function (err)
{
cb(err, db);
});
} // ifxdb.open
module.exports.openSync = function (connStr, options)
{
var db = new Database(options);
db.openSync(connStr);
return db;
} // ifxdb.openSync
module.exports.close = function (db)
{
if (db && typeof (db) == "object")
{
for (key in db)
{
delete db[key];
}
delete db;
db = undefined;
}
} // ifxdb.close
function Database(options)
{
var self = this;
options = options || {};
if (odbc.loadODBCLibrary)
{
if (!options.library && !module.exports.library)
{
throw new Error("You must specify a library when complied with dynodbc, "
+ "otherwise this jams will segfault.");
}
if (!odbc.loadODBCLibrary(options.library || module.exports.library))
{
throw new Error("Could not load library. You may need to specify full "
+ "path.");
}
}
self.odbc = (options.odbc) ? options.odbc : ((ENV) ? ENV : new odbc.ODBC());
if (!ENV) ENV = self.odbc;
self.queue = new SimpleQueue();
self.fetchMode = options.fetchMode || null;
self.connected = false;
self.connectTimeout = options.connectTimeout || null;
} // Database()
//Expose constants
Object.keys(odbc.ODBC).forEach(function (key)
{
if (typeof odbc.ODBC[key] !== "function")
{
//On the database prototype
Database.prototype[key] = odbc.ODBC[key];
//On the exports
module.exports[key] = odbc.ODBC[key];
}
});
Database.prototype.open = function (connStr, cb)
{
var self = this;
if (typeof (connStr) == "object")
{
var obj = connStr;
connStr = "";
Object.keys(obj).forEach(function (key)
{
connStr += key + "=" + obj[key] + ";";
});
}
self.odbc.createConnection(function (err, conn)
{
if (err) return cb(err);
self.conn = conn;
if (self.connectTimeout || self.connectTimeout === 0)
{
self.conn.connectTimeout = self.connectTimeout;
}
self.conn.open(connStr, function (err, result)
{
if (err) return cb(err);
self.connected = true;
return cb(err, result);
}); //conn.open
}); // odbc.createConnection
}; // Database.open function
Database.prototype.openSync = function (connStr)
{
var self = this;
self.conn = self.odbc.createConnectionSync();
if (self.connectTimeout || self.connectTimeout === 0)
{
self.conn.connectTimeout = self.connectTimeout;
}
if (typeof (connStr) == "object")
{
var obj = connStr;
connStr = "";
Object.keys(obj).forEach(function (key)
{
connStr += key + "=" + obj[key] + ";";
});
}
var result = self.conn.openSync(connStr);
if (result)
{
self.connected = true;
}
return result;
} // Database.openSync
Database.prototype.close = function (cb)
{
var self = this;
self.queue.push(function (next)
{
if (self.conn)
{
self.conn.close(function (err)
{
self.connected = false;
delete self.conn;
if (cb) cb(err);
return next();
});
}
else
{
self.connected = false;
}
}); // self.queue.push
}; // Database.close
Database.prototype.closeSync = function ()
{
var self = this;
var resutl;
if (self.conn) result = self.conn.closeSync();
self.connected = false;
delete self.conn;
return result
} // closeSync
Database.prototype.query = function (sql, params, cb)
{
var self = this;
if (typeof (params) == 'function')
{
cb = params;
params = null;
}
if (!self.connected)
{
return cb({ message: "Connection not open." }, [], false);
}
self.queue.push(function (next)
{
function cbQuery(initialErr, result)
{
fetchMore();
function fetchMore()
{
if (self.fetchMode)
{
result.fetchMode = self.fetchMode;
}
result.fetchAll(function (err, data)
{
var moreResults, moreResultsError = null;
// If there is any error, return it now only.
if (err || initialErr)
{
cb(err || initialErr, data, moreResults);
result.closeSync();
initialErr = null;
err = null;
return next();
}
// Get the result data
try
{
moreResults = result.moreResultsSync();
}
catch (e)
{
moreResultsError = e;
moreResults = false;
}
//close the result before calling back
//if there are not more result sets
if (!moreResults)
{
result.closeSync();
}
// send exception error and/or data to callback function.
cb(moreResultsError, data, moreResults);
moreResultsError = null;
if (moreResults)
{
return fetchMore();
}
else
{
return next();
}
});
}
} //function cbQuery
if (params)
{
self.conn.query(sql, params, cbQuery);
}
else
{
self.conn.query(sql, cbQuery);
}
}); //self.queue.push
}; // Database.query
Database.prototype.queryResult = function (sql, params, cb)
{
var self = this;
if (typeof (params) == 'function')
{
cb = params;
params = null;
}
if (!self.connected)
{
return cb({ message: "Connection not open." }, null);
}
self.queue.push(function (next)
{
//ODBCConnection.query() is the fastest-path querying mechanism.
if (params)
{
self.conn.query(sql, params, cbQuery);
}
else
{
self.conn.query(sql, cbQuery);
}
function cbQuery(err, result)
{
if (err)
{
cb(err, null);
return next();
}
if (self.fetchMode)
{
result.fetchMode = self.fetchMode;
}
cb(err, result);
return next();
} // function cbQuery
}); //self.queue.push
}; // Database.queryResult
Database.prototype.queryResultSync = function (sql, params)
{
var self = this, result;
if (!self.connected)
{
throw ({ message: "Connection not open." });
}
if (params)
{
result = self.conn.querySync(sql, params);
}
else
{
result = self.conn.querySync(sql);
}
if (self.fetchMode)
{
result.fetchMode = self.fetchMode;
}
return result;
}; // Database.queryResultSync
Database.prototype.querySync = function (sql, params)
{
var self = this, result;
if (!self.connected)
{
throw ({ message: "Connection not open." });
}
if (params)
{
result = self.conn.querySync(sql, params);
}
else
{
result = self.conn.querySync(sql);
}
if (self.fetchMode)
{
result.fetchMode = self.fetchMode;
}
var data = result.fetchAllSync();
result.closeSync();
return data;
}; // Database.querySync
Database.prototype.beginTransaction = function (cb)
{
var self = this;
self.conn.beginTransaction(cb);
self.conn.inTransaction = true;
return self;
};
Database.prototype.endTransaction = function (rollback, cb)
{
var self = this;
self.conn.endTransaction(rollback, cb);
self.conn.inTransaction = false;
return self;
};
Database.prototype.commitTransaction = function (cb)
{
var self = this;
self.conn.endTransaction(false, cb); //don't rollback
self.conn.inTransaction = false;
return self;
};
Database.prototype.rollbackTransaction = function (cb)
{
var self = this;
self.conn.endTransaction(true, cb); //rollback
self.conn.inTransaction = false;
return self;
};
Database.prototype.beginTransactionSync = function ()
{
var self = this;
self.conn.beginTransactionSync();
self.conn.inTransaction = true;
return self;
};
Database.prototype.endTransactionSync = function (rollback)
{
var self = this;
self.conn.endTransactionSync(rollback);
self.conn.inTransaction = false;
return self;
};
Database.prototype.commitTransactionSync = function ()
{
var self = this;
self.conn.endTransactionSync(false); //don't rollback
self.conn.inTransaction = false;
return self;
};
Database.prototype.rollbackTransactionSync = function ()
{
var self = this;
self.conn.endTransactionSync(true); //rollback
self.conn.inTransaction = false;
return self;
};
Database.prototype.columns = function (catalog, schema, table, column, callback)
{
var self = this;
if (!self.queue) self.queue = [];
callback = callback || arguments[arguments.length - 1];
self.queue.push(function (next)
{
self.conn.columns(catalog, schema, table, column, function (err, result)
{
if (err) return callback(err, [], false);
result.fetchAll(function (err, data)
{
result.closeSync();
callback(err, data);
return next();
});
});
});
};
Database.prototype.tables = function (catalog, schema, table, type, callback)
{
var self = this;
if (!self.queue) self.queue = [];
callback = callback || arguments[arguments.length - 1];
self.queue.push(function (next)
{
self.conn.tables(catalog, schema, table, type, function (err, result)
{
if (err) return callback(err, [], false);
result.fetchAll(function (err, data)
{
result.closeSync();
callback(err, data);
return next();
});
});
});
};
Database.prototype.describe = function (obj, callback)
{
var self = this;
if (typeof (callback) != "function")
{
throw ({
error: "[node-odbc] Missing Arguments",
message: "You must specify a callback function in order for the describe method to work."
});
return false;
}
if (typeof (obj) != "object")
{
callback({
error: "[node-odbc] Missing Arguments",
message: "You must pass an object as argument 0 if you want anything productive to happen in the describe method."
}, []);
return false;
}
if (!obj.database)
{
callback({
error: "[node-odbc] Missing Arguments",
message: "The object you passed did not contain a database property. This is required for the describe method to work."
}, []);
return false;
}
//set some defaults if they weren't passed
obj.schema = obj.schema || "%";
obj.type = obj.type || "table";
if (obj.table && obj.column)
{
//get the column details
self.columns(obj.database, obj.schema, obj.table, obj.column, callback);
}
else if (obj.table)
{
//get the columns in the table
self.columns(obj.database, obj.schema, obj.table, "%", callback);
}
else
{
//get the tables in the database
self.tables(obj.database, obj.schema, null, obj.type || "table", callback);
}
}; //Database.describe
Database.prototype.prepare = function (sql, cb)
{
var self = this;
self.conn.createStatement(function (err, stmt)
{
if (err) return cb(err);
stmt.queue = new SimpleQueue();
stmt.prepare(sql, function (err)
{
if (err) return cb(err);
return cb(null, stmt);
});
});
}
Database.prototype.prepareSync = function (sql, cb)
{
var self = this;
var stmt = self.conn.createStatementSync();
stmt.queue = new SimpleQueue();
stmt.prepareSync(sql);
return stmt;
}
//Proxy all of the asynchronous functions so that they are queued
odbc.ODBCStatement.prototype._execute = odbc.ODBCStatement.prototype.execute;
odbc.ODBCStatement.prototype._executeDirect = odbc.ODBCStatement.prototype.executeDirect;
odbc.ODBCStatement.prototype._executeNonQuery = odbc.ODBCStatement.prototype.executeNonQuery;
odbc.ODBCStatement.prototype._prepare = odbc.ODBCStatement.prototype.prepare;
odbc.ODBCStatement.prototype._bind = odbc.ODBCStatement.prototype.bind;
odbc.ODBCStatement.prototype.execute = function (params, cb)
{
var self = this;
self.queue = self.queue || new SimpleQueue();
if (!cb)
{
cb = params;
params = null;
}
self.queue.push(function (next)
{
//If params were passed to this function, then bind them and
//then execute.
if (params)
{
self._bind(params, function (err)
{
if (err)
{
return cb(err);
}
self._execute(function (err, result)
{
cb(err, result);
return next();
});
});
}
//Otherwise execute and pop the next bind call
else
{
self._execute(function (err, result)
{
cb(err, result);
//NOTE: We only execute the next queued bind call after
// we have called execute() or executeNonQuery(). This ensures
// that we don't call a bind() a bunch of times without ever
// actually executing that bind. Not
self.bindQueue && self.bindQueue.next();
return next();
});
}
});
};
odbc.ODBCStatement.prototype.executeDirect = function (sql, cb)
{
var self = this;
self.queue = self.queue || new SimpleQueue();
self.queue.push(function (next)
{
self._executeDirect(sql, function (err, result)
{
cb(err, result);
return next();
});
});
};
odbc.ODBCStatement.prototype.executeNonQuery = function (params, cb)
{
var self = this;
self.queue = self.queue || new SimpleQueue();
if (!cb)
{
cb = params;
params = null;
}
self.queue.push(function (next)
{
//If params were passed to this function, then bind them and
//then executeNonQuery.
if (params)
{
self._bind(params, function (err)
{
if (err)
{
return cb(err);
}
self._executeNonQuery(function (err, result)
{
cb(err, result);
return next();
});
});
}
//Otherwise executeNonQuery and pop the next bind call
else
{
self._executeNonQuery(function (err, result)
{
cb(err, result);
//NOTE: We only execute the next queued bind call after
// we have called execute() or executeNonQuery(). This ensures
// that we don't call a bind() a bunch of times without ever
// actually executing that bind. Not
self.bindQueue && self.bindQueue.next();
return next();
});
}
});
};
odbc.ODBCStatement.prototype.prepare = function (sql, cb)
{
var self = this;
self.queue = self.queue || new SimpleQueue();
self.queue.push(function (next)
{
self._prepare(sql, function (err)
{
cb(err);
return next();
});
});
};
odbc.ODBCStatement.prototype.bind = function (ary, cb)
{
var self = this;
self.bindQueue = self.bindQueue || new SimpleQueue();
self.bindQueue.push(function ()
{
self._bind(ary, function (err)
{
cb(err);
//NOTE: we do not call next() here because
//we want to pop the next bind call only
//after the next execute call
});
});
};
module.exports.Pool = Pool;
Pool.count = 0;
function Pool()
{
var self = this;
self.index = Pool.count++;
self.availablePool = {};
self.usedPool = {};
if (!ENV) ENV = new odbc.ODBC();
self.odbc = ENV;
}
Pool.prototype.open = function (connStr, callback)
{
var self = this
, db
;
//check to see if we already have a connection for this connection string
if (self.availablePool[connStr] && self.availablePool[connStr].length)
{
db = self.availablePool[connStr].shift()
self.usedPool[connStr].push(db)
callback(null, db);
}
else
{
db = new Database({ odbc: self.odbc });
db.realClose = db.close;
db.close = function (cb)
{
//call back early, we can do the rest of this stuff after the client
//thinks that the connection is closed.
cb(null);
// If this connection has some active transaction, rollback the
// transaction to free up the held resorces before moving back to
// the pool. So that, next request can get afresh connection from pool.
if (db.conn.inTransaction)
{
db.rollbackTransaction(function (err) { });
}
//remove this db from the usedPool
self.usedPool[connStr].splice(self.usedPool[connStr].indexOf(db), 1);
//move this connection back to the connection pool
self.availablePool[connStr] = self.availablePool[connStr] || [];
self.availablePool[connStr].push(db);
exports.debug && console.dir(self);
}; // db.close function
db.open(connStr, function (error)
{
exports.debug && console.log(
"odbc.js : pool[%s] : pool.db.open callback()", self.index);
self.usedPool[connStr] = self.usedPool[connStr] || [];
self.usedPool[connStr].push(db);
callback(error, db);
}); //db.open
}
};
Pool.prototype.close = function (callback)
{
var self = this
, required = 0
, received = 0
, connections
, key
, x
;
exports.debug && console.log("odbc.js : pool[%s] : pool.close()", self.index);
//we set a timeout because a previous db.close() may
//have caused the a behind the scenes db.open() to prepare
//a new connection
setTimeout(function ()
{
//merge the available pool and the usedPool
var pools = {};
for (key in self.availablePool)
{
pools[key] = (pools[key] || []).concat(self.availablePool[key]);
}
for (key in self.usedPool)
{
pools[key] = (pools[key] || []).concat(self.usedPool[key]);
}
exports.debug && console.log(
"odbc.js : pool[%s] : pool.close() - setTimeout() callback", self.index);
exports.debug && console.dir(pools);
if (Object.keys(pools).length == 0)
{
return callback();
}
for (key in pools)
{
connections = pools[key];
required += connections.length;
exports.debug && console.log("odbc.js : pool[%s] : pool.close() - processing pools %s - connections: %s",
self.index, key, connections.length);
for (x = 0 ; x < connections.length; x++)
{
(function (x)
{
//call the realClose method to avoid
//automatically re-opening the connection
exports.debug && console.log("odbc.js : pool[%s] : pool.close() - calling realClose() for connection #%s",
self.index, x);
connections[x].realClose(function ()
{
exports.debug && console.log("odbc.js : pool[%s] : pool.close() - realClose() callback for connection #%s",
self.index, x);
module.exports.close(connections[x]);
received += 1;
if (received === required)
{
callback();
//prevent mem leaks
self = null;
connections = null;
required = null;
received = null;
key = null;
return;
}
}); // connections[x].realClose i.e. conn.close().
})(x);
} //for loop.
} //for (key in pools)
}, 2000); //setTimeout
}; //Pool.close()