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

Commit 9ee88b92 by Mick Hansen

[merge]

2 parents feb56a6e a343f106
Notice: All 1.7.x changes are present in 2.0.x aswell
# v1.7.0-rc5 (next)
- [BUG] Encode binary strings as bytea in postgres, and fix a case where using a binary as key in an association would produce an error [1364](https://github.com/sequelize/sequelize/pull/1364). Thanks to @SohumB
# v1.7.0-rc5
- [FEATURE] sync() now correctly returns with an error when foreign key constraints reference unknown tables
- [BUG] sync() no longer fails with foreign key constraints references own table (toposort self-dependency error)
- [FEATURE] makes it possible to specify exactly what timestamp attributes you want to utilize [#1334](https://github.com/sequelize/sequelize/pull/1334)
......
......@@ -17,8 +17,10 @@ module.exports = (function() {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.source.options.underscored)
}
if (!this.as) {
this.as = this.options.as = Utils.singularize(this.target.tableName, this.target.options.language)
if (this.as) {
this.isAliased = true
} else {
this.as = Utils.singularize(this.target.tableName, this.target.options.language)
}
this.associationAccessor = this.isSelfAssociation
......
......@@ -21,9 +21,10 @@ module.exports = (function() {
this.isMultiAssociation = true
this.isSelfAssociation = this.source === this.target
this.doubleLinked = false
this.as = this.options.as
this.combinedTableName = Utils.combineTableNames(
this.source.tableName,
this.isSelfAssociation ? (this.options.as || this.target.tableName) : this.target.tableName
this.isSelfAssociation ? (this.as || this.target.tableName) : this.target.tableName
)
/*
......@@ -48,7 +49,7 @@ module.exports = (function() {
* Determine associationAccessor, especially for include options to identify the correct model
*/
this.associationAccessor = this.options.as
this.associationAccessor = this.as
if (!this.associationAccessor && (typeof this.through === "string" || Object(this.through) === this.through)) {
this.associationAccessor = this.through.tableName || this.through
}
......@@ -112,16 +113,20 @@ module.exports = (function() {
this.options.tableName = this.combinedName = (this.through === Object(this.through) ? this.through.tableName : this.through)
var as = (this.options.as || Utils.pluralize(this.target.tableName, this.target.options.language))
if (this.as) {
this.isAliased = true
} else {
this.as = Utils.pluralize(this.target.tableName, this.target.options.language)
}
this.accessors = {
get: Utils._.camelize('get_' + as),
set: Utils._.camelize('set_' + as),
add: Utils._.camelize(Utils.singularize('add_' + as, this.target.options.language)),
create: Utils._.camelize(Utils.singularize('create_' + as, this.target.options.language)),
remove: Utils._.camelize(Utils.singularize('remove_' + as, this.target.options.language)),
hasSingle: Utils._.camelize(Utils.singularize('has_' + as, this.target.options.language)),
hasAll: Utils._.camelize('has_' + as)
get: Utils._.camelize('get_' + this.as),
set: Utils._.camelize('set_' + this.as),
add: Utils._.camelize(Utils.singularize('add_' + this.as, this.target.options.language)),
create: Utils._.camelize(Utils.singularize('create_' + this.as, this.target.options.language)),
remove: Utils._.camelize(Utils.singularize('remove_' + this.as, this.target.options.language)),
hasSingle: Utils._.camelize(Utils.singularize('has_' + this.as, this.target.options.language)),
hasAll: Utils._.camelize('has_' + this.as)
}
}
......
......@@ -11,25 +11,28 @@ module.exports = (function() {
this.options = options
this.isSingleAssociation = true
this.isSelfAssociation = (this.source.tableName == this.target.tableName)
this.as = this.options.as
if (this.isSelfAssociation && !this.options.foreignKey && !!this.options.as) {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.options.as, this.target.options.language) + "Id", this.options.underscored)
if (this.isSelfAssociation && !this.options.foreignKey && !!this.as) {
this.options.foreignKey = Utils._.underscoredIf(Utils.singularize(this.as, this.target.options.language) + "Id", this.options.underscored)
}
if (!this.options.as) {
this.options.as = Utils.singularize(this.target.tableName, this.target.options.language)
if (this.as) {
this.isAliased = true
} else {
this.as = Utils.singularize(this.target.tableName, this.target.options.language)
}
this.associationAccessor = this.isSelfAssociation
? Utils.combineTableNames(this.target.tableName, this.options.as)
: this.options.as
? Utils.combineTableNames(this.target.tableName, this.as)
: this.as
this.options.useHooks = options.useHooks
this.accessors = {
get: Utils._.camelize('get_' + this.options.as),
set: Utils._.camelize('set_' + this.options.as),
create: Utils._.camelize('create_' + this.options.as)
get: Utils._.camelize('get_' + this.as),
set: Utils._.camelize('set_' + this.as),
create: Utils._.camelize('create_' + this.as)
}
}
......
......@@ -59,36 +59,32 @@ Mixin.hasMany = function(associatedDAOFactory, options) {
return this
}
Mixin.getAssociation = function(target) {
var result = null
Mixin.getAssociation = function(target, alias) {
for (var associationName in this.associations) {
if (this.associations.hasOwnProperty(associationName)) {
var association = this.associations[associationName]
if (!result && (association.target === target)) {
result = association
if (association.target === target && (alias === undefined ? !association.isAliased : association.as === alias)) {
return association
}
}
}
return result
return null
}
Mixin.getAssociationByAlias = function(alias) {
var result = null
for (var associationName in this.associations) {
if (this.associations.hasOwnProperty(associationName)) {
var association = this.associations[associationName]
if (!result && (association.options.as === alias)) {
result = association
if (association.as === alias) {
return association
}
}
}
return result
return null
}
/* example for instance methods:
......
......@@ -526,7 +526,10 @@ module.exports = (function() {
options = { where: parsedId }
} else if (typeof options === 'object') {
options = Utils._.clone(options)
options = Utils._.clone(options, function(thing) {
if (Buffer.isBuffer(thing)) { return thing }
return undefined;
})
if (options.hasOwnProperty('include') && options.include) {
hasJoin = true
......@@ -1353,100 +1356,85 @@ module.exports = (function() {
var validateIncludedElement = function(include, parent) {
if (include instanceof DAOFactory) {
include = { daoFactory: include, as: include.tableName }
include = { daoFactory: include }
}
if (typeof parent === "undefined") {
parent = this
}
if (typeof include === 'object') {
if (include.hasOwnProperty('model')) {
include.daoFactory = include.model
delete include.model
}
if (typeof include !== 'object') {
throw new Error('Include unexpected. Element has to be either an instance of DAOFactory or an object.')
}
if (!include.hasOwnProperty('as')) {
include.as = include.daoFactory.tableName
}
if (include.hasOwnProperty('model')) {
include.daoFactory = include.model
delete include.model
} else if (!include.hasOwnProperty('daoFactory')) {
throw new Error('Include malformed. Expected attributes: daoFactory, as!')
}
if (include.hasOwnProperty('attributes')) {
var primaryKeys;
if (include.daoFactory.hasPrimaryKeys) {
primaryKeys = []
for (var field_name in include.daoFactory.primaryKeys) {
primaryKeys.push(field_name)
}
} else {
primaryKeys = ['id']
if (include.hasOwnProperty('attributes')) {
var primaryKeys;
if (include.daoFactory.hasPrimaryKeys) {
primaryKeys = []
for (var field_name in include.daoFactory.primaryKeys) {
primaryKeys.push(field_name)
}
include.attributes = include.attributes.concat(primaryKeys)
} else {
include.attributes = Object.keys(include.daoFactory.attributes)
primaryKeys = ['id']
}
include.attributes = include.attributes.concat(primaryKeys)
} else {
include.attributes = Object.keys(include.daoFactory.attributes)
}
// pseudo include just needed the attribute logic, return
if (include._pseudo) return include
if (include.hasOwnProperty('daoFactory') && (include.hasOwnProperty('as'))) {
var usesAlias = (include.as !== include.daoFactory.tableName)
, association = (usesAlias ? parent.getAssociationByAlias(include.as) : parent.getAssociation(include.daoFactory))
// If single (1:1) association, we singularize the alias, so it will match the automatically generated alias of belongsTo/HasOne
if (association && !usesAlias && association.isSingleAssociation) {
include.as = Utils.singularize(include.daoFactory.tableName, include.daoFactory.options.language)
// pseudo include just needed the attribute logic, return
if (include._pseudo) return include
// check if the current daoFactory is actually associated with the passed daoFactory - or it's a pseudo include
var association = parent.getAssociation(include.daoFactory, include.as)
if (association) {
include.association = association
include.as = association.as
// If through, we create a pseudo child include, to ease our parsing later on
if (Object(include.association.through) === include.association.through) {
if (!include.include) include.include = []
var through = include.association.through
include.through = {
daoFactory: through,
as: Utils.singularize(through.tableName, through.options.language),
association: {
isSingleAssociation: true
},
_pseudo: true
}
// check if the current daoFactory is actually associated with the passed daoFactory - or it's a pseudo include
if (association && (!association.options.as || (association.options.as === include.as))) {
include.association = association
// If through, we create a pseudo child include, to ease our parsing later on
if (Object(include.association.through) === include.association.through) {
if (!include.include) include.include = []
var through = include.association.through
include.through = {
daoFactory: through,
as: Utils.singularize(through.tableName, through.options.language),
association: {
isSingleAssociation: true
},
_pseudo: true
}
include.include.push(include.through)
}
include.include.push(include.through)
}
if (include.required === undefined) {
include.required = false
if (include.where) {
include.required = true
}
}
if (include.required === undefined) {
include.required = !!include.where
}
// Validate child includes
if (include.hasOwnProperty('include')) {
validateIncludedElements(include)
}
// Validate child includes
if (include.hasOwnProperty('include')) {
validateIncludedElements(include)
}
return include
} else {
var msg = include.daoFactory.name
return include
} else {
var msg = include.daoFactory.name
if (usesAlias) {
msg += " (" + include.as + ")"
}
if (include.as) {
msg += " (" + include.as + ")"
}
msg += " is not associated to " + this.name + "!"
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.')
throw new Error(msg)
}
}
......@@ -1462,11 +1450,14 @@ module.exports = (function() {
var optClone = function (options) {
return Utils._.cloneDeep(options, function (elem) {
// The DAOFactories used for include are pass by ref, so don't clone them. Otherwise return undefined, meaning, 'handle this lodash'
// The DAOFactories used for include are pass by ref, so don't clone them.
if (elem instanceof DAOFactory || elem instanceof Utils.col || elem instanceof Utils.literal || elem instanceof Utils.cast || elem instanceof Utils.fn || elem instanceof Utils.and || elem instanceof Utils.or) {
return elem
}
// Unfortunately, lodash.cloneDeep doesn't preserve Buffer.isBuffer, which we have to rely on for binary data
if (Buffer.isBuffer(elem)) { return elem; }
// Otherwise return undefined, meaning, 'handle this lodash'
return undefined
})
}
......
......@@ -356,58 +356,44 @@ module.exports = (function() {
} else if (Array.isArray(obj)) {
// loop through array, adding table names of models to quoted
// (checking associations to see if names should be singularised or not)
var quoted = []
, i
var tableNames = []
, parentAssociation
, len = obj.length
for (i = 0; i < len - 1; i++) {
for (var 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
}
var model, as
if (item instanceof daoFactory) {
item = {model: item}
model = item
} else {
model = item.model
as = item.as
}
// 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
})
// check if model provided is through table
var association
if (!as && parentAssociation && parentAssociation.through === model) {
association = {as: Utils.singularize(model.tableName, model.options.language)}
} 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
// find applicable association for linking parent to this model
association = parent.getAssociation(model, as)
}
quoted[i] = as
if (!association) {
throw new Error('\'' + quoted.join('.') + '\' in order / group clause is not valid association')
if (association) {
tableNames[i] = association.as
parent = model
parentAssociation = association
} else {
tableNames[i] = model.tableName
throw new Error('\'' + tableNames.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)
var sql = (i > 0 ? this.quoteIdentifier(tableNames.join('.')) + '.' : '') + this.quote(obj[i], parent, force)
if (i < len - 1) {
sql += ' ' + obj[i + 1]
}
......@@ -915,6 +901,8 @@ module.exports = (function() {
result = this.hashToWhereConditions(smth)
} else if (typeof smth === "string") {
result = smth
} else if (Buffer.isBuffer(smth)) {
result = this.escape(smth)
} else if (Array.isArray(smth)) {
var treatAsAnd = smth.reduce(function(treatAsAnd, arg) {
if (treatAsAnd) {
......
......@@ -173,10 +173,10 @@ module.exports = (function() {
}
}
if ( col && ((!!coltype && coltype.match(/\[\]$/) !== null) || (col.toString().match(/\[\]$/) !== null))) {
_value = 'ARRAY[' + value.map(this.escape).join(',') + ']::' + (!!col.type ? col.type : col.toString())
_value = 'ARRAY[' + value.map(this.escape.bind(this)).join(',') + ']::' + (!!col.type ? col.type : col.toString())
return [_key, _value].join(" && ")
} else {
_value = "(" + value.map(this.escape).join(',') + ")"
_value = "(" + value.map(this.escape.bind(this)).join(',') + ")"
return [_key, _value].join(" " + logicResult + " ")
}
},
......@@ -789,7 +789,7 @@ module.exports = (function() {
dataType = dataType.replace(/NOT NULL/, '')
}
if (dataType.lastIndexOf('BLOB') !== -1) {
if (dataType.lastIndexOf('BLOB') !== -1 || dataType.lastIndexOf('BINARY') !== -1) {
dataType = 'bytea'
}
......
......@@ -416,7 +416,7 @@ module.exports = (function() {
.query('SELECT 1+1 AS result', null, { raw: true, plain: true })
.complete(function(err, result) {
if (!!err) {
emitter.emit('error', new Error('Invalid credentials.'))
emitter.emit('error', new Error(err))
} else {
emitter.emit('success')
}
......@@ -424,6 +424,8 @@ module.exports = (function() {
}).run()
}
Sequelize.prototype.validate = Sequelize.prototype.authenticate;
Sequelize.fn = Sequelize.prototype.fn = function (fn) {
return new Utils.fn(fn, Array.prototype.slice.call(arguments, 1))
}
......
......@@ -180,7 +180,7 @@ var Utils = module.exports = {
_where[i].in = _where[i].in || []
_where[i].in.concat(where[i])
}
else if (type === "object") {
else if (Utils.isHash(where[i])) {
Object.keys(where[i]).forEach(function(ii) {
logic = self.getWhereLogic(ii, where[i][ii]);
......@@ -212,7 +212,7 @@ var Utils = module.exports = {
}
})
}
else if (type === "string" || type === "number" || type === "boolean") {
else if (type === "string" || type === "number" || type === "boolean" || Buffer.isBuffer(where[i])) {
_where[i].lazy = _where[i].lazy || {conditions: [], bindings: []}
if (type === "boolean") {
_where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ' + SqlString.escape(where[i], false, null, dialect) // sqlite is special
......@@ -321,7 +321,7 @@ var Utils = module.exports = {
}
},
isHash: function(obj) {
return Utils._.isObject(obj) && !Array.isArray(obj);
return Utils._.isObject(obj) && !Array.isArray(obj) && !Buffer.isBuffer(obj);
},
hasChanged: function(attrValue, value) {
//If attribute value is Date, check value as a date
......@@ -348,7 +348,7 @@ var Utils = module.exports = {
if (['number', 'string'].indexOf(typeof arg) !== -1) {
result = true
} else {
result = (arg instanceof Date)
result = (arg instanceof Date) || Buffer.isBuffer(arg);
}
}
})
......
......@@ -2,6 +2,7 @@
"name": "sequelize",
"description": "Multi dialect ORM for Node.JS",
"version": "2.0.0-dev5",
"version": "1.7.0-rc5",
"author": "Sascha Depold <sascha@depold.com>",
"contributors": [
{
......
......@@ -511,7 +511,7 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
'user_id',
'message'
],
include: [{ model: User, as: User.tableName, attributes: ['username'] }]
include: [{ model: User, attributes: ['username'] }]
}).success(function(messages) {
......
......@@ -24,7 +24,8 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
data: DataTypes.STRING,
intVal: DataTypes.INTEGER,
theDate: DataTypes.DATE,
aBool: DataTypes.BOOLEAN
aBool: DataTypes.BOOLEAN,
binary: DataTypes.STRING(16, true)
})
this.User.sync({ force: true }).success(function() {
......@@ -63,9 +64,12 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
beforeEach(function(done) {
var self = this
this.buf = new Buffer(16);
this.buf.fill('\x01');
this.User.bulkCreate([
{username: 'boo', intVal: 5, theDate: '2013-01-01 12:00'},
{username: 'boo2', intVal: 10, theDate: '2013-01-10 12:00'}
{username: 'boo2', intVal: 10, theDate: '2013-01-10 12:00', binary: this.buf }
]).success(function(user2) {
done()
})
......@@ -92,6 +96,19 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
});
})
it('should not break when using smart syntax on binary fields', function (done) {
this.User.findAll({
where: {
binary: [ this.buf, this.buf ]
}
}).success(function(users){
expect(users).to.have.length(1)
expect(users[0].binary).to.be.an.instanceof.string
expect(users[0].username).to.equal('boo2')
done();
});
});
it('should be able to find a row using like', function(done) {
this.User.findAll({
where: {
......@@ -220,6 +237,60 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
it('should be able to handle binary values through associations as well...', function(done) {
var User = this.User;
var Binary = this.sequelize.define('Binary', {
id: {
type: DataTypes.STRING(16, true),
primaryKey: true
}
})
var buf1 = this.buf
var buf2 = new Buffer(16)
buf2.fill('\x02')
User.belongsTo(Binary, { foreignKey: 'binary' })
User.sync({ force: true }).success(function() {
Binary.sync({ force: true }).success(function() {
User.bulkCreate([
{username: 'boo5', aBool: false},
{username: 'boo6', aBool: true}
]).success(function() {
Binary.bulkCreate([
{id: buf1},
{id: buf2}
]).success(function() {
User.find(1).success(function(user) {
Binary.find(buf1).success(function(binary) {
user.setBinary(binary).success(function() {
User.find(2).success(function(_user) {
Binary.find(buf2).success(function(_binary) {
_user.setBinary(_binary).success(function() {
_user.getBinary().success(function(_binaryRetrieved) {
user.getBinary().success(function(binaryRetrieved) {
expect(binaryRetrieved.id).to.be.an.instanceof.string
expect(_binaryRetrieved.id).to.be.an.instanceof.string
expect(binaryRetrieved.id).to.have.length(16)
expect(_binaryRetrieved.id).to.have.length(16)
expect(binaryRetrieved.id.toString()).to.be.equal(buf1.toString())
expect(_binaryRetrieved.id.toString()).to.be.equal(buf2.toString())
done()
})
})
})
})
})
})
})
})
})
})
})
})
})
it('should be able to return a record with primaryKey being null for new inserts', function(done) {
var Session = this.sequelize.define('Session', {
token: { type: DataTypes.TEXT, allowNull: false },
......
......@@ -59,9 +59,43 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
})
})
describe('with an invalid connection', function() {
beforeEach(function() {
var options = _.extend({}, this.sequelize.options, { port: "99999" })
this.sequelizeWithInvalidConnection = new Sequelize("wat", "trololo", "wow", options)
})
it('triggers the error event', function(done) {
this
.sequelizeWithInvalidConnection
.authenticate()
.complete(function(err, result) {
expect(err).to.not.be.null
done()
})
})
it('triggers the actual adapter error', function(done) {
this
.sequelizeWithInvalidConnection
.authenticate()
.complete(function(err, result) {
if (dialect === 'mariadb') {
expect(err.message).to.match(/Access denied for user/)
} else if (dialect === 'postgres') {
expect(err.message).to.match(/invalid port number/)
} else {
expect(err.message).to.match(/Failed to authenticate/)
}
done()
})
})
})
describe('with invalid credentials', function() {
beforeEach(function() {
this.sequelizeWithInvalidCredentials = new Sequelize("omg", "wtf", "lol", this.sequelize.options)
this.sequelizeWithInvalidCredentials = new Sequelize("localhost", "wtf", "lol", this.sequelize.options)
})
it('triggers the error event', function(done) {
......@@ -91,6 +125,12 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
})
})
})
describe('validate', function() {
it('is an alias for .authenticate()', function() {
expect(this.sequelize.validate).to.equal(this.sequelize.authenticate)
})
})
}
describe('getDialect', function() {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!