2018-10-30 12:58:02 +00:00
|
|
|
const mysql = require('mysql');
|
2018-11-02 14:44:27 +00:00
|
|
|
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
2018-10-30 12:58:02 +00:00
|
|
|
const MySQL = require('loopback-connector-mysql').MySQL;
|
|
|
|
const EnumFactory = require('loopback-connector-mysql').EnumFactory;
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
|
|
class VnMySQL extends MySQL {
|
|
|
|
toColumnValue(prop, val) {
|
|
|
|
if (val == null || !prop || prop.type !== Date)
|
|
|
|
return MySQL.prototype.toColumnValue.call(this, prop, val);
|
2018-07-09 10:47:22 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
val = new Date(val);
|
2018-07-09 10:47:22 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
return val.getFullYear() + '-' +
|
|
|
|
fillZeros(val.getMonth() + 1) + '-' +
|
|
|
|
fillZeros(val.getDate()) + ' ' +
|
|
|
|
fillZeros(val.getHours()) + ':' +
|
|
|
|
fillZeros(val.getMinutes()) + ':' +
|
|
|
|
fillZeros(val.getSeconds());
|
2018-07-09 10:47:22 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
function fillZeros(v) {
|
|
|
|
return v < 10 ? '0' + v : v;
|
|
|
|
}
|
|
|
|
}
|
2018-07-09 10:47:22 +00:00
|
|
|
|
2019-02-25 13:02:47 +00:00
|
|
|
fromColumnValue(prop, val) {
|
|
|
|
if (val == null || !prop || prop.type !== Date)
|
|
|
|
return MySQL.prototype.fromColumnValue.call(this, prop, val);
|
|
|
|
|
|
|
|
let date = new Date(val);
|
|
|
|
|
|
|
|
return date;
|
|
|
|
}
|
|
|
|
|
|
|
|
isIsoDate(dateString) {
|
|
|
|
let isoRegexp = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/g;
|
|
|
|
return isoRegexp.test(dateString);
|
|
|
|
}
|
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
/**
|
|
|
|
* 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) {
|
2019-02-25 13:02:47 +00:00
|
|
|
if (params) {
|
|
|
|
for (let param of params) {
|
|
|
|
if (param && typeof param.getMonth === 'function' || this.isIsoDate(param)) {
|
|
|
|
if (this.isIsoDate(param)) param = new Date(param);
|
|
|
|
let locale = new Date(param);
|
|
|
|
let offset = locale.getTimezoneOffset() * 60000;
|
|
|
|
param.setTime(param.getTime() - offset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
this.execute(query, params, options, (error, response) => {
|
|
|
|
if (cb)
|
|
|
|
cb(error, response);
|
|
|
|
if (error)
|
|
|
|
reject(error);
|
|
|
|
else
|
|
|
|
resolve(response);
|
2018-07-09 10:47:22 +00:00
|
|
|
});
|
2018-10-30 12:58:02 +00:00
|
|
|
});
|
2018-07-09 10:47:22 +00:00
|
|
|
}
|
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
2018-07-09 10:47:22 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2018-07-09 10:47:22 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
});
|
2018-07-09 10:47:22 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
return wrappedConnector.buildWhere(null, where);
|
|
|
|
}
|
2018-07-09 10:47:22 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
/**
|
|
|
|
* 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];
|
2018-09-04 13:08:36 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
let clauses = [];
|
2018-07-09 10:47:22 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
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(', ')}`;
|
2018-07-09 10:47:22 +00:00
|
|
|
}
|
2018-08-21 11:38:16 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
/**
|
|
|
|
* 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);
|
2018-08-21 11:38:16 +00:00
|
|
|
}
|
2018-10-30 12:58:02 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
]);
|
2018-08-21 11:38:16 +00:00
|
|
|
}
|
2018-10-30 12:58:02 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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);
|
2018-08-21 11:38:16 +00:00
|
|
|
}
|
2018-10-30 12:58:02 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return wrappedConnector.buildWhere(model, where);
|
2018-08-21 11:38:16 +00:00
|
|
|
}
|
2018-10-30 12:58:02 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
]);
|
2018-08-21 11:38:16 +00:00
|
|
|
}
|
2018-10-30 12:58:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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() {});
|
2018-08-21 11:38:16 +00:00
|
|
|
|
2018-10-30 12:58:02 +00:00
|
|
|
dataSource.EnumFactory = EnumFactory;
|
|
|
|
|
2019-02-25 09:04:11 +00:00
|
|
|
if (callback) {
|
2018-10-30 12:58:02 +00:00
|
|
|
if (dataSource.settings.lazyConnect) {
|
|
|
|
process.nextTick(function() {
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
} else
|
|
|
|
dataSource.connector.connect(callback);
|
2019-02-25 09:04:11 +00:00
|
|
|
}
|
2018-08-21 11:38:16 +00:00
|
|
|
};
|