Source: src/jsDbList.js

/**
 * DB list
 */
/*jslint node: true */
'use strict';
/* globals Connection, Environment */

const EncryptedFile = require('./jsEncryptedFile');

/**
 *
 * {Deferred}
 */
const Deferred = require("JQDeferred");

const DataColumn = require("./../client/components/metadata/jsDataSet").DataColumn;
const DataTable = require("./../client/components/metadata/jsDataSet").DataTable;
const DataAccess = require("./jsDataAccess").DataAccess;
const _ = require('lodash');





/**
 * Maintains a list of db connection information, each identified by a dbCode
 * @module dbList
 */










/**
 * Execution context for a request
 * @class Context
 */

function Context(){
    /**
     * @property dbCode
     * {string} dbCode
     */
    this.dbCode=undefined;


    /**
     *  dbDescriptor for the current context
     * @property dbDescriptor
     * {DbDescriptor} dbDescriptor
     */
    this.dbDescriptor=undefined;

    /**
     *  @property createPostData
     * {function} createPostData
     */
    this.createPostData=undefined;



    /**
     * @property getDataCreator
     */
    this.getDataCreator=undefined;

    /**
     * @property formatter
     * {sqlFormatter} formatter
     */
    this.formatter=undefined;

    /**
     * @property sqlConn
     * {Connection} sqlConn
     */
    this.sqlConn=undefined;

    /**
     * @property environment
     * {Environment} environment
     */
    this.environment=undefined;

    /**
     * property dataAccess
     * {DataAccess} dataAccess
     */
    this.dataAccess=undefined;


}

/**
 * @class DbDescriptor
 * A dbDescriptor takes track of the structure of a database. It doesn't manage different schemas.
 * The structure of a table is described with a TableDescriptor
 */
let dbListFile, dbList;

/**
 * Initializes dbList
 * @method init
 * @param options
 * @param {string} [options.fileName] Name of the clean config file to encrypt
 * @param {string} [options.encryptedFileName] name of the config file to be created
 * @param {boolean} options.encrypt true if the config file has to be encrypted
 * @param {boolean} options.decrypt true if the config file has to be decrypted
 * @param {object} [options.secret] object containing key,iv,pwd to replace the config

 */
function init(options) {
    dbListFile = new EncryptedFile(options);
    dbList = dbListFile.read();
}
/**
 * Creates a DbDescriptor, given a database Connection
 * @class DbDescriptor
 * @param {Connection} sqlConn
 * @constructor
 */
function DbDescriptor(sqlConn) {

    /**
     * Dictionary of stored TableDescriptor
     * @private
     * @property tables
     * @type {{TableDescriptor}}
     */
    this.tables = {};
    this.sqlConn = sqlConn;

    /* Security */
    this.security = null;

    /* SecurityProvider */
    this.securityProvider=null;
}

DbDescriptor.prototype = {
    constructor: DbDescriptor
};

/**
 * Get/Set the structure of a table in a JQuery fashioned style
 * @method table
 * @param {string} tableName
 * @param {TableDescriptor} [tableDescriptor]
 * @returns {Promise<TableDescriptor>} or undefined
 */
DbDescriptor.prototype.table = function (tableName, tableDescriptor) {
    const that = this;
    if (tableDescriptor === undefined) {
        const def = Deferred();
        if (this.tables[tableName]) {
            def.resolve(this.tables[tableName]);
            return def.promise();
        }
        this.sqlConn
            .tableDescriptor(tableName)
            .done(function (results) {
                that.tables[tableName] = new TableDescriptor(results.name, results.xtype,
                    results.isDbo, results.columns);
                def.resolve(that.tables[tableName]);
            })
            .fail(function (err) {
                def.reject(err);
            });
        return def.promise();
    }
    this.tables[tableName] = tableDescriptor;
};


/**
 * Creates a DataTable or view having the specified name
 * @param {string} tableName
 * @returns {Promise<DataTable>}
 */
DbDescriptor.prototype.createTable = function (tableName){
    return this.table(tableName)
        .then(descriptor => {
            let t = new DataTable(tableName);
            descriptor.describeTable(t);
            return t;
        });
};


/**
 * Clears the information stored about a table
 * @public
 * @method forgetTable
 * @param {string} tableName
 * @returns {*}
 */
DbDescriptor.prototype.forgetTable = function (tableName) {
    delete this.tables[tableName];
};


/**
 * @class TableDescriptor
 * The structure of a table is described with a TableDescriptor.
 * A TableDescriptor is an object having those properties:
 * {string} xtype:      T for  tables, V for Views
 * {string} name:       table or view name
 * {ColumnDescriptor[]} columns
 *
 */


/**
 * creates a TableDescriptor
 * @private
 * @method TableDescriptor
 * @private
 * @constructor
 * @param {string} name
 * @param {'T'|'V'} xtype  T for tables, V for views
 * @param {boolean} isDbo true if is DBO table/view
 * @param {ColumnDescriptor[]} columns
 */
function TableDescriptor(name, xtype, isDbo, columns) {

    /**
     * Table name
     * @type {string}
     */
    this.name = name;

    /**
     * T for tables, V for views
     * @type {string|string}
     */
    this.xtype = xtype;

    /**
     * isDbo true if is DBO table/view
     * @type {boolean}
     */
    this.dbo = isDbo;

    /**
     * Array of column descriptor
     * @type {ColumnDescriptor[]}
     */
    this.columns = columns;
}


TableDescriptor.prototype = {
    constructor: TableDescriptor
};


