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

Commit e440f263 by Mick Hansen

feat(sql): rudimentary groupedLimit support for selectQuery

1 parent f767ed06
...@@ -39,6 +39,9 @@ ...@@ -39,6 +39,9 @@
"afterEach", "afterEach",
"suite", "suite",
"setup", "setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"test" "test"
] ]
} }
\ No newline at end of file
'use strict'; 'use strict';
var Utils = require('../../utils') var Utils = require('../../utils')
, SqlString = require('../../sql-string') , SqlString = require('../../sql-string')
...@@ -766,6 +766,7 @@ var QueryGenerator = { ...@@ -766,6 +766,7 @@ var QueryGenerator = {
, model , model
, as , as
, association; , association;
for (var i = 0; i < len - 1; i++) { for (var i = 0; i < len - 1; i++) {
item = obj[i]; item = obj[i];
if (item._modelAttribute || Utils._.isString(item) || item._isSequelizeMethod || 'raw' in item) { if (item._modelAttribute || Utils._.isString(item) || item._isSequelizeMethod || 'raw' in item) {
...@@ -799,7 +800,7 @@ var QueryGenerator = { ...@@ -799,7 +800,7 @@ var QueryGenerator = {
} }
// add 1st string as quoted, 2nd as unquoted raw // add 1st string as quoted, 2nd as unquoted raw
var sql = (i > 0 ? this.quoteIdentifier(tableNames.join('.')) + '.' : (Utils._.isString(obj[0]) ? this.quoteIdentifier(parent.name) + '.' : '')) + this.quote(obj[i], parent, force); var sql = (i > 0 ? this.quoteIdentifier(tableNames.join('.')) + '.' : (Utils._.isString(obj[0]) && parent ? this.quoteIdentifier(parent.name) + '.' : '')) + this.quote(obj[i], parent, force);
if (i < len - 1) { if (i < len - 1) {
if (obj[i + 1]._isSequelizeMethod) { if (obj[i + 1]._isSequelizeMethod) {
sql += this.handleSequelizeMethod(obj[i + 1]); sql += this.handleSequelizeMethod(obj[i + 1]);
...@@ -953,7 +954,7 @@ var QueryGenerator = { ...@@ -953,7 +954,7 @@ var QueryGenerator = {
, limit = options.limit , limit = options.limit
, mainModel = model , mainModel = model
, mainQueryItems = [] , mainQueryItems = []
, mainAttributes = options.attributes && options.attributes.slice(0) , mainAttributes = options.attributes && options.attributes.slice()
, mainJoinQueries = [] , mainJoinQueries = []
// We'll use a subquery if we have a hasMany association and a limit // We'll use a subquery if we have a hasMany association and a limit
, subQuery = options.subQuery === undefined ? , subQuery = options.subQuery === undefined ?
...@@ -967,10 +968,10 @@ var QueryGenerator = { ...@@ -967,10 +968,10 @@ var QueryGenerator = {
if (options.tableAs) { if (options.tableAs) {
mainTableAs = this.quoteTable(options.tableAs); mainTableAs = this.quoteTable(options.tableAs);
} else if (!Array.isArray(tableName) && model) { } else if (!Array.isArray(tableName) && model) {
options.tableAs = mainTableAs = this.quoteTable(model.name); mainTableAs = this.quoteTable(model.name);
} }
options.table = table = !Array.isArray(tableName) ? this.quoteTable(tableName) : tableName.map(function(t) { table = !Array.isArray(tableName) ? this.quoteTable(tableName) : tableName.map(function(t) {
if (Array.isArray(t)) { if (Array.isArray(t)) {
return this.quoteTable(t[0], t[1]); return this.quoteTable(t[0], t[1]);
} }
...@@ -1001,6 +1002,7 @@ var QueryGenerator = { ...@@ -1001,6 +1002,7 @@ var QueryGenerator = {
} }
if (Array.isArray(attr) && attr.length === 2) { if (Array.isArray(attr) && attr.length === 2) {
attr = attr.slice();
if (attr[0]._isSequelizeMethod) { if (attr[0]._isSequelizeMethod) {
attr[0] = self.handleSequelizeMethod(attr[0]); attr[0] = self.handleSequelizeMethod(attr[0]);
...@@ -1299,7 +1301,8 @@ var QueryGenerator = { ...@@ -1299,7 +1301,8 @@ var QueryGenerator = {
joinQueryItem = ' ' + self.joinIncludeQuery({ joinQueryItem = ' ' + self.joinIncludeQuery({
model: mainModel, model: mainModel,
subQuery: options.subQuery, subQuery: options.subQuery,
include: include include: include,
groupedLimit: options.groupedLimit
}); });
} }
...@@ -1333,7 +1336,7 @@ var QueryGenerator = { ...@@ -1333,7 +1336,7 @@ var QueryGenerator = {
options.include.filter(function (include) { options.include.filter(function (include) {
return !include.seperate; return !include.seperate;
}).forEach(function(include) { }).forEach(function(include) {
var joinQueries = generateJoinQueries(include, options.tableAs); var joinQueries = generateJoinQueries(include, mainTableAs);
subJoinQueries = subJoinQueries.concat(joinQueries.subQuery); subJoinQueries = subJoinQueries.concat(joinQueries.subQuery);
mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery); mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery);
...@@ -1343,7 +1346,7 @@ var QueryGenerator = { ...@@ -1343,7 +1346,7 @@ var QueryGenerator = {
// 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 ' + subQueryAttributes.join(', ') + ' FROM ' + options.table); subQueryItems.push('SELECT ' + subQueryAttributes.join(', ') + ' FROM ' + table);
if (mainTableAs) { if (mainTableAs) {
subQueryItems.push(' AS ' + mainTableAs); subQueryItems.push(' AS ' + mainTableAs);
} }
...@@ -1351,7 +1354,30 @@ var QueryGenerator = { ...@@ -1351,7 +1354,30 @@ var QueryGenerator = {
// Else do it the reguar way // Else do it the reguar way
} else { } else {
mainQueryItems.push('SELECT ' + mainAttributes.join(', ') + ' FROM ' + options.table); if (options.groupedLimit) {
if (!mainTableAs) {
mainTableAs = table;
}
mainQueryItems.push('SELECT * FROM ('+
options.groupedLimit.values.map(function (value) {
var where = _.assign({}, options.where);
where[options.groupedLimit.on] = value;
return '('+self.selectQuery(
options.table,
{
attributes: options.attributes,
limit: options.groupedLimit.limit,
order: options.order,
where: where
}
).replace(/;$/, '')+')';
}).join(' UNION ALL ')
+')');
} else {
mainQueryItems.push('SELECT ' + mainAttributes.join(', ') + ' FROM ' + table);
}
if (mainTableAs) { if (mainTableAs) {
mainQueryItems.push(' AS ' + mainTableAs); mainQueryItems.push(' AS ' + mainTableAs);
} }
...@@ -1359,7 +1385,7 @@ var QueryGenerator = { ...@@ -1359,7 +1385,7 @@ var QueryGenerator = {
} }
// Add WHERE to sub or main query // Add WHERE to sub or main query
if (options.hasOwnProperty('where')) { if (options.hasOwnProperty('where') && !options.groupedLimit) {
options.where = this.getWhereConditions(options.where, mainTableAs || tableName, model, options); options.where = this.getWhereConditions(options.where, mainTableAs || tableName, model, options);
if (options.where) { if (options.where) {
if (subQuery) { if (subQuery) {
...@@ -1390,7 +1416,7 @@ var QueryGenerator = { ...@@ -1390,7 +1416,7 @@ var QueryGenerator = {
} }
} }
// Add ORDER to sub or main query // Add ORDER to sub or main query
if (options.order) { if (options.order && !options.groupedLimit) {
var mainQueryOrder = []; var mainQueryOrder = [];
var subQueryOrder = []; var subQueryOrder = [];
...@@ -1443,7 +1469,7 @@ var QueryGenerator = { ...@@ -1443,7 +1469,7 @@ var QueryGenerator = {
// Add LIMIT, OFFSET to sub or main query // Add LIMIT, OFFSET to sub or main query
var limitOrder = this.addLimitAndOffset(options, model); var limitOrder = this.addLimitAndOffset(options, model);
if (limitOrder) { if (limitOrder && !options.groupedLimit) {
if (subQuery) { if (subQuery) {
subQueryItems.push(limitOrder); subQueryItems.push(limitOrder);
} else { } else {
...@@ -1455,7 +1481,7 @@ var QueryGenerator = { ...@@ -1455,7 +1481,7 @@ var QueryGenerator = {
if (subQuery) { if (subQuery) {
query = 'SELECT ' + mainAttributes.join(', ') + ' FROM ('; query = 'SELECT ' + mainAttributes.join(', ') + ' FROM (';
query += subQueryItems.join(''); query += subQueryItems.join('');
query += ') AS ' + options.tableAs; query += ') AS ' + mainTableAs;
query += mainJoinQueries.join(''); query += mainJoinQueries.join('');
query += mainQueryItems.join(''); query += mainQueryItems.join('');
} else { } else {
...@@ -1529,7 +1555,7 @@ var QueryGenerator = { ...@@ -1529,7 +1555,7 @@ var QueryGenerator = {
this.quoteIdentifier(fieldLeft) this.quoteIdentifier(fieldLeft)
].join('.'); ].join('.');
if (subQuery && include.parent.subQuery && !include.subQuery) { if (options.groupedLimit || subQuery && include.parent.subQuery && !include.subQuery) {
if (parentIsTop) { if (parentIsTop) {
// The main model attributes is not aliased to a prefix // The main model attributes is not aliased to a prefix
joinOn = [ joinOn = [
......
...@@ -1219,7 +1219,6 @@ Model.prototype.findAll = function(options) { ...@@ -1219,7 +1219,6 @@ Model.prototype.findAll = function(options) {
, originalOptions; , originalOptions;
tableNames[this.getTableName(options)] = true; tableNames[this.getTableName(options)] = true;
originalOptions = optClone(options);
options = optClone(options); options = optClone(options);
_.defaults(options, { hooks: true }); _.defaults(options, { hooks: true });
...@@ -1243,7 +1242,6 @@ Model.prototype.findAll = function(options) { ...@@ -1243,7 +1242,6 @@ Model.prototype.findAll = function(options) {
options.hasJoin = true; options.hasJoin = true;
validateIncludedElements.call(this, options, tableNames); validateIncludedElements.call(this, options, tableNames);
validateIncludedElements.call(this, originalOptions, tableNames);
// If we're not raw, we have to make sure we include the primary key for deduplication // If we're not raw, we have to make sure we include the primary key for deduplication
if (options.attributes && !options.raw) { if (options.attributes && !options.raw) {
...@@ -1269,6 +1267,7 @@ Model.prototype.findAll = function(options) { ...@@ -1269,6 +1267,7 @@ Model.prototype.findAll = function(options) {
return this.runHooks('beforeFindAfterOptions', options); return this.runHooks('beforeFindAfterOptions', options);
} }
}).then(function() { }).then(function() {
originalOptions = optClone(options);
options.tableNames = Object.keys(tableNames); options.tableNames = Object.keys(tableNames);
return this.QueryInterface.select(this, this.getTableName(options), options); return this.QueryInterface.select(this, this.getTableName(options), options);
}).tap(function(results) { }).tap(function(results) {
...@@ -1281,9 +1280,10 @@ Model.prototype.findAll = function(options) { ...@@ -1281,9 +1280,10 @@ Model.prototype.findAll = function(options) {
}; };
Model.$findSeperate = function(results, options) { Model.$findSeperate = function(results, options) {
if (!options.include) return Promise.resolve(results); if (!options.include || options.raw || !results) return Promise.resolve(results);
var original = results; var original = results;
if (!Array.isArray(results)) results = [results]; if (options.plain) results = [results];
return Promise.map(options.include, function (include) { return Promise.map(options.include, function (include) {
if (!include.seperate) { if (!include.seperate) {
...@@ -1296,7 +1296,7 @@ Model.$findSeperate = function(results, options) { ...@@ -1296,7 +1296,7 @@ Model.$findSeperate = function(results, options) {
}, []), }, []),
_.assign( _.assign(
{}, {},
_.omit(options, 'include', 'attributes', 'order', 'where', 'limit'), _.omit(options, 'include', 'attributes', 'order', 'where', 'limit', 'plain'),
{include: include.include || []} {include: include.include || []}
) )
); );
...@@ -1304,7 +1304,7 @@ Model.$findSeperate = function(results, options) { ...@@ -1304,7 +1304,7 @@ Model.$findSeperate = function(results, options) {
return include.association.get(results, _.assign( return include.association.get(results, _.assign(
{}, {},
_.omit(options, 'include', 'attributes', 'order', 'where', 'limit'), _.omit(options, 'include', 'attributes', 'order', 'where', 'limit', 'plain'),
include include
)).then(function (map) { )).then(function (map) {
results.forEach(function (result) { results.forEach(function (result) {
......
...@@ -103,6 +103,21 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -103,6 +103,21 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
testsql({ testsql({
model: User, model: User,
subQuery: true, subQuery: true,
groupedLimit: {},
include: Sequelize.Model.$validateIncludedElements({
limit: 3,
model: User,
include: [
User.Company
]
}).include[0]
}, {
default: "LEFT OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id]"
});
testsql({
model: User,
subQuery: true,
include: Sequelize.Model.$validateIncludedElements({ include: Sequelize.Model.$validateIncludedElements({
limit: 3, limit: 3,
model: User, model: User,
...@@ -213,5 +228,20 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -213,5 +228,20 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
// The primary key of the main model will be aliased because it's coming from a subquery that the :M join is not a part of // The primary key of the main model will be aliased because it's coming from a subquery that the :M join is not a part of
default: "LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id] = [Tasks].[user_id]" default: "LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id] = [Tasks].[user_id]"
}); });
testsql({
model: User,
subQuery: true,
include: Sequelize.Model.$validateIncludedElements({
limit: 3,
model: User,
include: [
User.Tasks
]
}).include[0]
}, {
// The primary key of the main model will be aliased because it's coming from a subquery that the :M join is not a part of
default: "LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id] = [Tasks].[user_id]"
});
}); });
}); });
\ No newline at end of file
...@@ -19,7 +19,7 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -19,7 +19,7 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
test(util.inspect(options, {depth: 2}), function () { test(util.inspect(options, {depth: 2}), function () {
return expectsql( return expectsql(
sql.selectQuery( sql.selectQuery(
options.table || option.model && option.model.getTableName(), options.table || model && model.getTableName(),
options, options,
options.model options.model
), ),
...@@ -33,11 +33,114 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -33,11 +33,114 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
attributes: [ attributes: [
'email', 'email',
['first_name', 'firstName'] ['first_name', 'firstName']
] ],
where: {
email: 'jon.snow@gmail.com'
}
}, { }, {
default: 'SELECT [email], [first_name] AS [firstName] FROM [User];' default: "SELECT [email], [first_name] AS [firstName] FROM [User] WHERE [User].[email] = 'jon.snow@gmail.com';"
}); });
testsql({
table: 'User',
attributes: [
'email',
['first_name', 'firstName'],
['last_name', 'lastName']
],
order: [
['last_name', 'ASC']
],
groupedLimit: {
limit: 3,
on: 'companyId',
values: [
1,
5
]
}
}, {
default: 'SELECT * FROM ('+
[
'(SELECT [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [User] WHERE [User].[companyId] = 1 ORDER BY [last_name] ASC LIMIT 3)',
'(SELECT [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [User] WHERE [User].[companyId] = 5 ORDER BY [last_name] ASC LIMIT 3)'
].join(' UNION ALL ')
+') AS [User];'
});
(function () {
var User = Support.sequelize.define('user', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
field: 'id_user'
},
email: DataTypes.STRING,
firstName: {
type: DataTypes.STRING,
field: 'first_name'
},
lastName: {
type: DataTypes.STRING,
field: 'last_name'
}
},
{
tableName: 'users'
});
var Post = Support.sequelize.define('Post', {
title: DataTypes.STRING,
userId: {
type: DataTypes.INTEGER,
field: 'user_id'
}
},
{
tableName: 'post'
});
User.Posts = User.hasMany(Post, {foreignKey: 'userId', as: 'POSTS'});
var include = Model.$validateIncludedElements({
include: [{
attributes: ['title'],
association: User.Posts
}],
model: User
}).include;
testsql({
table: User.getTableName(),
model: User,
include: include,
attributes: [
['id_user', 'id'],
'email',
['first_name', 'firstName'],
['last_name', 'lastName']
],
order: [
['last_name', 'ASC']
],
groupedLimit: {
limit: 3,
on: 'companyId',
values: [
1,
5
]
}
}, {
default: 'SELECT * FROM ('+
[
'(SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] WHERE [users].[companyId] = 1 ORDER BY [last_name] ASC LIMIT 3)',
'(SELECT [id_user] AS [id], [email], [first_name] AS [firstName], [last_name] AS [lastName] FROM [users] WHERE [users].[companyId] = 5 ORDER BY [last_name] ASC LIMIT 3)'
].join(' UNION ALL ')
+') AS [user] LEFT OUTER JOIN [post] AS [POSTS] ON [user].[id] = [POSTS].[user_id];'
});
})();
it('include (left outer join)', function () { it('include (left outer join)', function () {
var User = Support.sequelize.define('User', { var User = Support.sequelize.define('User', {
name: DataTypes.STRING, name: DataTypes.STRING,
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!