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

Commit 62ad873a by Sascha Depold

Merge branch 'refactorings/eager_loading'

2 parents 6c06ac2a 7ef6a12d
......@@ -19,7 +19,5 @@ env:
language: node_js
node_js:
# - 0.6
- 0.8
# - 0.9
......@@ -14,7 +14,7 @@
- [BUG] fixed updateAttributes for models/tables without primary key (thanks to durango)
- [BUG] fixed the location of the foreign key when using belongsTo (thanks to ricardograca)
- [BUG] don't return timestamps if only specific attributes have been seleceted (thanks to ricardograca)
- [FEATURE] added association prefetching for find and findAll
- [FEATURE] added association prefetching /eager loading for find and findAll. #465
- [FEATURE] it's now possible to use callbacks of async functions inside migrations (thanks to mphilpot)
- [FEATURE] improved comfort of sequelize.query. just pass an sql string to it and wait for the result
- [FEATURE] Migrations now understand NODE_ENV (thanks to gavri)
......
/**
The entry point.
@module sequelize
**/
module.exports = require("./lib/sequelize")
......@@ -19,11 +19,11 @@ Mixin.hasOne = function(associatedDAO, options) {
Mixin.belongsTo = function(associatedDAO, options) {
// the id is in this table
var association = new BelongsTo(this, associatedDAO, Utils._.extend((options||{}), this.options))
var association = new BelongsTo(this, associatedDAO, Utils._.extend((options || {}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype);
association.injectSetter(this.DAO.prototype);
association.injectGetter(this.DAO.prototype)
association.injectSetter(this.DAO.prototype)
return this
}
......@@ -33,8 +33,8 @@ Mixin.hasMany = function(associatedDAO, options) {
var association = new HasMany(this, associatedDAO, Utils._.extend((options||{}), this.options))
this.associations[association.associationAccessor] = association.injectAttributes()
association.injectGetter(this.DAO.prototype);
association.injectSetter(this.DAO.prototype);
association.injectGetter(this.DAO.prototype)
association.injectSetter(this.DAO.prototype)
return this
}
......
......@@ -143,25 +143,25 @@ module.exports = (function() {
var hasJoin = false
var options = Utils._.clone(options)
if ((typeof options === 'object') && (options.hasOwnProperty('include'))) {
var includes = options.include
if (typeof options === 'object') {
hasJoin = true
options.include = {}
includes.forEach(function(daoName) {
options.include[daoName] = this.daoFactoryManager.getDAO(daoName)
if (options.hasOwnProperty('include')) {
hasJoin = true
if (!options.include[daoName]) {
options.include[daoName] = this.getAssociationByAlias(daoName).target
}
options.include = options.include.map(function(include) {
return validateIncludedElement.call(this, include)
}.bind(this))
}
// whereCollection is used for non-primary key updates
this.options.whereCollection = options.where || null
}
return this.QueryInterface.select(this, this.tableName, options, { type: 'SELECT', hasJoin: hasJoin })
return this.QueryInterface.select(this, this.tableName, options, {
type: 'SELECT',
hasJoin: hasJoin
})
}
//right now, the caller (has-many-double-linked) is in charge of the where clause
......@@ -175,8 +175,16 @@ module.exports = (function() {
return this.QueryInterface.select(this, [this.tableName, joinTableName], optcpy, { type: 'SELECT' })
}
/**
* Search for an instance.
*
* @param {Object} options Options to describe the scope of the search.
* @param {Array} include A list of associations which shall get eagerly loaded. Supported is either { include: [ DaoFactory1, DaoFactory2, ...] } or { include: [ { daoFactory: DaoFactory1, as: 'Alias' } ] }.
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`.
*/
DAOFactory.prototype.find = function(options) {
var hasJoin = false
// no options defined?
// return an emitter which emits null
if ([null, undefined].indexOf(options) !== -1) {
......@@ -210,20 +218,13 @@ module.exports = (function() {
options = { where: parsedId }
} else if (typeof options === 'object') {
var options = Utils._.clone(options)
var includes
options = Utils._.clone(options)
if (options.hasOwnProperty('include')) {
hasJoin = true
includes = options.include
options.include = {}
includes.forEach(function(daoName) {
options.include[daoName] = this.daoFactoryManager.getDAO(daoName)
if (!options.include[daoName]) {
options.include[daoName] = this.getAssociationByAlias(daoName).target
}
options.include = options.include.map(function(include) {
return validateIncludedElement.call(this, include)
}.bind(this))
}
......@@ -233,7 +234,11 @@ module.exports = (function() {
options.limit = 1
return this.QueryInterface.select(this, this.tableName, options, { plain: true, type: 'SELECT', hasJoin: hasJoin })
return this.QueryInterface.select(this, this.tableName, options, {
plain: true,
type: 'SELECT',
hasJoin: hasJoin
})
}
DAOFactory.prototype.count = function(options) {
......@@ -260,7 +265,7 @@ module.exports = (function() {
}
DAOFactory.prototype.build = function(values, options) {
options = options || {isNewRecord: true}
options = options || { isNewRecord: true }
var self = this
, instance = new this.DAO(values, this.options, options.isNewRecord)
......@@ -283,7 +288,7 @@ module.exports = (function() {
}).success(function (instance) {
if (instance === null) {
for (var attrname in defaults) {
params[attrname] = defaults[attrname];
params[attrname] = defaults[attrname]
}
self.create(params)
......@@ -292,7 +297,7 @@ module.exports = (function() {
})
.error( function (error) {
emitter.emit('error', error)
});
})
} else {
emitter.emit('success', instance)
}
......@@ -368,6 +373,45 @@ module.exports = (function() {
}.bind(this))
}
var validateIncludedElement = function(include) {
if (include instanceof DAOFactory) {
include = { daoFactory: include, as: include.tableName }
}
if (typeof include === 'object') {
if (include.hasOwnProperty('model')) {
include.daoFactory = include.model
delete include.model
}
if (include.hasOwnProperty('daoFactory') && (include.hasOwnProperty('as'))) {
var usesAlias = (include.as !== include.daoFactory.tableName)
, association = (usesAlias ? this.getAssociationByAlias(include.as) : this.getAssociation(include.daoFactory))
// check if the current daoFactory is actually associated with the passed daoFactory
if (!!association && (!association.options.as || (association.options.as === include.as))) {
include.association = association
return include
} else {
var msg = include.daoFactory.name
if (usesAlias) {
msg += " (" + include.as + ")"
}
msg += " is not associated to " + this.name + "!"
throw new Error(msg)
}
} else {
throw new Error('Include malformed. Expected attributes: daoFactory, as!')
}
} else {
throw new Error('Include unexpected. Element has to be either an instance of DAOFactory or an object.')
}
}
Utils._.extend(DAOFactory.prototype, require("./associations/mixin"))
return DAOFactory
......
......@@ -141,29 +141,21 @@ module.exports = (function() {
* @return {String} The found tableName / alias.
*/
var findTableNameInAttribute = function(attribute) {
var tableName = null
this.sequelize.daoFactoryManager.daos.forEach(function(daoFactory) {
if (!!tableName) {
return
} else if (attribute.indexOf(daoFactory.tableName + ".") === 0) {
tableName = daoFactory.tableName
} else if (attribute.indexOf(Utils.singularize(daoFactory.tableName) + ".") === 0) {
tableName = Utils.singularize(daoFactory.tableName)
} else {
for (var associationName in daoFactory.associations) {
if (daoFactory.associations.hasOwnProperty(associationName)) {
var association = daoFactory.associations[associationName]
if (attribute.indexOf(association.options.as + ".") === 0) {
tableName = association.options.as
}
}
}
if (!this.options.include) {
return null
}
var tableNames = this.options.include.map(function(include) {
return include.as
}).filter(function(include) {
return attribute.indexOf(include + '.') === 0
})
return tableName
if (tableNames.length === 1) {
return tableNames[0]
} else {
return null
}
}
var queryResultHasJoin = function(results) {
......
......@@ -131,49 +131,24 @@ module.exports = (function() {
if (options.include) {
var optAttributes = [options.table + '.*']
for (var daoName in options.include) {
if (options.include.hasOwnProperty(daoName)) {
var dao = options.include[daoName]
, daoFactory = dao.daoFactoryManager.getDAO(tableName, {
attribute: 'tableName'
options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
var template = Utils._.template("`<%= as %>`.`<%= attr %>` AS `<%= as %>.<%= attr %>`")
return template({ as: include.as, attr: attr })
})
, _tableName = Utils.addTicks(dao.tableName)
, association = dao.getAssociation(daoFactory)
if (association.connectorDAO) {
var foreignIdentifier = Object.keys(association.connectorDAO.rawAttributes).filter(function(attrName) {
return (!!attrName.match(/.+Id$/) || !!attrName.match(/.+_id$/)) && (attrName !== association.identifier)
})[0]
query += ' LEFT OUTER JOIN ' + Utils.addTicks(association.connectorDAO.tableName) + ' ON '
query += Utils.addTicks(association.connectorDAO.tableName) + '.'
query += Utils.addTicks(foreignIdentifier) + '='
query += Utils.addTicks(table) + '.' + Utils.addTicks('id')
query += ' LEFT OUTER JOIN ' + Utils.addTicks(dao.tableName) + ' ON '
query += Utils.addTicks(dao.tableName) + '.'
query += Utils.addTicks('id') + '='
query += Utils.addTicks(association.connectorDAO.tableName) + '.' + Utils.addTicks(association.identifier)
} else {
query += ' LEFT OUTER JOIN ' + Utils.addTicks(dao.tableName) + ' ON '
query += Utils.addTicks(association.associationType === 'BelongsTo' ? dao.tableName : tableName) + '.'
query += Utils.addTicks(association.identifier) + '='
query += Utils.addTicks(association.associationType === 'BelongsTo' ? tableName : dao.tableName) + '.' + Utils.addTicks('id')
}
var aliasAssoc = daoFactory.getAssociationByAlias(daoName)
, aliasName = !!aliasAssoc ? Utils.addTicks(daoName) : _tableName
optAttributes = optAttributes.concat(attributes)
optAttributes = optAttributes.concat(
Object.keys(dao.attributes).map(function(attr) {
return '' +
[_tableName, Utils.addTicks(attr)].join('.') +
' AS ' +
Utils.addTicks([aliasName, attr].join('.'))
var joinQuery = " LEFT OUTER JOIN `<%= table %>` AS `<%= as %>` ON `<%= tableLeft %>`.`<%= attrLeft %>` = `<%= tableRight %>`.`<%= attrRight %>`"
query += Utils._.template(joinQuery)({
table: include.daoFactory.tableName,
as: include.as,
tableLeft: ((include.association.associationType === 'BelongsTo') ? include.as : tableName),
attrLeft: 'id',
tableRight: ((include.association.associationType === 'BelongsTo') ? tableName : include.as),
attrRight: include.association.identifier
})
})
)
}
}
options.attributes = optAttributes.join(', ')
}
......
......@@ -31,7 +31,9 @@ module.exports = (function() {
}
var query = new Query(this.client, this.sequelize, callee, options || {})
self.pendingQueries += 1
return query.run(sql)
.success(function() { self.endQuery.call(self) })
.error(function() { self.endQuery.call(self) })
......
......@@ -233,49 +233,24 @@ module.exports = (function() {
if (options.include) {
var optAttributes = [options.table + '.*']
for (var daoName in options.include) {
if (options.include.hasOwnProperty(daoName)) {
var dao = options.include[daoName]
, daoFactory = dao.daoFactoryManager.getDAO(tableName, {
attribute: 'tableName'
options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
var template = Utils._.template('"<%= as %>"."<%= attr %>" AS "<%= as %>.<%= attr %>"')
return template({ as: include.as, attr: attr })
})
, _tableName = addQuotes(dao.tableName)
, association = dao.getAssociation(daoFactory)
if (association.connectorDAO) {
var foreignIdentifier = Object.keys(association.connectorDAO.rawAttributes).filter(function(attrName) {
return (!!attrName.match(/.+Id$/) || !!attrName.match(/.+_id$/)) && (attrName !== association.identifier)
})[0]
query += ' LEFT OUTER JOIN ' + addQuotes(association.connectorDAO.tableName) + ' ON '
query += addQuotes(association.connectorDAO.tableName) + '.'
query += addQuotes(foreignIdentifier) + '='
query += addQuotes(table) + '.' + addQuotes('id')
query += ' LEFT OUTER JOIN ' + addQuotes(dao.tableName) + ' ON '
query += addQuotes(dao.tableName) + '.'
query += addQuotes('id') + '='
query += addQuotes(association.connectorDAO.tableName) + '.' + addQuotes(association.identifier)
} else {
query += ' LEFT OUTER JOIN ' + addQuotes(dao.tableName) + ' ON '
query += addQuotes(association.associationType === 'BelongsTo' ? dao.tableName : tableName) + '.'
query += addQuotes(association.identifier) + '='
query += addQuotes(association.associationType === 'BelongsTo' ? tableName : dao.tableName) + '.' + addQuotes('id')
}
var aliasAssoc = daoFactory.getAssociationByAlias(daoName)
, aliasName = !!aliasAssoc ? addQuotes(daoName) : _tableName
optAttributes = optAttributes.concat(attributes)
optAttributes = optAttributes.concat(
Object.keys(dao.attributes).map(function(attr) {
return '' +
[_tableName, addQuotes(attr)].join('.') +
' AS "' +
removeQuotes([aliasName, attr].join('.')) + '"'
var joinQuery = ' LEFT OUTER JOIN "<%= table %>" AS "<%= as %>" ON "<%= tableLeft %>"."<%= attrLeft %>" = "<%= tableRight %>"."<%= attrRight %>"'
query += Utils._.template(joinQuery)({
table: include.daoFactory.tableName,
as: include.as,
tableLeft: ((include.association.associationType === 'BelongsTo') ? include.as : tableName),
attrLeft: 'id',
tableRight: ((include.association.associationType === 'BelongsTo') ? tableName : include.as),
attrRight: include.association.identifier
})
})
)
}
}
options.attributes = optAttributes.join(', ')
}
......
......@@ -198,14 +198,17 @@ module.exports = (function() {
}
QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) {
options = options || {}
var sql = this.QueryGenerator.selectQuery(tableName, options)
queryOptions = Utils._.extend({}, queryOptions, { include: options.include })
return queryAndEmit.call(this, [sql, factory, queryOptions], 'select')
}
QueryInterface.prototype.rawSelect = function(tableName, options, attributeSelector) {
var self = this
if (attributeSelector == undefined) {
if (attributeSelector === undefined) {
throw new Error('Please pass an attribute selector!')
}
......
......@@ -10,20 +10,28 @@ if (typeof process != 'undefined' && parseFloat(process.version.replace('v', '')
module.exports = (function() {
/**
Main constructor of the project.
Main class of the project.
Params:
@param {String} database The name of the database.
@param {String} username The username which is used to authenticate against the database.
@param {String} [password] The password which is used to authenticate against the database.
@param {Object} [options] An object with options.
- `database`
- `username`
- `password`, optional, default: null
- `options`, optional, default: {}
@example
// without password and options
var sequelize = new Sequelize('database', 'username')
Examples:
// without options
var sequelize = new Sequelize('database', 'username', 'password')
mymodule.write('foo')
mymodule.write('foo', { stream: process.stderr })
// without password / with blank password
var sequelize = new Sequelize('database', 'username', null, {})
// with password and options
var sequelize = new Sequelize('my_database', 'john', 'doe', {})
@class Sequelize
@constructor
*/
var Sequelize = function(database, username, password, options) {
this.options = Utils._.extend({
......
......@@ -36,8 +36,8 @@
"mysql": "~2.0.0-alpha7",
"pg": "~0.10.2",
"buster": "~0.6.0",
"dox-foundation": "~0.3.0",
"watchr": "~2.2.0"
"watchr": "~2.2.0",
"yuidocjs": "~0.3.36"
},
"keywords": [
"mysql",
......@@ -55,7 +55,7 @@
"test-buster-postgres": "DIALECT=postgres ./node_modules/.bin/buster-test",
"test-buster-postgres-native": "DIALECT=postgres-native ./node_modules/.bin/buster-test",
"test-buster-sqlite": "DIALECT=sqlite ./node_modules/.bin/buster-test",
"generate-docs": "node_modules/.bin/dox-foundation --source ./lib --target ./docs --title Sequelize"
"docs": "node_modules/.bin/yuidoc . -o docs"
},
"bin": {
"sequelize": "bin/sequelize"
......
......@@ -2,6 +2,7 @@ const Sequelize = require(__dirname + "/../index")
, DataTypes = require(__dirname + "/../lib/data-types")
, config = require(__dirname + "/config/config")
, fs = require('fs')
, buster = require("buster")
var BusterHelpers = module.exports = {
Sequelize: Sequelize,
......@@ -95,5 +96,14 @@ var BusterHelpers = module.exports = {
} else {
throw new Error('Undefined expectation for "' + dialect + '"!')
}
},
assertException: function(block, msg) {
try {
block()
throw new Error('Passed function did not throw an error')
} catch(e) {
buster.assert.equals(e.message, msg)
}
}
}
if(typeof require === 'function') {
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!