不要怂,就是干,撸起袖子干!

Commit 49a7e543 by Felix Becker Committed by Mick Hansen

ES6 Refactor: dialects / abstract (#6046)

* Make abstract ConnectionManager an ES6 class

* ES6 refactor of abstract ConnectionManager

let, const, arrow functions, property shorthands

* ES6 refactor of AbstractDialect

classes, export default

* Make AbstractQuery an ES6 class

* ES6 refactor of AbstractQuery

let, const, arrow functions, for of

* ES6 refactor of abstract QueryGenerator

use let, const, arrow functions, method shorthands, for..of, sometimes
template strings.
Changes variable declarations so they are declared where initialized
(instead of on the top of the function) as let/const is not hoisted anymore.
Removes some IIFEs in favor of block-scoped variables.
Does _not_ yet refactor lodash template strings to ES6 template strings.
1 parent f50469b6
'use strict'; 'use strict';
var Pooling = require('generic-pool') const Pooling = require('generic-pool');
, Promise = require('../../promise') const Promise = require('../../promise');
, _ = require('lodash') const _ = require('lodash');
, semver = require('semver') const semver = require('semver');
, defaultPoolingConfig = { const defaultPoolingConfig = {
max: 5, max: 5,
min: 0, min: 0,
idle: 10000, idle: 10000,
handleDisconnects: true handleDisconnects: true
} };
, ConnectionManager;
ConnectionManager = function(dialect, sequelize) {
var config = _.cloneDeep(sequelize.config);
this.sequelize = sequelize;
this.config = config;
this.dialect = dialect;
this.versionPromise = null;
this.dialectName = this.sequelize.options.dialect;
if (config.pool !== false) {
config.pool =_.defaults(config.pool || {}, defaultPoolingConfig, {
validate: this.$validate.bind(this)
}) ;
} else {
throw new Error('Support for pool:false was removed in v4.0');
}
// Save a reference to the bound version so we can remove it with removeListener class ConnectionManager {
this.onProcessExit = this.onProcessExit.bind(this);
process.on('exit', this.onProcessExit); constructor(dialect, sequelize) {
}; const config = _.cloneDeep(sequelize.config);
ConnectionManager.prototype.refreshTypeParser = function(dataTypes) { this.sequelize = sequelize;
_.each(dataTypes, function (dataType, key) { this.config = config;
if (dataType.hasOwnProperty('parse')) { this.dialect = dialect;
if (dataType.types[this.dialectName]) { this.versionPromise = null;
this.$refreshTypeParser(dataType); this.dialectName = this.sequelize.options.dialect;
} else {
throw new Error('Parse function not supported for type ' + dataType.key + ' in dialect ' + this.dialectName); if (config.pool !== false) {
} config.pool =_.defaults(config.pool || {}, defaultPoolingConfig, {
validate: this.$validate.bind(this)
}) ;
} else {
throw new Error('Support for pool:false was removed in v4.0');
} }
}.bind(this));
};
ConnectionManager.prototype.onProcessExit = function() { // Save a reference to the bound version so we can remove it with removeListener
var self = this; this.onProcessExit = this.onProcessExit.bind(this);
if (this.pool) { process.on('exit', this.onProcessExit);
this.pool.drain(function() {
self.pool.destroyAllNow();
});
} }
};
ConnectionManager.prototype.close = function () {
this.onProcessExit();
process.removeListener('exit', this.onProcessExit); // Remove the listener, so all references to this instance can be garbage collected.
this.getConnection = function () { refreshTypeParser(dataTypes) {
return Promise.reject(new Error('ConnectionManager.getConnection was called after the connection manager was closed!')); _.each(dataTypes, (dataType, key) => {
}; if (dataType.hasOwnProperty('parse')) {
}; if (dataType.types[this.dialectName]) {
this.$refreshTypeParser(dataType);
// This cannot happen in the constructor because the user can specify a min. number of connections to have in the pool } else {
// If he does this, generic-pool will try to call connect before the dialect-specific connection manager has been correctly set up throw new Error('Parse function not supported for type ' + dataType.key + ' in dialect ' + this.dialectName);
ConnectionManager.prototype.initPools = function () { }
var self = this }
, config = this.config;
if (!config.replication) {
this.pool = Pooling.Pool({
name: 'sequelize-connection',
create: function(callback) {
self.$connect(config).nodeify(function (err, connection) {
callback(err, connection); // For some reason this is needed, else generic-pool things err is a connection or some shit
});
},
destroy: function(connection) {
self.$disconnect(connection);
return null;
},
max: config.pool.max,
min: config.pool.min,
validate: config.pool.validate,
idleTimeoutMillis: config.pool.idle
}); });
return;
} }
var reads = 0; onProcessExit() {
if (this.pool) {
if (!Array.isArray(config.replication.read)) { this.pool.drain(() => {
config.replication.read = [config.replication.read]; this.pool.destroyAllNow();
});
}
} }
// Map main connection config close() {
config.replication.write = _.defaults(config.replication.write, _.omit(config, 'replication')); this.onProcessExit();
process.removeListener('exit', this.onProcessExit); // Remove the listener, so all references to this instance can be garbage collected.
// Apply defaults to each read config this.getConnection = function getConnection() {
config.replication.read = _.map(config.replication.read, function(readConfig) { return Promise.reject(new Error('ConnectionManager.getConnection was called after the connection manager was closed!'));
return _.defaults(readConfig, _.omit(self.config, 'replication')); };
}); }
// I'll make my own pool, with blackjack and hookers! (original credit goes to @janzeh) // This cannot happen in the constructor because the user can specify a min. number of connections to have in the pool
this.pool = { // If he does this, generic-pool will try to call connect before the dialect-specific connection manager has been correctly set up
release: function(client) { initPools() {
if (client.queryType === 'read') { const config = this.config;
return self.pool.read.release(client);
} else { if (!config.replication) {
return self.pool.write.release(client); this.pool = Pooling.Pool({
} name: 'sequelize-connection',
}, create: (callback) => {
acquire: function(callback, priority, queryType, useMaster) { this.$connect(config).nodeify((err, connection) => {
useMaster = _.isUndefined(useMaster) ? false : useMaster; callback(err, connection); // For some reason this is needed, else generic-pool things err is a connection or some shit
if (queryType === 'SELECT' && !useMaster) { });
self.pool.read.acquire(callback, priority); },
} else { destroy: (connection) => {
self.pool.write.acquire(callback, priority); this.$disconnect(connection);
} return null;
}, },
destroy: function(connection) { max: config.pool.max,
return self.pool[connection.queryType].destroy(connection); min: config.pool.min,
}, validate: config.pool.validate,
destroyAllNow: function() { idleTimeoutMillis: config.pool.idle
self.pool.read.destroyAllNow();
self.pool.write.destroyAllNow();
},
drain: function(cb) {
self.pool.write.drain(function() {
self.pool.read.drain(cb);
}); });
}, return;
read: Pooling.Pool({ }
name: 'sequelize-connection-read',
create: function(callback) { let reads = 0;
// Simple round robin config
var nextRead = reads++ % config.replication.read.length; if (!Array.isArray(config.replication.read)) {
self.$connect(config.replication.read[nextRead]).tap(function (connection) { config.replication.read = [config.replication.read];
connection.queryType = 'read'; }
}).nodeify(function (err, connection) {
callback(err, connection); // For some reason this is needed, else generic-pool things err is a connection or some shit // Map main connection config
}); config.replication.write = _.defaults(config.replication.write, _.omit(config, 'replication'));
// Apply defaults to each read config
config.replication.read = _.map(config.replication.read, readConfig =>
_.defaults(readConfig, _.omit(this.config, 'replication'))
);
// I'll make my own pool, with blackjack and hookers! (original credit goes to @janzeh)
this.pool = {
release: client => {
if (client.queryType === 'read') {
return this.pool.read.release(client);
} else {
return this.pool.write.release(client);
}
}, },
destroy: function(connection) { acquire: (callback, priority, queryType, useMaster) => {
self.$disconnect(connection); useMaster = _.isUndefined(useMaster) ? false : useMaster;
return null; if (queryType === 'SELECT' && !useMaster) {
this.pool.read.acquire(callback, priority);
} else {
this.pool.write.acquire(callback, priority);
}
}, },
validate: config.pool.validate, destroy: (connection) => {
max: config.pool.max, return this.pool[connection.queryType].destroy(connection);
min: config.pool.min,
idleTimeoutMillis: config.pool.idle
}),
write: Pooling.Pool({
name: 'sequelize-connection-write',
create: function(callback) {
self.$connect(config.replication.write).tap(function (connection) {
connection.queryType = 'write';
}).nodeify(function (err, connection) {
callback(err, connection); // For some reason this is needed, else generic-pool things err is a connection or some shit
});
}, },
destroy: function(connection) { destroyAllNow: () => {
self.$disconnect(connection); this.pool.read.destroyAllNow();
return null; this.pool.write.destroyAllNow();
}, },
validate: config.pool.validate, drain: (cb) => {
max: config.pool.max, this.pool.write.drain(() => {
min: config.pool.min, this.pool.read.drain(cb);
idleTimeoutMillis: config.pool.idle });
}) },
}; read: Pooling.Pool({
}; name: 'sequelize-connection-read',
create: (callback) => {
ConnectionManager.prototype.getConnection = function(options) { // Simple round robin config
var self = this; const nextRead = reads++ % config.replication.read.length;
options = options || {}; this.$connect(config.replication.read[nextRead]).tap(connection => {
connection.queryType = 'read';
var promise; }).nodeify((err, connection) => {
if (this.sequelize.options.databaseVersion === 0) { callback(err, connection); // For some reason this is needed, else generic-pool things err is a connection or some shit
if (this.versionPromise) { });
promise = this.versionPromise; },
} else { destroy: connection => {
promise = this.versionPromise = self.$connect(self.config.replication.write || self.config).then(function (connection) { this.$disconnect(connection);
var _options = {}; return null;
_options.transaction = { connection: connection }; // Cheat .query to use our private connection },
_options.logging = function () {}; validate: config.pool.validate,
_options.logging.__testLoggingFn = true; max: config.pool.max,
min: config.pool.min,
return self.sequelize.databaseVersion(_options).then(function (version) { idleTimeoutMillis: config.pool.idle
self.sequelize.options.databaseVersion = semver.valid(version) ? version : self.defaultVersion; }),
write: Pooling.Pool({
name: 'sequelize-connection-write',
create: callback => {
this.$connect(config.replication.write).tap(connection => {
connection.queryType = 'write';
}).nodeify((err, connection) => {
callback(err, connection); // For some reason this is needed, else generic-pool things err is a connection or some shit
});
},
destroy: connection => {
this.$disconnect(connection);
return null;
},
validate: config.pool.validate,
max: config.pool.max,
min: config.pool.min,
idleTimeoutMillis: config.pool.idle
})
};
}
self.versionPromise = null; getConnection(options) {
options = options || {};
self.$disconnect(connection); let promise;
return null; if (this.sequelize.options.databaseVersion === 0) {
if (this.versionPromise) {
promise = this.versionPromise;
} else {
promise = this.versionPromise = this.$connect(this.config.replication.write || this.config).then(connection => {
const _options = {};
_options.transaction = {connection}; // Cheat .query to use our private connection
_options.logging = () => {};
_options.logging.__testLoggingFn = true;
return this.sequelize.databaseVersion(_options).then(version => {
this.sequelize.options.databaseVersion = semver.valid(version) ? version : this.defaultVersion;
this.versionPromise = null;
this.$disconnect(connection);
return null;
});
}).catch(err => {
this.versionPromise = null;
throw err;
}); });
}).catch(function (err) { }
self.versionPromise = null; } else {
throw err; promise = Promise.resolve();
});
} }
} else {
promise = Promise.resolve();
}
return promise.then(function () { return promise.then(() => new Promise((resolve, reject) => {
return new Promise(function (resolve, reject) { this.pool.acquire((err, connection) => {
self.pool.acquire(function(err, connection) {
if (err) return reject(err); if (err) return reject(err);
resolve(connection); resolve(connection);
}, options.priority, options.type, options.useMaster); }, options.priority, options.type, options.useMaster);
}); }));
}); }
};
ConnectionManager.prototype.releaseConnection = function(connection) { releaseConnection(connection) {
var self = this; return new Promise((resolve, reject) => {
this.pool.release(connection);
resolve();
});
}
return new Promise(function (resolve, reject) { $connect(config) {
self.pool.release(connection); return this.sequelize.runHooks('beforeConnect', config)
resolve(); .then(() => this.dialect.connectionManager.connect(config));
}); }
};
ConnectionManager.prototype.$connect = function(config) { $disconnect(connection) {
return this.sequelize.runHooks('beforeConnect', config).bind(this).then(function () { return this.dialect.connectionManager.disconnect(connection);
return this.dialect.connectionManager.connect(config).then(function (connection) { }
return connection;
});
});
};
ConnectionManager.prototype.$disconnect = function(connection) {
return this.dialect.connectionManager.disconnect(connection);
};
ConnectionManager.prototype.$validate = function(connection) { $validate(connection) {
if (!this.dialect.connectionManager.validate) return Promise.resolve(); if (!this.dialect.connectionManager.validate) return Promise.resolve();
return this.dialect.connectionManager.validate(connection); return this.dialect.connectionManager.validate(connection);
}; }
}
module.exports = ConnectionManager; module.exports = ConnectionManager;
module.exports.ConnectionManager = ConnectionManager;
module.exports.default = ConnectionManager;
'use strict'; 'use strict';
var AbstractDialect = function() { class AbstractDialect {}
};
AbstractDialect.prototype.supports = { AbstractDialect.prototype.supports = {
'DEFAULT': true, 'DEFAULT': true,
...@@ -62,3 +60,5 @@ AbstractDialect.prototype.supports = { ...@@ -62,3 +60,5 @@ AbstractDialect.prototype.supports = {
}; };
module.exports = AbstractDialect; module.exports = AbstractDialect;
module.exports.AbstractDialect = AbstractDialect;
module.exports.default = AbstractDialect;
'use strict'; 'use strict';
var Utils = require('../../utils') const Utils = require('../../utils');
, SqlString = require('../../sql-string') const SqlString = require('../../sql-string');
, Model = require('../../model') const Model = require('../../model');
, DataTypes = require('../../data-types') const DataTypes = require('../../data-types');
, _ = require('lodash') const _ = require('lodash');
, util = require('util') const util = require('util');
, Dottie = require('dottie') const Dottie = require('dottie');
, BelongsTo = require('../../associations/belongs-to') const BelongsTo = require('../../associations/belongs-to');
, uuid = require('node-uuid') const uuid = require('node-uuid');
, semver = require('semver'); const semver = require('semver');
/* istanbul ignore next */ /* istanbul ignore next */
var throwMethodUndefined = function(methodName) { function throwMethodUndefined(methodName) {
throw new Error('The method "' + methodName + '" is not defined! Please add it to your sql dialect.'); throw new Error('The method "' + methodName + '" is not defined! Please add it to your sql dialect.');
}; }
var QueryGenerator = { const QueryGenerator = {
options: {}, options: {},
extractTableDetails: function(tableName, options) { extractTableDetails(tableName, options) {
options = options || {}; options = options || {};
tableName = tableName || {}; tableName = tableName || {};
return { return {
...@@ -29,8 +29,8 @@ var QueryGenerator = { ...@@ -29,8 +29,8 @@ var QueryGenerator = {
}; };
}, },
addSchema: function(param) { addSchema(param) {
var self = this; const self = this;
if (!param.$schema) return param.tableName || param; if (!param.$schema) return param.tableName || param;
...@@ -40,7 +40,7 @@ var QueryGenerator = { ...@@ -40,7 +40,7 @@ var QueryGenerator = {
name: param.name || param, name: param.name || param,
schema: param.$schema, schema: param.$schema,
delimiter: param.$schemaDelimiter || '.', delimiter: param.$schemaDelimiter || '.',
toString: function() { toString() {
return self.quoteTable(this); return self.quoteTable(this);
} }
}; };
...@@ -49,7 +49,7 @@ var QueryGenerator = { ...@@ -49,7 +49,7 @@ var QueryGenerator = {
/* /*
Returns a query for dropping a schema Returns a query for dropping a schema
*/ */
dropSchema: function(tableName, options) { dropSchema(tableName, options) {
return this.dropTableQuery(tableName, options); return this.dropTableQuery(tableName, options);
}, },
...@@ -65,18 +65,18 @@ var QueryGenerator = { ...@@ -65,18 +65,18 @@ var QueryGenerator = {
Defaults: { engine: 'InnoDB', charset: null } Defaults: { engine: 'InnoDB', charset: null }
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
createTableQuery: function(tableName, attributes, options) { createTableQuery(tableName, attributes, options) {
throwMethodUndefined('createTableQuery'); throwMethodUndefined('createTableQuery');
}, },
versionQuery: function(tableName, attributes, options) { versionQuery(tableName, attributes, options) {
throwMethodUndefined('versionQuery'); throwMethodUndefined('versionQuery');
}, },
describeTableQuery: function(tableName, schema, schemaDelimiter) { describeTableQuery(tableName, schema, schemaDelimiter) {
var table = this.quoteTable( const table = this.quoteTable(
this.addSchema({ this.addSchema({
tableName: tableName, tableName,
$schema: schema, $schema: schema,
$schemaDelimiter: schemaDelimiter $schemaDelimiter: schemaDelimiter
}) })
...@@ -88,14 +88,10 @@ var QueryGenerator = { ...@@ -88,14 +88,10 @@ var QueryGenerator = {
/* /*
Returns a query for dropping a table. Returns a query for dropping a table.
*/ */
dropTableQuery: function(tableName, options) { dropTableQuery(tableName, options) {
options = options || {}; options = options || {};
var query = 'DROP TABLE IF EXISTS <%= table %>;'; return `DROP TABLE IF EXISTS ${this.quoteTable(tableName)};`;
return Utils._.template(query)({
table: this.quoteTable(tableName)
});
}, },
/* /*
...@@ -104,19 +100,15 @@ var QueryGenerator = { ...@@ -104,19 +100,15 @@ var QueryGenerator = {
- originalTableName: Name of the table before execution. - originalTableName: Name of the table before execution.
- futureTableName: Name of the table after execution. - futureTableName: Name of the table after execution.
*/ */
renameTableQuery: function(before, after) { renameTableQuery(before, after) {
var query = 'ALTER TABLE <%= before %> RENAME TO <%= after %>;'; return `ALTER TABLE ${this.quoteTable(before)} RENAME TO ${this.quoteTable(after)};`;
return Utils._.template(query)({
before: this.quoteTable(before),
after: this.quoteTable(after)
});
}, },
/* /*
Returns a query, which gets all available table names in the database. Returns a query, which gets all available table names in the database.
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
showTablesQuery: function() { showTablesQuery() {
throwMethodUndefined('showTablesQuery'); throwMethodUndefined('showTablesQuery');
}, },
...@@ -132,7 +124,7 @@ var QueryGenerator = { ...@@ -132,7 +124,7 @@ var QueryGenerator = {
- allowNull: Boolean - allowNull: Boolean
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
addColumnQuery: function(tableName, attributes) { addColumnQuery(tableName, attributes) {
throwMethodUndefined('addColumnQuery'); throwMethodUndefined('addColumnQuery');
}, },
...@@ -143,7 +135,7 @@ var QueryGenerator = { ...@@ -143,7 +135,7 @@ var QueryGenerator = {
- attributeName: Name of the obsolete attribute. - attributeName: Name of the obsolete attribute.
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
removeColumnQuery: function(tableName, attributeName) { removeColumnQuery(tableName, attributeName) {
throwMethodUndefined('removeColumnQuery'); throwMethodUndefined('removeColumnQuery');
}, },
...@@ -159,7 +151,7 @@ var QueryGenerator = { ...@@ -159,7 +151,7 @@ var QueryGenerator = {
- allowNull: Boolean - allowNull: Boolean
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
changeColumnQuery: function(tableName, attributes) { changeColumnQuery(tableName, attributes) {
throwMethodUndefined('changeColumnQuery'); throwMethodUndefined('changeColumnQuery');
}, },
...@@ -171,37 +163,28 @@ var QueryGenerator = { ...@@ -171,37 +163,28 @@ var QueryGenerator = {
- attrNameAfter: The name of the attribute, after renaming. - attrNameAfter: The name of the attribute, after renaming.
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
renameColumnQuery: function(tableName, attrNameBefore, attrNameAfter) { renameColumnQuery(tableName, attrNameBefore, attrNameAfter) {
throwMethodUndefined('renameColumnQuery'); throwMethodUndefined('renameColumnQuery');
}, },
/* /*
Returns an insert into command. Parameters: table name + hash of attribute-value-pairs. Returns an insert into command. Parameters: table name + hash of attribute-value-pairs.
*/ */
insertQuery: function(table, valueHash, modelAttributes, options) { insertQuery(table, valueHash, modelAttributes, options) {
options = options || {}; options = options || {};
const modelAttributeMap = {};
const fields = [];
var query const values = [];
, valueQuery = '<%= tmpTable %>INSERT<%= ignore %> INTO <%= table %> (<%= attributes %>)<%= output %> VALUES (<%= values %>)' let query;
, emptyQuery = '<%= tmpTable %>INSERT<%= ignore %> INTO <%= table %><%= output %>' let valueQuery = '<%= tmpTable %>INSERT<%= ignore %> INTO <%= table %> (<%= attributes %>)<%= output %> VALUES (<%= values %>)';
, outputFragment let emptyQuery = '<%= tmpTable %>INSERT<%= ignore %> INTO <%= table %><%= output %>';
, fields = [] let outputFragment;
, values = [] let identityWrapperRequired = false;
, key let tmpTable = ''; //tmpTable declaration for trigger
, value
, identityWrapperRequired = false
, modelAttributeMap = {}
, tmpTable = '' //tmpTable declaration for trigger
, selectFromTmp = '' //Select statement for trigger
, tmpColumns = '' //Columns for temp table for trigger
, outputColumns = '' //Columns to capture into temp table for trigger
, attribute //Model attribute holder
, modelKey; //key for model
if (modelAttributes) { if (modelAttributes) {
Utils._.each(modelAttributes, function(attribute, key) { Utils._.each(modelAttributes, (attribute, key) => {
modelAttributeMap[key] = attribute; modelAttributeMap[key] = attribute;
if (attribute.field) { if (attribute.field) {
modelAttributeMap[attribute.field] = attribute; modelAttributeMap[attribute.field] = attribute;
...@@ -224,31 +207,34 @@ var QueryGenerator = { ...@@ -224,31 +207,34 @@ var QueryGenerator = {
//To capture output rows when there is a trigger on MSSQL DB //To capture output rows when there is a trigger on MSSQL DB
if (modelAttributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) { if (modelAttributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) {
tmpTable = 'declare @tmp table (<%= columns %>); ';
for (modelKey in modelAttributes){
attribute = modelAttributes[modelKey];
if(!(attribute.type instanceof DataTypes.VIRTUAL)){
if (tmpColumns.length > 0){
tmpColumns += ',';
outputColumns += ',';
}
tmpColumns += this.quoteIdentifier(attribute.field) + ' ' + attribute.type.toSql(); let tmpColumns = '';
outputColumns += 'INSERTED.' + this.quoteIdentifier(attribute.field); let outputColumns = '';
tmpTable = 'declare @tmp table (<%= columns %>); ';
for (const modelKey in modelAttributes){
const attribute = modelAttributes[modelKey];
if(!(attribute.type instanceof DataTypes.VIRTUAL)){
if (tmpColumns.length > 0){
tmpColumns += ',';
outputColumns += ',';
} }
tmpColumns += this.quoteIdentifier(attribute.field) + ' ' + attribute.type.toSql();
outputColumns += 'INSERTED.' + this.quoteIdentifier(attribute.field);
} }
}
var replacement ={ const replacement = {
columns : tmpColumns columns: tmpColumns
}; };
tmpTable = Utils._.template(tmpTable)(replacement).trim(); tmpTable = Utils._.template(tmpTable)(replacement).trim();
outputFragment = ' OUTPUT ' + outputColumns + ' into @tmp'; outputFragment = ' OUTPUT ' + outputColumns + ' into @tmp';
selectFromTmp = ';select * from @tmp'; const selectFromTmp = ';select * from @tmp';
valueQuery += selectFromTmp; valueQuery += selectFromTmp;
emptyQuery += selectFromTmp; emptyQuery += selectFromTmp;
} }
} }
} }
...@@ -258,7 +244,7 @@ var QueryGenerator = { ...@@ -258,7 +244,7 @@ var QueryGenerator = {
// pg_temp functions are private per connection, so we never risk this function interfering with another one. // pg_temp functions are private per connection, so we never risk this function interfering with another one.
if (semver.gte(this.sequelize.options.databaseVersion, '9.2.0')) { if (semver.gte(this.sequelize.options.databaseVersion, '9.2.0')) {
// >= 9.2 - Use a UUID but prefix with 'func_' (numbers first not allowed) // >= 9.2 - Use a UUID but prefix with 'func_' (numbers first not allowed)
var delimiter = '$func_' + uuid.v4().replace(/-/g, '') + '$'; const delimiter = '$func_' + uuid.v4().replace(/-/g, '') + '$';
options.exception = 'WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;'; options.exception = 'WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;';
valueQuery = 'CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response <%= table %>, OUT sequelize_caught_exception text) RETURNS RECORD AS ' + delimiter + valueQuery = 'CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response <%= table %>, OUT sequelize_caught_exception text) RETURNS RECORD AS ' + delimiter +
...@@ -276,9 +262,9 @@ var QueryGenerator = { ...@@ -276,9 +262,9 @@ var QueryGenerator = {
} }
valueHash = Utils.removeNullValuesFromHash(valueHash, this.options.omitNull); valueHash = Utils.removeNullValuesFromHash(valueHash, this.options.omitNull);
for (key in valueHash) { for (const key in valueHash) {
if (valueHash.hasOwnProperty(key)) { if (valueHash.hasOwnProperty(key)) {
value = valueHash[key]; const value = valueHash[key];
fields.push(this.quoteIdentifier(key)); fields.push(this.quoteIdentifier(key));
// SERIALS' can't be NULL in postgresql, use DEFAULT where supported // SERIALS' can't be NULL in postgresql, use DEFAULT where supported
...@@ -300,13 +286,13 @@ var QueryGenerator = { ...@@ -300,13 +286,13 @@ var QueryGenerator = {
} }
} }
var replacements = { const replacements = {
ignore: options.ignore ? this._dialect.supports.IGNORE : '', ignore: options.ignore ? this._dialect.supports.IGNORE : '',
table: this.quoteTable(table), table: this.quoteTable(table),
attributes: fields.join(','), attributes: fields.join(','),
output: outputFragment, output: outputFragment,
values: values.join(','), values: values.join(','),
tmpTable: tmpTable tmpTable
}; };
query = (replacements.attributes.length ? valueQuery : emptyQuery) + ';'; query = (replacements.attributes.length ? valueQuery : emptyQuery) + ';';
...@@ -325,18 +311,18 @@ var QueryGenerator = { ...@@ -325,18 +311,18 @@ var QueryGenerator = {
Returns an insert into command for multiple values. Returns an insert into command for multiple values.
Parameters: table name + list of hashes of attribute-value-pairs. Parameters: table name + list of hashes of attribute-value-pairs.
*/ */
bulkInsertQuery: function(tableName, attrValueHashes, options, rawAttributes) { bulkInsertQuery(tableName, attrValueHashes, options, rawAttributes) {
options = options || {}; options = options || {};
rawAttributes = rawAttributes || {}; rawAttributes = rawAttributes || {};
var query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %><%= onDuplicateKeyUpdate %><%= returning %>;' const query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %><%= onDuplicateKeyUpdate %><%= returning %>;';
, tuples = [] const tuples = [];
, serials = [] const serials = [];
, allAttributes = [] const allAttributes = [];
, onDuplicateKeyUpdate = ''; let onDuplicateKeyUpdate = '';
attrValueHashes.forEach(function(attrValueHash) { for (const attrValueHash of attrValueHashes) {
_.forOwn(attrValueHash, function(value, key) { _.forOwn(attrValueHash, (value, key) => {
if (allAttributes.indexOf(key) === -1) { if (allAttributes.indexOf(key) === -1) {
allAttributes.push(key); allAttributes.push(key);
} }
...@@ -345,35 +331,31 @@ var QueryGenerator = { ...@@ -345,35 +331,31 @@ var QueryGenerator = {
serials.push(key); serials.push(key);
} }
}); });
}); }
attrValueHashes.forEach(function(attrValueHash) { for (const attrValueHash of attrValueHashes) {
tuples.push('(' + tuples.push('(' + allAttributes.map(key => {
allAttributes.map(function(key) { if (this._dialect.supports.bulkDefault && serials.indexOf(key) !== -1) {
if (this._dialect.supports.bulkDefault && serials.indexOf(key) !== -1) { return attrValueHash[key] || 'DEFAULT';
return attrValueHash[key] || 'DEFAULT'; }
} return this.escape(attrValueHash[key], rawAttributes[key], { context: 'INSERT' });
return this.escape(attrValueHash[key], rawAttributes[key], { context: 'INSERT' }); }).join(',') + ')');
}, this).join(',') + }
')');
}, this);
if (this._dialect.supports.updateOnDuplicate && options.updateOnDuplicate) { if (this._dialect.supports.updateOnDuplicate && options.updateOnDuplicate) {
onDuplicateKeyUpdate += ' ON DUPLICATE KEY UPDATE ' + options.updateOnDuplicate.map(function(attr) { onDuplicateKeyUpdate += ' ON DUPLICATE KEY UPDATE ' + options.updateOnDuplicate.map(attr => {
var field = rawAttributes && rawAttributes[attr] && rawAttributes[attr].field || attr; const field = rawAttributes && rawAttributes[attr] && rawAttributes[attr].field || attr;
var key = this.quoteIdentifier(field); const key = this.quoteIdentifier(field);
return key + '=VALUES(' + key + ')'; return key + '=VALUES(' + key + ')';
}, this).join(','); }).join(',');
} }
var replacements = { const replacements = {
ignoreDuplicates: options.ignoreDuplicates ? this._dialect.supports.ignoreDuplicates : '', ignoreDuplicates: options.ignoreDuplicates ? this._dialect.supports.ignoreDuplicates : '',
table: this.quoteTable(tableName), table: this.quoteTable(tableName),
attributes: allAttributes.map(function(attr) { attributes: allAttributes.map(attr => this.quoteIdentifier(attr)).join(','),
return this.quoteIdentifier(attr);
}, this).join(','),
tuples: tuples.join(','), tuples: tuples.join(','),
onDuplicateKeyUpdate: onDuplicateKeyUpdate, onDuplicateKeyUpdate,
returning: this._dialect.supports.returnValues && options.returning ? ' RETURNING *' : '' returning: this._dialect.supports.returnValues && options.returning ? ' RETURNING *' : ''
}; };
...@@ -390,24 +372,18 @@ var QueryGenerator = { ...@@ -390,24 +372,18 @@ var QueryGenerator = {
OR a string with conditions (e.g. 'name="foo"'). OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own. If you use a string, you have to escape it on your own.
*/ */
updateQuery: function(tableName, attrValueHash, where, options, attributes) { updateQuery(tableName, attrValueHash, where, options, attributes) {
options = options || {}; options = options || {};
_.defaults(options, this.options); _.defaults(options, this.options);
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options); attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options);
var query const values = [];
, values = [] const modelAttributeMap = {};
, outputFragment let query = '<%= tmpTable %>UPDATE <%= table %> SET <%= values %><%= output %> <%= where %>';
, modelAttributeMap = {} let outputFragment;
, tmpTable = '' //tmpTable declaration for trigger let tmpTable = ''; // tmpTable declaration for trigger
, selectFromTmp = '' //Select statement for trigger let selectFromTmp = ''; // Select statement for trigger
, tmpColumns = '' //Columns for temp table for trigger
, outputColumns = '' //Columns to capture into temp table for trigger
, attribute //Model attribute holder
, modelKey; //key for model
query = '<%= tmpTable %>UPDATE <%= table %> SET <%= values %><%= output %> <%= where %>';
if (this._dialect.supports['LIMIT ON UPDATE'] && options.limit) { if (this._dialect.supports['LIMIT ON UPDATE'] && options.limit) {
query += ' LIMIT ' + this.escape(options.limit) + ' '; query += ' LIMIT ' + this.escape(options.limit) + ' ';
...@@ -420,30 +396,32 @@ var QueryGenerator = { ...@@ -420,30 +396,32 @@ var QueryGenerator = {
//To capture output rows when there is a trigger on MSSQL DB //To capture output rows when there is a trigger on MSSQL DB
if (attributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) { if (attributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) {
tmpTable = 'declare @tmp table (<%= columns %>); '; tmpTable = 'declare @tmp table (<%= columns %>); ';
let tmpColumns = '';
for (modelKey in attributes){ let outputColumns = '';
attribute = attributes[modelKey];
if(!(attribute.type instanceof DataTypes.VIRTUAL)){ for (const modelKey in attributes){
if (tmpColumns.length > 0){ const attribute = attributes[modelKey];
tmpColumns += ','; if(!(attribute.type instanceof DataTypes.VIRTUAL)){
outputColumns += ','; if (tmpColumns.length > 0){
} tmpColumns += ',';
outputColumns += ',';
tmpColumns += this.quoteIdentifier(attribute.field) + ' ' + attribute.type.toSql();
outputColumns += 'INSERTED.' + this.quoteIdentifier(attribute.field);
} }
tmpColumns += this.quoteIdentifier(attribute.field) + ' ' + attribute.type.toSql();
outputColumns += 'INSERTED.' + this.quoteIdentifier(attribute.field);
} }
}
var replacement ={ const replacement ={
columns : tmpColumns columns : tmpColumns
}; };
tmpTable = Utils._.template(tmpTable)(replacement).trim(); tmpTable = Utils._.template(tmpTable)(replacement).trim();
outputFragment = ' OUTPUT ' + outputColumns + ' into @tmp'; outputFragment = ' OUTPUT ' + outputColumns + ' into @tmp';
selectFromTmp = ';select * from @tmp'; selectFromTmp = ';select * from @tmp';
query += selectFromTmp; query += selectFromTmp;
} }
} else if (this._dialect.supports.returnValues && options.returning) { } else if (this._dialect.supports.returnValues && options.returning) {
// ensure that the return output is properly mapped to model fields. // ensure that the return output is properly mapped to model fields.
...@@ -453,7 +431,7 @@ var QueryGenerator = { ...@@ -453,7 +431,7 @@ var QueryGenerator = {
} }
if (attributes) { if (attributes) {
Utils._.each(attributes, function(attribute, key) { Utils._.each(attributes, (attribute, key) => {
modelAttributeMap[key] = attribute; modelAttributeMap[key] = attribute;
if (attribute.field) { if (attribute.field) {
modelAttributeMap[attribute.field] = attribute; modelAttributeMap[attribute.field] = attribute;
...@@ -461,7 +439,7 @@ var QueryGenerator = { ...@@ -461,7 +439,7 @@ var QueryGenerator = {
}); });
} }
for (var key in attrValueHash) { for (const key in attrValueHash) {
if (modelAttributeMap && modelAttributeMap[key] && if (modelAttributeMap && modelAttributeMap[key] &&
modelAttributeMap[key].autoIncrement === true && modelAttributeMap[key].autoIncrement === true &&
!this._dialect.supports.autoIncrement.update) { !this._dialect.supports.autoIncrement.update) {
...@@ -469,16 +447,16 @@ var QueryGenerator = { ...@@ -469,16 +447,16 @@ var QueryGenerator = {
continue; continue;
} }
var value = attrValueHash[key]; const value = attrValueHash[key];
values.push(this.quoteIdentifier(key) + '=' + this.escape(value, (modelAttributeMap && modelAttributeMap[key] || undefined), { context: 'UPDATE' })); values.push(this.quoteIdentifier(key) + '=' + this.escape(value, (modelAttributeMap && modelAttributeMap[key] || undefined), { context: 'UPDATE' }));
} }
var replacements = { const replacements = {
table: this.quoteTable(tableName), table: this.quoteTable(tableName),
values: values.join(','), values: values.join(','),
output: outputFragment, output: outputFragment,
where: this.whereQuery(where), where: this.whereQuery(where),
tmpTable: tmpTable tmpTable
}; };
if (values.length === 0) { if (values.length === 0) {
...@@ -491,7 +469,7 @@ var QueryGenerator = { ...@@ -491,7 +469,7 @@ var QueryGenerator = {
/* /*
Returns an upsert query. Returns an upsert query.
*/ */
upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) { upsertQuery(tableName, insertValues, updateValues, where, rawAttributes, options) {
throwMethodUndefined('upsertQuery'); throwMethodUndefined('upsertQuery');
}, },
...@@ -512,7 +490,7 @@ var QueryGenerator = { ...@@ -512,7 +490,7 @@ var QueryGenerator = {
Note that truncate must ignore limit and where Note that truncate must ignore limit and where
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
deleteQuery: function(tableName, where, options) { deleteQuery(tableName, where, options) {
throwMethodUndefined('deleteQuery'); throwMethodUndefined('deleteQuery');
}, },
...@@ -526,16 +504,13 @@ var QueryGenerator = { ...@@ -526,16 +504,13 @@ var QueryGenerator = {
OR a string with conditions (e.g. 'name="foo"'). OR a string with conditions (e.g. 'name="foo"').
If you use a string, you have to escape it on your own. If you use a string, you have to escape it on your own.
*/ */
incrementQuery: function(tableName, attrValueHash, where, options) { incrementQuery(tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull); attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull);
var query const values = [];
, key let query = 'UPDATE <%= table %> SET <%= values %><%= output %> <%= where %>';
, value let outputFragment;
, values = []
, outputFragment;
query = 'UPDATE <%= table %> SET <%= values %><%= output %> <%= where %>';
if (this._dialect.supports.returnValues) { if (this._dialect.supports.returnValues) {
if (!!this._dialect.supports.returnValues.returning) { if (!!this._dialect.supports.returnValues.returning) {
query += ' RETURNING *'; query += ' RETURNING *';
...@@ -544,18 +519,18 @@ var QueryGenerator = { ...@@ -544,18 +519,18 @@ var QueryGenerator = {
} }
} }
for (key in attrValueHash) { for (const key in attrValueHash) {
value = attrValueHash[key]; const value = attrValueHash[key];
values.push(this.quoteIdentifier(key) + '=' + this.quoteIdentifier(key) + ' + ' + this.escape(value)); values.push(this.quoteIdentifier(key) + '=' + this.quoteIdentifier(key) + ' + ' + this.escape(value));
} }
options = options || {}; options = options || {};
for (key in options) { for (const key in options) {
value = options[key]; const value = options[key];
values.push(this.quoteIdentifier(key) + '=' + this.escape(value)); values.push(this.quoteIdentifier(key) + '=' + this.escape(value));
} }
var replacements = { const replacements = {
table: this.quoteTable(tableName), table: this.quoteTable(tableName),
values: values.join(','), values: values.join(','),
output: outputFragment, output: outputFragment,
...@@ -565,13 +540,10 @@ var QueryGenerator = { ...@@ -565,13 +540,10 @@ var QueryGenerator = {
return Utils._.template(query)(replacements); return Utils._.template(query)(replacements);
}, },
nameIndexes: function (indexes, rawTablename) { nameIndexes(indexes, rawTablename) {
return Utils._.map(indexes, function (index) { return Utils._.map(indexes, index => {
if (!index.hasOwnProperty('name')) { if (!index.hasOwnProperty('name')) {
var onlyAttributeNames = index.fields.map(function(field) { const onlyAttributeNames = index.fields.map(field => (typeof field === 'string') ? field : (field.name || field.attribute));
return (typeof field === 'string') ? field : (field.name || field.attribute);
}.bind(this));
index.name = Utils.inflection.underscore(rawTablename + '_' + onlyAttributeNames.join('_')); index.name = Utils.inflection.underscore(rawTablename + '_' + onlyAttributeNames.join('_'));
} }
...@@ -594,8 +566,7 @@ var QueryGenerator = { ...@@ -594,8 +566,7 @@ var QueryGenerator = {
- parser - parser
- rawTablename, the name of the table, without schema. Used to create the name of the index - rawTablename, the name of the table, without schema. Used to create the name of the index
*/ */
addIndexQuery: function(tableName, attributes, options, rawTablename) { addIndexQuery(tableName, attributes, options, rawTablename) {
var fieldsSql;
options = options || {}; options = options || {};
if (!Array.isArray(attributes)) { if (!Array.isArray(attributes)) {
...@@ -622,13 +593,13 @@ var QueryGenerator = { ...@@ -622,13 +593,13 @@ var QueryGenerator = {
options.prefix = options.prefix.replace(/(\"|\')/g, ''); options.prefix = options.prefix.replace(/(\"|\')/g, '');
} }
fieldsSql = options.fields.map(function(field) { const fieldsSql = options.fields.map(field => {
if (typeof field === 'string') { if (typeof field === 'string') {
return this.quoteIdentifier(field); return this.quoteIdentifier(field);
} else if (field._isSequelizeMethod) { } else if (field._isSequelizeMethod) {
return this.handleSequelizeMethod(field); return this.handleSequelizeMethod(field);
} else { } else {
var result = ''; let result = '';
if (field.attribute) { if (field.attribute) {
field.name = field.attribute; field.name = field.attribute;
...@@ -654,7 +625,7 @@ var QueryGenerator = { ...@@ -654,7 +625,7 @@ var QueryGenerator = {
return result; return result;
} }
}.bind(this)); });
if (!options.name) { if (!options.name) {
// Mostly for cases where addIndex is called directly by the user without an options object (for example in migrations) // Mostly for cases where addIndex is called directly by the user without an options object (for example in migrations)
...@@ -678,8 +649,8 @@ var QueryGenerator = { ...@@ -678,8 +649,8 @@ var QueryGenerator = {
tableName = this.quoteTable(tableName); tableName = this.quoteTable(tableName);
} }
var concurrently = this._dialect.supports.index.concurrently && options.concurrently ? 'CONCURRENTLY' : undefined const concurrently = this._dialect.supports.index.concurrently && options.concurrently ? 'CONCURRENTLY' : undefined;
, ind; let ind;
if (this._dialect.supports.indexViaAlter) { if (this._dialect.supports.indexViaAlter) {
ind = [ ind = [
'ALTER TABLE', 'ALTER TABLE',
...@@ -715,7 +686,7 @@ var QueryGenerator = { ...@@ -715,7 +686,7 @@ var QueryGenerator = {
- database: Name of the database. - database: Name of the database.
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
showIndexesQuery: function(tableName, options) { showIndexesQuery(tableName, options) {
throwMethodUndefined('showIndexesQuery'); throwMethodUndefined('showIndexesQuery');
}, },
...@@ -726,7 +697,7 @@ var QueryGenerator = { ...@@ -726,7 +697,7 @@ var QueryGenerator = {
- indexNameOrAttributes: The name of the index as string or an array of attribute names. - indexNameOrAttributes: The name of the index as string or an array of attribute names.
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
removeIndexQuery: function(tableName, indexNameOrAttributes) { removeIndexQuery(tableName, indexNameOrAttributes) {
throwMethodUndefined('removeIndexQuery'); throwMethodUndefined('removeIndexQuery');
}, },
...@@ -735,7 +706,7 @@ var QueryGenerator = { ...@@ -735,7 +706,7 @@ var QueryGenerator = {
sql attribute definition. sql attribute definition.
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
attributesToSQL: function(attributes) { attributesToSQL(attributes) {
throwMethodUndefined('attributesToSQL'); throwMethodUndefined('attributesToSQL');
}, },
...@@ -743,13 +714,13 @@ var QueryGenerator = { ...@@ -743,13 +714,13 @@ var QueryGenerator = {
Returns all auto increment fields of a factory. Returns all auto increment fields of a factory.
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
findAutoIncrementField: function(factory) { findAutoIncrementField(factory) {
throwMethodUndefined('findAutoIncrementField'); throwMethodUndefined('findAutoIncrementField');
}, },
quoteTable: function(param, as) { quoteTable(param, as) {
var table = ''; let table = '';
if (as === true) { if (as === true) {
as = param.as || param.name || param; as = param.as || param.name || param;
...@@ -803,21 +774,22 @@ var QueryGenerator = { ...@@ -803,21 +774,22 @@ var QueryGenerator = {
Currently this function is only used for ordering / grouping columns and Sequelize.col(), but it could Currently this function is only used for ordering / grouping columns and Sequelize.col(), but it could
potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values) potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values)
*/ */
quote: function(obj, parent, force) { quote(obj, parent, force) {
if (Utils._.isString(obj)) { if (Utils._.isString(obj)) {
return this.quoteIdentifiers(obj, force); return this.quoteIdentifiers(obj, force);
} else if (Array.isArray(obj)) { } else if (Array.isArray(obj)) {
// loop through array, adding table names of models to quoted // loop through array, adding table names of models to quoted
// (checking associations to see if names should be singularised or not) // (checking associations to see if names should be singularised or not)
var tableNames = [] const len = obj.length;
, parentAssociation const tableNames = [];
, len = obj.length let parentAssociation;
, item let item;
, model let model;
, as let as;
, association; let association;
let i = 0;
for (var i = 0; i < len - 1; i++) {
for (i = 0; i < len - 1; i++) {
item = obj[i]; item = obj[i];
if (item._modelAttribute || Utils._.isString(item) || item._isSequelizeMethod || 'raw' in item) { if (item._modelAttribute || Utils._.isString(item) || item._isSequelizeMethod || 'raw' in item) {
break; break;
...@@ -850,7 +822,7 @@ var QueryGenerator = { ...@@ -850,7 +822,7 @@ var QueryGenerator = {
} }
// add 1st string as quoted, 2nd as unquoted raw // add 1st string as quoted, 2nd as unquoted raw
var sql = (i > 0 ? this.quoteIdentifier(tableNames.join('.')) + '.' : (Utils._.isString(obj[0]) && parent ? this.quoteIdentifier(parent.name) + '.' : '')) + this.quote(obj[i], parent, force); let sql = (i > 0 ? this.quoteIdentifier(tableNames.join('.')) + '.' : (Utils._.isString(obj[0]) && parent ? this.quoteIdentifier(parent.name) + '.' : '')) + this.quote(obj[i], parent, force);
if (i < len - 1) { if (i < len - 1) {
if (obj[i + 1]._isSequelizeMethod) { if (obj[i + 1]._isSequelizeMethod) {
sql += this.handleSequelizeMethod(obj[i + 1]); sql += this.handleSequelizeMethod(obj[i + 1]);
...@@ -874,7 +846,7 @@ var QueryGenerator = { ...@@ -874,7 +846,7 @@ var QueryGenerator = {
Create a trigger Create a trigger
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
createTrigger: function(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray) { createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray) {
throwMethodUndefined('createTrigger'); throwMethodUndefined('createTrigger');
}, },
...@@ -882,7 +854,7 @@ var QueryGenerator = { ...@@ -882,7 +854,7 @@ var QueryGenerator = {
Drop a trigger Drop a trigger
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
dropTrigger: function(tableName, triggerName) { dropTrigger(tableName, triggerName) {
throwMethodUndefined('dropTrigger'); throwMethodUndefined('dropTrigger');
}, },
...@@ -890,7 +862,7 @@ var QueryGenerator = { ...@@ -890,7 +862,7 @@ var QueryGenerator = {
Rename a trigger Rename a trigger
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
renameTrigger: function(tableName, oldTriggerName, newTriggerName) { renameTrigger(tableName, oldTriggerName, newTriggerName) {
throwMethodUndefined('renameTrigger'); throwMethodUndefined('renameTrigger');
}, },
...@@ -898,7 +870,7 @@ var QueryGenerator = { ...@@ -898,7 +870,7 @@ var QueryGenerator = {
Create a function Create a function
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
createFunction: function(functionName, params, returnType, language, body, options) { createFunction(functionName, params, returnType, language, body, options) {
throwMethodUndefined('createFunction'); throwMethodUndefined('createFunction');
}, },
...@@ -906,7 +878,7 @@ var QueryGenerator = { ...@@ -906,7 +878,7 @@ var QueryGenerator = {
Drop a function Drop a function
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
dropFunction: function(functionName, params) { dropFunction(functionName, params) {
throwMethodUndefined('dropFunction'); throwMethodUndefined('dropFunction');
}, },
...@@ -914,7 +886,7 @@ var QueryGenerator = { ...@@ -914,7 +886,7 @@ var QueryGenerator = {
Rename a function Rename a function
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
renameFunction: function(oldFunctionName, params, newFunctionName) { renameFunction(oldFunctionName, params, newFunctionName) {
throwMethodUndefined('renameFunction'); throwMethodUndefined('renameFunction');
}, },
...@@ -922,14 +894,14 @@ var QueryGenerator = { ...@@ -922,14 +894,14 @@ var QueryGenerator = {
Escape an identifier (e.g. a table or attribute name) Escape an identifier (e.g. a table or attribute name)
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
quoteIdentifier: function(identifier, force) { quoteIdentifier(identifier, force) {
throwMethodUndefined('quoteIdentifier'); throwMethodUndefined('quoteIdentifier');
}, },
/* /*
Split an identifier into .-separated tokens and quote each part Split an identifier into .-separated tokens and quote each part
*/ */
quoteIdentifiers: function(identifiers, force) { quoteIdentifiers(identifiers, force) {
if (identifiers.indexOf('.') !== -1) { if (identifiers.indexOf('.') !== -1) {
identifiers = identifiers.split('.'); identifiers = identifiers.split('.');
return this.quoteIdentifier(identifiers.slice(0, identifiers.length - 1).join('.')) + '.' + this.quoteIdentifier(identifiers[identifiers.length - 1]); return this.quoteIdentifier(identifiers.slice(0, identifiers.length - 1).join('.')) + '.' + this.quoteIdentifier(identifiers[identifiers.length - 1]);
...@@ -941,7 +913,7 @@ var QueryGenerator = { ...@@ -941,7 +913,7 @@ var QueryGenerator = {
/* /*
Escape a value (e.g. a string, number or date) Escape a value (e.g. a string, number or date)
*/ */
escape: function(value, field, options) { escape(value, field, options) {
options = options || {}; options = options || {};
if (value !== null && value !== undefined) { if (value !== null && value !== undefined) {
...@@ -951,9 +923,9 @@ var QueryGenerator = { ...@@ -951,9 +923,9 @@ var QueryGenerator = {
if (field && field.type) { if (field && field.type) {
if (this.typeValidation && field.type.validate && value) { if (this.typeValidation && field.type.validate && value) {
if (options.isList && Array.isArray(value)) { if (options.isList && Array.isArray(value)) {
_.forEach(value, function(item) { for (const item of value) {
field.type.validate(item, options); field.type.validate(item, options);
}); }
} else { } else {
field.type.validate(value, options); field.type.validate(value, options);
} }
...@@ -961,9 +933,9 @@ var QueryGenerator = { ...@@ -961,9 +933,9 @@ var QueryGenerator = {
if (field.type.stringify) { if (field.type.stringify) {
// Users shouldn't have to worry about these args - just give them a function that takes a single arg // Users shouldn't have to worry about these args - just give them a function that takes a single arg
var simpleEscape = _.partialRight(SqlString.escape, this.options.timezone, this.dialect); const simpleEscape = _.partialRight(SqlString.escape, this.options.timezone, this.dialect);
value = field.type.stringify(value, { escape: simpleEscape, field: field, timezone: this.options.timezone }); value = field.type.stringify(value, { escape: simpleEscape, field, timezone: this.options.timezone });
if (field.type.escape === false) { if (field.type.escape === false) {
// The data-type already did the required escaping // The data-type already did the required escaping
...@@ -985,7 +957,7 @@ var QueryGenerator = { ...@@ -985,7 +957,7 @@ var QueryGenerator = {
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
getForeignKeysQuery: function(tableName, schemaName) { getForeignKeysQuery(tableName, schemaName) {
throwMethodUndefined('getForeignKeysQuery'); throwMethodUndefined('getForeignKeysQuery');
}, },
...@@ -997,7 +969,7 @@ var QueryGenerator = { ...@@ -997,7 +969,7 @@ var QueryGenerator = {
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
/* istanbul ignore next */ /* istanbul ignore next */
dropForeignKeyQuery: function(tableName, foreignKey) { dropForeignKeyQuery(tableName, foreignKey) {
throwMethodUndefined('dropForeignKeyQuery'); throwMethodUndefined('dropForeignKeyQuery');
}, },
...@@ -1016,27 +988,24 @@ var QueryGenerator = { ...@@ -1016,27 +988,24 @@ var QueryGenerator = {
- offset -> An offset value to start from. Only useable with limit! - offset -> An offset value to start from. Only useable with limit!
*/ */
selectQuery: function(tableName, options, model) { selectQuery(tableName, options, model) {
// Enter and change at your own peril -- Mick Hansen // Enter and change at your own peril -- Mick Hansen
options = options || {}; options = options || {};
var table = null const limit = options.limit;
, self = this const mainModel = model;
, query const mainQueryItems = [];
, limit = options.limit const subQuery = options.subQuery === undefined ? limit && options.hasMultiAssociation : options.subQuery;
, mainModel = model const subQueryItems = [];
, mainQueryItems = [] let table = null;
, mainAttributes = options.attributes && options.attributes.slice() let query;
, mainJoinQueries = [] let mainAttributes = options.attributes && options.attributes.slice();
// We'll use a subquery if we have a hasMany association and a limit let mainJoinQueries = [];
, subQuery = options.subQuery === undefined ? // We'll use a subquery if we have a hasMany association and a limit
limit && options.hasMultiAssociation : let subQueryAttributes = null;
options.subQuery let subJoinQueries = [];
, subQueryItems = [] let mainTableAs = null;
, subQueryAttributes = null
, subJoinQueries = []
, mainTableAs = null;
if (options.tableAs) { if (options.tableAs) {
mainTableAs = this.quoteTable(options.tableAs); mainTableAs = this.quoteTable(options.tableAs);
...@@ -1044,44 +1013,42 @@ var QueryGenerator = { ...@@ -1044,44 +1013,42 @@ var QueryGenerator = {
mainTableAs = this.quoteTable(model.name); mainTableAs = this.quoteTable(model.name);
} }
table = !Array.isArray(tableName) ? this.quoteTable(tableName) : tableName.map(function(t) { table = !Array.isArray(tableName) ? this.quoteTable(tableName) : tableName.map(t => {
if (Array.isArray(t)) { if (Array.isArray(t)) {
return this.quoteTable(t[0], t[1]); return this.quoteTable(t[0], t[1]);
} }
return this.quoteTable(t, true); return this.quoteTable(t, true);
}.bind(this)).join(', '); }).join(', ');
if (subQuery && mainAttributes) { if (subQuery && mainAttributes) {
model.primaryKeyAttributes.forEach(function(keyAtt) { for (const keyAtt of model.primaryKeyAttributes) {
// Check if mainAttributes contain the primary key of the model either as a field or an aliased field // Check if mainAttributes contain the primary key of the model either as a field or an aliased field
if (!_.find(mainAttributes, function (attr) { if (!_.find(mainAttributes, attr => keyAtt === attr || keyAtt === attr[0] || keyAtt === attr[1])) {
return keyAtt === attr || keyAtt === attr[0] || keyAtt === attr[1];
})) {
mainAttributes.push(model.rawAttributes[keyAtt].field ? [keyAtt, model.rawAttributes[keyAtt].field] : keyAtt); mainAttributes.push(model.rawAttributes[keyAtt].field ? [keyAtt, model.rawAttributes[keyAtt].field] : keyAtt);
} }
}); }
} }
// Escape attributes // Escape attributes
mainAttributes = mainAttributes && mainAttributes.map(function(attr) { mainAttributes = mainAttributes && mainAttributes.map(attr => {
var addTable = true; let addTable = true;
if (attr._isSequelizeMethod) { if (attr._isSequelizeMethod) {
return self.handleSequelizeMethod(attr); return this.handleSequelizeMethod(attr);
} }
if (Array.isArray(attr) && attr.length === 2) { if (Array.isArray(attr) && attr.length === 2) {
attr = attr.slice(); attr = attr.slice();
if (attr[0]._isSequelizeMethod) { if (attr[0]._isSequelizeMethod) {
attr[0] = self.handleSequelizeMethod(attr[0]); attr[0] = this.handleSequelizeMethod(attr[0]);
addTable = false; addTable = false;
} else if (attr[0].indexOf('(') === -1 && attr[0].indexOf(')') === -1) { } else if (attr[0].indexOf('(') === -1 && attr[0].indexOf(')') === -1) {
attr[0] = self.quoteIdentifier(attr[0]); attr[0] = this.quoteIdentifier(attr[0]);
} }
attr = [attr[0], self.quoteIdentifier(attr[1])].join(' AS '); attr = [attr[0], this.quoteIdentifier(attr[1])].join(' AS ');
} else { } else {
attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? self.quoteIdentifiers(attr) : attr; attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? this.quoteIdentifiers(attr) : attr;
} }
if (options.include && attr.indexOf('.') === -1 && addTable) { if (options.include && attr.indexOf('.') === -1 && addTable) {
...@@ -1101,21 +1068,21 @@ var QueryGenerator = { ...@@ -1101,21 +1068,21 @@ var QueryGenerator = {
} }
if (options.include) { if (options.include) {
var generateJoinQueries = function(include, parentTable) { const generateJoinQueries = (include, parentTable) => {
var table = include.model.getTableName() const association = include.association;
, as = include.as const through = include.through;
, joinQueryItem = '' const joinType = include.required ? ' INNER JOIN ' : ' LEFT OUTER JOIN ';
, joinQueries = { const parentIsTop = !include.parent.association && include.parent.model.name === options.model.name;
mainQuery: [], const whereOptions = Utils._.clone(options);
subQuery: [] const table = include.model.getTableName();
} const joinQueries = {
, attributes mainQuery: [],
, association = include.association subQuery: []
, through = include.through };
, joinType = include.required ? ' INNER JOIN ' : ' LEFT OUTER JOIN ' let as = include.as;
, parentIsTop = !include.parent.association && include.parent.model.name === options.model.name let joinQueryItem = '';
, whereOptions = Utils._.clone(options) let attributes;
, targetWhere; let targetWhere;
whereOptions.keysEscaped = true; whereOptions.keysEscaped = true;
...@@ -1125,9 +1092,9 @@ var QueryGenerator = { ...@@ -1125,9 +1092,9 @@ var QueryGenerator = {
// includeIgnoreAttributes is used by aggregate functions // includeIgnoreAttributes is used by aggregate functions
if (options.includeIgnoreAttributes !== false) { if (options.includeIgnoreAttributes !== false) {
attributes = include.attributes.map(function(attr) { attributes = include.attributes.map(attr => {
var attrAs = attr, let attrAs = attr;
verbatim = false; let verbatim = false;
if (Array.isArray(attr) && attr.length === 2) { if (Array.isArray(attr) && attr.length === 2) {
if (attr[0]._isSequelizeMethod) { if (attr[0]._isSequelizeMethod) {
...@@ -1139,30 +1106,26 @@ var QueryGenerator = { ...@@ -1139,30 +1106,26 @@ var QueryGenerator = {
} }
} }
attr = attr.map(function($attr) { attr = attr.map($attr => $attr._isSequelizeMethod ? this.handleSequelizeMethod($attr) : $attr);
return $attr._isSequelizeMethod ? self.handleSequelizeMethod($attr) : $attr;
});
attrAs = attr[1]; attrAs = attr[1];
attr = attr[0]; attr = attr[0];
} else if (attr instanceof Utils.literal) { } else if (attr instanceof Utils.literal) {
return attr.val; // We trust the user to rename the field correctly return attr.val; // We trust the user to rename the field correctly
} else if (attr instanceof Utils.cast || } else if (attr instanceof Utils.cast || attr instanceof Utils.fn) {
attr instanceof Utils.fn
) {
throw new Error( throw new Error(
'Tried to select attributes using Sequelize.cast or Sequelize.fn without specifying an alias for the result, during eager loading. ' + 'Tried to select attributes using Sequelize.cast or Sequelize.fn without specifying an alias for the result, during eager loading. ' +
'This means the attribute will not be added to the returned instance' 'This means the attribute will not be added to the returned instance'
); );
} }
var prefix; let prefix;
if (verbatim === true) { if (verbatim === true) {
prefix = attr; prefix = attr;
} else { } else {
prefix = self.quoteIdentifier(as) + '.' + self.quoteIdentifier(attr); prefix = this.quoteIdentifier(as) + '.' + this.quoteIdentifier(attr);
} }
return prefix + ' AS ' + self.quoteIdentifier(as + '.' + attrAs, true); return prefix + ' AS ' + this.quoteIdentifier(as + '.' + attrAs, true);
}); });
if (include.subQuery && subQuery) { if (include.subQuery && subQuery) {
subQueryAttributes = subQueryAttributes.concat(attributes); subQueryAttributes = subQueryAttributes.concat(attributes);
...@@ -1172,26 +1135,25 @@ var QueryGenerator = { ...@@ -1172,26 +1135,25 @@ var QueryGenerator = {
} }
if (through) { if (through) {
var throughTable = through.model.getTableName() const throughTable = through.model.getTableName();
, throughAs = as + '.' + through.as const throughAs = as + '.' + through.as;
, throughAttributes = through.attributes.map(function(attr) { const throughAttributes = through.attributes.map(attr =>
return self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr) + this.quoteIdentifier(throughAs) + '.' + this.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr)
' AS ' + + ' AS '
self.quoteIdentifier(throughAs + '.' + (Array.isArray(attr) ? attr[1] : attr)); + this.quoteIdentifier(throughAs + '.' + (Array.isArray(attr) ? attr[1] : attr))
}) );
, primaryKeysSource = association.source.primaryKeyAttributes const primaryKeysSource = association.source.primaryKeyAttributes;
, tableSource = parentTable const tableSource = parentTable;
, identSource = association.identifierField const identSource = association.identifierField;
, attrSource = primaryKeysSource[0] const primaryKeysTarget = association.target.primaryKeyAttributes;
, primaryKeysTarget = association.target.primaryKeyAttributes const tableTarget = as;
, tableTarget = as const identTarget = association.foreignIdentifierField;
, identTarget = association.foreignIdentifierField const attrTarget = association.target.rawAttributes[primaryKeysTarget[0]].field || primaryKeysTarget[0];
, attrTarget = association.target.rawAttributes[primaryKeysTarget[0]].field || primaryKeysTarget[0]
let attrSource = primaryKeysSource[0];
, sourceJoinOn let sourceJoinOn;
, targetJoinOn let targetJoinOn;
let throughWhere;
, throughWhere;
if (options.includeIgnoreAttributes !== false) { if (options.includeIgnoreAttributes !== false) {
// Through includes are always hasMany, so we need to add the attributes to the mainAttributes no matter what (Real join will never be executed in subquery) // Through includes are always hasMany, so we need to add the attributes to the mainAttributes no matter what (Real join will never be executed in subquery)
...@@ -1212,26 +1174,26 @@ var QueryGenerator = { ...@@ -1212,26 +1174,26 @@ var QueryGenerator = {
// If parent include was in a subquery need to join on the aliased attribute // If parent include was in a subquery need to join on the aliased attribute
if (subQuery && !include.subQuery && include.parent.subQuery && !parentIsTop) { if (subQuery && !include.subQuery && include.parent.subQuery && !parentIsTop) {
sourceJoinOn = self.quoteIdentifier(tableSource + '.' + attrSource) + ' = '; sourceJoinOn = this.quoteIdentifier(tableSource + '.' + attrSource) + ' = ';
} else { } else {
sourceJoinOn = self.quoteTable(tableSource) + '.' + self.quoteIdentifier(attrSource) + ' = '; sourceJoinOn = this.quoteTable(tableSource) + '.' + this.quoteIdentifier(attrSource) + ' = ';
} }
sourceJoinOn += self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(identSource); sourceJoinOn += this.quoteIdentifier(throughAs) + '.' + this.quoteIdentifier(identSource);
// Filter statement for right side of through // Filter statement for right side of through
// Used by both join and subquery where // Used by both join and subquery where
targetJoinOn = self.quoteIdentifier(tableTarget) + '.' + self.quoteIdentifier(attrTarget) + ' = '; targetJoinOn = this.quoteIdentifier(tableTarget) + '.' + this.quoteIdentifier(attrTarget) + ' = ';
targetJoinOn += self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(identTarget); targetJoinOn += this.quoteIdentifier(throughAs) + '.' + this.quoteIdentifier(identTarget);
if (include.through.where) { if (include.through.where) {
throughWhere = self.getWhereConditions(include.through.where, self.sequelize.literal(self.quoteIdentifier(throughAs)), include.through.model); throughWhere = this.getWhereConditions(include.through.where, this.sequelize.literal(this.quoteIdentifier(throughAs)), include.through.model);
} }
if (self._dialect.supports.joinTableDependent) { if (this._dialect.supports.joinTableDependent) {
// Generate a wrapped join so that the through table join can be dependent on the target join // Generate a wrapped join so that the through table join can be dependent on the target join
joinQueryItem += joinType + '('; joinQueryItem += joinType + '(';
joinQueryItem += self.quoteTable(throughTable, throughAs); joinQueryItem += this.quoteTable(throughTable, throughAs);
joinQueryItem += ' INNER JOIN ' + self.quoteTable(table, as) + ' ON '; joinQueryItem += ' INNER JOIN ' + this.quoteTable(table, as) + ' ON ';
joinQueryItem += targetJoinOn; joinQueryItem += targetJoinOn;
if (throughWhere) { if (throughWhere) {
...@@ -1241,11 +1203,11 @@ var QueryGenerator = { ...@@ -1241,11 +1203,11 @@ var QueryGenerator = {
joinQueryItem += ') ON '+sourceJoinOn; joinQueryItem += ') ON '+sourceJoinOn;
} else { } else {
// Generate join SQL for left side of through // Generate join SQL for left side of through
joinQueryItem += joinType + self.quoteTable(throughTable, throughAs) + ' ON '; joinQueryItem += joinType + this.quoteTable(throughTable, throughAs) + ' ON ';
joinQueryItem += sourceJoinOn; joinQueryItem += sourceJoinOn;
// Generate join SQL for right side of through // Generate join SQL for right side of through
joinQueryItem += joinType + self.quoteTable(table, as) + ' ON '; joinQueryItem += joinType + this.quoteTable(table, as) + ' ON ';
joinQueryItem += targetJoinOn; joinQueryItem += targetJoinOn;
if (throughWhere) { if (throughWhere) {
...@@ -1256,90 +1218,84 @@ var QueryGenerator = { ...@@ -1256,90 +1218,84 @@ var QueryGenerator = {
if (include.where || include.through.where) { if (include.where || include.through.where) {
if (include.where) { if (include.where) {
targetWhere = self.getWhereConditions(include.where, self.sequelize.literal(self.quoteIdentifier(as)), include.model, whereOptions); targetWhere = this.getWhereConditions(include.where, this.sequelize.literal(this.quoteIdentifier(as)), include.model, whereOptions);
if (targetWhere) { if (targetWhere) {
joinQueryItem += ' AND ' + targetWhere; joinQueryItem += ' AND ' + targetWhere;
} }
} }
if (subQuery && include.required) { if (subQuery && include.required) {
if (!options.where) options.where = {}; if (!options.where) options.where = {};
(function (include) {
// Closure to use sane local variables
var parent = include
, child = include
, nestedIncludes = []
, topParent
, topInclude
, $query;
while (parent = parent.parent) {
nestedIncludes = [_.extend({}, child, {include: nestedIncludes})];
child = parent;
}
topInclude = nestedIncludes[0]; let parent = include;
topParent = topInclude.parent; let child = include;
let nestedIncludes = [];
if (topInclude.through && Object(topInclude.through.model) === topInclude.through.model) { let $query;
$query = self.selectQuery(topInclude.through.model.getTableName(), {
attributes: [topInclude.through.model.primaryKeyField], while (parent = parent.parent) {
include: Model.$validateIncludedElements({ nestedIncludes = [_.extend({}, child, {include: nestedIncludes})];
model: topInclude.through.model, child = parent;
include: [{ }
association: topInclude.association.toTarget,
required: true const topInclude = nestedIncludes[0];
}] const topParent = topInclude.parent;
}).include,
if (topInclude.through && Object(topInclude.through.model) === topInclude.through.model) {
$query = this.selectQuery(topInclude.through.model.getTableName(), {
attributes: [topInclude.through.model.primaryKeyField],
include: Model.$validateIncludedElements({
model: topInclude.through.model, model: topInclude.through.model,
where: { $and: [ include: [{
self.sequelize.asIs([ association: topInclude.association.toTarget,
self.quoteTable(topParent.model.name) + '.' + self.quoteIdentifier(topParent.model.primaryKeyField), required: true
self.quoteIdentifier(topInclude.through.model.name) + '.' + self.quoteIdentifier(topInclude.association.identifierField) }]
].join(' = ')), }).include,
topInclude.through.where model: topInclude.through.model,
]}, where: { $and: [
limit: 1, this.sequelize.asIs([
includeIgnoreAttributes: false this.quoteTable(topParent.model.name) + '.' + this.quoteIdentifier(topParent.model.primaryKeyField),
}, topInclude.through.model); this.quoteIdentifier(topInclude.through.model.name) + '.' + this.quoteIdentifier(topInclude.association.identifierField)
} else { ].join(' = ')),
$query = self.selectQuery(topInclude.model.tableName, { topInclude.through.where
attributes: [topInclude.model.primaryKeyAttributes[0]], ]},
include: topInclude.include, limit: 1,
where: { includeIgnoreAttributes: false
$join: self.sequelize.asIs([ }, topInclude.through.model);
self.quoteTable(topParent.model.name) + '.' + self.quoteIdentifier(topParent.model.primaryKeyAttributes[0]), } else {
self.quoteIdentifier(topInclude.model.name) + '.' + self.quoteIdentifier(topInclude.association.identifierField) $query = this.selectQuery(topInclude.model.tableName, {
].join(' = ')) attributes: [topInclude.model.primaryKeyAttributes[0]],
}, include: topInclude.include,
limit: 1, where: {
includeIgnoreAttributes: false $join: this.sequelize.asIs([
}, topInclude.model); this.quoteTable(topParent.model.name) + '.' + this.quoteIdentifier(topParent.model.primaryKeyAttributes[0]),
} this.quoteIdentifier(topInclude.model.name) + '.' + this.quoteIdentifier(topInclude.association.identifierField)
].join(' = '))
},
limit: 1,
includeIgnoreAttributes: false
}, topInclude.model);
}
options.where['__' + throughAs] = self.sequelize.asIs([ options.where['__' + throughAs] = this.sequelize.asIs([
'(', '(',
$query.replace(/\;$/, ''), $query.replace(/\;$/, ''),
')', ')',
'IS NOT NULL' 'IS NOT NULL'
].join(' ')); ].join(' '));
})(include);
} }
} }
} else { } else {
if (subQuery && include.subQueryFilter) { if (subQuery && include.subQueryFilter) {
var associationWhere = {} const associationWhere = {};
, $query
, subQueryWhere;
associationWhere[association.identifierField] = { associationWhere[association.identifierField] = {
$raw: self.quoteTable(parentTable) + '.' + self.quoteIdentifier(association.source.primaryKeyField) $raw: this.quoteTable(parentTable) + '.' + this.quoteIdentifier(association.source.primaryKeyField)
}; };
if (!options.where) options.where = {}; if (!options.where) options.where = {};
// Creating the as-is where for the subQuery, checks that the required association exists // Creating the as-is where for the subQuery, checks that the required association exists
$query = self.selectQuery(include.model.getTableName(), { const $query = this.selectQuery(include.model.getTableName(), {
attributes: [association.identifierField], attributes: [association.identifierField],
where: { where: {
$and: [ $and: [
...@@ -1350,9 +1306,9 @@ var QueryGenerator = { ...@@ -1350,9 +1306,9 @@ var QueryGenerator = {
limit: 1 limit: 1
}, include.model); }, include.model);
subQueryWhere = self.sequelize.asIs([ const subQueryWhere = this.sequelize.asIs([
'(', '(',
$query.replace(/\;$/, ''), $query.replace(/\;$/, ''),
')', ')',
'IS NOT NULL' 'IS NOT NULL'
].join(' ')); ].join(' '));
...@@ -1364,10 +1320,10 @@ var QueryGenerator = { ...@@ -1364,10 +1320,10 @@ var QueryGenerator = {
} }
} }
joinQueryItem = ' ' + self.joinIncludeQuery({ joinQueryItem = ' ' + this.joinIncludeQuery({
model: mainModel, model: mainModel,
subQuery: options.subQuery, subQuery: options.subQuery,
include: include, include,
groupedLimit: options.groupedLimit groupedLimit: options.groupedLimit
}); });
} }
...@@ -1379,11 +1335,13 @@ var QueryGenerator = { ...@@ -1379,11 +1335,13 @@ var QueryGenerator = {
} }
if (include.include) { if (include.include) {
include.include.filter(function (include) { for (const childInclude of include.include) {
return !include.separate;
}).forEach(function(childInclude) { if (childInclude.separate || childInclude._pseudo) {
if (childInclude._pseudo) return; continue;
var childJoinQueries = generateJoinQueries(childInclude, as); }
const childJoinQueries = generateJoinQueries(childInclude, as);
if (childInclude.subQuery && subQuery) { if (childInclude.subQuery && subQuery) {
joinQueries.subQuery = joinQueries.subQuery.concat(childJoinQueries.subQuery); joinQueries.subQuery = joinQueries.subQuery.concat(childJoinQueries.subQuery);
...@@ -1392,22 +1350,24 @@ var QueryGenerator = { ...@@ -1392,22 +1350,24 @@ var QueryGenerator = {
joinQueries.mainQuery = joinQueries.mainQuery.concat(childJoinQueries.mainQuery); joinQueries.mainQuery = joinQueries.mainQuery.concat(childJoinQueries.mainQuery);
} }
}.bind(this)); }
} }
return joinQueries; return joinQueries;
}; };
// Loop through includes and generate subqueries // Loop through includes and generate subqueries
options.include.filter(function (include) { for (const include of options.include) {
return !include.separate; if (include.separate) {
}).forEach(function(include) { continue;
var joinQueries = generateJoinQueries(include, mainTableAs); }
const joinQueries = generateJoinQueries(include, mainTableAs);
subJoinQueries = subJoinQueries.concat(joinQueries.subQuery); subJoinQueries = subJoinQueries.concat(joinQueries.subQuery);
mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery); mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery);
}.bind(this)); }
} }
// If using subQuery select defined subQuery attributes and join subJoinQueries // If using subQuery select defined subQuery attributes and join subJoinQueries
...@@ -1422,22 +1382,22 @@ var QueryGenerator = { ...@@ -1422,22 +1382,22 @@ var QueryGenerator = {
mainTableAs = table; mainTableAs = table;
} }
mainQueryItems.push(this.selectFromTableFragment(options, model, mainAttributes, '('+ mainQueryItems.push(this.selectFromTableFragment(options, model, mainAttributes, '('+
options.groupedLimit.values.map(function (value) { options.groupedLimit.values.map(value => {
var where = _.assign({}, options.where); const where = _.assign({}, options.where);
where[options.groupedLimit.on] = value; where[options.groupedLimit.on] = value;
return '('+self.selectQuery( return '('+this.selectQuery(
table, table,
{ {
attributes: options.attributes, attributes: options.attributes,
limit: options.groupedLimit.limit, limit: options.groupedLimit.limit,
order: options.order, order: options.order,
where: where where
}, },
model model
).replace(/;$/, '')+')'; ).replace(/;$/, '')+')';
}).join( }).join(
self._dialect.supports['UNION ALL'] ?' UNION ALL ' : ' UNION ' this._dialect.supports['UNION ALL'] ?' UNION ALL ' : ' UNION '
) )
+')', mainTableAs)); +')', mainTableAs));
} else { } else {
...@@ -1455,18 +1415,18 @@ var QueryGenerator = { ...@@ -1455,18 +1415,18 @@ var QueryGenerator = {
} else { } else {
mainQueryItems.push(' WHERE ' + options.where); mainQueryItems.push(' WHERE ' + options.where);
// Walk the main query to update all selects // Walk the main query to update all selects
_.each(mainQueryItems, function(value, key) { _.each(mainQueryItems, (value, key) => {
if(value.match(/^SELECT/)) { if(value.match(/^SELECT/)) {
mainQueryItems[key] = this.selectFromTableFragment(options, model, mainAttributes, table, mainTableAs, options.where); mainQueryItems[key] = this.selectFromTableFragment(options, model, mainAttributes, table, mainTableAs, options.where);
} }
}.bind(this)); });
} }
} }
} }
// Add GROUP BY to sub or main query // Add GROUP BY to sub or main query
if (options.group) { if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(t) { return this.quote(t, model); }.bind(this)).join(', ') : options.group; options.group = Array.isArray(options.group) ? options.group.map(t => this.quote(t, model)).join(', ') : options.group;
if (subQuery) { if (subQuery) {
subQueryItems.push(' GROUP BY ' + options.group); subQueryItems.push(' GROUP BY ' + options.group);
} else { } else {
...@@ -1485,7 +1445,7 @@ var QueryGenerator = { ...@@ -1485,7 +1445,7 @@ var QueryGenerator = {
} }
// Add ORDER to sub or main query // Add ORDER to sub or main query
if (options.order && !options.groupedLimit) { if (options.order && !options.groupedLimit) {
var orders = this.getQueryOrders(options, model, subQuery); const orders = this.getQueryOrders(options, model, subQuery);
if (orders.mainQueryOrder.length) { if (orders.mainQueryOrder.length) {
mainQueryItems.push(' ORDER BY ' + orders.mainQueryOrder.join(', ')); mainQueryItems.push(' ORDER BY ' + orders.mainQueryOrder.join(', '));
...@@ -1496,7 +1456,7 @@ var QueryGenerator = { ...@@ -1496,7 +1456,7 @@ var QueryGenerator = {
} }
// Add LIMIT, OFFSET to sub or main query // Add LIMIT, OFFSET to sub or main query
var limitOrder = this.addLimitAndOffset(options, model); const limitOrder = this.addLimitAndOffset(options, model);
if (limitOrder && !options.groupedLimit) { if (limitOrder && !options.groupedLimit) {
if (subQuery) { if (subQuery) {
subQueryItems.push(limitOrder); subQueryItems.push(limitOrder);
...@@ -1517,7 +1477,7 @@ var QueryGenerator = { ...@@ -1517,7 +1477,7 @@ var QueryGenerator = {
} }
if (options.lock && this._dialect.supports.lock) { if (options.lock && this._dialect.supports.lock) {
var lock = options.lock; let lock = options.lock;
if (typeof options.lock === 'object') { if (typeof options.lock === 'object') {
lock = options.lock.level; lock = options.lock.level;
} }
...@@ -1538,11 +1498,11 @@ var QueryGenerator = { ...@@ -1538,11 +1498,11 @@ var QueryGenerator = {
return query; return query;
}, },
getQueryOrders: function(options, model, subQuery) { getQueryOrders(options, model, subQuery) {
var mainQueryOrder = []; const mainQueryOrder = [];
var subQueryOrder = []; const subQueryOrder = [];
var validateOrder = function(order) { const validateOrder = order => {
if (order instanceof Utils.literal) return; if (order instanceof Utils.literal) return;
if (!_.includes([ if (!_.includes([
...@@ -1560,7 +1520,7 @@ var QueryGenerator = { ...@@ -1560,7 +1520,7 @@ var QueryGenerator = {
}; };
if (Array.isArray(options.order)) { if (Array.isArray(options.order)) {
options.order.forEach(function(t) { for (const t of options.order) {
if (Array.isArray(t) && _.size(t) > 1) { if (Array.isArray(t) && _.size(t) > 1) {
if ((typeof t[0] === 'function' && t[0].prototype instanceof Model) || (typeof t[0].model === 'function' && t[0].model.prototype instanceof Model)) { if ((typeof t[0] === 'function' && t[0].prototype instanceof Model) || (typeof t[0].model === 'function' && t[0].model.prototype instanceof Model)) {
if (typeof t[t.length - 2] === 'string') { if (typeof t[t.length - 2] === 'string') {
...@@ -1576,19 +1536,16 @@ var QueryGenerator = { ...@@ -1576,19 +1536,16 @@ var QueryGenerator = {
} }
mainQueryOrder.push(this.quote(t, model)); mainQueryOrder.push(this.quote(t, model));
}.bind(this)); }
} else { } else {
mainQueryOrder.push(this.quote(typeof options.order === 'string' ? new Utils.literal(options.order) : options.order, model)); mainQueryOrder.push(this.quote(typeof options.order === 'string' ? new Utils.literal(options.order) : options.order, model));
} }
return { return {mainQueryOrder, subQueryOrder};
mainQueryOrder: mainQueryOrder,
subQueryOrder: subQueryOrder
};
}, },
selectFromTableFragment: function(options, model, attributes, tables, mainTableAs, whereClause) { selectFromTableFragment(options, model, attributes, tables, mainTableAs, whereClause) {
var fragment = 'SELECT ' + attributes.join(', ') + ' FROM ' + tables; let fragment = 'SELECT ' + attributes.join(', ') + ' FROM ' + tables;
if(mainTableAs) { if(mainTableAs) {
fragment += ' AS ' + mainTableAs; fragment += ' AS ' + mainTableAs;
...@@ -1597,34 +1554,33 @@ var QueryGenerator = { ...@@ -1597,34 +1554,33 @@ var QueryGenerator = {
return fragment; return fragment;
}, },
joinIncludeQuery: function(options) { joinIncludeQuery(options) {
var subQuery = options.subQuery const subQuery = options.subQuery;
, include = options.include const include = options.include;
, association = include.association const association = include.association;
, parent = include.parent const parent = include.parent;
, parentIsTop = !include.parent.association && include.parent.model.name === options.model.name const parentIsTop = !include.parent.association && include.parent.model.name === options.model.name;
, $parent const joinType = include.required ? 'INNER JOIN ' : 'LEFT OUTER JOIN ';
, joinType = include.required ? 'INNER JOIN ' : 'LEFT OUTER JOIN ' let $parent;
, joinOn let joinWhere;
, joinWhere
/* Attributes for the left side */
/* Attributes for the left side */ const left = association.source;
, left = association.source const attrLeft = association instanceof BelongsTo ?
, asLeft association.identifier :
, attrLeft = association instanceof BelongsTo ? left.primaryKeyAttribute;
association.identifier : const fieldLeft = association instanceof BelongsTo ?
left.primaryKeyAttribute association.identifierField :
, fieldLeft = association instanceof BelongsTo ? left.rawAttributes[left.primaryKeyAttribute].field;
association.identifierField : let asLeft;
left.rawAttributes[left.primaryKeyAttribute].field
/* Attributes for the right side */
/* Attributes for the right side */ const right = include.model;
, right = include.model const tableRight = right.getTableName();
, asRight = include.as const fieldRight = association instanceof BelongsTo ?
, tableRight = right.getTableName()
, fieldRight = association instanceof BelongsTo ?
right.rawAttributes[association.targetIdentifier || right.primaryKeyAttribute].field : right.rawAttributes[association.targetIdentifier || right.primaryKeyAttribute].field :
association.identifierField; association.identifierField;
let asRight = include.as;
while (($parent = ($parent && $parent.parent || include.parent)) && $parent.association) { while (($parent = ($parent && $parent.parent || include.parent)) && $parent.association) {
if (asLeft) { if (asLeft) {
...@@ -1637,7 +1593,7 @@ var QueryGenerator = { ...@@ -1637,7 +1593,7 @@ var QueryGenerator = {
if (!asLeft) asLeft = parent.as || parent.model.name; if (!asLeft) asLeft = parent.as || parent.model.name;
else asRight = [asLeft, asRight].join('.'); else asRight = [asLeft, asRight].join('.');
joinOn = [ let joinOn = [
this.quoteTable(asLeft), this.quoteTable(asLeft),
this.quoteIdentifier(fieldLeft) this.quoteIdentifier(fieldLeft)
].join('.'); ].join('.');
...@@ -1687,7 +1643,7 @@ var QueryGenerator = { ...@@ -1687,7 +1643,7 @@ var QueryGenerator = {
* @param {Object} options An object with options. * @param {Object} options An object with options.
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
setAutocommitQuery: function(value, options) { setAutocommitQuery(value, options) {
if (options.parent) { if (options.parent) {
return; return;
} }
...@@ -1707,7 +1663,7 @@ var QueryGenerator = { ...@@ -1707,7 +1663,7 @@ var QueryGenerator = {
* @param {Object} options An object with options. * @param {Object} options An object with options.
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
setIsolationLevelQuery: function(value, options) { setIsolationLevelQuery(value, options) {
if (options.parent) { if (options.parent) {
return; return;
} }
...@@ -1722,7 +1678,7 @@ var QueryGenerator = { ...@@ -1722,7 +1678,7 @@ var QueryGenerator = {
* @param {Object} options An object with options. * @param {Object} options An object with options.
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
startTransactionQuery: function(transaction) { startTransactionQuery(transaction) {
if (transaction.parent) { if (transaction.parent) {
// force quoting of savepoint identifiers for postgres // force quoting of savepoint identifiers for postgres
return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name, true) + ';'; return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name, true) + ';';
...@@ -1738,11 +1694,11 @@ var QueryGenerator = { ...@@ -1738,11 +1694,11 @@ var QueryGenerator = {
* @param {Object} options An object with options. * @param {Object} options An object with options.
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
deferConstraintsQuery: function () {}, deferConstraintsQuery() {},
setConstraintQuery: function () {}, setConstraintQuery() {},
setDeferredQuery: function () {}, setDeferredQuery() {},
setImmediateQuery: function () {}, setImmediateQuery() {},
/** /**
* Returns a query that commits a transaction. * Returns a query that commits a transaction.
...@@ -1750,7 +1706,7 @@ var QueryGenerator = { ...@@ -1750,7 +1706,7 @@ var QueryGenerator = {
* @param {Object} options An object with options. * @param {Object} options An object with options.
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
commitTransactionQuery: function(transaction) { commitTransactionQuery(transaction) {
if (transaction.parent) { if (transaction.parent) {
return; return;
} }
...@@ -1765,7 +1721,7 @@ var QueryGenerator = { ...@@ -1765,7 +1721,7 @@ var QueryGenerator = {
* @param {Object} options An object with options. * @param {Object} options An object with options.
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
rollbackTransactionQuery: function(transaction) { rollbackTransactionQuery(transaction) {
if (transaction.parent) { if (transaction.parent) {
// force quoting of savepoint identifiers for postgres // force quoting of savepoint identifiers for postgres
return 'ROLLBACK TO SAVEPOINT ' + this.quoteIdentifier(transaction.name, true) + ';'; return 'ROLLBACK TO SAVEPOINT ' + this.quoteIdentifier(transaction.name, true) + ';';
...@@ -1781,8 +1737,8 @@ var QueryGenerator = { ...@@ -1781,8 +1737,8 @@ var QueryGenerator = {
* @param {Object} options The model passed to the selectQuery. * @param {Object} options The model passed to the selectQuery.
* @return {String} The generated sql query. * @return {String} The generated sql query.
*/ */
addLimitAndOffset: function(options, model) { addLimitAndOffset(options, model) {
var fragment = ''; let fragment = '';
/*jshint eqeqeq:false*/ /*jshint eqeqeq:false*/
if (options.offset != null && options.limit == null) { if (options.offset != null && options.limit == null) {
...@@ -1798,13 +1754,12 @@ var QueryGenerator = { ...@@ -1798,13 +1754,12 @@ var QueryGenerator = {
return fragment; return fragment;
}, },
handleSequelizeMethod: function (smth, tableName, factory, options, prepend) { handleSequelizeMethod(smth, tableName, factory, options, prepend) {
var self = this let result;
, result;
if (smth instanceof Utils.where) { if (smth instanceof Utils.where) {
var value = smth.logic let value = smth.logic;
, key; let key;
if (smth.attribute._isSequelizeMethod) { if (smth.attribute._isSequelizeMethod) {
key = this.getWhereConditions(smth.attribute, tableName, factory, options, prepend); key = this.getWhereConditions(smth.attribute, tableName, factory, options, prepend);
...@@ -1840,11 +1795,11 @@ var QueryGenerator = { ...@@ -1840,11 +1795,11 @@ var QueryGenerator = {
result = 'CAST(' + result + ' AS ' + smth.type.toUpperCase() + ')'; result = 'CAST(' + result + ' AS ' + smth.type.toUpperCase() + ')';
} else if (smth instanceof Utils.fn) { } else if (smth instanceof Utils.fn) {
result = smth.fn + '(' + smth.args.map(function(arg) { result = smth.fn + '(' + smth.args.map(arg => {
if (arg._isSequelizeMethod) { if (arg._isSequelizeMethod) {
return self.handleSequelizeMethod(arg, tableName, factory, options, prepend); return this.handleSequelizeMethod(arg, tableName, factory, options, prepend);
} else { } else {
return self.escape(arg); return this.escape(arg);
} }
}).join(', ') + ')'; }).join(', ') + ')';
} else if (smth instanceof Utils.col) { } else if (smth instanceof Utils.col) {
...@@ -1863,14 +1818,14 @@ var QueryGenerator = { ...@@ -1863,14 +1818,14 @@ var QueryGenerator = {
return result; return result;
}, },
whereQuery: function(where, options) { whereQuery(where, options) {
var query = this.whereItemsQuery(where, options); const query = this.whereItemsQuery(where, options);
if (query && query.length) { if (query && query.length) {
return 'WHERE '+query; return 'WHERE '+query;
} }
return ''; return '';
}, },
whereItemsQuery: function(where, options, binding) { whereItemsQuery(where, options, binding) {
if ( if (
(Array.isArray(where) && where.length === 0) || (Array.isArray(where) && where.length === 0) ||
(_.isPlainObject(where) && _.isEmpty(where)) || (_.isPlainObject(where) && _.isEmpty(where)) ||
...@@ -1885,42 +1840,35 @@ var QueryGenerator = { ...@@ -1885,42 +1840,35 @@ var QueryGenerator = {
throw new Error('where: "raw query" has been removed, please use where ["raw query", [replacements]]'); throw new Error('where: "raw query" has been removed, please use where ["raw query", [replacements]]');
} }
var self = this const items = [];
, items = [];
binding = binding || 'AND'; binding = binding || 'AND';
if (binding.substr(0, 1) !== ' ') binding = ' '+binding+' '; if (binding.substr(0, 1) !== ' ') binding = ' '+binding+' ';
if (_.isPlainObject(where)) { if (_.isPlainObject(where)) {
_.forOwn(where, function (value, key) { _.forOwn(where, (value, key) => {
items.push(self.whereItemQuery(key, value, options)); items.push(this.whereItemQuery(key, value, options));
}); });
} else { } else {
items.push(self.whereItemQuery(undefined, where, options)); items.push(this.whereItemQuery(undefined, where, options));
} }
return items.length && items.filter(function (item) { return items.length && items.filter(item => item && item.length).join(binding) || '';
return item && item.length;
}).join(binding) || '';
}, },
whereItemQuery: function(key, value, options) { whereItemQuery(key, value, options) {
options = options || {}; options = options || {};
var self = this let binding;
, binding let outerBinding;
, outerBinding let comparator = '=';
, comparatorMap let field = options.field || options.model && options.model.rawAttributes && options.model.rawAttributes[key] || options.model && options.model.fieldRawAttributesMap && options.model.fieldRawAttributesMap[key];
, aliasMap let fieldType = options.type || (field && field.type);
, comparator = '='
, field = options.field || options.model && options.model.rawAttributes && options.model.rawAttributes[key] || options.model && options.model.fieldRawAttributesMap && options.model.fieldRawAttributesMap[key]
, fieldType = options.type || (field && field.type)
, tmp;
if (key && typeof key === 'string' && key.indexOf('.') !== -1 && options.model) { if (key && typeof key === 'string' && key.indexOf('.') !== -1 && options.model) {
if (options.model.rawAttributes[key.split('.')[0]] && options.model.rawAttributes[key.split('.')[0]].type instanceof DataTypes.JSON) { if (options.model.rawAttributes[key.split('.')[0]] && options.model.rawAttributes[key.split('.')[0]].type instanceof DataTypes.JSON) {
field = options.model.rawAttributes[key.split('.')[0]]; field = options.model.rawAttributes[key.split('.')[0]];
fieldType = field.type; fieldType = field.type;
tmp = value; const tmp = value;
value = {}; value = {};
Dottie.set(value, key.split('.').slice(1), tmp); Dottie.set(value, key.split('.').slice(1), tmp);
...@@ -1928,7 +1876,7 @@ var QueryGenerator = { ...@@ -1928,7 +1876,7 @@ var QueryGenerator = {
} }
} }
comparatorMap = { const comparatorMap = {
$eq: '=', $eq: '=',
$ne: '!=', $ne: '!=',
$gte: '>=', $gte: '>=',
...@@ -1954,7 +1902,7 @@ var QueryGenerator = { ...@@ -1954,7 +1902,7 @@ var QueryGenerator = {
}; };
// Maintain BC // Maintain BC
aliasMap = { const aliasMap = {
'ne': '$ne', 'ne': '$ne',
'in': '$in', 'in': '$in',
'not': '$not', 'not': '$not',
...@@ -1982,7 +1930,7 @@ var QueryGenerator = { ...@@ -1982,7 +1930,7 @@ var QueryGenerator = {
key = aliasMap[key] || key; key = aliasMap[key] || key;
if (_.isPlainObject(value)) { if (_.isPlainObject(value)) {
_.forOwn(value, function (item, key) { _.forOwn(value, (item, key) => {
if (aliasMap[key]) { if (aliasMap[key]) {
value[aliasMap[key]] = item; value[aliasMap[key]] = item;
delete value[key]; delete value[key];
...@@ -2020,15 +1968,13 @@ var QueryGenerator = { ...@@ -2020,15 +1968,13 @@ var QueryGenerator = {
if (key === '$not') outerBinding = 'NOT '; if (key === '$not') outerBinding = 'NOT ';
if (Array.isArray(value)) { if (Array.isArray(value)) {
value = value.map(function (item) { value = value.map(item => {
var itemQuery = self.whereItemsQuery(item, options, ' AND '); let itemQuery = this.whereItemsQuery(item, options, ' AND ');
if ((Array.isArray(item) || _.isPlainObject(item)) && _.size(item) > 1) { if ((Array.isArray(item) || _.isPlainObject(item)) && _.size(item) > 1) {
itemQuery = '('+itemQuery+')'; itemQuery = '('+itemQuery+')';
} }
return itemQuery; return itemQuery;
}).filter(function (item) { }).filter(item => item && item.length);
return item && item.length;
});
// $or: [] should return no data. // $or: [] should return no data.
// $not of no restriction should also return no data // $not of no restriction should also return no data
...@@ -2038,7 +1984,7 @@ var QueryGenerator = { ...@@ -2038,7 +1984,7 @@ var QueryGenerator = {
return value.length ? outerBinding + '('+value.join(binding)+')' : undefined; return value.length ? outerBinding + '('+value.join(binding)+')' : undefined;
} else { } else {
value = self.whereItemsQuery(value, options, binding); value = this.whereItemsQuery(value, options, binding);
if ((key === '$or' || key === '$not') && !value) { if ((key === '$or' || key === '$not') && !value) {
return '0 = 1'; return '0 = 1';
...@@ -2053,120 +1999,105 @@ var QueryGenerator = { ...@@ -2053,120 +1999,105 @@ var QueryGenerator = {
value = value.$or || value.$and; value = value.$or || value.$and;
if (_.isPlainObject(value)) { if (_.isPlainObject(value)) {
value = _.reduce(value, function (result, _value, key) { value = _.reduce(value, (result, _value, key) => {
result.push(_.zipObject([key], [_value])); result.push(_.zipObject([key], [_value]));
return result; return result;
}, []); }, []);
} }
value = value.map(function (_value) { value = value.map(_value => this.whereItemQuery(key, _value, options)).filter(item => item && item.length);
return self.whereItemQuery(key, _value, options);
}).filter(function (item) {
return item && item.length;
});
return value.length ? '('+value.join(binding)+')' : undefined; return value.length ? '('+value.join(binding)+')' : undefined;
} }
if (_.isPlainObject(value) && fieldType instanceof DataTypes.JSON && options.json !== false) { if (_.isPlainObject(value) && fieldType instanceof DataTypes.JSON && options.json !== false) {
return (function () { const $items = [];
var $items = [] const traverse = (prop, item, path) => {
, result const $where = {};
, traverse; let $cast;
traverse = function (prop, item, path) { if (path[path.length - 1].indexOf('::') > -1) {
var $where = {} const $tmp = path[path.length - 1].split('::');
, $key $cast = $tmp[1];
, $cast path[path.length - 1] = $tmp[0];
, $baseKey }
, $tmp
, castKey;
if (path[path.length - 1].indexOf('::') > -1) {
$tmp = path[path.length - 1].split('::');
$cast = $tmp[1];
path[path.length - 1] = $tmp[0];
}
$baseKey = self.quoteIdentifier(key)+'#>>\'{'+path.join(', ')+'}\''; let $baseKey = this.quoteIdentifier(key)+'#>>\'{'+path.join(', ')+'}\'';
if (options.prefix) { if (options.prefix) {
if (options.prefix instanceof Utils.literal) { if (options.prefix instanceof Utils.literal) {
$baseKey = self.handleSequelizeMethod(options.prefix)+'.'+$baseKey; $baseKey = this.handleSequelizeMethod(options.prefix)+'.'+$baseKey;
} else { } else {
$baseKey = self.quoteTable(options.prefix)+'.'+$baseKey; $baseKey = this.quoteTable(options.prefix)+'.'+$baseKey;
}
} }
}
$baseKey = '('+$baseKey+')'; $baseKey = '('+$baseKey+')';
castKey = function ($item) { const castKey = $item => {
var key = $baseKey; let key = $baseKey;
if (!$cast) { if (!$cast) {
if (typeof $item === 'number') { if (typeof $item === 'number') {
$cast = 'double precision'; $cast = 'double precision';
} else if ($item instanceof Date) { } else if ($item instanceof Date) {
$cast = 'timestamptz'; $cast = 'timestamptz';
} else if (typeof $item === 'boolean') { } else if (typeof $item === 'boolean') {
$cast = 'boolean'; $cast = 'boolean';
}
} }
}
if ($cast) { if ($cast) {
key += '::'+$cast; key += '::'+$cast;
} }
return key; return key;
}; };
if (_.isPlainObject(item)) { if (_.isPlainObject(item)) {
_.forOwn(item, function ($item, $prop) { _.forOwn(item, ($item, $prop) => {
if ($prop.indexOf('$') === 0) { if ($prop.indexOf('$') === 0) {
$where[$prop] = $item; $where[$prop] = $item;
$key = castKey($item); const $key = castKey($item);
$items.push(self.whereItemQuery(new Utils.literal($key), $where/*, _.pick(options, 'prefix')*/)); $items.push(this.whereItemQuery(new Utils.literal($key), $where/*, _.pick(options, 'prefix')*/));
} else { } else {
traverse($prop, $item, path.concat([$prop])); traverse($prop, $item, path.concat([$prop]));
} }
}); });
} else { } else {
$where.$eq = item; $where.$eq = item;
$key = castKey(item); const $key = castKey(item);
$items.push(self.whereItemQuery(new Utils.literal($key), $where/*, _.pick(options, 'prefix')*/)); $items.push(this.whereItemQuery(new Utils.literal($key), $where/*, _.pick(options, 'prefix')*/));
} }
}; };
_.forOwn(value, function (item, prop) { _.forOwn(value, (item, prop) => {
if (prop.indexOf('$') === 0) { if (prop.indexOf('$') === 0) {
var $where = {}; const $where = {};
$where[prop] = item; $where[prop] = item;
$items.push(self.whereItemQuery(key, $where, _.assign({}, options, {json: false}))); $items.push(this.whereItemQuery(key, $where, _.assign({}, options, {json: false})));
return; return;
} }
traverse(prop, item, [prop]); traverse(prop, item, [prop]);
}); });
result = $items.join(' AND '); const result = $items.join(' AND ');
return $items.length > 1 ? '('+result+')' : result; return $items.length > 1 ? '('+result+')' : result;
})();
} }
// If multiple keys we combine the different logic conditions // If multiple keys we combine the different logic conditions
if (_.isPlainObject(value) && Object.keys(value).length > 1) { if (_.isPlainObject(value) && Object.keys(value).length > 1) {
return (function () { const $items = [];
var $items = []; _.forOwn(value, (item, logic) => {
_.forOwn(value, function (item, logic) { const $where = {};
var $where = {}; $where[logic] = item;
$where[logic] = item; $items.push(this.whereItemQuery(key, $where, options));
$items.push(self.whereItemQuery(key, $where, options)); });
});
return '('+$items.join(' AND ')+')'; return '('+$items.join(' AND ')+')';
})();
} }
// Do [] to $in/$notIn normalization // Do [] to $in/$notIn normalization
...@@ -2197,9 +2128,7 @@ var QueryGenerator = { ...@@ -2197,9 +2128,7 @@ var QueryGenerator = {
if ((value.$in || value.$notIn) instanceof Utils.literal) { if ((value.$in || value.$notIn) instanceof Utils.literal) {
value = (value.$in || value.$notIn).val; value = (value.$in || value.$notIn).val;
} else if ((value.$in || value.$notIn).length) { } else if ((value.$in || value.$notIn).length) {
value = '('+(value.$in || value.$notIn).map(function (item) { value = '('+(value.$in || value.$notIn).map(item => this.escape(item)).join(', ')+')';
return self.escape(item);
}).join(', ')+')';
} else { } else {
if (value.$in) { if (value.$in) {
value = '(NULL)'; value = '(NULL)';
...@@ -2210,9 +2139,7 @@ var QueryGenerator = { ...@@ -2210,9 +2139,7 @@ var QueryGenerator = {
} else if (value && (value.$any || value.$all)) { } else if (value && (value.$any || value.$all)) {
comparator = value.$any ? '= ANY' : '= ALL'; comparator = value.$any ? '= ANY' : '= ALL';
if (value.$any && value.$any.$values || value.$all && value.$all.$values) { if (value.$any && value.$any.$values || value.$all && value.$all.$values) {
value = '(VALUES '+(value.$any && value.$any.$values || value.$all && value.$all.$values).map(function (value) { value = '(VALUES '+(value.$any && value.$any.$values || value.$all && value.$all.$values).map(value => '('+this.escape(value)+')').join(', ')+')';
return '('+this.escape(value)+')';
}.bind(this)).join(', ')+')';
} else { } else {
value = '('+this.escape(value.$any || value.$all, field)+')'; value = '('+this.escape(value.$any || value.$all, field)+')';
} }
...@@ -2220,9 +2147,7 @@ var QueryGenerator = { ...@@ -2220,9 +2147,7 @@ var QueryGenerator = {
comparator = 'BETWEEN'; comparator = 'BETWEEN';
if (value.$notBetween) comparator = 'NOT BETWEEN'; if (value.$notBetween) comparator = 'NOT BETWEEN';
value = (value.$between || value.$notBetween).map(function (item) { value = (value.$between || value.$notBetween).map(item => this.escape(item)).join(' AND ');
return self.escape(item);
}).join(' AND ');
} else if (value && value.$raw) { } else if (value && value.$raw) {
value = value.$raw; value = value.$raw;
} else if (value && value.$col) { } else if (value && value.$col) {
...@@ -2235,13 +2160,13 @@ var QueryGenerator = { ...@@ -2235,13 +2160,13 @@ var QueryGenerator = {
]; ];
} }
value = value.map(this.quoteIdentifier.bind(this)).join('.'); value = value.map(identifier => this.quoteIdentifier(identifier)).join('.');
} else { } else {
var escapeValue = true; let escapeValue = true;
var escapeOptions = {}; const escapeOptions = {};
if (_.isPlainObject(value)) { if (_.isPlainObject(value)) {
_.forOwn(value, function (item, key) { _.forOwn(value, (item, key) => {
if (comparatorMap[key]) { if (comparatorMap[key]) {
comparator = comparatorMap[key]; comparator = comparatorMap[key];
value = item; value = item;
...@@ -2259,7 +2184,7 @@ var QueryGenerator = { ...@@ -2259,7 +2184,7 @@ var QueryGenerator = {
value = this.whereItemQuery(null, value); value = this.whereItemQuery(null, value);
} }
} }
}.bind(this)); });
} }
if (comparator === '=' && value === null) { if (comparator === '=' && value === null) {
...@@ -2275,13 +2200,13 @@ var QueryGenerator = { ...@@ -2275,13 +2200,13 @@ var QueryGenerator = {
//if ANY is used with like, add parentheses to generate correct query //if ANY is used with like, add parentheses to generate correct query
if (escapeOptions.acceptStrings && (comparator.indexOf('ANY') > comparator.indexOf('LIKE'))) { if (escapeOptions.acceptStrings && (comparator.indexOf('ANY') > comparator.indexOf('LIKE'))) {
value = '(' + value + ')'; value = '(' + value + ')';
} }
} }
} }
if (key) { if (key) {
var prefix = true; let prefix = true;
if (key._isSequelizeMethod) { if (key._isSequelizeMethod) {
key = this.handleSequelizeMethod(key); key = this.handleSequelizeMethod(key);
} else if (Utils.isColString(key)) { } else if (Utils.isColString(key)) {
...@@ -2294,7 +2219,7 @@ var QueryGenerator = { ...@@ -2294,7 +2219,7 @@ var QueryGenerator = {
]; ];
} }
key = key.map(this.quoteIdentifier.bind(this)).join('.'); key = key.map(identifier => this.quoteIdentifier(identifier)).join('.');
prefix = false; prefix = false;
} else { } else {
key = this.quoteIdentifier(key); key = this.quoteIdentifier(key);
...@@ -2315,10 +2240,9 @@ var QueryGenerator = { ...@@ -2315,10 +2240,9 @@ var QueryGenerator = {
/* /*
Takes something and transforms it into values of a where condition. Takes something and transforms it into values of a where condition.
*/ */
getWhereConditions: function(smth, tableName, factory, options, prepend) { getWhereConditions(smth, tableName, factory, options, prepend) {
var result = null let result = null;
, where = {} const where = {};
, self = this;
if (Array.isArray(tableName)) { if (Array.isArray(tableName)) {
tableName = tableName[0]; tableName = tableName[0];
...@@ -2336,12 +2260,12 @@ var QueryGenerator = { ...@@ -2336,12 +2260,12 @@ var QueryGenerator = {
if (smth && smth._isSequelizeMethod === true) { // Checking a property is cheaper than a lot of instanceof calls if (smth && smth._isSequelizeMethod === true) { // Checking a property is cheaper than a lot of instanceof calls
result = this.handleSequelizeMethod(smth, tableName, factory, options, prepend); result = this.handleSequelizeMethod(smth, tableName, factory, options, prepend);
} else if (Utils._.isPlainObject(smth)) { } else if (Utils._.isPlainObject(smth)) {
return self.whereItemsQuery(smth, { return this.whereItemsQuery(smth, {
model: factory, model: factory,
prefix: prepend && tableName prefix: prepend && tableName
}); });
} else if (typeof smth === 'number') { } else if (typeof smth === 'number') {
var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : []; let primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : [];
if (primaryKeys.length > 0) { if (primaryKeys.length > 0) {
// Since we're just a number, assume only the first key // Since we're just a number, assume only the first key
...@@ -2352,12 +2276,12 @@ var QueryGenerator = { ...@@ -2352,12 +2276,12 @@ var QueryGenerator = {
where[primaryKeys] = smth; where[primaryKeys] = smth;
return self.whereItemsQuery(where, { return this.whereItemsQuery(where, {
model: factory, model: factory,
prefix: prepend && tableName prefix: prepend && tableName
}); });
} else if (typeof smth === 'string') { } else if (typeof smth === 'string') {
return self.whereItemsQuery(smth, { return this.whereItemsQuery(smth, {
model: factory, model: factory,
prefix: prepend && tableName prefix: prepend && tableName
}); });
...@@ -2366,13 +2290,13 @@ var QueryGenerator = { ...@@ -2366,13 +2290,13 @@ var QueryGenerator = {
} else if (Array.isArray(smth)) { } else if (Array.isArray(smth)) {
if (smth.length === 0) return '1=1'; if (smth.length === 0) return '1=1';
if (Utils.canTreatArrayAsAnd(smth)) { if (Utils.canTreatArrayAsAnd(smth)) {
var _smth = { $and: smth }; const _smth = { $and: smth };
result = self.getWhereConditions(_smth, tableName, factory, options, prepend); result = this.getWhereConditions(_smth, tableName, factory, options, prepend);
} else { } else {
result = Utils.format(smth, this.dialect); result = Utils.format(smth, this.dialect);
} }
} else if (smth === null) { } else if (smth === null) {
return self.whereItemsQuery(smth, { return this.whereItemsQuery(smth, {
model: factory, model: factory,
prefix: prepend && tableName prefix: prepend && tableName
}); });
...@@ -2381,7 +2305,7 @@ var QueryGenerator = { ...@@ -2381,7 +2305,7 @@ var QueryGenerator = {
return result ? result : '1=1'; return result ? result : '1=1';
}, },
booleanValue: function(value) { booleanValue(value) {
return value; return value;
} }
}; };
......
'use strict'; 'use strict';
var Utils = require('../../utils') const Utils = require('../../utils');
, SqlString = require('../../sql-string') const SqlString = require('../../sql-string');
, Dot = require('dottie') const Dot = require('dottie');
, QueryTypes = require('../../query-types'); const QueryTypes = require('../../query-types');
var AbstractQuery = function(database, sequelize, options) {}; class AbstractQuery {
/** /**
The function takes the result of the query execution and groups * rewrite query with parameters
the associated data by the callee. *
* Examples:
Example: *
groupJoinData([ * query.formatBindParameters('select $1 as foo', ['fooval']);
{ *
some: 'data', * query.formatBindParameters('select $foo as foo', { foo: 'fooval' });
id: 1, *
association: { foo: 'bar', id: 1 } * Options
}, { * skipUnescape: bool, skip unescaping $$
some: 'data', * skipValueReplace: bool, do not replace (but do unescape $$). Check correct syntax and if all values are available
id: 1, */
association: { foo: 'bar', id: 2 } static formatBindParameters(sql, values, dialect, replacementFunc, options) {
}, { if (!values) {
some: 'data', return [sql, []];
id: 1, }
association: { foo: 'bar', id: 3 }
options = options || {};
if (typeof replacementFunc !== 'function') {
options = replacementFunc || {};
replacementFunc = undefined;
}
if (!replacementFunc) {
if (options.skipValueReplace) {
replacementFunc = (match, key, values, timeZone, dialect, options) => {
if (values[key] !== undefined) {
return match;
}
return undefined;
};
} else {
replacementFunc = (match, key, values, timeZone, dialect, options) => {
if (values[key] !== undefined) {
return SqlString.escape(values[key], false, timeZone, dialect);
}
return undefined;
};
}
} else {
if (options.skipValueReplace) {
const origReplacementFunc = replacementFunc;
replacementFunc = (match, key, values, timeZone, dialect, options) => {
if (origReplacementFunc(match, key, values, timeZone, dialect, options) !== undefined) {
return match;
}
return undefined;
};
} }
]) }
Result: const timeZone = null;
Something like this: const list = Array.isArray(values);
[ sql = sql.replace(/\$(\$|\w+)/g, (match, key) => {
{ if ('$' === key) {
some: 'data', return options.skipUnescape ? match : key;
id: 1,
association: [
{ foo: 'bar', id: 1 },
{ foo: 'bar', id: 2 },
{ foo: 'bar', id: 3 }
]
} }
]
*/ let replVal;
/* if (list) {
* Assumptions if (key.match(/^[1-9]\d*$/)) {
* ID is not necessarily the first field key = key - 1;
* All fields for a level is grouped in the same set (i.e. Panel.id, Task.id, Panel.title is not possible) replVal = replacementFunc(match, key, values, timeZone, dialect, options);
* Parent keys will be seen before any include/child keys }
* Previous set won't necessarily be parent set (one parent could have two children, one child would then be previous set for the other) } else {
*/ if (!key.match(/^\d*$/)) {
replVal = replacementFunc(match, key, values, timeZone, dialect, options);
/* }
* Author (MH) comment: This code is an unreadable mess, but its performant. }
* groupJoinData is a performance critical function so we prioritize perf over readability. if (replVal === undefined) {
*/ throw new Error('Named bind parameter "' + match + '" has no value in the given object.');
}
var groupJoinData = function(rows, includeOptions, options) { return replVal;
if (!rows.length) { });
return []; return [sql, []];
} }
var /**
* Execute the passed sql query.
*
* Examples:
*
* query.run('SELECT 1')
*
* @param {String} sql - The SQL query which should be executed.
* @api public
*/
run() {
throw new Error('The run method wasn\'t overwritten!');
}
/**
* Check the logging option of the instance and print deprecation warnings.
*
* @return {void}
*/
checkLoggingOption() {
if (this.options.logging === true) {
console.log('DEPRECATION WARNING: The logging-option should be either a function or false. Default: console.log');
this.options.logging = console.log;
}
}
/**
* Get the attributes of an insert query, which contains the just inserted id.
*
* @return {String} The field name.
*/
getInsertIdField() {
return 'insertId';
}
/**
* Iterate over all known tables and search their names inside the sql query.
* This method will also check association aliases ('as' option).
*
* @param {String} attribute An attribute of a SQL query. (?)
* @return {String} The found tableName / alias.
*/
findTableNameInAttribute(attribute) {
if (!this.options.include) {
return null;
}
if (!this.options.includeNames) {
this.options.includeNames = this.options.include.map(include => include.as);
}
const tableNames = this.options.includeNames.filter(include => attribute.indexOf(include + '.') === 0);
if (tableNames.length === 1) {
return tableNames[0];
} else {
return null;
}
}
getUniqueConstraintErrorMessage(field) {
let message = field + ' must be unique';
if (this.model) {
for (const key of Object.keys(this.model.uniqueKeys)) {
if (this.model.uniqueKeys[key].fields.indexOf(field.replace(/"/g, '')) >= 0) {
if (this.model.uniqueKeys[key].msg) {
message = this.model.uniqueKeys[key].msg;
}
}
}
}
return message;
}
isRawQuery() {
return this.options.type === QueryTypes.RAW;
}
isVersionQuery() {
return this.options.type === QueryTypes.VERSION;
}
isUpsertQuery() {
return this.options.type === QueryTypes.UPSERT;
}
isInsertQuery(results, metaData) {
let result = true;
if (this.options.type === QueryTypes.INSERT) {
return true;
}
// is insert query if sql contains insert into
result = result && (this.sql.toLowerCase().indexOf('insert into') === 0);
// is insert query if no results are passed or if the result has the inserted id
result = result && (!results || results.hasOwnProperty(this.getInsertIdField()));
// is insert query if no metadata are passed or if the metadata has the inserted id
result = result && (!metaData || metaData.hasOwnProperty(this.getInsertIdField()));
return result;
}
handleInsertQuery(results, metaData) {
if (this.instance) {
// add the inserted row id to the instance
const autoIncrementField = this.model.autoIncrementField;
let id = null;
id = id || (results && results[this.getInsertIdField()]);
id = id || (metaData && metaData[this.getInsertIdField()]);
this.instance[autoIncrementField] = id;
}
}
isShowTablesQuery() {
return this.options.type === QueryTypes.SHOWTABLES;
}
handleShowTablesQuery(results) {
return Utils._.flatten(results.map(resultSet => Utils._.values(resultSet)));
}
isShowIndexesQuery() {
return this.options.type === QueryTypes.SHOWINDEXES;
}
isDescribeQuery() {
return this.options.type === QueryTypes.DESCRIBE;
}
isSelectQuery() {
return this.options.type === QueryTypes.SELECT;
}
isBulkUpdateQuery() {
return this.options.type === QueryTypes.BULKUPDATE;
}
isBulkDeleteQuery() {
return this.options.type === QueryTypes.BULKDELETE;
}
isForeignKeysQuery() {
return this.options.type === QueryTypes.FOREIGNKEYS;
}
isUpdateQuery() {
return this.options.type === QueryTypes.UPDATE;
}
handleSelectQuery(results) {
let result = null;
// Map raw fields to names if a mapping is provided
if (this.options.fieldMap) {
const fieldMap = this.options.fieldMap;
results = Utils._.map(results, result => Utils._.reduce(fieldMap, (result, name, field) => {
if (result[field] !== undefined) {
result[name] = result[field];
delete result[field];
}
return result;
}, result));
}
// Raw queries
if (this.options.raw) {
result = results.map(result => {
let o = {};
for (const key in result) {
if (result.hasOwnProperty(key)) {
o[key] = result[key];
}
}
if (this.options.nest) {
o = Dot.transform(o);
}
return o;
});
// Queries with include
} else if (this.options.hasJoin === true) {
results = AbstractQuery.$groupJoinData(results, {
model: this.model,
includeMap: this.options.includeMap,
includeNames: this.options.includeNames
}, {
checkExisting: this.options.hasMultiAssociation
});
result = this.model.bulkBuild(results, {
isNewRecord: false,
include: this.options.include,
includeNames: this.options.includeNames,
includeMap: this.options.includeMap,
includeValidated: true,
attributes: this.options.originalAttributes || this.options.attributes,
raw: true
});
// Regular queries
} else {
result = this.model.bulkBuild(results, {
isNewRecord: false,
raw: true,
attributes: this.options.attributes
});
}
// return the first real model instance if options.plain is set (e.g. Model.find)
if (this.options.plain) {
result = (result.length === 0) ? null : result[0];
}
return result;
}
isShowOrDescribeQuery() {
let result = false;
result = result || (this.sql.toLowerCase().indexOf('show') === 0);
result = result || (this.sql.toLowerCase().indexOf('describe') === 0);
return result;
}
isCallQuery() {
return this.sql.toLowerCase().indexOf('call') === 0;
}
/**
* The function takes the result of the query execution and groups
* the associated data by the callee.
*
* Example:
* groupJoinData([
* {
* some: 'data',
* id: 1,
* association: { foo: 'bar', id: 1 }
* }, {
* some: 'data',
* id: 1,
* association: { foo: 'bar', id: 2 }
* }, {
* some: 'data',
* id: 1,
* association: { foo: 'bar', id: 3 }
* }
* ])
*
* Result:
* Something like this:
*
* [
* {
* some: 'data',
* id: 1,
* association: [
* { foo: 'bar', id: 1 },
* { foo: 'bar', id: 2 },
* { foo: 'bar', id: 3 }
* ]
* }
* ]
*/
static $groupJoinData(rows, includeOptions, options) {
/*
* Assumptions
* ID is not necessarily the first field
* All fields for a level is grouped in the same set (i.e. Panel.id, Task.id, Panel.title is not possible)
* Parent keys will be seen before any include/child keys
* Previous set won't necessarily be parent set (one parent could have two children, one child would then be previous set for the other)
*/
/*
* Author (MH) comment: This code is an unreadable mess, but its performant.
* groupJoinData is a performance critical function so we prioritize perf over readability.
*/
if (!rows.length) {
return [];
}
// Generic looping // Generic looping
i let i;
, length let length;
, $i let $i;
, $length let $length;
// Row specific looping // Row specific looping
, rowsI let rowsI;
, rowsLength = rows.length let row;
, row const rowsLength = rows.length;
// Key specific looping // Key specific looping
, keys let keys;
, key let key;
, keyI let keyI;
, keyLength let keyLength;
, prevKey let prevKey;
, values let values;
, topValues let topValues;
, topExists let topExists;
, checkExisting = options.checkExisting const checkExisting = options.checkExisting;
// If we don't have to deduplicate we can pre-allocate the resulting array // If we don't have to deduplicate we can pre-allocate the resulting array
, results = checkExisting ? [] : new Array(rowsLength) let itemHash;
, resultMap = {} let parentHash;
, includeMap = {} let topHash;
, itemHash const results = checkExisting ? [] : new Array(rowsLength);
, parentHash const resultMap = {};
, topHash const includeMap = {};
// Result variables for the respective functions // Result variables for the respective functions
, $keyPrefix let $keyPrefix;
, $keyPrefixString let $keyPrefixString;
, $prevKeyPrefixString let $prevKeyPrefixString;
, $prevKeyPrefix let $prevKeyPrefix;
, $lastKeyPrefix let $lastKeyPrefix;
, $current let $current;
, $parent let $parent;
// Map each key to an include option // Map each key to an include option
, previousPiece let previousPiece;
, buildIncludeMap = function (piece) { const buildIncludeMap = piece => {
if ($current.includeMap[piece]) { if ($current.includeMap[piece]) {
includeMap[key] = $current = $current.includeMap[piece]; includeMap[key] = $current = $current.includeMap[piece];
if (previousPiece) { if (previousPiece) {
...@@ -108,603 +418,276 @@ var groupJoinData = function(rows, includeOptions, options) { ...@@ -108,603 +418,276 @@ var groupJoinData = function(rows, includeOptions, options) {
} }
includeMap[previousPiece] = $current; includeMap[previousPiece] = $current;
} }
} };
// Calculate the string prefix of a key ('User.Results' for 'User.Results.id') // Calculate the string prefix of a key ('User.Results' for 'User.Results.id')
, keyPrefixStringMemo = {} const keyPrefixStringMemo = {};
, keyPrefixString = function (key, memo) { const keyPrefixString = (key, memo) => {
if (!memo[key]) { if (!memo[key]) {
memo[key] = key.substr(0, key.lastIndexOf('.')); memo[key] = key.substr(0, key.lastIndexOf('.'));
} }
return memo[key]; return memo[key];
} };
// Removes the prefix from a key ('id' for 'User.Results.id') // Removes the prefix from a key ('id' for 'User.Results.id')
, removeKeyPrefixMemo = {} const removeKeyPrefixMemo = {};
, removeKeyPrefix = function (key) { const removeKeyPrefix = key => {
if (!removeKeyPrefixMemo[key]) { if (!removeKeyPrefixMemo[key]) {
var index = key.lastIndexOf('.'); const index = key.lastIndexOf('.');
removeKeyPrefixMemo[key] = key.substr(index === -1 ? 0 : index + 1); removeKeyPrefixMemo[key] = key.substr(index === -1 ? 0 : index + 1);
} }
return removeKeyPrefixMemo[key]; return removeKeyPrefixMemo[key];
} };
// Calculates the array prefix of a key (['User', 'Results'] for 'User.Results.id') // Calculates the array prefix of a key (['User', 'Results'] for 'User.Results.id')
, keyPrefixMemo = {} const keyPrefixMemo = {};
, keyPrefix = function (key) { const keyPrefix = key => {
// We use a double memo and keyPrefixString so that different keys with the same prefix will receive the same array instead of differnet arrays with equal values // We use a double memo and keyPrefixString so that different keys with the same prefix will receive the same array instead of differnet arrays with equal values
if (!keyPrefixMemo[key]) { if (!keyPrefixMemo[key]) {
var prefixString = keyPrefixString(key, keyPrefixStringMemo); const prefixString = keyPrefixString(key, keyPrefixStringMemo);
if (!keyPrefixMemo[prefixString]) { if (!keyPrefixMemo[prefixString]) {
keyPrefixMemo[prefixString] = prefixString ? prefixString.split('.') : []; keyPrefixMemo[prefixString] = prefixString ? prefixString.split('.') : [];
} }
keyPrefixMemo[key] = keyPrefixMemo[prefixString]; keyPrefixMemo[key] = keyPrefixMemo[prefixString];
} }
return keyPrefixMemo[key]; return keyPrefixMemo[key];
} };
// Calcuate the last item in the array prefix ('Results' for 'User.Results.id') // Calcuate the last item in the array prefix ('Results' for 'User.Results.id')
, lastKeyPrefixMemo = {} const lastKeyPrefixMemo = {};
, lastKeyPrefix = function (key) { const lastKeyPrefix = key => {
if (!lastKeyPrefixMemo[key]) { if (!lastKeyPrefixMemo[key]) {
var prefix = keyPrefix(key) const prefix = keyPrefix(key);
, length = prefix.length; const length = prefix.length;
lastKeyPrefixMemo[key] = !length ? '' : prefix[length - 1]; lastKeyPrefixMemo[key] = !length ? '' : prefix[length - 1];
} }
return lastKeyPrefixMemo[key]; return lastKeyPrefixMemo[key];
} };
, getUniqueKeyAttributes = function (model) { const getUniqueKeyAttributes = model => {
var uniqueKeyAttributes = Utils._.chain(model.uniqueKeys); let uniqueKeyAttributes = Utils._.chain(model.uniqueKeys);
uniqueKeyAttributes = uniqueKeyAttributes uniqueKeyAttributes = uniqueKeyAttributes
.result(uniqueKeyAttributes.findKey() + '.fields') .result(uniqueKeyAttributes.findKey() + '.fields')
.map(function(field){ .map(field => Utils._.findKey(model.attributes, chr => chr.field === field))
return Utils._.findKey(model.attributes, function(chr) {
return chr.field === field;
});
})
.value(); .value();
return uniqueKeyAttributes; return uniqueKeyAttributes;
} };
, primaryKeyAttributes let primaryKeyAttributes;
, uniqueKeyAttributes let uniqueKeyAttributes;
, prefix; let prefix;
for (rowsI = 0; rowsI < rowsLength; rowsI++) { for (rowsI = 0; rowsI < rowsLength; rowsI++) {
row = rows[rowsI]; row = rows[rowsI];
// Keys are the same for all rows, so only need to compute them on the first row // Keys are the same for all rows, so only need to compute them on the first row
if (rowsI === 0) { if (rowsI === 0) {
keys = Object.keys(row); keys = Object.keys(row);
keyLength = keys.length; keyLength = keys.length;
} }
if (checkExisting) { if (checkExisting) {
topExists = false; topExists = false;
// Compute top level hash key (this is usually just the primary key values) // Compute top level hash key (this is usually just the primary key values)
$length = includeOptions.model.primaryKeyAttributes.length; $length = includeOptions.model.primaryKeyAttributes.length;
topHash = ''; topHash = '';
if ($length === 1) { if ($length === 1) {
topHash = row[includeOptions.model.primaryKeyAttributes[0]]; topHash = row[includeOptions.model.primaryKeyAttributes[0]];
}
else if ($length > 1) {
for ($i = 0; $i < $length; $i++) {
topHash += row[includeOptions.model.primaryKeyAttributes[$i]];
} }
} else if ($length > 1) {
else if (!Utils._.isEmpty(includeOptions.model.uniqueKeys)) { for ($i = 0; $i < $length; $i++) {
uniqueKeyAttributes = getUniqueKeyAttributes(includeOptions.model); topHash += row[includeOptions.model.primaryKeyAttributes[$i]];
for ($i = 0; $i < uniqueKeyAttributes.length; $i++) { }
topHash += row[uniqueKeyAttributes[$i]]; }
else if (!Utils._.isEmpty(includeOptions.model.uniqueKeys)) {
uniqueKeyAttributes = getUniqueKeyAttributes(includeOptions.model);
for ($i = 0; $i < uniqueKeyAttributes.length; $i++) {
topHash += row[uniqueKeyAttributes[$i]];
}
} }
} }
}
topValues = values = {}; topValues = values = {};
$prevKeyPrefix = undefined; $prevKeyPrefix = undefined;
for (keyI = 0; keyI < keyLength; keyI++) { for (keyI = 0; keyI < keyLength; keyI++) {
key = keys[keyI]; key = keys[keyI];
// The string prefix isn't actualy needed // The string prefix isn't actualy needed
// We use it so keyPrefix for different keys will resolve to the same array if they have the same prefix // We use it so keyPrefix for different keys will resolve to the same array if they have the same prefix
// TODO: Find a better way? // TODO: Find a better way?
$keyPrefixString = keyPrefixString(key, keyPrefixStringMemo); $keyPrefixString = keyPrefixString(key, keyPrefixStringMemo);
$keyPrefix = keyPrefix(key); $keyPrefix = keyPrefix(key);
// On the first row we compute the includeMap // On the first row we compute the includeMap
if (rowsI === 0 && includeMap[key] === undefined) { if (rowsI === 0 && includeMap[key] === undefined) {
if (!$keyPrefix.length) { if (!$keyPrefix.length) {
includeMap[key] = includeMap[''] = includeOptions; includeMap[key] = includeMap[''] = includeOptions;
} else { } else {
$current = includeOptions; $current = includeOptions;
previousPiece = undefined; previousPiece = undefined;
$keyPrefix.forEach(buildIncludeMap); $keyPrefix.forEach(buildIncludeMap);
}
} }
} // End of key set
// End of key set if ($prevKeyPrefix !== undefined && $prevKeyPrefix !== $keyPrefix) {
if ($prevKeyPrefix !== undefined && $prevKeyPrefix !== $keyPrefix) { if (checkExisting) {
if (checkExisting) { // Compute hash key for this set instance
// Compute hash key for this set instance // TODO: Optimize
// TODO: Optimize length = $prevKeyPrefix.length;
length = $prevKeyPrefix.length; $parent = null;
$parent = null; parentHash = null;
parentHash = null;
if (length) {
if (length) { for (i = 0; i < length; i++) {
for (i = 0; i < length; i++) { prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i];
prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i]; primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes;
primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes; $length = primaryKeyAttributes.length;
$length = primaryKeyAttributes.length; itemHash = prefix;
itemHash = prefix; if ($length === 1) {
if ($length === 1) { itemHash += row[prefix+'.'+primaryKeyAttributes[0]];
itemHash += row[prefix+'.'+primaryKeyAttributes[0]];
}
else if ($length > 1) {
for ($i = 0; $i < $length; $i++) {
itemHash += row[prefix+'.'+primaryKeyAttributes[$i]];
} }
} else if ($length > 1) {
else if (!Utils._.isEmpty(includeMap[prefix].model.uniqueKeys)) { for ($i = 0; $i < $length; $i++) {
uniqueKeyAttributes = getUniqueKeyAttributes(includeMap[prefix].model); itemHash += row[prefix+'.'+primaryKeyAttributes[$i]];
for ($i = 0; $i < uniqueKeyAttributes.length; $i++) { }
itemHash += row[prefix+'.'+uniqueKeyAttributes[$i]]; }
else if (!Utils._.isEmpty(includeMap[prefix].model.uniqueKeys)) {
uniqueKeyAttributes = getUniqueKeyAttributes(includeMap[prefix].model);
for ($i = 0; $i < uniqueKeyAttributes.length; $i++) {
itemHash += row[prefix+'.'+uniqueKeyAttributes[$i]];
}
}
if (!parentHash) {
parentHash = topHash;
} }
}
if (!parentHash) {
parentHash = topHash;
}
itemHash = parentHash + itemHash; itemHash = parentHash + itemHash;
$parent = prefix; $parent = prefix;
if (i < length - 1) { if (i < length - 1) {
parentHash = itemHash; parentHash = itemHash;
}
} }
}
} else {
itemHash = topHash;
}
if (itemHash === topHash) {
if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else { } else {
topExists = true; itemHash = topHash;
} }
} else {
if (!resultMap[itemHash]) {
$parent = resultMap[parentHash];
$lastKeyPrefix = lastKeyPrefix(prevKey);
if (includeMap[prevKey].association.isSingleAssociation) { if (itemHash === topHash) {
$parent[$lastKeyPrefix] = resultMap[itemHash] = values; if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else { } else {
if (!$parent[$lastKeyPrefix]) { topExists = true;
$parent[$lastKeyPrefix] = []; }
} else {
if (!resultMap[itemHash]) {
$parent = resultMap[parentHash];
$lastKeyPrefix = lastKeyPrefix(prevKey);
if (includeMap[prevKey].association.isSingleAssociation) {
$parent[$lastKeyPrefix] = resultMap[itemHash] = values;
} else {
if (!$parent[$lastKeyPrefix]) {
$parent[$lastKeyPrefix] = [];
}
$parent[$lastKeyPrefix].push(resultMap[itemHash] = values);
} }
$parent[$lastKeyPrefix].push(resultMap[itemHash] = values);
} }
} }
}
// Reset values // Reset values
values = {}; values = {};
} else { } else {
// If checkExisting is false it's because there's only 1:1 associations in this query // If checkExisting is false it's because there's only 1:1 associations in this query
// However we still need to map onto the appropriate parent // However we still need to map onto the appropriate parent
// For 1:1 we map forward, initializing the value object on the parent to be filled in the next iterations of the loop // For 1:1 we map forward, initializing the value object on the parent to be filled in the next iterations of the loop
$current = topValues; $current = topValues;
length = $keyPrefix.length; length = $keyPrefix.length;
if (length) { if (length) {
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
if (i === length -1) { if (i === length -1) {
values = $current[$keyPrefix[i]] = {}; values = $current[$keyPrefix[i]] = {};
}
$current = $current[$keyPrefix[i]];
} }
$current = $current[$keyPrefix[i]];
} }
} }
} }
}
// End of iteration, set value and set prev values (for next iteration) // End of iteration, set value and set prev values (for next iteration)
values[removeKeyPrefix(key)] = row[key]; values[removeKeyPrefix(key)] = row[key];
prevKey = key; prevKey = key;
$prevKeyPrefix = $keyPrefix; $prevKeyPrefix = $keyPrefix;
$prevKeyPrefixString = $keyPrefixString; $prevKeyPrefixString = $keyPrefixString;
} }
if (checkExisting) { if (checkExisting) {
length = $prevKeyPrefix.length; length = $prevKeyPrefix.length;
$parent = null; $parent = null;
parentHash = null; parentHash = null;
if (length) { if (length) {
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i]; prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i];
primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes; primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes;
$length = primaryKeyAttributes.length; $length = primaryKeyAttributes.length;
itemHash = prefix; itemHash = prefix;
if ($length === 1) { if ($length === 1) {
itemHash += row[prefix+'.'+primaryKeyAttributes[0]]; itemHash += row[prefix+'.'+primaryKeyAttributes[0]];
}
else if ($length > 0) {
for ($i = 0; $i < $length; $i++) {
itemHash += row[prefix+'.'+primaryKeyAttributes[$i]];
} }
} else if ($length > 0) {
else if (!Utils._.isEmpty(includeMap[prefix].model.uniqueKeys)) { for ($i = 0; $i < $length; $i++) {
uniqueKeyAttributes = getUniqueKeyAttributes(includeMap[prefix].model); itemHash += row[prefix+'.'+primaryKeyAttributes[$i]];
for ($i = 0; $i < uniqueKeyAttributes.length; $i++) { }
itemHash += row[prefix+'.'+uniqueKeyAttributes[$i]]; }
else if (!Utils._.isEmpty(includeMap[prefix].model.uniqueKeys)) {
uniqueKeyAttributes = getUniqueKeyAttributes(includeMap[prefix].model);
for ($i = 0; $i < uniqueKeyAttributes.length; $i++) {
itemHash += row[prefix+'.'+uniqueKeyAttributes[$i]];
}
}
if (!parentHash) {
parentHash = topHash;
} }
}
if (!parentHash) {
parentHash = topHash;
}
itemHash = parentHash + itemHash; itemHash = parentHash + itemHash;
$parent = prefix; $parent = prefix;
if (i < length - 1) { if (i < length - 1) {
parentHash = itemHash; parentHash = itemHash;
}
} }
}
} else {
itemHash = topHash;
}
if (itemHash === topHash) {
if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else { } else {
topExists = true; itemHash = topHash;
} }
} else {
if (!resultMap[itemHash]) {
$parent = resultMap[parentHash];
$lastKeyPrefix = lastKeyPrefix(prevKey);
if (includeMap[prevKey].association.isSingleAssociation) { if (itemHash === topHash) {
$parent[$lastKeyPrefix] = resultMap[itemHash] = values; if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else { } else {
if (!$parent[$lastKeyPrefix]) { topExists = true;
$parent[$lastKeyPrefix] = [];
}
$parent[$lastKeyPrefix].push(resultMap[itemHash] = values);
} }
} } else {
} if (!resultMap[itemHash]) {
if (!topExists) { $parent = resultMap[parentHash];
results.push(topValues); $lastKeyPrefix = lastKeyPrefix(prevKey);
}
} else {
results[rowsI] = topValues;
}
}
return results;
};
/**
* rewrite query with parameters
*
* Examples:
*
* query.formatBindParameters('select $1 as foo', ['fooval']);
*
* query.formatBindParameters('select $foo as foo', { foo: 'fooval' });
*
* Options
* skipUnescape: bool, skip unescaping $$
* skipValueReplace: bool, do not replace (but do unescape $$). Check correct syntax and if all values are available
*/
AbstractQuery.formatBindParameters = function(sql, values, dialect, replacementFunc, options) {
if (!values) {
return [sql, []];
}
options = options || {};
if (typeof replacementFunc !== 'function') {
options = replacementFunc || {};
replacementFunc = undefined;
}
if (!replacementFunc) { if (includeMap[prevKey].association.isSingleAssociation) {
if (options.skipValueReplace) { $parent[$lastKeyPrefix] = resultMap[itemHash] = values;
replacementFunc = function(match, key, values, timeZone, dialect, options) { } else {
if (values[key] !== undefined) { if (!$parent[$lastKeyPrefix]) {
return match; $parent[$lastKeyPrefix] = [];
} }
return undefined; $parent[$lastKeyPrefix].push(resultMap[itemHash] = values);
}; }
} else { }
replacementFunc = function(match, key, values, timeZone, dialect, options) {
if (values[key] !== undefined) {
return SqlString.escape(values[key], false, timeZone, dialect);
} }
return undefined; if (!topExists) {
}; results.push(topValues);
}
} else {
if (options.skipValueReplace) {
var origReplacementFunc = replacementFunc;
replacementFunc = function(match, key, values, timeZone, dialect, options) {
if (origReplacementFunc(match, key, values, timeZone, dialect, options) !== undefined) {
return match;
} }
return undefined; } else {
}; results[rowsI] = topValues;
}
}
var timeZone = null;
var list = Array.isArray(values);
sql = sql.replace(/\$(\$|\w+)/g, function(match, key) {
if ('$' === key) {
return options.skipUnescape ? match : key;
}
var replVal;
if (list) {
if (key.match(/^[1-9]\d*$/)) {
key = key - 1;
replVal = replacementFunc(match, key, values, timeZone, dialect, options);
}
} else {
if (!key.match(/^\d*$/)) {
replVal = replacementFunc(match, key, values, timeZone, dialect, options);
} }
} }
if (replVal === undefined) {
throw new Error('Named bind parameter "' + match + '" has no value in the given object.');
}
return replVal;
});
return [sql, []];
};
/**
* Execute the passed sql query.
*
* Examples:
*
* query.run('SELECT 1')
*
* @param {String} sql - The SQL query which should be executed.
* @api public
*/
AbstractQuery.prototype.run = function() {
throw new Error('The run method wasn\'t overwritten!');
};
/**
* Check the logging option of the instance and print deprecation warnings.
*
* @return {void}
*/
AbstractQuery.prototype.checkLoggingOption = function() {
if (this.options.logging === true) {
console.log('DEPRECATION WARNING: The logging-option should be either a function or false. Default: console.log');
this.options.logging = console.log;
}
};
/**
* Get the attributes of an insert query, which contains the just inserted id.
*
* @return {String} The field name.
*/
AbstractQuery.prototype.getInsertIdField = function() {
return 'insertId';
};
/**
* Iterate over all known tables and search their names inside the sql query.
* This method will also check association aliases ('as' option).
*
* @param {String} attribute An attribute of a SQL query. (?)
* @return {String} The found tableName / alias.
*/
AbstractQuery.prototype.findTableNameInAttribute = function(attribute) {
if (!this.options.include) {
return null;
}
if (!this.options.includeNames) {
this.options.includeNames = this.options.include.map(function(include) {
return include.as;
});
}
var tableNames = this.options.includeNames.filter(function(include) {
return attribute.indexOf(include + '.') === 0;
});
if (tableNames.length === 1) {
return tableNames[0];
} else {
return null;
}
};
AbstractQuery.prototype.getUniqueConstraintErrorMessage = function(field) {
var message = field + ' must be unique';
var self = this;
if (self.model) {
Object.keys(self.model.uniqueKeys).forEach(function(key) {
if (self.model.uniqueKeys[key].fields.indexOf(field.replace(/"/g, '')) >= 0) {
if (self.model.uniqueKeys[key].msg) {
message = self.model.uniqueKeys[key].msg;
}
}
});
}
return message;
};
AbstractQuery.prototype.isRawQuery = function () {
return this.options.type === QueryTypes.RAW;
};
AbstractQuery.prototype.isVersionQuery = function () {
return this.options.type === QueryTypes.VERSION;
};
AbstractQuery.prototype.isUpsertQuery = function () {
return this.options.type === QueryTypes.UPSERT;
};
AbstractQuery.prototype.isInsertQuery = function(results, metaData) {
var result = true;
if (this.options.type === QueryTypes.INSERT) {
return true;
}
// is insert query if sql contains insert into
result = result && (this.sql.toLowerCase().indexOf('insert into') === 0);
// is insert query if no results are passed or if the result has the inserted id
result = result && (!results || results.hasOwnProperty(this.getInsertIdField()));
// is insert query if no metadata are passed or if the metadata has the inserted id return results;
result = result && (!metaData || metaData.hasOwnProperty(this.getInsertIdField()));
return result;
};
AbstractQuery.prototype.handleInsertQuery = function(results, metaData) {
if (this.instance) {
// add the inserted row id to the instance
var autoIncrementField = this.model.autoIncrementField
, id = null;
id = id || (results && results[this.getInsertIdField()]);
id = id || (metaData && metaData[this.getInsertIdField()]);
this.instance[autoIncrementField] = id;
}
};
AbstractQuery.prototype.isShowTablesQuery = function() {
return this.options.type === QueryTypes.SHOWTABLES;
};
AbstractQuery.prototype.handleShowTablesQuery = function(results) {
return Utils._.flatten(results.map(function(resultSet) {
return Utils._.values(resultSet);
}));
};
AbstractQuery.prototype.isShowIndexesQuery = function () {
return this.options.type === QueryTypes.SHOWINDEXES;
};
AbstractQuery.prototype.isDescribeQuery = function () {
return this.options.type === QueryTypes.DESCRIBE;
};
AbstractQuery.prototype.isSelectQuery = function() {
return this.options.type === QueryTypes.SELECT;
};
AbstractQuery.prototype.isBulkUpdateQuery = function() {
return this.options.type === QueryTypes.BULKUPDATE;
};
AbstractQuery.prototype.isBulkDeleteQuery = function() {
return this.options.type === QueryTypes.BULKDELETE;
};
AbstractQuery.prototype.isForeignKeysQuery = function() {
return this.options.type === QueryTypes.FOREIGNKEYS;
};
AbstractQuery.prototype.isUpdateQuery = function() {
return this.options.type === QueryTypes.UPDATE;
};
AbstractQuery.prototype.handleSelectQuery = function(results) {
var result = null;
// Map raw fields to names if a mapping is provided
if (this.options.fieldMap) {
var fieldMap = this.options.fieldMap;
results = Utils._.map(results, function(result) {
return Utils._.reduce(fieldMap, function(result, name, field) {
if (result[field] !== undefined) {
result[name] = result[field];
delete result[field];
}
return result;
}, result);
});
}
// Raw queries
if (this.options.raw) {
result = results.map(function(result) {
var o = {};
for (var key in result) {
if (result.hasOwnProperty(key)) {
o[key] = result[key];
}
}
if (this.options.nest) {
o = Dot.transform(o);
}
return o;
}, this);
// Queries with include
} else if (this.options.hasJoin === true) {
results = groupJoinData(results, {
model: this.model,
includeMap: this.options.includeMap,
includeNames: this.options.includeNames
}, {
checkExisting: this.options.hasMultiAssociation
});
result = this.model.bulkBuild(results, {
isNewRecord: false,
include: this.options.include,
includeNames: this.options.includeNames,
includeMap: this.options.includeMap,
includeValidated: true,
attributes: this.options.originalAttributes || this.options.attributes,
raw: true
});
// Regular queries
} else {
result = this.model.bulkBuild(results, {
isNewRecord: false,
raw: true,
attributes: this.options.attributes
});
}
// return the first real model instance if options.plain is set (e.g. Model.find)
if (this.options.plain) {
result = (result.length === 0) ? null : result[0];
} }
return result; }
};
AbstractQuery.prototype.isShowOrDescribeQuery = function() {
var result = false;
result = result || (this.sql.toLowerCase().indexOf('show') === 0);
result = result || (this.sql.toLowerCase().indexOf('describe') === 0);
return result;
};
AbstractQuery.prototype.isCallQuery = function() {
var result = false;
result = result || (this.sql.toLowerCase().indexOf('call') === 0);
return result;
};
AbstractQuery.$groupJoinData = groupJoinData;
module.exports = AbstractQuery; module.exports = AbstractQuery;
module.exports.AbstractQuery = AbstractQuery;
module.exports.default = AbstractQuery;
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!