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

Commit 6e13407a by Matt Broadstone

unify selectQuery between abstract and mssql dialects

1 parent d1b914f7
...@@ -1083,7 +1083,9 @@ module.exports = (function() { ...@@ -1083,7 +1083,9 @@ module.exports = (function() {
} else { } else {
if (association.associationType !== 'BelongsTo') { if (association.associationType !== 'BelongsTo') {
// Alias the left attribute if the left attribute is not from a subqueried main table // Alias the left attribute if the left attribute is not from a subqueried main table
// When doing a query like SELECT aliasedKey FROM (SELECT primaryKey FROM primaryTable) only aliasedKey is available to the join, this is not the case when doing a regular select where you can't used the aliased attribute // When doing a query like SELECT aliasedKey FROM (SELECT primaryKey FROM primaryTable)
// only aliasedKey is available to the join, this is not the case when doing a regular
// select where you can't used the aliased attribute
if (!subQuery || (subQuery && !include.subQuery && include.parent.model !== mainModel)) { if (!subQuery || (subQuery && !include.subQuery && include.parent.model !== mainModel)) {
if (left.rawAttributes[attrLeft].field) { if (left.rawAttributes[attrLeft].field) {
attrLeft = left.rawAttributes[attrLeft].field; attrLeft = left.rawAttributes[attrLeft].field;
......
...@@ -70,9 +70,18 @@ module.exports = (function() { ...@@ -70,9 +70,18 @@ module.exports = (function() {
return query; return query;
}, },
/*
Returns a rename table query.
Parameters:
- originalTableName: Name of the table before execution.
- futureTableName: Name of the table after execution.
*/
renameTableQuery: function(before, after) { renameTableQuery: function(before, after) {
throwMethodUndefined('renameTableQuery'); var query = 'EXEC sp_rename <%= before %>, <%= after %>;';
return Utils._.template(query)({
before: this.quoteTable(before),
after: this.quoteTable(after)
});
}, },
showTablesQuery: function () { showTablesQuery: function () {
...@@ -591,6 +600,7 @@ module.exports = (function() { ...@@ -591,6 +600,7 @@ module.exports = (function() {
selectQuery: function(tableName, options, model) { selectQuery: function(tableName, options, model) {
// Enter and change at your own peril -- Mick Hansen // Enter and change at your own peril -- Mick Hansen
options = options || {}; options = options || {};
var table = null var table = null
...@@ -610,14 +620,17 @@ module.exports = (function() { ...@@ -610,14 +620,17 @@ module.exports = (function() {
, subJoinQueries = [] , subJoinQueries = []
, mainTableAs = null; , mainTableAs = null;
if (!Array.isArray(tableName) && model) { if (options.tableAs) {
options.tableAs = mainTableAs = SqlGenerator.quoteIdentifier(model.name); mainTableAs = this.quoteTable(options.tableAs);
} else if (!Array.isArray(tableName) && model) {
options.tableAs = mainTableAs = this.quoteTable(model.name);
} }
options.table = table = !Array.isArray(tableName) ? SqlGenerator.quoteTable(tableName) : tableName.map(function(t) {
options.table = table = !Array.isArray(tableName) ? this.quoteTable(tableName) : tableName.map(function(t) {
if (Array.isArray(t)) { if (Array.isArray(t)) {
return SqlGenerator.quoteTable(t[0], t[1]); return this.quoteTable(t[0], t[1]);
} }
return SqlGenerator.quoteTable(t, true); return this.quoteTable(t, true);
}.bind(this)).join(', '); }.bind(this)).join(', ');
if (subQuery && mainAttributes) { if (subQuery && mainAttributes) {
...@@ -631,7 +644,6 @@ module.exports = (function() { ...@@ -631,7 +644,6 @@ module.exports = (function() {
}); });
} }
// Escape attributes // Escape attributes
mainAttributes = mainAttributes && mainAttributes.map(function(attr) { mainAttributes = mainAttributes && mainAttributes.map(function(attr) {
var addTable = true; var addTable = true;
...@@ -646,10 +658,10 @@ module.exports = (function() { ...@@ -646,10 +658,10 @@ module.exports = (function() {
addTable = false; addTable = false;
} else { } else {
if (attr[0].indexOf('(') === -1 && attr[0].indexOf(')') === -1) { if (attr[0].indexOf('(') === -1 && attr[0].indexOf(')') === -1) {
attr[0] = SqlGenerator.quoteIdentifier(attr[0]); attr[0] = self.quoteIdentifier(attr[0]);
} }
} }
attr = [attr[0], SqlGenerator.quoteIdentifier(attr[1])].join(' AS '); attr = [attr[0], self.quoteIdentifier(attr[1])].join(' AS ');
} else { } else {
attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? self.quoteIdentifiers(attr) : attr; attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? self.quoteIdentifiers(attr) : attr;
} }
...@@ -670,8 +682,6 @@ module.exports = (function() { ...@@ -670,8 +682,6 @@ module.exports = (function() {
mainAttributes = [mainTableAs + '.*']; mainAttributes = [mainTableAs + '.*'];
} }
var topSql = this.getTop(options, query);
if (options.include) { if (options.include) {
var generateJoinQueries = function(include, parentTable) { var generateJoinQueries = function(include, parentTable) {
var table = include.model.getTableName() var table = include.model.getTableName()
...@@ -701,7 +711,6 @@ module.exports = (function() { ...@@ -701,7 +711,6 @@ module.exports = (function() {
var attrAs = attr, var attrAs = attr,
verbatim = false; verbatim = false;
if (Array.isArray(attr) && attr.length === 2) { if (Array.isArray(attr) && attr.length === 2) {
if (attr[0]._isSequelizeMethod) { if (attr[0]._isSequelizeMethod) {
if (attr[0] instanceof Utils.literal || if (attr[0] instanceof Utils.literal ||
...@@ -731,10 +740,9 @@ module.exports = (function() { ...@@ -731,10 +740,9 @@ module.exports = (function() {
if (verbatim === true) { if (verbatim === true) {
prefix = attr; prefix = attr;
} else { } else {
prefix = SqlGenerator.quoteIdentifier(as) + '.' + SqlGenerator.quoteIdentifier(attr); prefix = self.quoteIdentifier(as) + '.' + self.quoteIdentifier(attr);
} }
return prefix + ' AS ' + self.quoteIdentifier(as + '.' + attrAs);
return prefix + ' AS ' + SqlGenerator.quoteIdentifier(as + '.' + attrAs);
}); });
if (include.subQuery && subQuery) { if (include.subQuery && subQuery) {
subQueryAttributes = subQueryAttributes.concat(attributes); subQueryAttributes = subQueryAttributes.concat(attributes);
...@@ -747,9 +755,9 @@ module.exports = (function() { ...@@ -747,9 +755,9 @@ module.exports = (function() {
var throughTable = through.model.getTableName() var throughTable = through.model.getTableName()
, throughAs = as + '.' + through.as , throughAs = as + '.' + through.as
, throughAttributes = through.attributes.map(function(attr) { , throughAttributes = through.attributes.map(function(attr) {
return SqlGenerator.quoteIdentifier(throughAs) + '.' + SqlGenerator.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr) + return self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr) +
' AS ' + ' AS ' +
SqlGenerator.quoteIdentifier(throughAs + '.' + (Array.isArray(attr) ? attr[1] : attr)); self.quoteIdentifier(throughAs + '.' + (Array.isArray(attr) ? attr[1] : attr));
}) })
, primaryKeysSource = association.source.primaryKeyAttributes , primaryKeysSource = association.source.primaryKeyAttributes
, tableSource = parentTable , tableSource = parentTable
...@@ -765,6 +773,7 @@ module.exports = (function() { ...@@ -765,6 +773,7 @@ module.exports = (function() {
, sourceJoinOn , sourceJoinOn
, targetJoinOn , targetJoinOn
, targetWhere; , targetWhere;
if (options.includeIgnoreAttributes !== false) { if (options.includeIgnoreAttributes !== false) {
// Through includes are always hasMany, so we need to add the attributes to the mainAttributes no matter what (Real join will never be executed in subquery) // Through includes are always hasMany, so we need to add the attributes to the mainAttributes no matter what (Real join will never be executed in subquery)
mainAttributes = mainAttributes.concat(throughAttributes); mainAttributes = mainAttributes.concat(throughAttributes);
...@@ -772,45 +781,104 @@ module.exports = (function() { ...@@ -772,45 +781,104 @@ module.exports = (function() {
// Filter statement for left side of through // Filter statement for left side of through
// Used by both join and subquery where // Used by both join and subquery where
sourceJoinOn = SqlGenerator.quoteIdentifier(tableSource) + '.' + SqlGenerator.quoteIdentifier(attrSource) + ' = '; sourceJoinOn = self.quoteTable(tableSource) + '.' + self.quoteIdentifier(attrSource) + ' = ';
sourceJoinOn += SqlGenerator.quoteIdentifier(throughAs) + '.' + SqlGenerator.quoteIdentifier(identSource); sourceJoinOn += self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(identSource);
// Filter statement for right side of through // Filter statement for right side of through
// Used by both join and subquery where // Used by both join and subquery where
targetJoinOn = SqlGenerator.quoteIdentifier(tableTarget) + '.' + SqlGenerator.quoteIdentifier(attrTarget) + ' = '; targetJoinOn = self.quoteIdentifier(tableTarget) + '.' + self.quoteIdentifier(attrTarget) + ' = ';
targetJoinOn += SqlGenerator.quoteIdentifier(throughAs) + '.' + SqlGenerator.quoteIdentifier(identTarget); targetJoinOn += self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(identTarget);
if (self._dialect.supports.joinTableDependent) { if (self._dialect.supports.joinTableDependent) {
// Generate a wrapped join so that the through table join can be dependent on the target join // Generate a wrapped join so that the through table join can be dependent on the target join
joinQueryItem += joinType + '('; joinQueryItem += joinType + '(';
joinQueryItem += SqlGenerator.quoteTable(throughTable, throughAs); joinQueryItem += self.quoteTable(throughTable, throughAs);
joinQueryItem += joinType + SqlGenerator.quoteTable(table, as) + ' ON '; joinQueryItem += joinType + self.quoteTable(table, as) + ' ON ';
joinQueryItem += targetJoinOn; joinQueryItem += targetJoinOn;
joinQueryItem += ') ON '+sourceJoinOn; joinQueryItem += ') ON '+sourceJoinOn;
} else { } else {
// Generate join SQL for left side of through // Generate join SQL for left side of through
joinQueryItem += joinType + SqlGenerator.quoteTable(throughTable, throughAs) + ' ON '; joinQueryItem += joinType + self.quoteTable(throughTable, throughAs) + ' ON ';
joinQueryItem += sourceJoinOn; joinQueryItem += sourceJoinOn;
// Generate join SQL for right side of through // Generate join SQL for right side of through
joinQueryItem += joinType + SqlGenerator.quoteTable(table, as) + ' ON '; joinQueryItem += joinType + self.quoteTable(table, as) + ' ON ';
joinQueryItem += targetJoinOn; joinQueryItem += targetJoinOn;
} }
if (include.where) { if (include.where) {
targetWhere = self.getWhereConditions(include.where, self.sequelize.literal(SqlGenerator.quoteIdentifier(as)), include.model, whereOptions); targetWhere = self.getWhereConditions(include.where, self.sequelize.literal(self.quoteIdentifier(as)), include.model, whereOptions);
joinQueryItem += ' AND ' + targetWhere; joinQueryItem += ' AND ' + targetWhere;
if (subQuery && include.required) { if (subQuery && include.required) {
if (!options.where) options.where = {}; if (!options.where) options.where = {};
(function (include) {
// Closure to use sane local variables
var parent = include
, child = include
, nestedIncludes = []
, topParent
, topInclude
, $query;
while (parent = parent.parent) {
nestedIncludes = [_.extend({}, child, {include: nestedIncludes})];
child = parent;
}
topInclude = nestedIncludes[0];
topParent = topInclude.parent;
if (topInclude.through && Object(topInclude.through.model) === topInclude.through.model) {
$query = self.selectQuery(topInclude.through.model.getTableName(), {
attributes: [topInclude.through.model.primaryKeyAttributes[0]],
include: [{
model: topInclude.model,
as: topInclude.model.name,
attributes: [],
association: {
associationType: 'BelongsTo',
isSingleAssociation: true,
source: topInclude.association.target,
target: topInclude.association.source,
identifier: topInclude.association.foreignIdentifier,
identifierField: topInclude.association.foreignIdentifierField
},
required: true,
include: topInclude.include,
_pseudo: true
}],
where: {
$join: self.sequelize.asIs([
self.quoteTable(topParent.model.name) + '.' + self.quoteIdentifier(topParent.model.primaryKeyAttributes[0]),
self.quoteIdentifier(topInclude.through.model.name) + '.' + self.quoteIdentifier(topInclude.association.identifierField)
].join(" = "))
},
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);
}
// Creating the as-is where for the subQuery, checks that the required association exists options.where['__' + throughAs] = self.sequelize.asIs([
options.where['__' + throughAs] = self.sequelize.asIs(['(', '(',
$query.replace(/\;$/, ""),
'SELECT TOP(1)' + SqlGenerator.quoteIdentifier(throughAs) + '.' + SqlGenerator.quoteIdentifier(identSource) + ' FROM ' + SqlGenerator.quoteTable(throughTable, throughAs), ')',
! include.required && joinType + SqlGenerator.quoteTable(association.source.tableName, tableSource) + ' ON ' + sourceJoinOn || '', 'IS NOT NULL'
joinType + SqlGenerator.quoteTable(table, as) + ' ON ' + targetJoinOn, ].join(' '));
'WHERE ' + (! include.required && targetWhere || sourceJoinOn + ' AND ' + targetWhere), })(include);
')', 'IS NOT NULL'].join(' '));
} }
} }
} else { } else {
...@@ -837,9 +905,7 @@ module.exports = (function() { ...@@ -837,9 +905,7 @@ module.exports = (function() {
} else { } else {
if (association.associationType !== 'BelongsTo') { if (association.associationType !== 'BelongsTo') {
// Alias the left attribute if the left attribute is not from a subqueried main table // Alias the left attribute if the left attribute is not from a subqueried main table
// When doing a query like SELECT aliasedKey FROM (SELECT primaryKey FROM primaryTable) // When doing a query like SELECT aliasedKey FROM (SELECT primaryKey FROM primaryTable) only aliasedKey is available to the join, this is not the case when doing a regular select where you can't used the aliased attribute
// only aliasedKey is available to the join, this is not the case when doing a regular
// select where you can't used the aliased attribute
if (!subQuery || (subQuery && !include.subQuery && include.parent.model !== mainModel)) { if (!subQuery || (subQuery && !include.subQuery && include.parent.model !== mainModel)) {
if (left.rawAttributes[attrLeft].field) { if (left.rawAttributes[attrLeft].field) {
attrLeft = left.rawAttributes[attrLeft].field; attrLeft = left.rawAttributes[attrLeft].field;
...@@ -857,15 +923,20 @@ module.exports = (function() { ...@@ -857,15 +923,20 @@ module.exports = (function() {
// If its a multi association we need to add a where query to the main where (executed in the subquery) // If its a multi association we need to add a where query to the main where (executed in the subquery)
if (subQuery && association.isMultiAssociation && include.required) { if (subQuery && association.isMultiAssociation && include.required) {
if (!options.where) options.where = {}; if (!options.where) options.where = {};
// Creating the as-is where for the subQuery, checks that the required association exists // Creating the as-is where for the subQuery, checks that the required association exists
options.where['__' + as] = self.sequelize.asIs(['(', var $query = self.selectQuery(include.model.getTableName(), {
tableAs: as,
'SELECT TOP(1)' + self.quoteIdentifier(attrRight), attributes: [attrRight],
'FROM ' + self.quoteTable(table, as), where: self.sequelize.asIs([joinOn]),
'WHERE ' + joinOn, limit: 1
}, include.model);
')', 'IS NOT NULL'].join(' ')); options.where['__' + as] = self.sequelize.asIs([
'(',
$query.replace(/\;$/, ""),
')',
'IS NOT NULL'
].join(' '));
} }
} }
// Generate join SQL // Generate join SQL
...@@ -908,7 +979,7 @@ module.exports = (function() { ...@@ -908,7 +979,7 @@ module.exports = (function() {
// If using subQuery select defined subQuery attributes and join subJoinQueries // If using subQuery select defined subQuery attributes and join subJoinQueries
if (subQuery) { if (subQuery) {
subQueryItems.push('SELECT ' + topSql + subQueryAttributes.join(', ') + ' FROM ' + options.table); subQueryItems.push('SELECT ' + subQueryAttributes.join(', ') + ' FROM ' + options.table);
if (mainTableAs) { if (mainTableAs) {
subQueryItems.push(' AS ' + mainTableAs); subQueryItems.push(' AS ' + mainTableAs);
} }
...@@ -916,7 +987,7 @@ module.exports = (function() { ...@@ -916,7 +987,7 @@ module.exports = (function() {
// Else do it the reguar way // Else do it the reguar way
} else { } else {
mainQueryItems.push('SELECT ' + topSql + mainAttributes.join(', ') + ' FROM ' + options.table); mainQueryItems.push('SELECT ' + mainAttributes.join(', ') + ' FROM ' + options.table);
if (mainTableAs) { if (mainTableAs) {
mainQueryItems.push(' AS ' + mainTableAs); mainQueryItems.push(' AS ' + mainTableAs);
} }
...@@ -964,7 +1035,6 @@ module.exports = (function() { ...@@ -964,7 +1035,6 @@ module.exports = (function() {
if (subQuery && !(t[0] instanceof Model) && !(t[0].model instanceof Model)) { if (subQuery && !(t[0] instanceof Model) && !(t[0].model instanceof Model)) {
subQueryOrder.push(this.quote(t, model)); subQueryOrder.push(this.quote(t, model));
} }
mainQueryOrder.push(this.quote(t, model)); mainQueryOrder.push(this.quote(t, model));
}.bind(this)); }.bind(this));
} else { } else {
...@@ -979,22 +1049,12 @@ module.exports = (function() { ...@@ -979,22 +1049,12 @@ module.exports = (function() {
} }
} }
//var limitOrder = this.addLimitAndOffset(options, query);
// Add LIMIT, OFFSET to sub or main query // Add LIMIT, OFFSET to sub or main query
if (options.offset) { var limitOrder = this.addLimitAndOffset(options, model);
//needs an order column for this to work if (limitOrder) {
//default to id
var limitOrder = this.getLimitAndOffset(options,query);
if (subQuery) { if (subQuery) {
if(!options.order){
subQueryItems.push(' ORDER BY ' + SqlGenerator.quoteIdentifier(model.primaryKeyAttribute));
}
subQueryItems.push(limitOrder); subQueryItems.push(limitOrder);
} else { } else {
if(!options.order){
mainQueryItems.push(' ORDER BY ' + SqlGenerator.quoteIdentifier(model.primaryKeyAttribute));
}
mainQueryItems.push(limitOrder); mainQueryItems.push(limitOrder);
} }
} }
...@@ -1019,9 +1079,9 @@ module.exports = (function() { ...@@ -1019,9 +1079,9 @@ module.exports = (function() {
} }
query += ';'; query += ';';
//console.log(query);
return query; return query;
}, },
/** /**
* Returns a query that starts a transaction. * Returns a query that starts a transaction.
* *
...@@ -1091,32 +1151,47 @@ module.exports = (function() { ...@@ -1091,32 +1151,47 @@ module.exports = (function() {
return 'ROLLBACK TRANSACTION;'; return 'ROLLBACK TRANSACTION;';
// return ''; // return '';
}, },
getTop: function(options){
var query = ''; addLimitAndOffset: function(options, model) {
if(options.limit && !options.offset){ var fragment = '';
query += ' TOP(' + options.limit + ') '; var offset = options.offset || 0
, isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation;
// FIXME: This is ripped from selectQuery to determine whether there is already
// an ORDER BY added for a subquery. Should be refactored so we dont' need
// the duplication. Also consider moving this logic inside the options.order
// check, so that we aren't compiling this twice for every invocation.
var mainQueryOrder = [];
var subQueryOrder = [];
if (options.order) {
if (Array.isArray(options.order)) {
options.order.forEach(function(t) {
if (isSubQuery && !(t[0] instanceof Model) && !(t[0].model instanceof Model)) {
subQueryOrder.push(this.quote(t, model));
}
mainQueryOrder.push(this.quote(t, model));
}.bind(this));
} else {
mainQueryOrder.push(options.order);
}
} }
return query;
},
getLimitAndOffset: function(options, query) {
query = query || '';
if(options.limit){ if (options.limit || options.offset) {
if(options.offset){ if (!options.order || (options.include && !subQueryOrder.length)) {
query += ' OFFSET ' + options.offset + ' ROWS' fragment += ' ORDER BY ' + this.quoteIdentifier(model.primaryKeyAttribute);
+ ' FETCH NEXT ' + options.limit + ' ROWS ONLY';
} }
}else if(options.offset){
query += ' OFFSET ' + options.offset + ' ROWS'; if (options.offset || options.limit) {
fragment += ' OFFSET ' + offset + ' ROWS';
} }
return query;
if (options.limit) {
fragment += ' FETCH NEXT ' + options.limit + ' ROWS ONLY';
}
}
return fragment;
}, },
/*
Takes something and transforms it into values of a where condition.
*/
/*
Takes something and transforms it into values of a where condition.
*/
/* /*
Takes something and transforms it into values of a where condition. Takes something and transforms it into values of a where condition.
...@@ -1133,7 +1208,6 @@ module.exports = (function() { ...@@ -1133,7 +1208,6 @@ module.exports = (function() {
} }
} }
options = options || {}; options = options || {};
if (typeof prepend === 'undefined') { if (typeof prepend === 'undefined') {
......
...@@ -229,7 +229,7 @@ describe(Support.getTestDialectTeaser("Include"), function () { ...@@ -229,7 +229,7 @@ describe(Support.getTestDialectTeaser("Include"), function () {
}, callback) }, callback)
}, },
function (err) { function (err) {
console.log('err', err); // console.log('err', err);
expect(err).not.to.be.ok expect(err).not.to.be.ok
done() done()
} }
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!