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

Commit e440f263 by Mick Hansen

feat(sql): rudimentary groupedLimit support for selectQuery

1 parent f767ed06
......@@ -39,6 +39,9 @@
"afterEach",
"suite",
"setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"test"
]
}
\ No newline at end of file
'use strict';
'use strict';
var Utils = require('../../utils')
, SqlString = require('../../sql-string')
......@@ -766,6 +766,7 @@ var QueryGenerator = {
, model
, as
, association;
for (var i = 0; i < len - 1; i++) {
item = obj[i];
if (item._modelAttribute || Utils._.isString(item) || item._isSequelizeMethod || 'raw' in item) {
......@@ -799,7 +800,7 @@ var QueryGenerator = {
}
// 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 (obj[i + 1]._isSequelizeMethod) {
sql += this.handleSequelizeMethod(obj[i + 1]);
......@@ -953,7 +954,7 @@ var QueryGenerator = {
, limit = options.limit
, mainModel = model
, mainQueryItems = []
, mainAttributes = options.attributes && options.attributes.slice(0)
, 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 ?
......@@ -967,10 +968,10 @@ var QueryGenerator = {
if (options.tableAs) {
mainTableAs = this.quoteTable(options.tableAs);
} 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)) {
return this.quoteTable(t[0], t[1]);
}
......@@ -1001,6 +1002,7 @@ var QueryGenerator = {
}
if (Array.isArray(attr) && attr.length === 2) {
attr = attr.slice();
if (attr[0]._isSequelizeMethod) {
attr[0] = self.handleSequelizeMethod(attr[0]);
......@@ -1299,7 +1301,8 @@ var QueryGenerator = {
joinQueryItem = ' ' + self.joinIncludeQuery({
model: mainModel,
subQuery: options.subQuery,
include: include
include: include,
groupedLimit: options.groupedLimit
});
}
......@@ -1333,7 +1336,7 @@ var QueryGenerator = {
options.include.filter(function (include) {
return !include.seperate;
}).forEach(function(include) {
var joinQueries = generateJoinQueries(include, options.tableAs);
var joinQueries = generateJoinQueries(include, mainTableAs);
subJoinQueries = subJoinQueries.concat(joinQueries.subQuery);
mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery);
......@@ -1343,7 +1346,7 @@ var QueryGenerator = {
// If using subQuery select defined subQuery attributes and join subJoinQueries
if (subQuery) {
subQueryItems.push('SELECT ' + subQueryAttributes.join(', ') + ' FROM ' + options.table);
subQueryItems.push('SELECT ' + subQueryAttributes.join(', ') + ' FROM ' + table);
if (mainTableAs) {
subQueryItems.push(' AS ' + mainTableAs);
}
......@@ -1351,7 +1354,30 @@ var QueryGenerator = {
// Else do it the reguar way
} 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) {
mainQueryItems.push(' AS ' + mainTableAs);
}
......@@ -1359,7 +1385,7 @@ var QueryGenerator = {
}
// 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);
if (options.where) {
if (subQuery) {
......@@ -1390,7 +1416,7 @@ var QueryGenerator = {
}
}
// Add ORDER to sub or main query
if (options.order) {
if (options.order && !options.groupedLimit) {
var mainQueryOrder = [];
var subQueryOrder = [];
......@@ -1443,7 +1469,7 @@ var QueryGenerator = {
// Add LIMIT, OFFSET to sub or main query
var limitOrder = this.addLimitAndOffset(options, model);
if (limitOrder) {
if (limitOrder && !options.groupedLimit) {
if (subQuery) {
subQueryItems.push(limitOrder);
} else {
......@@ -1455,7 +1481,7 @@ var QueryGenerator = {
if (subQuery) {
query = 'SELECT ' + mainAttributes.join(', ') + ' FROM (';
query += subQueryItems.join('');
query += ') AS ' + options.tableAs;
query += ') AS ' + mainTableAs;
query += mainJoinQueries.join('');
query += mainQueryItems.join('');
} else {
......@@ -1529,7 +1555,7 @@ var QueryGenerator = {
this.quoteIdentifier(fieldLeft)
].join('.');
if (subQuery && include.parent.subQuery && !include.subQuery) {
if (options.groupedLimit || subQuery && include.parent.subQuery && !include.subQuery) {
if (parentIsTop) {
// The main model attributes is not aliased to a prefix
joinOn = [
......
......@@ -1219,7 +1219,6 @@ Model.prototype.findAll = function(options) {
, originalOptions;
tableNames[this.getTableName(options)] = true;
originalOptions = optClone(options);
options = optClone(options);
_.defaults(options, { hooks: true });
......@@ -1243,7 +1242,6 @@ Model.prototype.findAll = function(options) {
options.hasJoin = true;
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 (options.attributes && !options.raw) {
......@@ -1269,6 +1267,7 @@ Model.prototype.findAll = function(options) {
return this.runHooks('beforeFindAfterOptions', options);
}
}).then(function() {
originalOptions = optClone(options);
options.tableNames = Object.keys(tableNames);
return this.QueryInterface.select(this, this.getTableName(options), options);
}).tap(function(results) {
......@@ -1281,9 +1280,10 @@ Model.prototype.findAll = function(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;
if (!Array.isArray(results)) results = [results];
if (options.plain) results = [results];
return Promise.map(options.include, function (include) {
if (!include.seperate) {
......@@ -1296,7 +1296,7 @@ Model.$findSeperate = function(results, options) {
}, []),
_.assign(
{},
_.omit(options, 'include', 'attributes', 'order', 'where', 'limit'),
_.omit(options, 'include', 'attributes', 'order', 'where', 'limit', 'plain'),
{include: include.include || []}
)
);
......@@ -1304,7 +1304,7 @@ Model.$findSeperate = function(results, options) {
return include.association.get(results, _.assign(
{},
_.omit(options, 'include', 'attributes', 'order', 'where', 'limit'),
_.omit(options, 'include', 'attributes', 'order', 'where', 'limit', 'plain'),
include
)).then(function (map) {
results.forEach(function (result) {
......
......@@ -103,6 +103,21 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
testsql({
model: User,
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({
limit: 3,
model: User,
......@@ -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
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() {
test(util.inspect(options, {depth: 2}), function () {
return expectsql(
sql.selectQuery(
options.table || option.model && option.model.getTableName(),
options.table || model && model.getTableName(),
options,
options.model
),
......@@ -33,11 +33,114 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
attributes: [
'email',
['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 () {
var User = Support.sequelize.define('User', {
name: DataTypes.STRING,
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!