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

Commit a31b5d28 by Mick Hansen

Merge pull request #1299 from overlookmotel/order-by-nested-associations-v2

Order by nested associations
2 parents 5ae3cc26 4d7444b5
var Utils = require("../../utils")
, SqlString = require("../../sql-string")
, daoFactory = require("../../dao-factory")
module.exports = (function() {
var QueryGenerator = {
......@@ -331,7 +332,14 @@ module.exports = (function() {
/*
Quote an object based on its type. This is a more general version of quoteIdentifiers
Strings: should proxy to quoteIdentifiers
Arrays: First argument should be qouted, second argument should be append without quoting
Arrays:
* Expects array in the form: [<model> (optional), <model> (optional),... String, String (optional)]
Each <model> can be a daoFactory or an object {model: DaoFactory, as: String}, matching include
* Zero or more models can be included in the array and are used to trace a path through the tree of
included nested associations. This produces the correct table name for the ORDER BY/GROUP BY SQL
and quotes it.
* If a single string is appended to end of array, it is quoted.
If two strings appended, the 1st string is quoted, the 2nd string unquoted.
Objects:
* If raw is set, that value should be returned verbatim, without quoting
* If fn is set, the string should start with the value of fn, starting paren, followed by
......@@ -339,14 +347,71 @@ module.exports = (function() {
unless they are themselves objects
* If direction is set, should be prepended
Currently this function is only used for ordering / grouping columns, but it could
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, force) {
quote: function(obj, parent, force) {
if (Utils._.isString(obj)) {
return this.quoteIdentifiers(obj, force)
} else if (Array.isArray(obj)) {
return this.quote(obj[0], force) + ' ' + obj[1]
// loop through array, adding table names of models to quoted
// (checking associations to see if names should be singularised or not)
var quoted = []
, i
, len = obj.length
for (i = 0; i < len - 1; i++) {
var item = obj[i]
if (Utils._.isString(item) || item instanceof Utils.fn || item instanceof Utils.col || item instanceof Utils.literal || item instanceof Utils.cast || 'raw' in item) {
break
}
if (item instanceof daoFactory) {
item = {model: item}
}
// find applicable association for linking parent to this model
var model = item.model
, as
, associations = parent.associations
, association
if (item.hasOwnProperty('as')) {
as = item.as
association = Utils._.find(associations, function(association, associationName) {
return association.target === model && associationName === as
})
} else {
association = Utils._.find(associations, function(association, associationName) {
return association.target === model ?
associationName === (
association.doubleLinked ?
association.combinedName:
(
association.isSingleAssociation ?
Utils.singularize(model.tableName, model.options.language) :
parent.tableName + model.tableName
)
) :
association.targetAssociation && association.targetAssociation.through === model
})
// NB association.target !== model clause below is to singularize names of through tables in hasMany-hasMany joins
as = (association && (association.isSingleAssociation || association.target !== model)) ? Utils.singularize(model.tableName, model.options.language) : model.tableName
}
quoted[i] = as
if (!association) {
throw new Error('\'' + quoted.join('.') + '\' in order / group clause is not valid association')
}
parent = model
}
// add 1st string as quoted, 2nd as unquoted raw
var sql = (i > 0 ? this.quoteIdentifier(quoted.join('.')) + '.' : '') + this.quote(obj[i], parent, force)
if (i < len - 1) {
sql += ' ' + obj[i + 1]
}
return sql
} else if (obj instanceof Utils.fn || obj instanceof Utils.col || obj instanceof Utils.literal || obj instanceof Utils.cast) {
return obj.toString(this)
} else if (Utils._.isObject(obj) && 'raw' in obj) {
......@@ -497,12 +562,12 @@ module.exports = (function() {
}
if (attr instanceof Utils.fn || attr instanceof Utils.col) {
return self.quote(attr)
return attr.toString(self)
}
if(Array.isArray(attr) && attr.length == 2) {
if (attr[0] instanceof Utils.fn || attr[0] instanceof Utils.col) {
attr[0] = self.quote(attr[0])
attr[0] = attr[0].toString(self)
addTable = false
}
attr = [attr[0], this.quoteIdentifier(attr[1])].join(' as ')
......@@ -703,7 +768,7 @@ module.exports = (function() {
// 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) }.bind(this)).join(', ') : options.group
options.group = Array.isArray(options.group) ? options.group.map(function (t) { return this.quote(t, factory) }.bind(this)).join(', ') : options.group
if (subQuery) {
subQueryItems.push(" GROUP BY " + options.group)
} else {
......@@ -723,7 +788,7 @@ module.exports = (function() {
// Add ORDER to sub or main query
if (options.order) {
options.order = Array.isArray(options.order) ? options.order.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.order
options.order = Array.isArray(options.order) ? options.order.map(function (t) { return this.quote(t, factory) }.bind(this)).join(', ') : options.order
if (subQuery) {
subQueryItems.push(" ORDER BY " + options.order)
......@@ -950,9 +1015,9 @@ module.exports = (function() {
})
if (options.include) {
return this.quoteIdentifier(keyParts.join('.')) + '.' + this.quote(attributePart);
return this.quoteIdentifier(keyParts.join('.')) + '.' + this.quoteIdentifiers(attributePart)
}
return this.quoteIdentifiers(dao.tableName + '.' + attributePart);
return this.quoteIdentifiers(dao.tableName + '.' + attributePart)
},
getConditionalJoins: function(options, originalDao){
......
......@@ -534,6 +534,9 @@ var Utils = module.exports = {
},
col: function (col) {
if (arguments.length > 1) {
col = Array.prototype.slice.call(arguments);
}
this.col = col
},
......@@ -588,23 +591,27 @@ Utils.cast.prototype.toString = function(queryGenerator) {
return 'CAST(' + this.val + ' AS ' + this.type.toUpperCase() + ')'
}
Utils.fn.prototype.toString = function(queryGenerator) {
Utils.fn.prototype.toString = function(queryGenerator, parentModel) {
return this.fn + '(' + this.args.map(function (arg) {
if (arg instanceof Utils.fn || arg instanceof Utils.col) {
return arg.toString(queryGenerator)
return arg.toString(queryGenerator, parentModel)
} else {
return queryGenerator.escape(arg)
}
}).join(', ') + ')'
}
Utils.col.prototype.toString = function (queryGenerator) {
if (this.col.indexOf('*') === 0) {
Utils.col.prototype.toString = function (queryGenerator, parentModel) {
if (Array.isArray(this.col)) {
if (!parent) {
throw new Error('Cannot call Sequelize.col() with array outside of order / group clause')
}
} else if (this.col.indexOf('*') === 0) {
return '*'
}
return queryGenerator.quote(this.col)
return queryGenerator.quote(this.col, parentModel)
}
Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter")
Utils.QueryChainer = require(__dirname + "/query-chainer")
Utils.Lingo = require("lingo")
\ No newline at end of file
Utils.Lingo = require("lingo")
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!