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

Commit 6e13407a by Matt Broadstone

unify selectQuery between abstract and mssql dialects

1 parent d1b914f7
......@@ -1083,7 +1083,9 @@ module.exports = (function() {
} else {
if (association.associationType !== 'BelongsTo') {
// 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 (left.rawAttributes[attrLeft].field) {
attrLeft = left.rawAttributes[attrLeft].field;
......
......@@ -70,9 +70,18 @@ module.exports = (function() {
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) {
throwMethodUndefined('renameTableQuery');
var query = 'EXEC sp_rename <%= before %>, <%= after %>;';
return Utils._.template(query)({
before: this.quoteTable(before),
after: this.quoteTable(after)
});
},
showTablesQuery: function () {
......@@ -591,6 +600,7 @@ module.exports = (function() {
selectQuery: function(tableName, options, model) {
// Enter and change at your own peril -- Mick Hansen
options = options || {};
var table = null
......@@ -610,14 +620,17 @@ module.exports = (function() {
, subJoinQueries = []
, mainTableAs = null;
if (!Array.isArray(tableName) && model) {
options.tableAs = mainTableAs = SqlGenerator.quoteIdentifier(model.name);
if (options.tableAs) {
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)) {
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(', ');
if (subQuery && mainAttributes) {
......@@ -631,7 +644,6 @@ module.exports = (function() {
});
}
// Escape attributes
mainAttributes = mainAttributes && mainAttributes.map(function(attr) {
var addTable = true;
......@@ -646,10 +658,10 @@ module.exports = (function() {
addTable = false;
} else {
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 {
attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? self.quoteIdentifiers(attr) : attr;
}
......@@ -670,8 +682,6 @@ module.exports = (function() {
mainAttributes = [mainTableAs + '.*'];
}
var topSql = this.getTop(options, query);
if (options.include) {
var generateJoinQueries = function(include, parentTable) {
var table = include.model.getTableName()
......@@ -701,7 +711,6 @@ module.exports = (function() {
var attrAs = attr,
verbatim = false;
if (Array.isArray(attr) && attr.length === 2) {
if (attr[0]._isSequelizeMethod) {
if (attr[0] instanceof Utils.literal ||
......@@ -731,10 +740,9 @@ module.exports = (function() {
if (verbatim === true) {
prefix = attr;
} else {
prefix = SqlGenerator.quoteIdentifier(as) + '.' + SqlGenerator.quoteIdentifier(attr);
prefix = self.quoteIdentifier(as) + '.' + self.quoteIdentifier(attr);
}
return prefix + ' AS ' + SqlGenerator.quoteIdentifier(as + '.' + attrAs);
return prefix + ' AS ' + self.quoteIdentifier(as + '.' + attrAs);
});
if (include.subQuery && subQuery) {
subQueryAttributes = subQueryAttributes.concat(attributes);
......@@ -747,9 +755,9 @@ module.exports = (function() {
var throughTable = through.model.getTableName()
, throughAs = as + '.' + through.as
, 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 ' +
SqlGenerator.quoteIdentifier(throughAs + '.' + (Array.isArray(attr) ? attr[1] : attr));
self.quoteIdentifier(throughAs + '.' + (Array.isArray(attr) ? attr[1] : attr));
})
, primaryKeysSource = association.source.primaryKeyAttributes
, tableSource = parentTable
......@@ -765,6 +773,7 @@ module.exports = (function() {
, sourceJoinOn
, targetJoinOn
, targetWhere;
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)
mainAttributes = mainAttributes.concat(throughAttributes);
......@@ -772,45 +781,104 @@ module.exports = (function() {
// Filter statement for left side of through
// Used by both join and subquery where
sourceJoinOn = SqlGenerator.quoteIdentifier(tableSource) + '.' + SqlGenerator.quoteIdentifier(attrSource) + ' = ';
sourceJoinOn += SqlGenerator.quoteIdentifier(throughAs) + '.' + SqlGenerator.quoteIdentifier(identSource);
sourceJoinOn = self.quoteTable(tableSource) + '.' + self.quoteIdentifier(attrSource) + ' = ';
sourceJoinOn += self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(identSource);
// Filter statement for right side of through
// Used by both join and subquery where
targetJoinOn = SqlGenerator.quoteIdentifier(tableTarget) + '.' + SqlGenerator.quoteIdentifier(attrTarget) + ' = ';
targetJoinOn += SqlGenerator.quoteIdentifier(throughAs) + '.' + SqlGenerator.quoteIdentifier(identTarget);
targetJoinOn = self.quoteIdentifier(tableTarget) + '.' + self.quoteIdentifier(attrTarget) + ' = ';
targetJoinOn += self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(identTarget);
if (self._dialect.supports.joinTableDependent) {
// Generate a wrapped join so that the through table join can be dependent on the target join
joinQueryItem += joinType + '(';
joinQueryItem += SqlGenerator.quoteTable(throughTable, throughAs);
joinQueryItem += joinType + SqlGenerator.quoteTable(table, as) + ' ON ';
joinQueryItem += self.quoteTable(throughTable, throughAs);
joinQueryItem += joinType + self.quoteTable(table, as) + ' ON ';
joinQueryItem += targetJoinOn;
joinQueryItem += ') ON '+sourceJoinOn;
} else {
// Generate join SQL for left side of through
joinQueryItem += joinType + SqlGenerator.quoteTable(throughTable, throughAs) + ' ON ';
joinQueryItem += joinType + self.quoteTable(throughTable, throughAs) + ' ON ';
joinQueryItem += sourceJoinOn;
// Generate join SQL for right side of through
joinQueryItem += joinType + SqlGenerator.quoteTable(table, as) + ' ON ';
joinQueryItem += joinType + self.quoteTable(table, as) + ' ON ';
joinQueryItem += targetJoinOn;
}
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;
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;
}
// Creating the as-is where for the subQuery, checks that the required association exists
options.where['__' + throughAs] = self.sequelize.asIs(['(',
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);
}
'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 || '',
joinType + SqlGenerator.quoteTable(table, as) + ' ON ' + targetJoinOn,
'WHERE ' + (! include.required && targetWhere || sourceJoinOn + ' AND ' + targetWhere),
')', 'IS NOT NULL'].join(' '));
options.where['__' + throughAs] = self.sequelize.asIs([
'(',
$query.replace(/\;$/, ""),
')',
'IS NOT NULL'
].join(' '));
})(include);
}
}
} else {
......@@ -837,9 +905,7 @@ module.exports = (function() {
} else {
if (association.associationType !== 'BelongsTo') {
// 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 (left.rawAttributes[attrLeft].field) {
attrLeft = left.rawAttributes[attrLeft].field;
......@@ -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 (subQuery && association.isMultiAssociation && include.required) {
if (!options.where) options.where = {};
// Creating the as-is where for the subQuery, checks that the required association exists
options.where['__' + as] = self.sequelize.asIs(['(',
'SELECT TOP(1)' + self.quoteIdentifier(attrRight),
'FROM ' + self.quoteTable(table, as),
'WHERE ' + joinOn,
')', 'IS NOT NULL'].join(' '));
var $query = self.selectQuery(include.model.getTableName(), {
tableAs: as,
attributes: [attrRight],
where: self.sequelize.asIs([joinOn]),
limit: 1
}, include.model);
options.where['__' + as] = self.sequelize.asIs([
'(',
$query.replace(/\;$/, ""),
')',
'IS NOT NULL'
].join(' '));
}
}
// Generate join SQL
......@@ -908,7 +979,7 @@ module.exports = (function() {
// If using subQuery select defined subQuery attributes and join subJoinQueries
if (subQuery) {
subQueryItems.push('SELECT ' + topSql + subQueryAttributes.join(', ') + ' FROM ' + options.table);
subQueryItems.push('SELECT ' + subQueryAttributes.join(', ') + ' FROM ' + options.table);
if (mainTableAs) {
subQueryItems.push(' AS ' + mainTableAs);
}
......@@ -916,7 +987,7 @@ module.exports = (function() {
// Else do it the reguar way
} else {
mainQueryItems.push('SELECT ' + topSql + mainAttributes.join(', ') + ' FROM ' + options.table);
mainQueryItems.push('SELECT ' + mainAttributes.join(', ') + ' FROM ' + options.table);
if (mainTableAs) {
mainQueryItems.push(' AS ' + mainTableAs);
}
......@@ -964,7 +1035,6 @@ module.exports = (function() {
if (subQuery && !(t[0] instanceof Model) && !(t[0].model instanceof Model)) {
subQueryOrder.push(this.quote(t, model));
}
mainQueryOrder.push(this.quote(t, model));
}.bind(this));
} else {
......@@ -979,22 +1049,12 @@ module.exports = (function() {
}
}
//var limitOrder = this.addLimitAndOffset(options, query);
// Add LIMIT, OFFSET to sub or main query
if (options.offset) {
//needs an order column for this to work
//default to id
var limitOrder = this.getLimitAndOffset(options,query);
var limitOrder = this.addLimitAndOffset(options, model);
if (limitOrder) {
if (subQuery) {
if(!options.order){
subQueryItems.push(' ORDER BY ' + SqlGenerator.quoteIdentifier(model.primaryKeyAttribute));
}
subQueryItems.push(limitOrder);
} else {
if(!options.order){
mainQueryItems.push(' ORDER BY ' + SqlGenerator.quoteIdentifier(model.primaryKeyAttribute));
}
mainQueryItems.push(limitOrder);
}
}
......@@ -1019,9 +1079,9 @@ module.exports = (function() {
}
query += ';';
//console.log(query);
return query;
},
/**
* Returns a query that starts a transaction.
*
......@@ -1091,34 +1151,49 @@ module.exports = (function() {
return 'ROLLBACK TRANSACTION;';
// return '';
},
getTop: function(options){
var query = '';
if(options.limit && !options.offset){
query += ' TOP(' + options.limit + ') ';
addLimitAndOffset: function(options, model) {
var fragment = '';
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.offset){
query += ' OFFSET ' + options.offset + ' ROWS'
+ ' FETCH NEXT ' + options.limit + ' ROWS ONLY';
if (options.limit || options.offset) {
if (!options.order || (options.include && !subQueryOrder.length)) {
fragment += ' ORDER BY ' + this.quoteIdentifier(model.primaryKeyAttribute);
}
if (options.offset || options.limit) {
fragment += ' OFFSET ' + offset + ' ROWS';
}
if (options.limit) {
fragment += ' FETCH NEXT ' + options.limit + ' ROWS ONLY';
}
}else if(options.offset){
query += ' OFFSET ' + options.offset + ' ROWS';
}
return query;
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.
*/
getWhereConditions: function(smth, tableName, factory, options, prepend) {
......@@ -1133,7 +1208,6 @@ module.exports = (function() {
}
}
options = options || {};
if (typeof prepend === 'undefined') {
......
......@@ -3,7 +3,7 @@
var chai = require('chai')
, Sequelize = require('../../index')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, Support = require(__dirname + '/../support')
, dialect = Support.getTestDialect()
, DataTypes = require(__dirname + "/../../lib/data-types")
, datetime = require('chai-datetime')
......@@ -229,7 +229,7 @@ describe(Support.getTestDialectTeaser("Include"), function () {
}, callback)
},
function (err) {
console.log('err', err);
// console.log('err', err);
expect(err).not.to.be.ok
done()
}
......@@ -256,14 +256,14 @@ describe(Support.getTestDialectTeaser("Include"), function () {
SubscriptionForm.belongsTo(Category, {foreignKey:'boundCategory'});
Category.hasMany(SubscriptionForm, {foreignKey:'boundCategory'});
Capital.hasMany(Category, { foreignKey: 'boundCapital'});
Category.belongsTo(Capital, {foreignKey:'boundCapital'});
Category.hasMany(SubCategory, {foreignKey:'boundCategory'});
SubCategory.belongsTo(Category, {foreignKey: 'boundCategory'});
return this.sequelize.sync({force: true}).then(function() {
return User.find({
include: [
......@@ -1162,7 +1162,7 @@ describe(Support.getTestDialectTeaser("Include"), function () {
chainer.run().done(callback)
}]
}, function (err) {
}, function (err) {
expect(err).not.to.be.ok
User.findAll({
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!