/**
 * gets a column descriptor given the column name
 * @public
 * @method columnNames
 * @returns {string[]}
 */
TableDescriptor.prototype.columnNames = function () {
    return _.map(this.columns, "name");
};


/**
 * gets a column descriptor given the column name
 * @method column
 * @param {string} columnName
 * @returns {ColumnDescriptor}
 */
TableDescriptor.prototype.column = function (columnName) {
    return _.find(this.columns, {'name': columnName});
};



/**
 * Add key anc column information to a datatable
 * @param {DataTable} t
 */
TableDescriptor.prototype.describeTable = function (t){
    const k = this.getKey();


    this.columns.forEach(c=>{
        let col = t.setDataColumn(c.name,c.ctype);
        col.allowNull = (c.is_nullable===1);
        col.maxLength = c.max_length;
    });
    if (k.length>0) {
        t.key(k);
    }

};

/**
 * gets an array of all primary key column names
 * @method getKey
 * @returns {Array}
 */
TableDescriptor.prototype.getKey = function () {
    return _.map(_.filter(this.columns, {pk: 1}), 'name');
};

/**
 * @class ColumnDescriptor
 * An object describing a column of a table. It is required to have the following fields:
 *  {string} name        - field name
 *  {string} type        - db type
 *  {string} ctype       - javascript type
 *  {number} max_length  - size of field in bytes
 *  {number} precision   - n. of integer digits managed
 *  {number} scale       - n. of decimal digits
 *  {boolean} is_nullable - true if it can be null
 *  {boolean} pk          - true if it is primary key
 */


/**
 * @private
 * @property allDescriptor
 * @type: Hash of DbDescriptor
 */
const allDescriptors = {};

/**
 * @method getDescriptor
 * @param {string} dbCode
 * @returns {DbDescriptor}
 */
function getDescriptor(dbCode) {
    if (allDescriptors.hasOwnProperty(dbCode)) {
        return allDescriptors[dbCode];
    }
    allDescriptors[dbCode] = new DbDescriptor(getConnection(dbCode));
    return allDescriptors[dbCode];
}


/**
 * gets a Connection eventually taking it from a pool, at the moment it simply returns a new Connection
 * @param {string} dbCode
 * @returns {Connection}
 */
function getConnection(dbCode) {
    const options = getDbInfo(dbCode);
    if (options) {
        options.dbCode = dbCode;
        const Connection = require("./"+options.sqlModule).Connection;
        if (Connection) {
            return new Connection(options);
        }
    }
    return undefined;
}

/**
 * Gets  a promise to a DataAccess
 * @param {string} dbCode
 * @returns {Promise<DataAccess>}
 */
function getDataAccess(dbCode) {
    const q = Deferred(),
        sqlConn = getConnection(dbCode),
         descriptor = getDescriptor(dbCode);
    let securityProviderParam;
    if (!descriptor.security){
        securityProviderParam= descriptor.securityProvider;
    }
    new DataAccess({
        sqlConn: sqlConn,
        securityProvider: securityProviderParam,
        security: descriptor.security,
        errCallBack: function (err) {
            q.reject(err);
        },
        doneCallBack: function (DA) {
            descriptor.security= DA.security; //shares the security class in next calls
            q.resolve(DA);
        }
    });


    return q.promise();
}

/**
 * Get information about a database
 * @method getDbInfo
 * @param {string} dbCode
 *  required for sqlConnection constructor:
 * {string} [driver='SQL Server Native Client 11.0'] Driver name
 * {string} [useTrustedConnection=true] is assumed true if no user name is provided
 * {string} [user] user name for connecting to db
 * {string} [pwd] user password for connecting to db
 * {string} [database] database name
 * {string} [defaultSchema=options.user ||'DBO'] default schema associated with user name
 * {string} [connectionString] connection string to connect (can be used instead of all previous listed)
 * {string} sqlModule module name to user for getting connection
 * @return {Object.<driver,useTrustedConnection,user,pwd,database,defaultSchema,connectionString>}
 */
function getDbInfo(dbCode) {
    if (dbList.hasOwnProperty(dbCode)) {
        return dbList[dbCode];
    }
    return undefined;
}


/**
 * sets information about a database
 * @method setDbInfo
 * @param {string}dbCode
 * @param {Object.<driver,useTrustedConnection,user,pwd,database,defaultSchema,connectionString>} dbData
 */
function setDbInfo(dbCode, dbData) {
    dbList[dbCode] = dbData;
    dbListFile.write(dbList);
}

/**
 * Deletes a Db from the list
 * @method delDbInfo
 * @param {string} dbCode
 * @returns {*}
 */
function delDbInfo(dbCode) {
    if (dbList.hasOwnProperty(dbCode)) {
        delete dbList[dbCode];
        dbListFile.write(dbList);
    }
}


/**
 * Check if a dbCode is present in the list
 * @method existsDbInfo
 * @param {string} dbCode
 * @returns {boolean}
 */
function existsDbInfo(dbCode) {
    return dbList.hasOwnProperty(dbCode);
}


module.exports = {
    init: init,

    getDbInfo: getDbInfo,
    setDbInfo: setDbInfo,
    delDbInfo: delDbInfo,
    existsDbInfo: existsDbInfo,

    getConnection: getConnection,
    getDataAccess: getDataAccess,

    DbDescriptor: DbDescriptor,
    TableDescriptor: TableDescriptor,
    getDescriptor: getDescriptor,

    Context:Context
};