@itentialopensource/adapter-db_sybase
Version:
Itential adapter to connect to Sybase
572 lines (515 loc) • 17.9 kB
JavaScript
/* @copyright Itential, LLC 2019 (pre-modifications) */
// Set globals
/* global log */
/* eslint no-underscore-dangle: warn */
/* eslint no-loop-func: warn */
/* eslint no-cond-assign: warn */
/* eslint no-unused-vars: warn */
/* eslint consistent-return: warn */
/* Required libraries. */
const fs = require('fs-extra');
const path = require('path');
// libraries to import
const SybaseCl = require('sybase');
// Itential framework event
const EventEmitter = require('events');
let myid = null;
let errors = [];
/**
* @summary Build a standard error object from the data provided
*
* @function formatErrorObject
* @param {String} origin - the originator of the error (optional).
* @param {String} type - the internal error type (optional).
* @param {String} variables - the variables to put into the error message (optional).
* @param {Integer} sysCode - the error code from the other system (optional).
* @param {Object} sysRes - the raw response from the other system (optional).
* @param {Exception} stack - any available stack trace from the issue (optional).
*
* @return {Object} - the error object, null if missing pertinent information
*/
function formatErrorObject(origin, type, variables, sysCode, sysRes, stack) {
log.trace(`${myid}-adapter-formatErrorObject`);
// add the required fields
const errorObject = {
icode: 'AD.999',
IAPerror: {
origin: `${myid}-unidentified`,
displayString: 'error not provided',
recommendation: 'report this issue to the adapter team!'
}
};
if (origin) {
errorObject.IAPerror.origin = origin;
}
if (type) {
errorObject.IAPerror.displayString = type;
}
// add the messages from the error.json
for (let e = 0; e < errors.length; e += 1) {
if (errors[e].key === type) {
errorObject.icode = errors[e].icode;
errorObject.IAPerror.displayString = errors[e].displayString;
errorObject.IAPerror.recommendation = errors[e].recommendation;
} else if (errors[e].icode === type) {
errorObject.icode = errors[e].icode;
errorObject.IAPerror.displayString = errors[e].displayString;
errorObject.IAPerror.recommendation = errors[e].recommendation;
}
}
// replace the variables
let varCnt = 0;
while (errorObject.IAPerror.displayString.indexOf('$VARIABLE$') >= 0) {
let curVar = '';
// get the current variable
if (variables && Array.isArray(variables) && variables.length >= varCnt + 1) {
curVar = variables[varCnt];
}
varCnt += 1;
errorObject.IAPerror.displayString = errorObject.IAPerror.displayString.replace('$VARIABLE$', curVar);
}
// add all of the optional fields
if (sysCode) {
errorObject.IAPerror.code = sysCode;
}
if (sysRes) {
errorObject.IAPerror.raw_response = sysRes;
}
if (stack) {
errorObject.IAPerror.stack = stack;
}
// return the object
return errorObject;
}
class Sybase extends EventEmitter {
constructor(prongid, properties) {
log.trace('adapter sybase loading');
// Instantiate the EventEmitter super class
super();
this.props = properties;
this.alive = false;
this.id = prongid;
myid = prongid;
// get the path for the specific error file
const errorFile = path.join(__dirname, '/error.json');
// if the file does not exist - error
if (!fs.existsSync(errorFile)) {
const origin = `${this.id}-adapter-constructor`;
log.warn(`${origin}: Could not locate ${errorFile} - errors will be missing details`);
}
// Read the action from the file system
const errorData = JSON.parse(fs.readFileSync(errorFile, 'utf-8'));
({ errors } = errorData);
}
/**
* Itential connect call for system green/red light
*
* @param {function} callback
*/
connect() {
const meth = 'adapter-connect';
const origin = `${this.id}-${meth}`;
log.trace(origin);
const config = {
host: this.props.host,
port: this.props.port,
username: this.props.authentication.username,
pw: this.props.authentication.password,
dbName: this.props.database
// requestTimeout: this.props.requestTimeout || 1000000,
// options: {
// encrypt: true
// }
};
/* TODO
if (this.props.encrypt !== undefined && this.props.encrypt !== null) {
config.options.encrypt = this.props.encrypt;
}
if (this.props.ssl) {
if (this.props.ssl.enabled === true) {
log.info('Connecting to Sybase with SSL.');
// validate the server's certificate
if (this.props.ssl.accept_invalid_cert === false) {
log.info('Certificate based SSL Sybase connections will be used.');
config.ssl = {
rejectUnauthorized: true
};
// if validation is enabled, we need to read the CA file
if (this.props.ssl.ca_file) {
try {
config.ssl.ca = fs.readFileSync(this.props.ssl.ca_file);
} catch (err) {
log.error(`Error: Unable to load Sybase CA file: ${err}`);
this.alive = false;
this.emit('OFFLINE', {
id: this.id
});
return;
}
} else {
log.error('Error: Certificate validation'
+ 'is enabled but a CA is not specified.');
this.alive = false;
this.emit('OFFLINE', {
id: this.id
});
return;
}
} else {
log.info('SSL Sybase connection without CA certificate validation.');
config.ssl = {
rejectUnauthorized: false
};
}
} else {
log.warn('WARNING: Connecting to Sybase without SSL.');
}
} else {
log.warn('WARNING: Connecting to Sybase without SSL.');
}
*/
this.request = new SybaseCl(config.host, config.port, config.dbName, config.username, config.pw);
// get connection
this.request.connect((error) => {
if (error) {
log.error(`error connecting to Sybase: ${error}`);
// emit failure
this.alive = false;
this.emit('OFFLINE', {
id: this.id
});
} else {
// emit success
this.alive = true;
this.emit('ONLINE', {
id: this.id
});
// log.info(`connected as id: ${this.connection.threadId}`);
}
});
// this.connection = sybase.createConnection(config);
// this.connection.connect((error) => {
// if (error) {
// log.error(`error connecting to Sybase: ${error}`);
// // emit failure
// this.alive = false;
// this.emit('OFFLINE', {
// id: this.id
// });
// } else {
// // emit success
// this.alive = true;
// this.emit('ONLINE', {
// id: this.id
// });
// log.info(`connected as id: ${this.connection.threadId}`);
// }
// });
}
/**
* Call to run a healthcheck on the sybase database
*
* @function healthCheck
* @param {healthCallback} callback - a callback function to return a result
* healthcheck success or failure
*/
healthCheck(callback) {
const meth = 'adapter-healthCheck';
const origin = `${this.id}-${meth}`;
log.trace(origin);
try {
// verify that we are connected to Sybase
if (!this.alive || !this.request) {
log.error('Error during healthcheck: Not connected to Sybase Database');
return callback({
id: this.id,
status: 'fail'
});
}
this.request.query('SELECT GETDATE();', (error, results) => {
if (error) {
log.error(`Error during healthcheck: ${error}`);
return callback({
id: this.id,
status: 'fail'
});
}
log.info(`result from sybase connect: ${JSON.stringify(results)}`);
return callback({
id: this.id,
status: 'success'
});
});
} catch (ex) {
log.error(`Exception during healthcheck: ${ex}`);
return callback({
id: this.id,
status: 'fail'
});
}
}
/**
* getAllFunctions is used to get all of the exposed function in the adapter
*
* @function getAllFunctions
*/
getAllFunctions() {
let myfunctions = [];
let obj = this;
// find the functions in this class
do {
const l = Object.getOwnPropertyNames(obj)
.concat(Object.getOwnPropertySymbols(obj).map((s) => s.toString()))
.sort()
.filter((p, i, arr) => typeof obj[p] === 'function' && p !== 'constructor' && (i === 0 || p !== arr[i - 1]) && myfunctions.indexOf(p) === -1);
myfunctions = myfunctions.concat(l);
}
while (
(obj = Object.getPrototypeOf(obj)) && Object.getPrototypeOf(obj)
);
return myfunctions;
}
/**
* getWorkflowFunctions is used to get all of the workflow function in the adapter
*
* @function getWorkflowFunctions
*/
getWorkflowFunctions() {
const myfunctions = this.getAllFunctions();
const wffunctions = [];
// remove the functions that should not be in a Workflow
for (let m = 0; m < myfunctions.length; m += 1) {
if (myfunctions[m] === 'addListener') {
// got to the second tier (adapterBase)
break;
}
if (myfunctions[m] !== 'connect' && myfunctions[m] !== 'healthCheck'
&& myfunctions[m] !== 'getAllFunctions' && myfunctions[m] !== 'getWorkflowFunctions') {
wffunctions.push(myfunctions[m]);
}
}
return wffunctions;
}
/**
* Call to query the Sybase server.
* @function query
* @param sql - a sql string (required)
* @param callback - a callback function to return a result
*/
query(sql, callback) {
const meth = 'adapter-query';
const origin = `${this.id}-${meth}`;
log.trace(origin);
log.trace(`sybase query started with sql: ${sql}`);
try {
// verify the required data has been provided
if (!sql) {
const errorObj = formatErrorObject(origin, 'Missing Data', ['sql'], null, null, null);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
// query connection
this.request.connect((error) => {
if (error) {
log.error(`error connecting to Sybase: ${error}`);
// emit failure
this.alive = false;
this.emit('OFFLINE', {
id: this.id
});
} else {
this.request.query(sql, (qerror, results, fields) => {
// close connection
this.request.disconnect();
log.debug(`result from query: ${JSON.stringify(results)}`);
log.debug(`result from query: ${JSON.stringify(fields)}`);
if (qerror) {
const errorObj = formatErrorObject(origin, 'Database Error', [qerror], null, null, null);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
return callback({
status: 'success',
code: 200,
response: results
});
});
}
});
} catch (ex) {
const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
}
/**
* Call to create a table into Sybase server.
* @function create
* @param sql - a sql string (required)
* @param callback - a callback function to return a result
*/
create(sql, callback) {
const meth = 'adapter-create';
const origin = `${this.id}-${meth}`;
log.trace(origin);
log.trace(`sybase create started with sql: ${sql}`);
try {
// verify the required data has been provided
if (!sql) {
const errorObj = formatErrorObject(origin, 'Missing Data', ['sql'], null, null, null);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
if (!sql.toLowerCase().startsWith('create')) {
return callback(null, 'SQL statement must start with "CREATE"');
}
this.query(sql, callback);
} catch (ex) {
const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
}
/**
* Call to select item from Sybase server.
* @function select
* @param sql - a sql string (required)
* @param callback - a callback function to return a result
*/
select(sql, callback) {
const meth = 'adapter-select';
const origin = `${this.id}-${meth}`;
log.trace(origin);
log.trace(`sybase insert started with sql: ${sql}`);
try {
// verify the required data has been provided
if (!sql) {
const errorObj = formatErrorObject(origin, 'Missing Data', ['sql'], null, null, null);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
if (!sql.toLowerCase().startsWith('select')) {
return callback(null, 'SQL statement must start with "SELECT"');
}
this.query(sql, callback);
} catch (ex) {
const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
}
/**
* Call to insert item into Sybase server.
* @function insert
* @param sql - a sql string (required)
* @param callback - a callback function to return a result
*/
insert(sql, callback) {
const meth = 'adapter-insert';
const origin = `${this.id}-${meth}`;
log.trace(origin);
log.trace(`sybase insert started with sql: ${sql}`);
try {
// verify the required data has been provided
if (!sql) {
const errorObj = formatErrorObject(origin, 'Missing Data', ['sql'], null, null, null);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
if (!sql.toLowerCase().startsWith('insert')) {
return callback(null, 'SQL statement must start with "INSERT"');
}
this.query(sql, callback);
} catch (ex) {
const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
}
/**
* Call to update item into Sybase server.
* @function update
* @param sql - a sql string (required)
* @param callback - a callback function to return a result
*/
update(sql, callback) {
const meth = 'adapter-update';
const origin = `${this.id}-${meth}`;
log.trace(origin);
log.trace(`sybase update started with sql: ${sql}`);
try {
// verify the required data has been provided
if (!sql) {
const errorObj = formatErrorObject(origin, 'Missing Data', ['sql'], null, null, null);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
if (!sql.toLowerCase().startsWith('update')) {
return callback(null, 'SQL statement must start with "UPDATE"');
}
this.query(sql, callback);
} catch (ex) {
const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
}
/**
* Call to delete item from Sybase server.
* @function delete
* @param sql - a sql string (required)
* @param callback - a callback function to return a result
*/
delete(sql, callback) {
const meth = 'adapter-delete';
const origin = `${this.id}-${meth}`;
log.trace(origin);
log.trace(`sybase delete started with sql: ${sql}`);
try {
// verify the required data has been provided
if (!sql) {
const errorObj = formatErrorObject(origin, 'Missing Data', ['sql'], null, null, null);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
if (!sql.toLowerCase().startsWith('delete')) {
return callback(null, 'SQL statement must start with "DELETE"');
}
this.query(sql, callback);
} catch (ex) {
const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
}
/**
* Call to drop table from Sybase server.
* @function drop
* @param sql - a sql string (required)
* @param callback - a callback function to return a result
*/
drop(sql, callback) {
const meth = 'adapter-drop';
const origin = `${this.id}-${meth}`;
log.trace(origin);
log.trace(`sybase drop started with sql: ${sql}`);
try {
// verify the required data has been provided
if (!sql) {
const errorObj = formatErrorObject(origin, 'Missing Data', ['sql'], null, null, null);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
if (!sql.toLowerCase().startsWith('drop')) {
return callback(null, 'SQL statement must start with "DROP"');
}
this.query(sql, callback);
} catch (ex) {
const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex);
log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
return callback(null, errorObj);
}
}
}
// export to Itential
module.exports = Sybase;