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

Commit 779dc404 by Jan Aagaard Meier

Merge master

2 parents 6469fa89 2c5b46e9
...@@ -5,6 +5,7 @@ Notice: All 1.7.x changes are present in 2.0.x aswell ...@@ -5,6 +5,7 @@ Notice: All 1.7.x changes are present in 2.0.x aswell
- [FEATURE] Allow setting plural and singular name on the model (`options.name` in `sequelize.define`) and in associations (`options.as`) to circumvent issues with weird pluralization. - [FEATURE] Allow setting plural and singular name on the model (`options.name` in `sequelize.define`) and in associations (`options.as`) to circumvent issues with weird pluralization.
- [FEATURE] Added support for passing an `indexes` array in options to `sequelize.define`. [#1485](https://github.com/sequelize/sequelize/issues/1485). See API reference for details. - [FEATURE] Added support for passing an `indexes` array in options to `sequelize.define`. [#1485](https://github.com/sequelize/sequelize/issues/1485). See API reference for details.
- [FEATURE/INTERNALS] Standardized the output from `QueryInterface.showIndex`. - [FEATURE/INTERNALS] Standardized the output from `QueryInterface.showIndex`.
- [FEATURE] Include deleted rows in find [#2083](https://github.com/sequelize/sequelize/pull/2083)
- [BUG] Hid `dottie.transform` on raw queries behind a flag (`nest`) [#2064](https://github.com/sequelize/sequelize/pull/2064) - [BUG] Hid `dottie.transform` on raw queries behind a flag (`nest`) [#2064](https://github.com/sequelize/sequelize/pull/2064)
- [BUG] Fixed problems with transcation parameter being removed / not passed on in associations [#1789](https://github.com/sequelize/sequelize/issues/1789) and [#1968](https://github.com/sequelize/sequelize/issues/1968) - [BUG] Fixed problems with transcation parameter being removed / not passed on in associations [#1789](https://github.com/sequelize/sequelize/issues/1789) and [#1968](https://github.com/sequelize/sequelize/issues/1968)
- [BUG] Fix problem with minConnections. [#2048](https://github.com/sequelize/sequelize/issues/2048) - [BUG] Fix problem with minConnections. [#2048](https://github.com/sequelize/sequelize/issues/2048)
......
...@@ -39,13 +39,22 @@ ConnectionManager = function(dialect, sequelize) { ...@@ -39,13 +39,22 @@ ConnectionManager = function(dialect, sequelize) {
if (config.pool.maxConnections) config.pool.max = config.pool.maxConnections; if (config.pool.maxConnections) config.pool.max = config.pool.maxConnections;
if (config.pool.minConnections) config.pool.min = config.pool.minConnections; if (config.pool.minConnections) config.pool.min = config.pool.minConnections;
this.onProcessExit = function() { process.on('exit', this.onProcessExit.bind(this));
// Cleanup };
self.pool.drain();
return; ConnectionManager.prototype.onProcessExit = function() {
}.bind(this); if (this.pool) {
this.pool.drain();
}
};
ConnectionManager.prototype.close = function () {
this.onProcessExit();
process.removeListener('exit', this.onProcessExit); // Remove the listener, so all references to this instance can be garbage collected.
process.on('exit', this.onProcessExit); this.getConnection = function () {
return Promise.reject(new Error("ConnectionManager.getConnection was called after the connection manager was closed!"));
};
}; };
// This cannot happen in the constructor because the user can specify a min. number of connections to have in the pool // This cannot happen in the constructor because the user can specify a min. number of connections to have in the pool
......
...@@ -1239,15 +1239,7 @@ module.exports = (function() { ...@@ -1239,15 +1239,7 @@ module.exports = (function() {
} else if (Buffer.isBuffer(smth)) { } else if (Buffer.isBuffer(smth)) {
result = this.escape(smth); result = this.escape(smth);
} else if (Array.isArray(smth)) { } else if (Array.isArray(smth)) {
var treatAsAnd = smth.reduce(function(treatAsAnd, arg) { if (Utils.canTreatArrayAsAnd(smth)) {
if (treatAsAnd) {
return treatAsAnd;
} else {
return !(arg instanceof Date) && ((arg instanceof Utils.and) || (arg instanceof Utils.or) || Utils._.isPlainObject(arg));
}
}, false);
if (treatAsAnd) {
var _smth = self.sequelize.and.apply(null, smth); var _smth = self.sequelize.and.apply(null, smth);
result = self.getWhereConditions(_smth, tableName, factory, options, prepend); result = self.getWhereConditions(_smth, tableName, factory, options, prepend);
} else { } else {
......
...@@ -97,6 +97,12 @@ module.exports = (function() { ...@@ -97,6 +97,12 @@ module.exports = (function() {
callback && callback(null, result); callback && callback(null, result);
}; };
var filterByMigrationId = function(migrations, migrationIds, callback) {
var result = migrations.filter(function(migration) { return migrationIds.indexOf(migration.migrationId) === -1;
});
callback && callback(null, result);
};
var migrationFiles = fs.readdirSync(this.options.path).filter(function(file) { var migrationFiles = fs.readdirSync(this.options.path).filter(function(file) {
return self.options.filesFilter.test(file); return self.options.filesFilter.test(file);
}); });
...@@ -109,6 +115,22 @@ module.exports = (function() { ...@@ -109,6 +115,22 @@ module.exports = (function() {
return parseInt(a.filename.split('-')[0]) - parseInt(b.filename.split('-')[0]); return parseInt(a.filename.split('-')[0]) - parseInt(b.filename.split('-')[0]);
}); });
var existingMigrationIds = migrations.map(function(migration) {
return migration.migrationId;
});
var getPendingMigrations = function(callback) {
getAllMigrationIdsFromDatabase.call(self).success(function(allMigrationIds) {
allMigrationIds = allMigrationIds.map(function(migration) {
return parseInt(migration.to);
});
if (allMigrationIds) {
filterByMigrationId(migrations, allMigrationIds, callback);
}
});
};
if (this.options.from) { if (this.options.from) {
filterFrom(migrations, this.options.from, function(err, migrations) { filterFrom(migrations, this.options.from, function(err, migrations) {
if (self.options.to) { if (self.options.to) {
...@@ -118,24 +140,13 @@ module.exports = (function() { ...@@ -118,24 +140,13 @@ module.exports = (function() {
} }
}); });
} else { } else {
getLastMigrationIdFromDatabase.call(this).success(function(lastMigrationId) { getPendingMigrations(function(err, pendingMigrations) {
if (lastMigrationId) { if (self.options.to) {
filterFrom(migrations, lastMigrationId, function(err, migrations) { filterTo(pendingMigrations, self.options.to, callback);
if (self.options.to) { }
filterTo(migrations, self.options.to, callback); else {
} else { callback && callback(err, pendingMigrations);
callback && callback(null, migrations);
}
}, { withoutEqual: true });
} else {
if (self.options.to) {
filterTo(migrations, self.options.to, callback);
} else {
callback && callback(null, migrations);
}
} }
}).error(function(err) {
callback && callback(err, null);
}); });
} }
}; };
...@@ -222,6 +233,31 @@ module.exports = (function() { ...@@ -222,6 +233,31 @@ module.exports = (function() {
// private // private
var getAllMigrationIdsFromDatabase = Migrator.prototype.getAllMigrationIdsFromDatabase = function() {
var self = this;
return new Utils.CustomEventEmitter(function(emitter) {
self
.findOrCreateSequelizeMetaDAO()
.success(function(SequelizeMeta) {
SequelizeMeta
.findAll({
order: 'id DESC',
attributes: ['to']
})
.success(function(meta) {
emitter.emit('success', meta ? meta : null);
})
.error(function(err) {
emitter.emit('error', err);
});
})
.error(function(err) {
emitter.emit('error', err);
});
}).run();
};
var getLastMigrationFromDatabase = Migrator.prototype.getLastMigrationFromDatabase = function() { var getLastMigrationFromDatabase = Migrator.prototype.getLastMigrationFromDatabase = function() {
var self = this; var self = this;
......
...@@ -633,6 +633,7 @@ module.exports = (function() { ...@@ -633,6 +633,7 @@ module.exports = (function() {
* @param {Object} [options] A hash of options to describe the scope of the search * @param {Object} [options] A hash of options to describe the scope of the search
* @param {Object} [options.where] A hash of attributes to describe your search. See above for examples. * @param {Object} [options.where] A hash of attributes to describe your search. See above for examples.
* @param {Array<String>} [options.attributes] A list of the attributes that you want to select. To rename an attribute, you can pass an array, with two elements - the first is the name of the attribute in the DB (or some kind of expression such as `Sequelize.literal`, `Sequelize.fn` and so on), and the second is the name you want the attribute to have in the returned instance * @param {Array<String>} [options.attributes] A list of the attributes that you want to select. To rename an attribute, you can pass an array, with two elements - the first is the name of the attribute in the DB (or some kind of expression such as `Sequelize.literal`, `Sequelize.fn` and so on), and the second is the name you want the attribute to have in the returned instance
* @param {Boolean} [options.paranoid=true] If false, will include columns which have a non-null deletedAt column.
* @param {Array<Object|Model>} [options.include] A list of associations to eagerly load using a left join. Supported is either `{ include: [ Model1, Model2, ...]}` or `{ include: [{ model: Model1, as: 'Alias' }]}`. If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in the as attribute when eager loading Y). * @param {Array<Object|Model>} [options.include] A list of associations to eagerly load using a left join. Supported is either `{ include: [ Model1, Model2, ...]}` or `{ include: [{ model: Model1, as: 'Alias' }]}`. If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in the as attribute when eager loading Y).
* @param {Model} [options.include[].model] The model you want to eagerly load * @param {Model} [options.include[].model] The model you want to eagerly load
* @param {String} [options.include[].as] The alias of the relation, in case the model you want to eagerly load is aliassed. For `hasOne` / `belongsTo`, this should be the singular name, and for `hasMany`, it should be the plural * @param {String} [options.include[].as] The alias of the relation, in case the model you want to eagerly load is aliassed. For `hasOne` / `belongsTo`, this should be the singular name, and for `hasMany`, it should be the plural
...@@ -1551,7 +1552,7 @@ module.exports = (function() { ...@@ -1551,7 +1552,7 @@ module.exports = (function() {
// private // private
var paranoidClause = function(options ) { var paranoidClause = function(options ) {
if (! this.options.timestamps || ! this.options.paranoid) return options || {}; if (! this.options.timestamps || ! this.options.paranoid || options.paranoid === false) return options || {};
options = options || {}; options = options || {};
options.where = options.where || {}; options.where = options.where || {};
...@@ -1583,12 +1584,25 @@ module.exports = (function() { ...@@ -1583,12 +1584,25 @@ module.exports = (function() {
options.where += ' AND ' + quoteIdentifiedDeletedAtCol + ' IS NULL '; options.where += ' AND ' + quoteIdentifiedDeletedAtCol + ' IS NULL ';
} }
else if (Array.isArray(options.where)) { else if (Array.isArray(options.where)) {
// Need to check how the array is being used, as it could be an array
// Don't overwrite our explicit deletedAt search value if we provide one // of string-likes/Dates or an array with objects, hashes, or other
if (options.where[0].indexOf(deletedAtCol) !== -1) { // complex data types
return options; if (Utils.canTreatArrayAsAnd(options.where)) {
// Don't overwrite our explicit deletedAt search value if we provide one
var whereClause = this.QueryGenerator.getWhereConditions(options.where, this.getTableName(), this, options);
if (whereClause.indexOf(deletedAtCol) !== -1) {
return options;
}
var whereObj = {};
whereObj[quoteIdentifiedDeletedAtCol] = null;
options.where.push(whereObj);
} else {
// Don't overwrite our explicit deletedAt search value if we provide one
if (options.where[0].indexOf(deletedAtCol) !== -1) {
return options;
}
options.where[0] += ' AND ' + quoteIdentifiedDeletedAtCol + ' IS NULL ';
} }
options.where[0] += ' AND ' + quoteIdentifiedDeletedAtCol + ' IS NULL ';
} else { } else {
options.where[deletedAtCol] = null; options.where[deletedAtCol] = null;
} }
......
...@@ -810,5 +810,15 @@ module.exports = (function() { ...@@ -810,5 +810,15 @@ module.exports = (function() {
} }
}; };
/**
* Close all connections used by this sequelize instance, and free all references so the instance can be garbage collected.
*
* Normally this is done on process exit, so you only need to call this method if you are creating multiple instances, and want
* to garbage collect some of them.
*/
Sequelize.prototype.close = function () {
this.connectionManager.close();
};
return Sequelize; return Sequelize;
})(); })();
...@@ -354,6 +354,16 @@ var Utils = module.exports = { ...@@ -354,6 +354,16 @@ var Utils = module.exports = {
} }
return result; return result;
}, },
canTreatArrayAsAnd: function(arr) {
return arr.reduce(function(treatAsAnd, arg) {
if (treatAsAnd) {
return treatAsAnd;
} else {
return !(arg instanceof Date) && ((arg instanceof Utils.and) || (arg instanceof Utils.or) || Utils._.isPlainObject(arg));
}
}, false);
},
combineTableNames: function(tableName1, tableName2) { combineTableNames: function(tableName1, tableName2) {
return (tableName1.toLowerCase() < tableName2.toLowerCase()) ? (tableName1 + tableName2) : (tableName2 + tableName1); return (tableName1.toLowerCase() < tableName2.toLowerCase()) ? (tableName1 + tableName2) : (tableName2 + tableName1);
}, },
......
...@@ -1041,6 +1041,80 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1041,6 +1041,80 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
describe('can find paranoid records if paranoid is marked as false in query', function() {
it('with the DAOFactory', function() {
var User = this.sequelize.define('UserCol', {
username: Sequelize.STRING
}, { paranoid: true })
return User.sync({ force: true })
.then(function() {
return User.bulkCreate([
{username: 'Toni'},
{username: 'Tobi'},
{username: 'Max'}
]);
})
.then(function() { return User.find(1) })
.then(function(user) { return user.destroy() })
.then(function() { return User.find({ where: 1, paranoid: false }) })
.then(function(user) {
expect(user).to.exist
return User.find(1)
})
.then(function(user) {
expect(user).to.be.null
return [User.count(), User.count({ paranoid: false })]
})
.spread(function(cnt, cntWithDeleted) {
expect(cnt).to.equal(2)
expect(cntWithDeleted).to.equal(3)
})
})
})
it('should include deleted associated records if include has paranoid marked as false', function() {
var User = this.sequelize.define('User', {
username: Sequelize.STRING
}, { paranoid: true })
var Pet = this.sequelize.define('Pet', {
name: Sequelize.STRING,
UserId: Sequelize.INTEGER
}, { paranoid: true })
User.hasMany(Pet)
Pet.belongsTo(User)
var user;
return User.sync({ force: true })
.then(function() { return Pet.sync({ force: true }) })
.then(function() { return User.create({ username: 'Joe' }) })
.then(function(_user) {
user = _user;
return Pet.bulkCreate([
{ name: 'Fido', UserId: user.id },
{ name: 'Fifi', UserId: user.id }
]);
})
.then(function () { return Pet.find(1) })
.then(function (pet) { return pet.destroy() })
.then(function () {
return [
User.find({ where: user.id, include: Pet }),
User.find({
where: user.id,
include: [{ model: Pet, paranoid: false }]
})
]
})
.spread(function (user, userWithDeletedPets) {
expect(user).to.exist
expect(user.Pets).to.have.length(1)
expect(userWithDeletedPets).to.exist
expect(userWithDeletedPets.Pets).to.have.length(2)
})
})
it('should delete a paranoid record if I set force to true', function(done) { it('should delete a paranoid record if I set force to true', function(done) {
var self = this var self = this
var User = this.sequelize.define('paranoiduser', { var User = this.sequelize.define('paranoiduser', {
...@@ -2109,6 +2183,20 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -2109,6 +2183,20 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it('should not fail when array contains Sequelize.or / and', function (done) {
this.User.findAll({
where: [
this.sequelize.or({ username: 'vader' }, { username: 'luke' }),
this.sequelize.and({ id: [1, 2, 3] })
]
})
.then(function(res) {
expect(res).to.have.length(2)
done()
})
.catch(function(e) { done(e) })
})
it('should not fail with an include', function(done) { it('should not fail with an include', function(done) {
this.User.findAll({ this.User.findAll({
where: [ where: [
...@@ -2154,6 +2242,23 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -2154,6 +2242,23 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}).error(done) }).error(done)
}) })
it('should not overwrite a specified deletedAt (complex query)', function (done) {
this.User.findAll({
where: [
this.sequelize.or({ username: 'leia' }, { username: 'luke' }),
this.sequelize.and(
{ id: [1, 2, 3] },
this.sequelize.or({ deletedAt: null }, { deletedAt: { gt: new Date(0) } })
)
]
})
.then(function(res) {
expect(res).to.have.length(2)
done()
})
.catch(function(e) { done(e) })
})
}) })
if (dialect !== 'sqlite') { if (dialect !== 'sqlite') {
......
...@@ -107,6 +107,21 @@ describe(Support.getTestDialectTeaser("Migrator"), function() { ...@@ -107,6 +107,21 @@ describe(Support.getTestDialectTeaser("Migrator"), function() {
}) })
}) })
}) })
it("returns pending migrations", function(done) {
this.init(undefined, function(migrator, SequelizeMeta) {
SequelizeMeta.create({ from: 20111117063700, to: 20111117063700 }).success(function() {
SequelizeMeta.create({ from: 20111117063700, to: 20130909185621 }).success(function() {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).to.be.null
expect(migrations).to.have.length(14)
expect(migrations[0].filename).to.equal('20111130161100-emptyMigration.js')
done()
})
})
})
})
})
}) })
describe('migrations', function() { describe('migrations', function() {
...@@ -184,6 +199,38 @@ describe(Support.getTestDialectTeaser("Migrator"), function() { ...@@ -184,6 +199,38 @@ describe(Support.getTestDialectTeaser("Migrator"), function() {
.error(function(err) { console.log(err) }) .error(function(err) { console.log(err) })
}) })
}) })
it("executes pending migrations", function(done) {
var self = this;
var options = {
path: __dirname + '/assets/migrations',
to: 20111117063700
};
var migrator = new Migrator(this.sequelize, options)
migrator
.migrate()
.success(function() {
self.init({ to: 20111205064000 }, function(migrator, SequelizeMeta) {
SequelizeMeta.create({ from: 20111205064000, to: 20111205064000 }).success(function() {
migrator
.migrate()
.success(function() {
SequelizeMeta.findAll().success(function(meta) {
expect(meta.length).to.equal(3);
done();
});
})
.error(function(err) { console.log(err) })
})
})
})
.error(function(err) { console.log(err) })
})
}) })
describe('renameTable', function() { describe('renameTable', function() {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!