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

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';
var Pooling = require('generic-pool')
, Promise = require('../../promise')
, _ = require('lodash')
, semver = require('semver')
, defaultPoolingConfig = {
max: 5,
min: 0,
idle: 10000,
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');
}
const Pooling = require('generic-pool');
const Promise = require('../../promise');
const _ = require('lodash');
const semver = require('semver');
const defaultPoolingConfig = {
max: 5,
min: 0,
idle: 10000,
handleDisconnects: true
};
// Save a reference to the bound version so we can remove it with removeListener
this.onProcessExit = this.onProcessExit.bind(this);
class ConnectionManager {
process.on('exit', this.onProcessExit);
};
constructor(dialect, sequelize) {
const config = _.cloneDeep(sequelize.config);
ConnectionManager.prototype.refreshTypeParser = function(dataTypes) {
_.each(dataTypes, function (dataType, key) {
if (dataType.hasOwnProperty('parse')) {
if (dataType.types[this.dialectName]) {
this.$refreshTypeParser(dataType);
} else {
throw new Error('Parse function not supported for type ' + dataType.key + ' in dialect ' + this.dialectName);
}
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');
}
}.bind(this));
};
ConnectionManager.prototype.onProcessExit = function() {
var self = this;
// Save a reference to the bound version so we can remove it with removeListener
this.onProcessExit = this.onProcessExit.bind(this);
if (this.pool) {
this.pool.drain(function() {
self.pool.destroyAllNow();
});
process.on('exit', this.onProcessExit);
}
};
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 () {
return Promise.reject(new Error('ConnectionManager.getConnection was called after the connection manager was closed!'));
};
};
// This cannot happen in the constructor because the user can specify a min. number of connections to have in the pool
// If he does this, generic-pool will try to call connect before the dialect-specific connection manager has been correctly set up
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
refreshTypeParser(dataTypes) {
_.each(dataTypes, (dataType, key) => {
if (dataType.hasOwnProperty('parse')) {
if (dataType.types[this.dialectName]) {
this.$refreshTypeParser(dataType);
} else {
throw new Error('Parse function not supported for type ' + dataType.key + ' in dialect ' + this.dialectName);
}
}
});
return;
}
var reads = 0;
if (!Array.isArray(config.replication.read)) {
config.replication.read = [config.replication.read];
onProcessExit() {
if (this.pool) {
this.pool.drain(() => {
this.pool.destroyAllNow();
});
}
}
// Map main connection config
config.replication.write = _.defaults(config.replication.write, _.omit(config, 'replication'));
close() {
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
config.replication.read = _.map(config.replication.read, function(readConfig) {
return _.defaults(readConfig, _.omit(self.config, 'replication'));
});
this.getConnection = function getConnection() {
return Promise.reject(new Error('ConnectionManager.getConnection was called after the connection manager was closed!'));
};
}
// I'll make my own pool, with blackjack and hookers! (original credit goes to @janzeh)
this.pool = {
release: function(client) {
if (client.queryType === 'read') {
return self.pool.read.release(client);
} else {
return self.pool.write.release(client);
}
},
acquire: function(callback, priority, queryType, useMaster) {
useMaster = _.isUndefined(useMaster) ? false : useMaster;
if (queryType === 'SELECT' && !useMaster) {
self.pool.read.acquire(callback, priority);
} else {
self.pool.write.acquire(callback, priority);
}
},
destroy: function(connection) {
return self.pool[connection.queryType].destroy(connection);
},
destroyAllNow: function() {
self.pool.read.destroyAllNow();
self.pool.write.destroyAllNow();
},
drain: function(cb) {
self.pool.write.drain(function() {
self.pool.read.drain(cb);
// This cannot happen in the constructor because the user can specify a min. number of connections to have in the pool
// If he does this, generic-pool will try to call connect before the dialect-specific connection manager has been correctly set up
initPools() {
const config = this.config;
if (!config.replication) {
this.pool = Pooling.Pool({
name: 'sequelize-connection',
create: (callback) => {
this.$connect(config).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;
},
max: config.pool.max,
min: config.pool.min,
validate: config.pool.validate,
idleTimeoutMillis: config.pool.idle
});
},
read: Pooling.Pool({
name: 'sequelize-connection-read',
create: function(callback) {
// Simple round robin config
var nextRead = reads++ % config.replication.read.length;
self.$connect(config.replication.read[nextRead]).tap(function (connection) {
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
});
return;
}
let reads = 0;
if (!Array.isArray(config.replication.read)) {
config.replication.read = [config.replication.read];
}
// 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) {
self.$disconnect(connection);
return null;
acquire: (callback, priority, queryType, useMaster) => {
useMaster = _.isUndefined(useMaster) ? false : useMaster;
if (queryType === 'SELECT' && !useMaster) {
this.pool.read.acquire(callback, priority);
} else {
this.pool.write.acquire(callback, priority);
}
},
validate: config.pool.validate,
max: config.pool.max,
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: (connection) => {
return this.pool[connection.queryType].destroy(connection);
},
destroy: function(connection) {
self.$disconnect(connection);
return null;
destroyAllNow: () => {
this.pool.read.destroyAllNow();
this.pool.write.destroyAllNow();
},
validate: config.pool.validate,
max: config.pool.max,
min: config.pool.min,
idleTimeoutMillis: config.pool.idle
})
};
};
ConnectionManager.prototype.getConnection = function(options) {
var self = this;
options = options || {};
var promise;
if (this.sequelize.options.databaseVersion === 0) {
if (this.versionPromise) {
promise = this.versionPromise;
} else {
promise = this.versionPromise = self.$connect(self.config.replication.write || self.config).then(function (connection) {
var _options = {};
_options.transaction = { connection: connection }; // Cheat .query to use our private connection
_options.logging = function () {};
_options.logging.__testLoggingFn = true;
return self.sequelize.databaseVersion(_options).then(function (version) {
self.sequelize.options.databaseVersion = semver.valid(version) ? version : self.defaultVersion;
drain: (cb) => {
this.pool.write.drain(() => {
this.pool.read.drain(cb);
});
},
read: Pooling.Pool({
name: 'sequelize-connection-read',
create: (callback) => {
// Simple round robin config
const nextRead = reads++ % config.replication.read.length;
this.$connect(config.replication.read[nextRead]).tap(connection => {
connection.queryType = 'read';
}).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
}),
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);
return null;
let promise;
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;
throw err;
});
}
} else {
promise = Promise.resolve();
}
} else {
promise = Promise.resolve();
}
return promise.then(function () {
return new Promise(function (resolve, reject) {
self.pool.acquire(function(err, connection) {
return promise.then(() => new Promise((resolve, reject) => {
this.pool.acquire((err, connection) => {
if (err) return reject(err);
resolve(connection);
}, options.priority, options.type, options.useMaster);
});
});
};
}));
}
ConnectionManager.prototype.releaseConnection = function(connection) {
var self = this;
releaseConnection(connection) {
return new Promise((resolve, reject) => {
this.pool.release(connection);
resolve();
});
}
return new Promise(function (resolve, reject) {
self.pool.release(connection);
resolve();
});
};
$connect(config) {
return this.sequelize.runHooks('beforeConnect', config)
.then(() => this.dialect.connectionManager.connect(config));
}
ConnectionManager.prototype.$connect = function(config) {
return this.sequelize.runHooks('beforeConnect', config).bind(this).then(function () {
return this.dialect.connectionManager.connect(config).then(function (connection) {
return connection;
});
});
};
ConnectionManager.prototype.$disconnect = function(connection) {
return this.dialect.connectionManager.disconnect(connection);
};
$disconnect(connection) {
return this.dialect.connectionManager.disconnect(connection);
}
ConnectionManager.prototype.$validate = function(connection) {
if (!this.dialect.connectionManager.validate) return Promise.resolve();
return this.dialect.connectionManager.validate(connection);
};
$validate(connection) {
if (!this.dialect.connectionManager.validate) return Promise.resolve();
return this.dialect.connectionManager.validate(connection);
}
}
module.exports = ConnectionManager;
module.exports.ConnectionManager = ConnectionManager;
module.exports.default = ConnectionManager;
'use strict';
var AbstractDialect = function() {
};
class AbstractDialect {}
AbstractDialect.prototype.supports = {
'DEFAULT': true,
......@@ -62,3 +60,5 @@ AbstractDialect.prototype.supports = {
};
module.exports = AbstractDialect;
module.exports.AbstractDialect = AbstractDialect;
module.exports.default = AbstractDialect;
'use strict';
var Utils = require('../../utils')
, SqlString = require('../../sql-string')
, Model = require('../../model')
, DataTypes = require('../../data-types')
, _ = require('lodash')
, util = require('util')
, Dottie = require('dottie')
, BelongsTo = require('../../associations/belongs-to')
, uuid = require('node-uuid')
, semver = require('semver');
'use strict';
const Utils = require('../../utils');
const SqlString = require('../../sql-string');
const Model = require('../../model');
const DataTypes = require('../../data-types');
const _ = require('lodash');
const util = require('util');
const Dottie = require('dottie');
const BelongsTo = require('../../associations/belongs-to');
const uuid = require('node-uuid');
const semver = require('semver');
/* 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.');
};
}
var QueryGenerator = {
const QueryGenerator = {
options: {},
extractTableDetails: function(tableName, options) {
extractTableDetails(tableName, options) {
options = options || {};
tableName = tableName || {};
return {
......@@ -29,8 +29,8 @@ var QueryGenerator = {
};
},
addSchema: function(param) {
var self = this;
addSchema(param) {
const self = this;
if (!param.$schema) return param.tableName || param;
......@@ -40,7 +40,7 @@ var QueryGenerator = {
name: param.name || param,
schema: param.$schema,
delimiter: param.$schemaDelimiter || '.',
toString: function() {
toString() {
return self.quoteTable(this);
}
};
......@@ -49,7 +49,7 @@ var QueryGenerator = {
/*
Returns a query for dropping a schema
*/
dropSchema: function(tableName, options) {
dropSchema(tableName, options) {
return this.dropTableQuery(tableName, options);
},
......@@ -65,18 +65,18 @@ var QueryGenerator = {
Defaults: { engine: 'InnoDB', charset: null }
*/
/* istanbul ignore next */
createTableQuery: function(tableName, attributes, options) {
createTableQuery(tableName, attributes, options) {
throwMethodUndefined('createTableQuery');
},
versionQuery: function(tableName, attributes, options) {
versionQuery(tableName, attributes, options) {
throwMethodUndefined('versionQuery');
},
describeTableQuery: function(tableName, schema, schemaDelimiter) {
var table = this.quoteTable(
describeTableQuery(tableName, schema, schemaDelimiter) {
const table = this.quoteTable(
this.addSchema({
tableName: tableName,
tableName,
$schema: schema,
$schemaDelimiter: schemaDelimiter
})
......@@ -88,14 +88,10 @@ var QueryGenerator = {
/*
Returns a query for dropping a table.
*/
dropTableQuery: function(tableName, options) {
dropTableQuery(tableName, options) {
options = options || {};
var query = 'DROP TABLE IF EXISTS <%= table %>;';
return Utils._.template(query)({
table: this.quoteTable(tableName)
});
return `DROP TABLE IF EXISTS ${this.quoteTable(tableName)};`;
},
/*
......@@ -104,19 +100,15 @@ var QueryGenerator = {
- originalTableName: Name of the table before execution.
- futureTableName: Name of the table after execution.
*/
renameTableQuery: function(before, after) {
var query = 'ALTER TABLE <%= before %> RENAME TO <%= after %>;';
return Utils._.template(query)({
before: this.quoteTable(before),
after: this.quoteTable(after)
});
renameTableQuery(before, after) {
return `ALTER TABLE ${this.quoteTable(before)} RENAME TO ${this.quoteTable(after)};`;
},
/*
Returns a query, which gets all available table names in the database.
*/
/* istanbul ignore next */
showTablesQuery: function() {
showTablesQuery() {
throwMethodUndefined('showTablesQuery');
},
......@@ -132,7 +124,7 @@ var QueryGenerator = {
- allowNull: Boolean
*/
/* istanbul ignore next */
addColumnQuery: function(tableName, attributes) {
addColumnQuery(tableName, attributes) {
throwMethodUndefined('addColumnQuery');
},
......@@ -143,7 +135,7 @@ var QueryGenerator = {
- attributeName: Name of the obsolete attribute.
*/
/* istanbul ignore next */
removeColumnQuery: function(tableName, attributeName) {
removeColumnQuery(tableName, attributeName) {
throwMethodUndefined('removeColumnQuery');
},
......@@ -159,7 +151,7 @@ var QueryGenerator = {
- allowNull: Boolean
*/
/* istanbul ignore next */
changeColumnQuery: function(tableName, attributes) {
changeColumnQuery(tableName, attributes) {
throwMethodUndefined('changeColumnQuery');
},
......@@ -171,37 +163,28 @@ var QueryGenerator = {
- attrNameAfter: The name of the attribute, after renaming.
*/
/* istanbul ignore next */
renameColumnQuery: function(tableName, attrNameBefore, attrNameAfter) {
renameColumnQuery(tableName, attrNameBefore, attrNameAfter) {
throwMethodUndefined('renameColumnQuery');
},
/*
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 || {};
var query
, valueQuery = '<%= tmpTable %>INSERT<%= ignore %> INTO <%= table %> (<%= attributes %>)<%= output %> VALUES (<%= values %>)'
, emptyQuery = '<%= tmpTable %>INSERT<%= ignore %> INTO <%= table %><%= output %>'
, outputFragment
, fields = []
, values = []
, key
, 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
const modelAttributeMap = {};
const fields = [];
const values = [];
let query;
let valueQuery = '<%= tmpTable %>INSERT<%= ignore %> INTO <%= table %> (<%= attributes %>)<%= output %> VALUES (<%= values %>)';
let emptyQuery = '<%= tmpTable %>INSERT<%= ignore %> INTO <%= table %><%= output %>';
let outputFragment;
let identityWrapperRequired = false;
let tmpTable = ''; //tmpTable declaration for trigger
if (modelAttributes) {
Utils._.each(modelAttributes, function(attribute, key) {
Utils._.each(modelAttributes, (attribute, key) => {
modelAttributeMap[key] = attribute;
if (attribute.field) {
modelAttributeMap[attribute.field] = attribute;
......@@ -224,31 +207,34 @@ var QueryGenerator = {
//To capture output rows when there is a trigger on MSSQL DB
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();
outputColumns += 'INSERTED.' + this.quoteIdentifier(attribute.field);
let tmpColumns = '';
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 ={
columns : tmpColumns
};
const replacement = {
columns: tmpColumns
};
tmpTable = Utils._.template(tmpTable)(replacement).trim();
outputFragment = ' OUTPUT ' + outputColumns + ' into @tmp';
selectFromTmp = ';select * from @tmp';
tmpTable = Utils._.template(tmpTable)(replacement).trim();
outputFragment = ' OUTPUT ' + outputColumns + ' into @tmp';
const selectFromTmp = ';select * from @tmp';
valueQuery += selectFromTmp;
emptyQuery += selectFromTmp;
valueQuery += selectFromTmp;
emptyQuery += selectFromTmp;
}
}
}
......@@ -258,7 +244,7 @@ var QueryGenerator = {
// 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')) {
// >= 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;';
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 = {
}
valueHash = Utils.removeNullValuesFromHash(valueHash, this.options.omitNull);
for (key in valueHash) {
for (const key in valueHash) {
if (valueHash.hasOwnProperty(key)) {
value = valueHash[key];
const value = valueHash[key];
fields.push(this.quoteIdentifier(key));
// SERIALS' can't be NULL in postgresql, use DEFAULT where supported
......@@ -300,13 +286,13 @@ var QueryGenerator = {
}
}
var replacements = {
const replacements = {
ignore: options.ignore ? this._dialect.supports.IGNORE : '',
table: this.quoteTable(table),
attributes: fields.join(','),
output: outputFragment,
values: values.join(','),
tmpTable: tmpTable
tmpTable
};
query = (replacements.attributes.length ? valueQuery : emptyQuery) + ';';
......@@ -325,18 +311,18 @@ var QueryGenerator = {
Returns an insert into command for multiple values.
Parameters: table name + list of hashes of attribute-value-pairs.
*/
bulkInsertQuery: function(tableName, attrValueHashes, options, rawAttributes) {
bulkInsertQuery(tableName, attrValueHashes, options, rawAttributes) {
options = options || {};
rawAttributes = rawAttributes || {};
var query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %><%= onDuplicateKeyUpdate %><%= returning %>;'
, tuples = []
, serials = []
, allAttributes = []
, onDuplicateKeyUpdate = '';
const query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %><%= onDuplicateKeyUpdate %><%= returning %>;';
const tuples = [];
const serials = [];
const allAttributes = [];
let onDuplicateKeyUpdate = '';
attrValueHashes.forEach(function(attrValueHash) {
_.forOwn(attrValueHash, function(value, key) {
for (const attrValueHash of attrValueHashes) {
_.forOwn(attrValueHash, (value, key) => {
if (allAttributes.indexOf(key) === -1) {
allAttributes.push(key);
}
......@@ -345,35 +331,31 @@ var QueryGenerator = {
serials.push(key);
}
});
});
}
attrValueHashes.forEach(function(attrValueHash) {
tuples.push('(' +
allAttributes.map(function(key) {
if (this._dialect.supports.bulkDefault && serials.indexOf(key) !== -1) {
return attrValueHash[key] || 'DEFAULT';
}
return this.escape(attrValueHash[key], rawAttributes[key], { context: 'INSERT' });
}, this).join(',') +
')');
}, this);
for (const attrValueHash of attrValueHashes) {
tuples.push('(' + allAttributes.map(key => {
if (this._dialect.supports.bulkDefault && serials.indexOf(key) !== -1) {
return attrValueHash[key] || 'DEFAULT';
}
return this.escape(attrValueHash[key], rawAttributes[key], { context: 'INSERT' });
}).join(',') + ')');
}
if (this._dialect.supports.updateOnDuplicate && options.updateOnDuplicate) {
onDuplicateKeyUpdate += ' ON DUPLICATE KEY UPDATE ' + options.updateOnDuplicate.map(function(attr) {
var field = rawAttributes && rawAttributes[attr] && rawAttributes[attr].field || attr;
var key = this.quoteIdentifier(field);
onDuplicateKeyUpdate += ' ON DUPLICATE KEY UPDATE ' + options.updateOnDuplicate.map(attr => {
const field = rawAttributes && rawAttributes[attr] && rawAttributes[attr].field || attr;
const key = this.quoteIdentifier(field);
return key + '=VALUES(' + key + ')';
}, this).join(',');
}).join(',');
}
var replacements = {
const replacements = {
ignoreDuplicates: options.ignoreDuplicates ? this._dialect.supports.ignoreDuplicates : '',
table: this.quoteTable(tableName),
attributes: allAttributes.map(function(attr) {
return this.quoteIdentifier(attr);
}, this).join(','),
attributes: allAttributes.map(attr => this.quoteIdentifier(attr)).join(','),
tuples: tuples.join(','),
onDuplicateKeyUpdate: onDuplicateKeyUpdate,
onDuplicateKeyUpdate,
returning: this._dialect.supports.returnValues && options.returning ? ' RETURNING *' : ''
};
......@@ -390,24 +372,18 @@ var QueryGenerator = {
OR a string with conditions (e.g. 'name="foo"').
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 || {};
_.defaults(options, this.options);
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options);
var query
, values = []
, outputFragment
, 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
query = '<%= tmpTable %>UPDATE <%= table %> SET <%= values %><%= output %> <%= where %>';
const values = [];
const modelAttributeMap = {};
let query = '<%= tmpTable %>UPDATE <%= table %> SET <%= values %><%= output %> <%= where %>';
let outputFragment;
let tmpTable = ''; // tmpTable declaration for trigger
let selectFromTmp = ''; // Select statement for trigger
if (this._dialect.supports['LIMIT ON UPDATE'] && options.limit) {
query += ' LIMIT ' + this.escape(options.limit) + ' ';
......@@ -420,30 +396,32 @@ var QueryGenerator = {
//To capture output rows when there is a trigger on MSSQL DB
if (attributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) {
tmpTable = 'declare @tmp table (<%= columns %>); ';
for (modelKey in attributes){
attribute = attributes[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);
tmpTable = 'declare @tmp table (<%= columns %>); ';
let tmpColumns = '';
let outputColumns = '';
for (const modelKey in attributes){
const attribute = attributes[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 ={
columns : tmpColumns
};
const replacement ={
columns : tmpColumns
};
tmpTable = Utils._.template(tmpTable)(replacement).trim();
outputFragment = ' OUTPUT ' + outputColumns + ' into @tmp';
selectFromTmp = ';select * from @tmp';
tmpTable = Utils._.template(tmpTable)(replacement).trim();
outputFragment = ' OUTPUT ' + outputColumns + ' into @tmp';
selectFromTmp = ';select * from @tmp';
query += selectFromTmp;
query += selectFromTmp;
}
} else if (this._dialect.supports.returnValues && options.returning) {
// ensure that the return output is properly mapped to model fields.
......@@ -453,7 +431,7 @@ var QueryGenerator = {
}
if (attributes) {
Utils._.each(attributes, function(attribute, key) {
Utils._.each(attributes, (attribute, key) => {
modelAttributeMap[key] = attribute;
if (attribute.field) {
modelAttributeMap[attribute.field] = attribute;
......@@ -461,7 +439,7 @@ var QueryGenerator = {
});
}
for (var key in attrValueHash) {
for (const key in attrValueHash) {
if (modelAttributeMap && modelAttributeMap[key] &&
modelAttributeMap[key].autoIncrement === true &&
!this._dialect.supports.autoIncrement.update) {
......@@ -469,16 +447,16 @@ var QueryGenerator = {
continue;
}
var value = attrValueHash[key];
const value = attrValueHash[key];
values.push(this.quoteIdentifier(key) + '=' + this.escape(value, (modelAttributeMap && modelAttributeMap[key] || undefined), { context: 'UPDATE' }));
}
var replacements = {
const replacements = {
table: this.quoteTable(tableName),
values: values.join(','),
output: outputFragment,
where: this.whereQuery(where),
tmpTable: tmpTable
tmpTable
};
if (values.length === 0) {
......@@ -491,7 +469,7 @@ var QueryGenerator = {
/*
Returns an upsert query.
*/
upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) {
upsertQuery(tableName, insertValues, updateValues, where, rawAttributes, options) {
throwMethodUndefined('upsertQuery');
},
......@@ -512,7 +490,7 @@ var QueryGenerator = {
Note that truncate must ignore limit and where
*/
/* istanbul ignore next */
deleteQuery: function(tableName, where, options) {
deleteQuery(tableName, where, options) {
throwMethodUndefined('deleteQuery');
},
......@@ -526,16 +504,13 @@ var QueryGenerator = {
OR a string with conditions (e.g. 'name="foo"').
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);
var query
, key
, value
, values = []
, outputFragment;
const values = [];
let query = 'UPDATE <%= table %> SET <%= values %><%= output %> <%= where %>';
let outputFragment;
query = 'UPDATE <%= table %> SET <%= values %><%= output %> <%= where %>';
if (this._dialect.supports.returnValues) {
if (!!this._dialect.supports.returnValues.returning) {
query += ' RETURNING *';
......@@ -544,18 +519,18 @@ var QueryGenerator = {
}
}
for (key in attrValueHash) {
value = attrValueHash[key];
for (const key in attrValueHash) {
const value = attrValueHash[key];
values.push(this.quoteIdentifier(key) + '=' + this.quoteIdentifier(key) + ' + ' + this.escape(value));
}
options = options || {};
for (key in options) {
value = options[key];
for (const key in options) {
const value = options[key];
values.push(this.quoteIdentifier(key) + '=' + this.escape(value));
}
var replacements = {
const replacements = {
table: this.quoteTable(tableName),
values: values.join(','),
output: outputFragment,
......@@ -565,13 +540,10 @@ var QueryGenerator = {
return Utils._.template(query)(replacements);
},
nameIndexes: function (indexes, rawTablename) {
return Utils._.map(indexes, function (index) {
nameIndexes(indexes, rawTablename) {
return Utils._.map(indexes, index => {
if (!index.hasOwnProperty('name')) {
var onlyAttributeNames = index.fields.map(function(field) {
return (typeof field === 'string') ? field : (field.name || field.attribute);
}.bind(this));
const onlyAttributeNames = index.fields.map(field => (typeof field === 'string') ? field : (field.name || field.attribute));
index.name = Utils.inflection.underscore(rawTablename + '_' + onlyAttributeNames.join('_'));
}
......@@ -594,8 +566,7 @@ var QueryGenerator = {
- parser
- rawTablename, the name of the table, without schema. Used to create the name of the index
*/
addIndexQuery: function(tableName, attributes, options, rawTablename) {
var fieldsSql;
addIndexQuery(tableName, attributes, options, rawTablename) {
options = options || {};
if (!Array.isArray(attributes)) {
......@@ -622,13 +593,13 @@ var QueryGenerator = {
options.prefix = options.prefix.replace(/(\"|\')/g, '');
}
fieldsSql = options.fields.map(function(field) {
const fieldsSql = options.fields.map(field => {
if (typeof field === 'string') {
return this.quoteIdentifier(field);
} else if (field._isSequelizeMethod) {
return this.handleSequelizeMethod(field);
} else {
var result = '';
let result = '';
if (field.attribute) {
field.name = field.attribute;
......@@ -654,7 +625,7 @@ var QueryGenerator = {
return result;
}
}.bind(this));
});
if (!options.name) {
// 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 = {
tableName = this.quoteTable(tableName);
}
var concurrently = this._dialect.supports.index.concurrently && options.concurrently ? 'CONCURRENTLY' : undefined
, ind;
const concurrently = this._dialect.supports.index.concurrently && options.concurrently ? 'CONCURRENTLY' : undefined;
let ind;
if (this._dialect.supports.indexViaAlter) {
ind = [
'ALTER TABLE',
......@@ -715,7 +686,7 @@ var QueryGenerator = {
- database: Name of the database.
*/
/* istanbul ignore next */
showIndexesQuery: function(tableName, options) {
showIndexesQuery(tableName, options) {
throwMethodUndefined('showIndexesQuery');
},
......@@ -726,7 +697,7 @@ var QueryGenerator = {
- indexNameOrAttributes: The name of the index as string or an array of attribute names.
*/
/* istanbul ignore next */
removeIndexQuery: function(tableName, indexNameOrAttributes) {
removeIndexQuery(tableName, indexNameOrAttributes) {
throwMethodUndefined('removeIndexQuery');
},
......@@ -735,7 +706,7 @@ var QueryGenerator = {
sql attribute definition.
*/
/* istanbul ignore next */
attributesToSQL: function(attributes) {
attributesToSQL(attributes) {
throwMethodUndefined('attributesToSQL');
},
......@@ -743,13 +714,13 @@ var QueryGenerator = {
Returns all auto increment fields of a factory.
*/
/* istanbul ignore next */
findAutoIncrementField: function(factory) {
findAutoIncrementField(factory) {
throwMethodUndefined('findAutoIncrementField');
},
quoteTable: function(param, as) {
var table = '';
quoteTable(param, as) {
let table = '';
if (as === true) {
as = param.as || param.name || param;
......@@ -803,21 +774,22 @@ var QueryGenerator = {
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)
*/
quote: function(obj, parent, force) {
quote(obj, parent, force) {
if (Utils._.isString(obj)) {
return this.quoteIdentifiers(obj, force);
} else if (Array.isArray(obj)) {
// loop through array, adding table names of models to quoted
// (checking associations to see if names should be singularised or not)
var tableNames = []
, parentAssociation
, len = obj.length
, item
, model
, as
, association;
for (var i = 0; i < len - 1; i++) {
const len = obj.length;
const tableNames = [];
let parentAssociation;
let item;
let model;
let as;
let association;
let i = 0;
for (i = 0; i < len - 1; i++) {
item = obj[i];
if (item._modelAttribute || Utils._.isString(item) || item._isSequelizeMethod || 'raw' in item) {
break;
......@@ -850,7 +822,7 @@ var QueryGenerator = {
}
// 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 (obj[i + 1]._isSequelizeMethod) {
sql += this.handleSequelizeMethod(obj[i + 1]);
......@@ -874,7 +846,7 @@ var QueryGenerator = {
Create a trigger
*/
/* istanbul ignore next */
createTrigger: function(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray) {
createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray) {
throwMethodUndefined('createTrigger');
},
......@@ -882,7 +854,7 @@ var QueryGenerator = {
Drop a trigger
*/
/* istanbul ignore next */
dropTrigger: function(tableName, triggerName) {
dropTrigger(tableName, triggerName) {
throwMethodUndefined('dropTrigger');
},
......@@ -890,7 +862,7 @@ var QueryGenerator = {
Rename a trigger
*/
/* istanbul ignore next */
renameTrigger: function(tableName, oldTriggerName, newTriggerName) {
renameTrigger(tableName, oldTriggerName, newTriggerName) {
throwMethodUndefined('renameTrigger');
},
......@@ -898,7 +870,7 @@ var QueryGenerator = {
Create a function
*/
/* istanbul ignore next */
createFunction: function(functionName, params, returnType, language, body, options) {
createFunction(functionName, params, returnType, language, body, options) {
throwMethodUndefined('createFunction');
},
......@@ -906,7 +878,7 @@ var QueryGenerator = {
Drop a function
*/
/* istanbul ignore next */
dropFunction: function(functionName, params) {
dropFunction(functionName, params) {
throwMethodUndefined('dropFunction');
},
......@@ -914,7 +886,7 @@ var QueryGenerator = {
Rename a function
*/
/* istanbul ignore next */
renameFunction: function(oldFunctionName, params, newFunctionName) {
renameFunction(oldFunctionName, params, newFunctionName) {
throwMethodUndefined('renameFunction');
},
......@@ -922,14 +894,14 @@ var QueryGenerator = {
Escape an identifier (e.g. a table or attribute name)
*/
/* istanbul ignore next */
quoteIdentifier: function(identifier, force) {
quoteIdentifier(identifier, force) {
throwMethodUndefined('quoteIdentifier');
},
/*
Split an identifier into .-separated tokens and quote each part
*/
quoteIdentifiers: function(identifiers, force) {
quoteIdentifiers(identifiers, force) {
if (identifiers.indexOf('.') !== -1) {
identifiers = identifiers.split('.');
return this.quoteIdentifier(identifiers.slice(0, identifiers.length - 1).join('.')) + '.' + this.quoteIdentifier(identifiers[identifiers.length - 1]);
......@@ -941,7 +913,7 @@ var QueryGenerator = {
/*
Escape a value (e.g. a string, number or date)
*/
escape: function(value, field, options) {
escape(value, field, options) {
options = options || {};
if (value !== null && value !== undefined) {
......@@ -951,9 +923,9 @@ var QueryGenerator = {
if (field && field.type) {
if (this.typeValidation && field.type.validate && value) {
if (options.isList && Array.isArray(value)) {
_.forEach(value, function(item) {
for (const item of value) {
field.type.validate(item, options);
});
}
} else {
field.type.validate(value, options);
}
......@@ -961,9 +933,9 @@ var QueryGenerator = {
if (field.type.stringify) {
// 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) {
// The data-type already did the required escaping
......@@ -985,7 +957,7 @@ var QueryGenerator = {
* @return {String} The generated sql query.
*/
/* istanbul ignore next */
getForeignKeysQuery: function(tableName, schemaName) {
getForeignKeysQuery(tableName, schemaName) {
throwMethodUndefined('getForeignKeysQuery');
},
......@@ -997,7 +969,7 @@ var QueryGenerator = {
* @return {String} The generated sql query.
*/
/* istanbul ignore next */
dropForeignKeyQuery: function(tableName, foreignKey) {
dropForeignKeyQuery(tableName, foreignKey) {
throwMethodUndefined('dropForeignKeyQuery');
},
......@@ -1016,27 +988,24 @@ var QueryGenerator = {
- 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
options = options || {};
var table = null
, self = this
, query
, limit = options.limit
, mainModel = model
, mainQueryItems = []
, mainAttributes = options.attributes && options.attributes.slice()
, mainJoinQueries = []
// We'll use a subquery if we have a hasMany association and a limit
, subQuery = options.subQuery === undefined ?
limit && options.hasMultiAssociation :
options.subQuery
, subQueryItems = []
, subQueryAttributes = null
, subJoinQueries = []
, mainTableAs = null;
const limit = options.limit;
const mainModel = model;
const mainQueryItems = [];
const subQuery = options.subQuery === undefined ? limit && options.hasMultiAssociation : options.subQuery;
const subQueryItems = [];
let table = null;
let query;
let mainAttributes = options.attributes && options.attributes.slice();
let mainJoinQueries = [];
// We'll use a subquery if we have a hasMany association and a limit
let subQueryAttributes = null;
let subJoinQueries = [];
let mainTableAs = null;
if (options.tableAs) {
mainTableAs = this.quoteTable(options.tableAs);
......@@ -1044,44 +1013,42 @@ var QueryGenerator = {
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)) {
return this.quoteTable(t[0], t[1]);
}
return this.quoteTable(t, true);
}.bind(this)).join(', ');
}).join(', ');
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
if (!_.find(mainAttributes, function (attr) {
return keyAtt === attr || keyAtt === attr[0] || keyAtt === attr[1];
})) {
if (!_.find(mainAttributes, attr => keyAtt === attr || keyAtt === attr[0] || keyAtt === attr[1])) {
mainAttributes.push(model.rawAttributes[keyAtt].field ? [keyAtt, model.rawAttributes[keyAtt].field] : keyAtt);
}
});
}
}
// Escape attributes
mainAttributes = mainAttributes && mainAttributes.map(function(attr) {
var addTable = true;
mainAttributes = mainAttributes && mainAttributes.map(attr => {
let addTable = true;
if (attr._isSequelizeMethod) {
return self.handleSequelizeMethod(attr);
return this.handleSequelizeMethod(attr);
}
if (Array.isArray(attr) && attr.length === 2) {
attr = attr.slice();
if (attr[0]._isSequelizeMethod) {
attr[0] = self.handleSequelizeMethod(attr[0]);
attr[0] = this.handleSequelizeMethod(attr[0]);
addTable = false;
} 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 {
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) {
......@@ -1101,21 +1068,21 @@ var QueryGenerator = {
}
if (options.include) {
var generateJoinQueries = function(include, parentTable) {
var table = include.model.getTableName()
, as = include.as
, joinQueryItem = ''
, joinQueries = {
mainQuery: [],
subQuery: []
}
, attributes
, association = include.association
, through = include.through
, joinType = include.required ? ' INNER JOIN ' : ' LEFT OUTER JOIN '
, parentIsTop = !include.parent.association && include.parent.model.name === options.model.name
, whereOptions = Utils._.clone(options)
, targetWhere;
const generateJoinQueries = (include, parentTable) => {
const association = include.association;
const through = include.through;
const joinType = include.required ? ' INNER JOIN ' : ' LEFT OUTER JOIN ';
const parentIsTop = !include.parent.association && include.parent.model.name === options.model.name;
const whereOptions = Utils._.clone(options);
const table = include.model.getTableName();
const joinQueries = {
mainQuery: [],
subQuery: []
};
let as = include.as;
let joinQueryItem = '';
let attributes;
let targetWhere;
whereOptions.keysEscaped = true;
......@@ -1125,9 +1092,9 @@ var QueryGenerator = {
// includeIgnoreAttributes is used by aggregate functions
if (options.includeIgnoreAttributes !== false) {
attributes = include.attributes.map(function(attr) {
var attrAs = attr,
verbatim = false;
attributes = include.attributes.map(attr => {
let attrAs = attr;
let verbatim = false;
if (Array.isArray(attr) && attr.length === 2) {
if (attr[0]._isSequelizeMethod) {
......@@ -1139,30 +1106,26 @@ var QueryGenerator = {
}
}
attr = attr.map(function($attr) {
return $attr._isSequelizeMethod ? self.handleSequelizeMethod($attr) : $attr;
});
attr = attr.map($attr => $attr._isSequelizeMethod ? this.handleSequelizeMethod($attr) : $attr);
attrAs = attr[1];
attr = attr[0];
} else if (attr instanceof Utils.literal) {
return attr.val; // We trust the user to rename the field correctly
} else if (attr instanceof Utils.cast ||
attr instanceof Utils.fn
) {
} else if (attr instanceof Utils.cast || attr instanceof Utils.fn) {
throw new Error(
'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'
);
}
var prefix;
let prefix;
if (verbatim === true) {
prefix = attr;
} 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) {
subQueryAttributes = subQueryAttributes.concat(attributes);
......@@ -1172,26 +1135,25 @@ var QueryGenerator = {
}
if (through) {
var throughTable = through.model.getTableName()
, throughAs = as + '.' + through.as
, throughAttributes = through.attributes.map(function(attr) {
return self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr) +
' AS ' +
self.quoteIdentifier(throughAs + '.' + (Array.isArray(attr) ? attr[1] : attr));
})
, primaryKeysSource = association.source.primaryKeyAttributes
, tableSource = parentTable
, identSource = association.identifierField
, attrSource = primaryKeysSource[0]
, primaryKeysTarget = association.target.primaryKeyAttributes
, tableTarget = as
, identTarget = association.foreignIdentifierField
, attrTarget = association.target.rawAttributes[primaryKeysTarget[0]].field || primaryKeysTarget[0]
, sourceJoinOn
, targetJoinOn
, throughWhere;
const throughTable = through.model.getTableName();
const throughAs = as + '.' + through.as;
const throughAttributes = through.attributes.map(attr =>
this.quoteIdentifier(throughAs) + '.' + this.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr)
+ ' AS '
+ this.quoteIdentifier(throughAs + '.' + (Array.isArray(attr) ? attr[1] : attr))
);
const primaryKeysSource = association.source.primaryKeyAttributes;
const tableSource = parentTable;
const identSource = association.identifierField;
const primaryKeysTarget = association.target.primaryKeyAttributes;
const tableTarget = as;
const identTarget = association.foreignIdentifierField;
const attrTarget = association.target.rawAttributes[primaryKeysTarget[0]].field || primaryKeysTarget[0];
let attrSource = primaryKeysSource[0];
let sourceJoinOn;
let targetJoinOn;
let throughWhere;
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)
......@@ -1212,26 +1174,26 @@ var QueryGenerator = {
// If parent include was in a subquery need to join on the aliased attribute
if (subQuery && !include.subQuery && include.parent.subQuery && !parentIsTop) {
sourceJoinOn = self.quoteIdentifier(tableSource + '.' + attrSource) + ' = ';
sourceJoinOn = this.quoteIdentifier(tableSource + '.' + attrSource) + ' = ';
} 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
// Used by both join and subquery where
targetJoinOn = self.quoteIdentifier(tableTarget) + '.' + self.quoteIdentifier(attrTarget) + ' = ';
targetJoinOn += self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(identTarget);
targetJoinOn = this.quoteIdentifier(tableTarget) + '.' + this.quoteIdentifier(attrTarget) + ' = ';
targetJoinOn += this.quoteIdentifier(throughAs) + '.' + this.quoteIdentifier(identTarget);
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
joinQueryItem += joinType + '(';
joinQueryItem += self.quoteTable(throughTable, throughAs);
joinQueryItem += ' INNER JOIN ' + self.quoteTable(table, as) + ' ON ';
joinQueryItem += this.quoteTable(throughTable, throughAs);
joinQueryItem += ' INNER JOIN ' + this.quoteTable(table, as) + ' ON ';
joinQueryItem += targetJoinOn;
if (throughWhere) {
......@@ -1241,11 +1203,11 @@ var QueryGenerator = {
joinQueryItem += ') ON '+sourceJoinOn;
} else {
// Generate join SQL for left side of through
joinQueryItem += joinType + self.quoteTable(throughTable, throughAs) + ' ON ';
joinQueryItem += joinType + this.quoteTable(throughTable, throughAs) + ' ON ';
joinQueryItem += sourceJoinOn;
// Generate join SQL for right side of through
joinQueryItem += joinType + self.quoteTable(table, as) + ' ON ';
joinQueryItem += joinType + this.quoteTable(table, as) + ' ON ';
joinQueryItem += targetJoinOn;
if (throughWhere) {
......@@ -1256,90 +1218,84 @@ var QueryGenerator = {
if (include.where || include.through.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) {
joinQueryItem += ' AND ' + targetWhere;
}
}
if (subQuery && include.required) {
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];
topParent = topInclude.parent;
if (topInclude.through && Object(topInclude.through.model) === topInclude.through.model) {
$query = self.selectQuery(topInclude.through.model.getTableName(), {
attributes: [topInclude.through.model.primaryKeyField],
include: Model.$validateIncludedElements({
model: topInclude.through.model,
include: [{
association: topInclude.association.toTarget,
required: true
}]
}).include,
let parent = include;
let child = include;
let nestedIncludes = [];
let $query;
while (parent = parent.parent) {
nestedIncludes = [_.extend({}, child, {include: nestedIncludes})];
child = parent;
}
const topInclude = nestedIncludes[0];
const topParent = topInclude.parent;
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,
where: { $and: [
self.sequelize.asIs([
self.quoteTable(topParent.model.name) + '.' + self.quoteIdentifier(topParent.model.primaryKeyField),
self.quoteIdentifier(topInclude.through.model.name) + '.' + self.quoteIdentifier(topInclude.association.identifierField)
].join(' = ')),
topInclude.through.where
]},
limit: 1,
includeIgnoreAttributes: false
}, topInclude.through.model);
} else {
$query = self.selectQuery(topInclude.model.tableName, {
attributes: [topInclude.model.primaryKeyAttributes[0]],
include: topInclude.include,
where: {
$join: self.sequelize.asIs([
self.quoteTable(topParent.model.name) + '.' + self.quoteIdentifier(topParent.model.primaryKeyAttributes[0]),
self.quoteIdentifier(topInclude.model.name) + '.' + self.quoteIdentifier(topInclude.association.identifierField)
].join(' = '))
},
limit: 1,
includeIgnoreAttributes: false
}, topInclude.model);
}
include: [{
association: topInclude.association.toTarget,
required: true
}]
}).include,
model: topInclude.through.model,
where: { $and: [
this.sequelize.asIs([
this.quoteTable(topParent.model.name) + '.' + this.quoteIdentifier(topParent.model.primaryKeyField),
this.quoteIdentifier(topInclude.through.model.name) + '.' + this.quoteIdentifier(topInclude.association.identifierField)
].join(' = ')),
topInclude.through.where
]},
limit: 1,
includeIgnoreAttributes: false
}, topInclude.through.model);
} else {
$query = this.selectQuery(topInclude.model.tableName, {
attributes: [topInclude.model.primaryKeyAttributes[0]],
include: topInclude.include,
where: {
$join: this.sequelize.asIs([
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([
'(',
$query.replace(/\;$/, ''),
')',
'IS NOT NULL'
].join(' '));
})(include);
options.where['__' + throughAs] = this.sequelize.asIs([
'(',
$query.replace(/\;$/, ''),
')',
'IS NOT NULL'
].join(' '));
}
}
} else {
if (subQuery && include.subQueryFilter) {
var associationWhere = {}
, $query
, subQueryWhere;
const associationWhere = {};
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 = {};
// 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],
where: {
$and: [
......@@ -1350,9 +1306,9 @@ var QueryGenerator = {
limit: 1
}, include.model);
subQueryWhere = self.sequelize.asIs([
const subQueryWhere = this.sequelize.asIs([
'(',
$query.replace(/\;$/, ''),
$query.replace(/\;$/, ''),
')',
'IS NOT NULL'
].join(' '));
......@@ -1364,10 +1320,10 @@ var QueryGenerator = {
}
}
joinQueryItem = ' ' + self.joinIncludeQuery({
joinQueryItem = ' ' + this.joinIncludeQuery({
model: mainModel,
subQuery: options.subQuery,
include: include,
include,
groupedLimit: options.groupedLimit
});
}
......@@ -1379,11 +1335,13 @@ var QueryGenerator = {
}
if (include.include) {
include.include.filter(function (include) {
return !include.separate;
}).forEach(function(childInclude) {
if (childInclude._pseudo) return;
var childJoinQueries = generateJoinQueries(childInclude, as);
for (const childInclude of include.include) {
if (childInclude.separate || childInclude._pseudo) {
continue;
}
const childJoinQueries = generateJoinQueries(childInclude, as);
if (childInclude.subQuery && subQuery) {
joinQueries.subQuery = joinQueries.subQuery.concat(childJoinQueries.subQuery);
......@@ -1392,22 +1350,24 @@ var QueryGenerator = {
joinQueries.mainQuery = joinQueries.mainQuery.concat(childJoinQueries.mainQuery);
}
}.bind(this));
}
}
return joinQueries;
};
// Loop through includes and generate subqueries
options.include.filter(function (include) {
return !include.separate;
}).forEach(function(include) {
var joinQueries = generateJoinQueries(include, mainTableAs);
for (const include of options.include) {
if (include.separate) {
continue;
}
const joinQueries = generateJoinQueries(include, mainTableAs);
subJoinQueries = subJoinQueries.concat(joinQueries.subQuery);
mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery);
}.bind(this));
}
}
// If using subQuery select defined subQuery attributes and join subJoinQueries
......@@ -1422,22 +1382,22 @@ var QueryGenerator = {
mainTableAs = table;
}
mainQueryItems.push(this.selectFromTableFragment(options, model, mainAttributes, '('+
options.groupedLimit.values.map(function (value) {
var where = _.assign({}, options.where);
options.groupedLimit.values.map(value => {
const where = _.assign({}, options.where);
where[options.groupedLimit.on] = value;
return '('+self.selectQuery(
return '('+this.selectQuery(
table,
{
attributes: options.attributes,
limit: options.groupedLimit.limit,
order: options.order,
where: where
where
},
model
).replace(/;$/, '')+')';
}).join(
self._dialect.supports['UNION ALL'] ?' UNION ALL ' : ' UNION '
this._dialect.supports['UNION ALL'] ?' UNION ALL ' : ' UNION '
)
+')', mainTableAs));
} else {
......@@ -1455,18 +1415,18 @@ var QueryGenerator = {
} else {
mainQueryItems.push(' WHERE ' + options.where);
// Walk the main query to update all selects
_.each(mainQueryItems, function(value, key) {
_.each(mainQueryItems, (value, key) => {
if(value.match(/^SELECT/)) {
mainQueryItems[key] = this.selectFromTableFragment(options, model, mainAttributes, table, mainTableAs, options.where);
}
}.bind(this));
});
}
}
}
// Add GROUP BY to sub or main query
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) {
subQueryItems.push(' GROUP BY ' + options.group);
} else {
......@@ -1485,7 +1445,7 @@ var QueryGenerator = {
}
// Add ORDER to sub or main query
if (options.order && !options.groupedLimit) {
var orders = this.getQueryOrders(options, model, subQuery);
const orders = this.getQueryOrders(options, model, subQuery);
if (orders.mainQueryOrder.length) {
mainQueryItems.push(' ORDER BY ' + orders.mainQueryOrder.join(', '));
......@@ -1496,7 +1456,7 @@ var QueryGenerator = {
}
// 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 (subQuery) {
subQueryItems.push(limitOrder);
......@@ -1517,7 +1477,7 @@ var QueryGenerator = {
}
if (options.lock && this._dialect.supports.lock) {
var lock = options.lock;
let lock = options.lock;
if (typeof options.lock === 'object') {
lock = options.lock.level;
}
......@@ -1538,11 +1498,11 @@ var QueryGenerator = {
return query;
},
getQueryOrders: function(options, model, subQuery) {
var mainQueryOrder = [];
var subQueryOrder = [];
getQueryOrders(options, model, subQuery) {
const mainQueryOrder = [];
const subQueryOrder = [];
var validateOrder = function(order) {
const validateOrder = order => {
if (order instanceof Utils.literal) return;
if (!_.includes([
......@@ -1560,7 +1520,7 @@ var QueryGenerator = {
};
if (Array.isArray(options.order)) {
options.order.forEach(function(t) {
for (const t of options.order) {
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[t.length - 2] === 'string') {
......@@ -1576,19 +1536,16 @@ var QueryGenerator = {
}
mainQueryOrder.push(this.quote(t, model));
}.bind(this));
}
} else {
mainQueryOrder.push(this.quote(typeof options.order === 'string' ? new Utils.literal(options.order) : options.order, model));
}
return {
mainQueryOrder: mainQueryOrder,
subQueryOrder: subQueryOrder
};
return {mainQueryOrder, subQueryOrder};
},
selectFromTableFragment: function(options, model, attributes, tables, mainTableAs, whereClause) {
var fragment = 'SELECT ' + attributes.join(', ') + ' FROM ' + tables;
selectFromTableFragment(options, model, attributes, tables, mainTableAs, whereClause) {
let fragment = 'SELECT ' + attributes.join(', ') + ' FROM ' + tables;
if(mainTableAs) {
fragment += ' AS ' + mainTableAs;
......@@ -1597,34 +1554,33 @@ var QueryGenerator = {
return fragment;
},
joinIncludeQuery: function(options) {
var subQuery = options.subQuery
, include = options.include
, association = include.association
, parent = include.parent
, parentIsTop = !include.parent.association && include.parent.model.name === options.model.name
, $parent
, joinType = include.required ? 'INNER JOIN ' : 'LEFT OUTER JOIN '
, joinOn
, joinWhere
/* Attributes for the left side */
, left = association.source
, asLeft
, attrLeft = association instanceof BelongsTo ?
association.identifier :
left.primaryKeyAttribute
, fieldLeft = association instanceof BelongsTo ?
association.identifierField :
left.rawAttributes[left.primaryKeyAttribute].field
/* Attributes for the right side */
, right = include.model
, asRight = include.as
, tableRight = right.getTableName()
, fieldRight = association instanceof BelongsTo ?
joinIncludeQuery(options) {
const subQuery = options.subQuery;
const include = options.include;
const association = include.association;
const parent = include.parent;
const parentIsTop = !include.parent.association && include.parent.model.name === options.model.name;
const joinType = include.required ? 'INNER JOIN ' : 'LEFT OUTER JOIN ';
let $parent;
let joinWhere;
/* Attributes for the left side */
const left = association.source;
const attrLeft = association instanceof BelongsTo ?
association.identifier :
left.primaryKeyAttribute;
const fieldLeft = association instanceof BelongsTo ?
association.identifierField :
left.rawAttributes[left.primaryKeyAttribute].field;
let asLeft;
/* Attributes for the right side */
const right = include.model;
const tableRight = right.getTableName();
const fieldRight = association instanceof BelongsTo ?
right.rawAttributes[association.targetIdentifier || right.primaryKeyAttribute].field :
association.identifierField;
let asRight = include.as;
while (($parent = ($parent && $parent.parent || include.parent)) && $parent.association) {
if (asLeft) {
......@@ -1637,7 +1593,7 @@ var QueryGenerator = {
if (!asLeft) asLeft = parent.as || parent.model.name;
else asRight = [asLeft, asRight].join('.');
joinOn = [
let joinOn = [
this.quoteTable(asLeft),
this.quoteIdentifier(fieldLeft)
].join('.');
......@@ -1687,7 +1643,7 @@ var QueryGenerator = {
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
setAutocommitQuery: function(value, options) {
setAutocommitQuery(value, options) {
if (options.parent) {
return;
}
......@@ -1707,7 +1663,7 @@ var QueryGenerator = {
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
setIsolationLevelQuery: function(value, options) {
setIsolationLevelQuery(value, options) {
if (options.parent) {
return;
}
......@@ -1722,7 +1678,7 @@ var QueryGenerator = {
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
startTransactionQuery: function(transaction) {
startTransactionQuery(transaction) {
if (transaction.parent) {
// force quoting of savepoint identifiers for postgres
return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name, true) + ';';
......@@ -1738,11 +1694,11 @@ var QueryGenerator = {
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
deferConstraintsQuery: function () {},
deferConstraintsQuery() {},
setConstraintQuery: function () {},
setDeferredQuery: function () {},
setImmediateQuery: function () {},
setConstraintQuery() {},
setDeferredQuery() {},
setImmediateQuery() {},
/**
* Returns a query that commits a transaction.
......@@ -1750,7 +1706,7 @@ var QueryGenerator = {
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
commitTransactionQuery: function(transaction) {
commitTransactionQuery(transaction) {
if (transaction.parent) {
return;
}
......@@ -1765,7 +1721,7 @@ var QueryGenerator = {
* @param {Object} options An object with options.
* @return {String} The generated sql query.
*/
rollbackTransactionQuery: function(transaction) {
rollbackTransactionQuery(transaction) {
if (transaction.parent) {
// force quoting of savepoint identifiers for postgres
return 'ROLLBACK TO SAVEPOINT ' + this.quoteIdentifier(transaction.name, true) + ';';
......@@ -1781,8 +1737,8 @@ var QueryGenerator = {
* @param {Object} options The model passed to the selectQuery.
* @return {String} The generated sql query.
*/
addLimitAndOffset: function(options, model) {
var fragment = '';
addLimitAndOffset(options, model) {
let fragment = '';
/*jshint eqeqeq:false*/
if (options.offset != null && options.limit == null) {
......@@ -1798,13 +1754,12 @@ var QueryGenerator = {
return fragment;
},
handleSequelizeMethod: function (smth, tableName, factory, options, prepend) {
var self = this
, result;
handleSequelizeMethod(smth, tableName, factory, options, prepend) {
let result;
if (smth instanceof Utils.where) {
var value = smth.logic
, key;
let value = smth.logic;
let key;
if (smth.attribute._isSequelizeMethod) {
key = this.getWhereConditions(smth.attribute, tableName, factory, options, prepend);
......@@ -1840,11 +1795,11 @@ var QueryGenerator = {
result = 'CAST(' + result + ' AS ' + smth.type.toUpperCase() + ')';
} else if (smth instanceof Utils.fn) {
result = smth.fn + '(' + smth.args.map(function(arg) {
result = smth.fn + '(' + smth.args.map(arg => {
if (arg._isSequelizeMethod) {
return self.handleSequelizeMethod(arg, tableName, factory, options, prepend);
return this.handleSequelizeMethod(arg, tableName, factory, options, prepend);
} else {
return self.escape(arg);
return this.escape(arg);
}
}).join(', ') + ')';
} else if (smth instanceof Utils.col) {
......@@ -1863,14 +1818,14 @@ var QueryGenerator = {
return result;
},
whereQuery: function(where, options) {
var query = this.whereItemsQuery(where, options);
whereQuery(where, options) {
const query = this.whereItemsQuery(where, options);
if (query && query.length) {
return 'WHERE '+query;
}
return '';
},
whereItemsQuery: function(where, options, binding) {
whereItemsQuery(where, options, binding) {
if (
(Array.isArray(where) && where.length === 0) ||
(_.isPlainObject(where) && _.isEmpty(where)) ||
......@@ -1885,42 +1840,35 @@ var QueryGenerator = {
throw new Error('where: "raw query" has been removed, please use where ["raw query", [replacements]]');
}
var self = this
, items = [];
const items = [];
binding = binding || 'AND';
if (binding.substr(0, 1) !== ' ') binding = ' '+binding+' ';
if (_.isPlainObject(where)) {
_.forOwn(where, function (value, key) {
items.push(self.whereItemQuery(key, value, options));
_.forOwn(where, (value, key) => {
items.push(this.whereItemQuery(key, value, options));
});
} else {
items.push(self.whereItemQuery(undefined, where, options));
items.push(this.whereItemQuery(undefined, where, options));
}
return items.length && items.filter(function (item) {
return item && item.length;
}).join(binding) || '';
return items.length && items.filter(item => item && item.length).join(binding) || '';
},
whereItemQuery: function(key, value, options) {
whereItemQuery(key, value, options) {
options = options || {};
var self = this
, binding
, outerBinding
, comparatorMap
, aliasMap
, 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;
let binding;
let outerBinding;
let comparator = '=';
let field = options.field || options.model && options.model.rawAttributes && options.model.rawAttributes[key] || options.model && options.model.fieldRawAttributesMap && options.model.fieldRawAttributesMap[key];
let fieldType = options.type || (field && field.type);
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) {
field = options.model.rawAttributes[key.split('.')[0]];
fieldType = field.type;
tmp = value;
const tmp = value;
value = {};
Dottie.set(value, key.split('.').slice(1), tmp);
......@@ -1928,7 +1876,7 @@ var QueryGenerator = {
}
}
comparatorMap = {
const comparatorMap = {
$eq: '=',
$ne: '!=',
$gte: '>=',
......@@ -1954,7 +1902,7 @@ var QueryGenerator = {
};
// Maintain BC
aliasMap = {
const aliasMap = {
'ne': '$ne',
'in': '$in',
'not': '$not',
......@@ -1982,7 +1930,7 @@ var QueryGenerator = {
key = aliasMap[key] || key;
if (_.isPlainObject(value)) {
_.forOwn(value, function (item, key) {
_.forOwn(value, (item, key) => {
if (aliasMap[key]) {
value[aliasMap[key]] = item;
delete value[key];
......@@ -2020,15 +1968,13 @@ var QueryGenerator = {
if (key === '$not') outerBinding = 'NOT ';
if (Array.isArray(value)) {
value = value.map(function (item) {
var itemQuery = self.whereItemsQuery(item, options, ' AND ');
value = value.map(item => {
let itemQuery = this.whereItemsQuery(item, options, ' AND ');
if ((Array.isArray(item) || _.isPlainObject(item)) && _.size(item) > 1) {
itemQuery = '('+itemQuery+')';
}
return itemQuery;
}).filter(function (item) {
return item && item.length;
});
}).filter(item => item && item.length);
// $or: [] should return no data.
// $not of no restriction should also return no data
......@@ -2038,7 +1984,7 @@ var QueryGenerator = {
return value.length ? outerBinding + '('+value.join(binding)+')' : undefined;
} else {
value = self.whereItemsQuery(value, options, binding);
value = this.whereItemsQuery(value, options, binding);
if ((key === '$or' || key === '$not') && !value) {
return '0 = 1';
......@@ -2053,120 +1999,105 @@ var QueryGenerator = {
value = value.$or || value.$and;
if (_.isPlainObject(value)) {
value = _.reduce(value, function (result, _value, key) {
value = _.reduce(value, (result, _value, key) => {
result.push(_.zipObject([key], [_value]));
return result;
}, []);
}
value = value.map(function (_value) {
return self.whereItemQuery(key, _value, options);
}).filter(function (item) {
return item && item.length;
});
value = value.map(_value => this.whereItemQuery(key, _value, options)).filter(item => item && item.length);
return value.length ? '('+value.join(binding)+')' : undefined;
}
if (_.isPlainObject(value) && fieldType instanceof DataTypes.JSON && options.json !== false) {
return (function () {
var $items = []
, result
, traverse;
traverse = function (prop, item, path) {
var $where = {}
, $key
, $cast
, $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];
}
const $items = [];
const traverse = (prop, item, path) => {
const $where = {};
let $cast;
if (path[path.length - 1].indexOf('::') > -1) {
const $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 instanceof Utils.literal) {
$baseKey = self.handleSequelizeMethod(options.prefix)+'.'+$baseKey;
} else {
$baseKey = self.quoteTable(options.prefix)+'.'+$baseKey;
}
if (options.prefix) {
if (options.prefix instanceof Utils.literal) {
$baseKey = this.handleSequelizeMethod(options.prefix)+'.'+$baseKey;
} else {
$baseKey = this.quoteTable(options.prefix)+'.'+$baseKey;
}
}
$baseKey = '('+$baseKey+')';
$baseKey = '('+$baseKey+')';
castKey = function ($item) {
var key = $baseKey;
const castKey = $item => {
let key = $baseKey;
if (!$cast) {
if (typeof $item === 'number') {
$cast = 'double precision';
} else if ($item instanceof Date) {
$cast = 'timestamptz';
} else if (typeof $item === 'boolean') {
$cast = 'boolean';
}
if (!$cast) {
if (typeof $item === 'number') {
$cast = 'double precision';
} else if ($item instanceof Date) {
$cast = 'timestamptz';
} else if (typeof $item === 'boolean') {
$cast = 'boolean';
}
}
if ($cast) {
key += '::'+$cast;
}
if ($cast) {
key += '::'+$cast;
}
return key;
};
return key;
};
if (_.isPlainObject(item)) {
_.forOwn(item, function ($item, $prop) {
if ($prop.indexOf('$') === 0) {
$where[$prop] = $item;
$key = castKey($item);
if (_.isPlainObject(item)) {
_.forOwn(item, ($item, $prop) => {
if ($prop.indexOf('$') === 0) {
$where[$prop] = $item;
const $key = castKey($item);
$items.push(self.whereItemQuery(new Utils.literal($key), $where/*, _.pick(options, 'prefix')*/));
} else {
traverse($prop, $item, path.concat([$prop]));
}
});
} else {
$where.$eq = item;
$key = castKey(item);
$items.push(this.whereItemQuery(new Utils.literal($key), $where/*, _.pick(options, 'prefix')*/));
} else {
traverse($prop, $item, path.concat([$prop]));
}
});
} else {
$where.$eq = 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) {
if (prop.indexOf('$') === 0) {
var $where = {};
$where[prop] = item;
$items.push(self.whereItemQuery(key, $where, _.assign({}, options, {json: false})));
return;
}
_.forOwn(value, (item, prop) => {
if (prop.indexOf('$') === 0) {
const $where = {};
$where[prop] = item;
$items.push(this.whereItemQuery(key, $where, _.assign({}, options, {json: false})));
return;
}
traverse(prop, item, [prop]);
});
traverse(prop, item, [prop]);
});
result = $items.join(' AND ');
return $items.length > 1 ? '('+result+')' : result;
})();
const result = $items.join(' AND ');
return $items.length > 1 ? '('+result+')' : result;
}
// If multiple keys we combine the different logic conditions
if (_.isPlainObject(value) && Object.keys(value).length > 1) {
return (function () {
var $items = [];
_.forOwn(value, function (item, logic) {
var $where = {};
$where[logic] = item;
$items.push(self.whereItemQuery(key, $where, options));
});
const $items = [];
_.forOwn(value, (item, logic) => {
const $where = {};
$where[logic] = item;
$items.push(this.whereItemQuery(key, $where, options));
});
return '('+$items.join(' AND ')+')';
})();
return '('+$items.join(' AND ')+')';
}
// Do [] to $in/$notIn normalization
......@@ -2197,9 +2128,7 @@ var QueryGenerator = {
if ((value.$in || value.$notIn) instanceof Utils.literal) {
value = (value.$in || value.$notIn).val;
} else if ((value.$in || value.$notIn).length) {
value = '('+(value.$in || value.$notIn).map(function (item) {
return self.escape(item);
}).join(', ')+')';
value = '('+(value.$in || value.$notIn).map(item => this.escape(item)).join(', ')+')';
} else {
if (value.$in) {
value = '(NULL)';
......@@ -2210,9 +2139,7 @@ var QueryGenerator = {
} else if (value && (value.$any || value.$all)) {
comparator = value.$any ? '= ANY' : '= ALL';
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) {
return '('+this.escape(value)+')';
}.bind(this)).join(', ')+')';
value = '(VALUES '+(value.$any && value.$any.$values || value.$all && value.$all.$values).map(value => '('+this.escape(value)+')').join(', ')+')';
} else {
value = '('+this.escape(value.$any || value.$all, field)+')';
}
......@@ -2220,9 +2147,7 @@ var QueryGenerator = {
comparator = 'BETWEEN';
if (value.$notBetween) comparator = 'NOT BETWEEN';
value = (value.$between || value.$notBetween).map(function (item) {
return self.escape(item);
}).join(' AND ');
value = (value.$between || value.$notBetween).map(item => this.escape(item)).join(' AND ');
} else if (value && value.$raw) {
value = value.$raw;
} else if (value && value.$col) {
......@@ -2235,13 +2160,13 @@ var QueryGenerator = {
];
}
value = value.map(this.quoteIdentifier.bind(this)).join('.');
value = value.map(identifier => this.quoteIdentifier(identifier)).join('.');
} else {
var escapeValue = true;
var escapeOptions = {};
let escapeValue = true;
const escapeOptions = {};
if (_.isPlainObject(value)) {
_.forOwn(value, function (item, key) {
_.forOwn(value, (item, key) => {
if (comparatorMap[key]) {
comparator = comparatorMap[key];
value = item;
......@@ -2259,7 +2184,7 @@ var QueryGenerator = {
value = this.whereItemQuery(null, value);
}
}
}.bind(this));
});
}
if (comparator === '=' && value === null) {
......@@ -2275,13 +2200,13 @@ var QueryGenerator = {
//if ANY is used with like, add parentheses to generate correct query
if (escapeOptions.acceptStrings && (comparator.indexOf('ANY') > comparator.indexOf('LIKE'))) {
value = '(' + value + ')';
value = '(' + value + ')';
}
}
}
if (key) {
var prefix = true;
let prefix = true;
if (key._isSequelizeMethod) {
key = this.handleSequelizeMethod(key);
} else if (Utils.isColString(key)) {
......@@ -2294,7 +2219,7 @@ var QueryGenerator = {
];
}
key = key.map(this.quoteIdentifier.bind(this)).join('.');
key = key.map(identifier => this.quoteIdentifier(identifier)).join('.');
prefix = false;
} else {
key = this.quoteIdentifier(key);
......@@ -2315,10 +2240,9 @@ var QueryGenerator = {
/*
Takes something and transforms it into values of a where condition.
*/
getWhereConditions: function(smth, tableName, factory, options, prepend) {
var result = null
, where = {}
, self = this;
getWhereConditions(smth, tableName, factory, options, prepend) {
let result = null;
const where = {};
if (Array.isArray(tableName)) {
tableName = tableName[0];
......@@ -2336,12 +2260,12 @@ var QueryGenerator = {
if (smth && smth._isSequelizeMethod === true) { // Checking a property is cheaper than a lot of instanceof calls
result = this.handleSequelizeMethod(smth, tableName, factory, options, prepend);
} else if (Utils._.isPlainObject(smth)) {
return self.whereItemsQuery(smth, {
return this.whereItemsQuery(smth, {
model: factory,
prefix: prepend && tableName
});
} else if (typeof smth === 'number') {
var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : [];
let primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : [];
if (primaryKeys.length > 0) {
// Since we're just a number, assume only the first key
......@@ -2352,12 +2276,12 @@ var QueryGenerator = {
where[primaryKeys] = smth;
return self.whereItemsQuery(where, {
return this.whereItemsQuery(where, {
model: factory,
prefix: prepend && tableName
});
} else if (typeof smth === 'string') {
return self.whereItemsQuery(smth, {
return this.whereItemsQuery(smth, {
model: factory,
prefix: prepend && tableName
});
......@@ -2366,13 +2290,13 @@ var QueryGenerator = {
} else if (Array.isArray(smth)) {
if (smth.length === 0) return '1=1';
if (Utils.canTreatArrayAsAnd(smth)) {
var _smth = { $and: smth };
result = self.getWhereConditions(_smth, tableName, factory, options, prepend);
const _smth = { $and: smth };
result = this.getWhereConditions(_smth, tableName, factory, options, prepend);
} else {
result = Utils.format(smth, this.dialect);
}
} else if (smth === null) {
return self.whereItemsQuery(smth, {
return this.whereItemsQuery(smth, {
model: factory,
prefix: prepend && tableName
});
......@@ -2381,7 +2305,7 @@ var QueryGenerator = {
return result ? result : '1=1';
},
booleanValue: function(value) {
booleanValue(value) {
return value;
}
};
......
'use strict';
var Utils = require('../../utils')
, SqlString = require('../../sql-string')
, Dot = require('dottie')
, QueryTypes = require('../../query-types');
var AbstractQuery = function(database, sequelize, options) {};
/**
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 }
const Utils = require('../../utils');
const SqlString = require('../../sql-string');
const Dot = require('dottie');
const QueryTypes = require('../../query-types');
class AbstractQuery {
/**
* 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
*/
static formatBindParameters(sql, values, dialect, replacementFunc, options) {
if (!values) {
return [sql, []];
}
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:
Something like this:
[
{
some: 'data',
id: 1,
association: [
{ foo: 'bar', id: 1 },
{ foo: 'bar', id: 2 },
{ foo: 'bar', id: 3 }
]
}
const timeZone = null;
const list = Array.isArray(values);
sql = sql.replace(/\$(\$|\w+)/g, (match, key) => {
if ('$' === key) {
return options.skipUnescape ? match : key;
}
]
*/
/*
* 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.
*/
var groupJoinData = function(rows, includeOptions, options) {
if (!rows.length) {
return [];
let 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, []];
}
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
i
, length
, $i
, $length
let i;
let length;
let $i;
let $length;
// Row specific looping
, rowsI
, rowsLength = rows.length
, row
let rowsI;
let row;
const rowsLength = rows.length;
// Key specific looping
, keys
, key
, keyI
, keyLength
, prevKey
, values
, topValues
, topExists
, checkExisting = options.checkExisting
let keys;
let key;
let keyI;
let keyLength;
let prevKey;
let values;
let topValues;
let topExists;
const checkExisting = options.checkExisting;
// If we don't have to deduplicate we can pre-allocate the resulting array
, results = checkExisting ? [] : new Array(rowsLength)
, resultMap = {}
, includeMap = {}
, itemHash
, parentHash
, topHash
let itemHash;
let parentHash;
let topHash;
const results = checkExisting ? [] : new Array(rowsLength);
const resultMap = {};
const includeMap = {};
// Result variables for the respective functions
, $keyPrefix
, $keyPrefixString
, $prevKeyPrefixString
, $prevKeyPrefix
, $lastKeyPrefix
, $current
, $parent
let $keyPrefix;
let $keyPrefixString;
let $prevKeyPrefixString;
let $prevKeyPrefix;
let $lastKeyPrefix;
let $current;
let $parent;
// Map each key to an include option
, previousPiece
, buildIncludeMap = function (piece) {
let previousPiece;
const buildIncludeMap = piece => {
if ($current.includeMap[piece]) {
includeMap[key] = $current = $current.includeMap[piece];
if (previousPiece) {
......@@ -108,603 +418,276 @@ var groupJoinData = function(rows, includeOptions, options) {
}
includeMap[previousPiece] = $current;
}
}
};
// Calculate the string prefix of a key ('User.Results' for 'User.Results.id')
, keyPrefixStringMemo = {}
, keyPrefixString = function (key, memo) {
const keyPrefixStringMemo = {};
const keyPrefixString = (key, memo) => {
if (!memo[key]) {
memo[key] = key.substr(0, key.lastIndexOf('.'));
}
return memo[key];
}
};
// Removes the prefix from a key ('id' for 'User.Results.id')
, removeKeyPrefixMemo = {}
, removeKeyPrefix = function (key) {
const removeKeyPrefixMemo = {};
const removeKeyPrefix = key => {
if (!removeKeyPrefixMemo[key]) {
var index = key.lastIndexOf('.');
const index = key.lastIndexOf('.');
removeKeyPrefixMemo[key] = key.substr(index === -1 ? 0 : index + 1);
}
return removeKeyPrefixMemo[key];
}
};
// Calculates the array prefix of a key (['User', 'Results'] for 'User.Results.id')
, keyPrefixMemo = {}
, keyPrefix = function (key) {
const keyPrefixMemo = {};
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
if (!keyPrefixMemo[key]) {
var prefixString = keyPrefixString(key, keyPrefixStringMemo);
const prefixString = keyPrefixString(key, keyPrefixStringMemo);
if (!keyPrefixMemo[prefixString]) {
keyPrefixMemo[prefixString] = prefixString ? prefixString.split('.') : [];
}
keyPrefixMemo[key] = keyPrefixMemo[prefixString];
}
return keyPrefixMemo[key];
}
};
// Calcuate the last item in the array prefix ('Results' for 'User.Results.id')
, lastKeyPrefixMemo = {}
, lastKeyPrefix = function (key) {
const lastKeyPrefixMemo = {};
const lastKeyPrefix = key => {
if (!lastKeyPrefixMemo[key]) {
var prefix = keyPrefix(key)
, length = prefix.length;
const prefix = keyPrefix(key);
const length = prefix.length;
lastKeyPrefixMemo[key] = !length ? '' : prefix[length - 1];
}
return lastKeyPrefixMemo[key];
}
, getUniqueKeyAttributes = function (model) {
var uniqueKeyAttributes = Utils._.chain(model.uniqueKeys);
uniqueKeyAttributes = uniqueKeyAttributes
};
const getUniqueKeyAttributes = model => {
let uniqueKeyAttributes = Utils._.chain(model.uniqueKeys);
uniqueKeyAttributes = uniqueKeyAttributes
.result(uniqueKeyAttributes.findKey() + '.fields')
.map(function(field){
return Utils._.findKey(model.attributes, function(chr) {
return chr.field === field;
});
})
.map(field => Utils._.findKey(model.attributes, chr => chr.field === field))
.value();
return uniqueKeyAttributes;
}
, primaryKeyAttributes
, uniqueKeyAttributes
, prefix;
return uniqueKeyAttributes;
};
let primaryKeyAttributes;
let uniqueKeyAttributes;
let prefix;
for (rowsI = 0; rowsI < rowsLength; rowsI++) {
row = rows[rowsI];
for (rowsI = 0; rowsI < rowsLength; rowsI++) {
row = rows[rowsI];
// Keys are the same for all rows, so only need to compute them on the first row
if (rowsI === 0) {
keys = Object.keys(row);
keyLength = keys.length;
}
// Keys are the same for all rows, so only need to compute them on the first row
if (rowsI === 0) {
keys = Object.keys(row);
keyLength = keys.length;
}
if (checkExisting) {
topExists = false;
if (checkExisting) {
topExists = false;
// Compute top level hash key (this is usually just the primary key values)
$length = includeOptions.model.primaryKeyAttributes.length;
topHash = '';
if ($length === 1) {
topHash = row[includeOptions.model.primaryKeyAttributes[0]];
}
else if ($length > 1) {
for ($i = 0; $i < $length; $i++) {
topHash += row[includeOptions.model.primaryKeyAttributes[$i]];
// Compute top level hash key (this is usually just the primary key values)
$length = includeOptions.model.primaryKeyAttributes.length;
topHash = '';
if ($length === 1) {
topHash = row[includeOptions.model.primaryKeyAttributes[0]];
}
}
else if (!Utils._.isEmpty(includeOptions.model.uniqueKeys)) {
uniqueKeyAttributes = getUniqueKeyAttributes(includeOptions.model);
for ($i = 0; $i < uniqueKeyAttributes.length; $i++) {
topHash += row[uniqueKeyAttributes[$i]];
else if ($length > 1) {
for ($i = 0; $i < $length; $i++) {
topHash += row[includeOptions.model.primaryKeyAttributes[$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 = {};
$prevKeyPrefix = undefined;
for (keyI = 0; keyI < keyLength; keyI++) {
key = keys[keyI];
// 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
// TODO: Find a better way?
$keyPrefixString = keyPrefixString(key, keyPrefixStringMemo);
$keyPrefix = keyPrefix(key);
// On the first row we compute the includeMap
if (rowsI === 0 && includeMap[key] === undefined) {
if (!$keyPrefix.length) {
includeMap[key] = includeMap[''] = includeOptions;
} else {
$current = includeOptions;
previousPiece = undefined;
$keyPrefix.forEach(buildIncludeMap);
topValues = values = {};
$prevKeyPrefix = undefined;
for (keyI = 0; keyI < keyLength; keyI++) {
key = keys[keyI];
// 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
// TODO: Find a better way?
$keyPrefixString = keyPrefixString(key, keyPrefixStringMemo);
$keyPrefix = keyPrefix(key);
// On the first row we compute the includeMap
if (rowsI === 0 && includeMap[key] === undefined) {
if (!$keyPrefix.length) {
includeMap[key] = includeMap[''] = includeOptions;
} else {
$current = includeOptions;
previousPiece = undefined;
$keyPrefix.forEach(buildIncludeMap);
}
}
}
// End of key set
if ($prevKeyPrefix !== undefined && $prevKeyPrefix !== $keyPrefix) {
if (checkExisting) {
// Compute hash key for this set instance
// TODO: Optimize
length = $prevKeyPrefix.length;
$parent = null;
parentHash = null;
if (length) {
for (i = 0; i < length; i++) {
prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i];
primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes;
$length = primaryKeyAttributes.length;
itemHash = prefix;
if ($length === 1) {
itemHash += row[prefix+'.'+primaryKeyAttributes[0]];
}
else if ($length > 1) {
for ($i = 0; $i < $length; $i++) {
itemHash += row[prefix+'.'+primaryKeyAttributes[$i]];
// End of key set
if ($prevKeyPrefix !== undefined && $prevKeyPrefix !== $keyPrefix) {
if (checkExisting) {
// Compute hash key for this set instance
// TODO: Optimize
length = $prevKeyPrefix.length;
$parent = null;
parentHash = null;
if (length) {
for (i = 0; i < length; i++) {
prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i];
primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes;
$length = primaryKeyAttributes.length;
itemHash = prefix;
if ($length === 1) {
itemHash += row[prefix+'.'+primaryKeyAttributes[0]];
}
}
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]];
else if ($length > 1) {
for ($i = 0; $i < $length; $i++) {
itemHash += row[prefix+'.'+primaryKeyAttributes[$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;
$parent = prefix;
if (i < length - 1) {
parentHash = itemHash;
itemHash = parentHash + itemHash;
$parent = prefix;
if (i < length - 1) {
parentHash = itemHash;
}
}
}
} else {
itemHash = topHash;
}
if (itemHash === topHash) {
if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else {
topExists = true;
itemHash = topHash;
}
} else {
if (!resultMap[itemHash]) {
$parent = resultMap[parentHash];
$lastKeyPrefix = lastKeyPrefix(prevKey);
if (includeMap[prevKey].association.isSingleAssociation) {
$parent[$lastKeyPrefix] = resultMap[itemHash] = values;
if (itemHash === topHash) {
if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else {
if (!$parent[$lastKeyPrefix]) {
$parent[$lastKeyPrefix] = [];
topExists = true;
}
} 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
values = {};
} else {
// 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
// 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;
length = $keyPrefix.length;
if (length) {
for (i = 0; i < length; i++) {
if (i === length -1) {
values = $current[$keyPrefix[i]] = {};
// Reset values
values = {};
} else {
// 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
// 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;
length = $keyPrefix.length;
if (length) {
for (i = 0; i < length; i++) {
if (i === length -1) {
values = $current[$keyPrefix[i]] = {};
}
$current = $current[$keyPrefix[i]];
}
$current = $current[$keyPrefix[i]];
}
}
}
}
// End of iteration, set value and set prev values (for next iteration)
values[removeKeyPrefix(key)] = row[key];
prevKey = key;
$prevKeyPrefix = $keyPrefix;
$prevKeyPrefixString = $keyPrefixString;
}
// End of iteration, set value and set prev values (for next iteration)
values[removeKeyPrefix(key)] = row[key];
prevKey = key;
$prevKeyPrefix = $keyPrefix;
$prevKeyPrefixString = $keyPrefixString;
}
if (checkExisting) {
length = $prevKeyPrefix.length;
$parent = null;
parentHash = null;
if (length) {
for (i = 0; i < length; i++) {
prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i];
primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes;
$length = primaryKeyAttributes.length;
itemHash = prefix;
if ($length === 1) {
itemHash += row[prefix+'.'+primaryKeyAttributes[0]];
}
else if ($length > 0) {
for ($i = 0; $i < $length; $i++) {
itemHash += row[prefix+'.'+primaryKeyAttributes[$i]];
if (checkExisting) {
length = $prevKeyPrefix.length;
$parent = null;
parentHash = null;
if (length) {
for (i = 0; i < length; i++) {
prefix = $parent ? $parent+'.'+$prevKeyPrefix[i] : $prevKeyPrefix[i];
primaryKeyAttributes = includeMap[prefix].model.primaryKeyAttributes;
$length = primaryKeyAttributes.length;
itemHash = prefix;
if ($length === 1) {
itemHash += row[prefix+'.'+primaryKeyAttributes[0]];
}
}
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]];
else if ($length > 0) {
for ($i = 0; $i < $length; $i++) {
itemHash += row[prefix+'.'+primaryKeyAttributes[$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;
$parent = prefix;
if (i < length - 1) {
parentHash = itemHash;
itemHash = parentHash + itemHash;
$parent = prefix;
if (i < length - 1) {
parentHash = itemHash;
}
}
}
} else {
itemHash = topHash;
}
if (itemHash === topHash) {
if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else {
topExists = true;
itemHash = topHash;
}
} else {
if (!resultMap[itemHash]) {
$parent = resultMap[parentHash];
$lastKeyPrefix = lastKeyPrefix(prevKey);
if (includeMap[prevKey].association.isSingleAssociation) {
$parent[$lastKeyPrefix] = resultMap[itemHash] = values;
if (itemHash === topHash) {
if (!resultMap[itemHash]) {
resultMap[itemHash] = values;
} else {
if (!$parent[$lastKeyPrefix]) {
$parent[$lastKeyPrefix] = [];
}
$parent[$lastKeyPrefix].push(resultMap[itemHash] = values);
topExists = true;
}
}
}
if (!topExists) {
results.push(topValues);
}
} 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;
}
} else {
if (!resultMap[itemHash]) {
$parent = resultMap[parentHash];
$lastKeyPrefix = lastKeyPrefix(prevKey);
if (!replacementFunc) {
if (options.skipValueReplace) {
replacementFunc = function(match, key, values, timeZone, dialect, options) {
if (values[key] !== undefined) {
return match;
}
return undefined;
};
} else {
replacementFunc = function(match, key, values, timeZone, dialect, options) {
if (values[key] !== undefined) {
return SqlString.escape(values[key], false, timeZone, dialect);
if (includeMap[prevKey].association.isSingleAssociation) {
$parent[$lastKeyPrefix] = resultMap[itemHash] = values;
} else {
if (!$parent[$lastKeyPrefix]) {
$parent[$lastKeyPrefix] = [];
}
$parent[$lastKeyPrefix].push(resultMap[itemHash] = values);
}
}
}
return undefined;
};
}
} 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;
if (!topExists) {
results.push(topValues);
}
return undefined;
};
}
}
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);
} else {
results[rowsI] = topValues;
}
}
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
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 results;
}
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 = 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!