const mysql = require('mysql'); const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const MySQL = require('loopback-connector-mysql').MySQL; const EnumFactory = require('loopback-connector-mysql').EnumFactory; const fs = require('fs'); class VnMySQL extends MySQL { /** * Promisified version of execute(). * * @param {String} query The SQL query string * @param {Array} params The query parameters * @param {Object} options The loopback options * @param {Function} cb The callback * @return {Promise} The operation promise */ executeP(query, params, options = {}, cb) { return new Promise((resolve, reject) => { this.execute(query, params, options, (error, response) => { if (cb) cb(error, response); if (error) reject(error); else resolve(response); }); }); } /** * Executes an SQL query from an Stmt. * * @param {ParameterizedSql} stmt - Stmt object * @param {Object} options Query options (Ex: {transaction}) * @return {Object} Connector promise */ executeStmt(stmt, options) { return this.executeP(stmt.sql, stmt.params, options); } /** * Executes a query from an SQL script. * * @param {String} sqlScript The sql script file * @param {Array} params The query parameters * @param {Object} options Query options (Ex: {transaction}) * @return {Object} Connector promise */ executeScript(sqlScript, params, options) { return new Promise((resolve, reject) => { fs.readFile(sqlScript, 'utf8', (err, contents) => { if (err) return reject(err); this.execute(contents, params, options) .then(resolve, reject); }); }); } /** * Build the SQL WHERE clause for the where object without checking that * properties exists in the model. * * @param {object} where An object for the where conditions * @return {ParameterizedSQL} The SQL WHERE clause */ makeWhere(where) { let wrappedConnector = Object.create(this); Object.assign(wrappedConnector, { getModelDefinition() { return { properties: new Proxy({}, { get: () => true }) }; }, toColumnValue(_, val) { return val; }, columnEscaped(_, property) { return this.escapeName(property); } }); return wrappedConnector.buildWhere(null, where); } /** * Constructs SQL GROUP BY clause from Loopback filter. * * @param {Object} group The group by definition * @return {String} Built SQL group by */ makeGroupBy(group) { if (!group) return ''; if (typeof group === 'string') group = [group]; let clauses = []; for (let clause of group) { let sqlGroup = ''; let t = clause.split(/[\s,]+/); sqlGroup += this.escapeName(t[0]); clauses.push(sqlGroup); } return `GROUP BY ${clauses.join(', ')}`; } /** * Constructs SQL order clause from Loopback filter. * * @param {Object} order The order definition * @return {String} Built SQL order */ makeOrderBy(order) { if (!order) return ''; if (typeof order === 'string') order = [order]; let clauses = []; for (let clause of order) { let sqlOrder = ''; let t = clause.split(/[\s,]+/); sqlOrder += this.escapeName(t[0]); if (t.length > 1) sqlOrder += ' ' + (t[1].toUpperCase() == 'ASC' ? 'ASC' : 'DESC'); clauses.push(sqlOrder); } return `ORDER BY ${clauses.join(', ')}`; } /** * Constructs SQL limit clause from Loopback filter. * * @param {Object} filter The loopback filter * @return {String} Built SQL limit */ makeLimit(filter) { let limit = parseInt(filter.limit); let offset = parseInt(filter.offset || filter.skip); return this._buildLimit(null, limit, offset); } /** * Constructs SQL pagination from Loopback filter. * * @param {Object} filter The loopback filter * @return {String} Built SQL pagination */ makePagination(filter) { return ParameterizedSQL.join([ this.makeOrderBy(filter.order), this.makeLimit(filter) ]); } /** * Constructs SQL filter including where, order and limit * clauses from Loopback filter. * * @param {Object} filter The loopback filter * @return {String} Built SQL filter */ makeSuffix(filter) { return ParameterizedSQL.join([ this.makeWhere(filter.where), this.makePagination(filter) ]); } /** * Constructs SQL where clause from Loopback filter discarding * properties that not pertain to the model. If defined, appends * the table alias to each field. * * @param {String} model The model name * @param {Object} where The loopback where filter * @param {String} tableAlias Query main table alias * @return {String} Built SQL where */ buildModelWhere(model, where, tableAlias) { let parent = this; let wrappedConnector = Object.create(this); Object.assign(wrappedConnector, { columnEscaped(model, property) { let sql = tableAlias ? this.escapeName(tableAlias) + '.' : ''; return sql + parent.columnEscaped(model, property); } }); return wrappedConnector.buildWhere(model, where); } /** * Constructs SQL where clause from Loopback filter discarding * properties that not pertain to the model. If defined, appends * the table alias to each field. * * @param {String} model The model name * @param {Object} filter The loopback filter * @param {String} tableAlias Query main table alias * @return {String} Built SQL suffix */ buildModelSuffix(model, filter, tableAlias) { return ParameterizedSQL.join([ this.buildModelWhere(model, filter.where, tableAlias), this.makePagination(filter) ]); } } exports.VnMySQL = VnMySQL; exports.initialize = function initialize(dataSource, callback) { dataSource.driver = mysql; dataSource.connector = new VnMySQL(dataSource.settings); dataSource.connector.dataSource = dataSource; const modelBuilder = dataSource.modelBuilder; const defineType = modelBuilder.defineValueType ? modelBuilder.defineValueType.bind(modelBuilder) : modelBuilder.constructor.registerType.bind(modelBuilder.constructor); defineType(function Point() {}); dataSource.EnumFactory = EnumFactory; if (callback) { if (dataSource.settings.lazyConnect) { process.nextTick(function() { callback(); }); } else dataSource.connector.connect(callback); } };