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

Commit 3f18dc0b by Sascha Depold

Merge branch 'master' into features/transactions

Conflicts:
	lib/dao.js
	lib/dialects/postgres/connector-manager.js
	lib/query-interface.js
	test/sequelize.test.js
2 parents 975f414f c6fd201b
# Sequelize [![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/1259407/Sequelize) #
# Sequelize [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/sequelize/sequelize/trend.png)](https://bitdeli.com/free "Bitdeli Badge") [![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/1259407/Sequelize) #
MySQL, MariaDB, PostgresSQL, and SQLite Object Relational Mapper (ORM) for [node](http://nodejs.org).
......
......@@ -34,19 +34,22 @@ var writeDefaultConfig = function(config) {
username: "root",
password: null,
database: 'database_development',
host: '127.0.0.1'
host: '127.0.0.1',
dialect: 'mysql'
},
test: {
username: "root",
password: null,
database: 'database_test',
host: '127.0.0.1'
host: '127.0.0.1',
dialect: 'mysql'
},
production: {
username: "root",
password: null,
database: 'database_production',
host: '127.0.0.1'
host: '127.0.0.1',
dialect: 'mysql'
}
}, undefined, 2) + "\n"
......
......@@ -42,6 +42,7 @@
- [BUG] Allow include when the same table is referenced multiple times using hasMany [#913](https://github.com/sequelize/sequelize/pull/913). thanks to janmeier
- [BUG] Allow definition of defaultValue for the timestamp columns (createdAt, updatedAt, deletedAt) [#930](https://github.com/sequelize/sequelize/pull/930). Thank to durango
- [BUG] Don't delete foreign keys of many-to-many associations, if still needed. [#961](https://github.com/sequelize/sequelize/pull/961). thanks to sdepold
- [BUG] Update timestamps when incrementing and decrementing [#1023](https://github.com/sequelize/sequelize/pull/1023). durango
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
......@@ -84,6 +85,7 @@
- [FEATURE] Model#findOrBuild. [#960](https://github.com/sequelize/sequelize/pull/960). Thanks to durango.
- [FEATURE] Support for MariaDB. [#948](https://github.com/sequelize/sequelize/pull/948). Thanks to reedog117 and janmeier.
- [FEATURE] Filter through associations. [#991](https://github.com/sequelize/sequelize/pull/991). Thanks to snit-ram.
- [FEATURE] Possibility to disable loging for .sync [#937](https://github.com/sequelize/sequelize/pull/937). Thanks to durango
- [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier
- [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier
- [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango
......
......@@ -223,6 +223,7 @@ module.exports = (function() {
// Only Postgres' QueryGenerator.dropTableQuery() will add schema manually
var isPostgres = this.options.dialect === "postgres" || (!!this.daoFactoryManager && this.daoFactoryManager.sequelize.options.dialect === "postgres")
, tableName = isPostgres ? this.tableName : this.getTableName()
return this.QueryInterface.dropTable(tableName, options)
}
......
......@@ -141,7 +141,7 @@ module.exports = (function() {
, ciCollation = !!self.daoFactory.options.collate && self.daoFactory.options.collate.match(/_ci$/i)
// Unfortunately for MySQL CI collation we need to map/lowercase values again
if (isEnum && isMySQL && ciCollation && (attrName in values)) {
if (isEnum && isMySQL && ciCollation && (attrName in values) && values[attrName]) {
var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase())
valueOutOfScope = scopeIndex === -1
......@@ -367,8 +367,9 @@ module.exports = (function() {
deprecationWarning: "Increment expects an object as second parameter. Please pass the incrementor as option! ~> instance.increment(" + JSON.stringify(fields) + ", { by: " + countOrOptions + " })"
})
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }
, values = {}
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }
, updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, values = {}
if (countOrOptions === undefined) {
countOrOptions = { by: 1, transaction: null }
......@@ -376,9 +377,10 @@ module.exports = (function() {
countOrOptions = { by: countOrOptions, transaction: null }
}
if (countOrOptions.by === undefined) {
countOrOptions.by = 1
}
countOrOptions = Utils._.extend({
by: 1,
attributes: {}
}, countOrOptions)
if (Utils._.isString(fields)) {
values[fields] = countOrOptions.by
......@@ -390,6 +392,12 @@ module.exports = (function() {
values = fields
}
if (this.__options.timestamps) {
if (!values[updatedAtAttr]) {
countOrOptions.attributes[updatedAtAttr] = Utils.now(this.daoFactory.daoFactoryManager.sequelize.options.dialect)
}
}
return this.QueryInterface.increment(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory.tableName, this.__factory.options.schema), values, identifier, countOrOptions)
}
......@@ -445,7 +453,7 @@ module.exports = (function() {
}
DAO.prototype.addAttribute = function(attribute, value) {
if (this.booleanValues.length && this.booleanValues.indexOf(attribute) !== -1 && value !== undefined) { // transform integer 0,1 into boolean
if (this.booleanValues.length && this.booleanValues.indexOf(attribute) !== -1 && value != null) { // transform integer 0,1 into boolean
value = !!value
}
......
......@@ -244,7 +244,7 @@ module.exports = (function() {
return query
},
incrementQuery: function (tableName, attrValueHash, where) {
incrementQuery: function (tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var values = []
......@@ -256,6 +256,12 @@ module.exports = (function() {
values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + " + " + _value)
}
options = options || {}
for (var key in options) {
var value = options[key];
values.push(this.quoteIdentifier(key) + "=" + this.escape(value))
}
var table = this.quoteIdentifier(tableName)
values = values.join(",")
where = this.getWhereConditions(where)
......
......@@ -126,6 +126,11 @@ module.exports = (function() {
} else {
self.setTimezone(client, 'UTC', timezoneCallback)
}
} else if (self.config.native) {
self.setTimezone(self.client, 'UTC', function() {
self.isConnected = true
emitter.emit('success', done)
})
} else {
done && done()
self.client = null
......
......@@ -366,7 +366,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements)
},
incrementQuery: function(tableName, attrValueHash, where) {
incrementQuery: function(tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> RETURNING *"
......@@ -377,6 +377,12 @@ module.exports = (function() {
values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + " + " + this.escape(value))
}
options = options || {}
for (var key in options) {
var value = options[key];
values.push(this.quoteIdentifier(key) + "=" + this.escape(value))
}
var replacements = {
table: this.quoteIdentifiers(tableName),
values: values.join(","),
......
......@@ -82,8 +82,15 @@ module.exports = (function() {
var length = dataType.match(/\(\s*\d+(\s*,\s*\d)?\s*\)/)
if (length && length.index < modifierLastIndex) {
dataType = dataType.replace(length[0], '')
dataType = Utils._.insert(dataType, modifierLastIndex, length[0])
// Since the legnth was placed before the modifier, removing the legnth has changed the index
if (length.index < modifierLastIndex) {
modifierLastIndex -= length[0].length
}
dataType = Utils._.insert(dataType, modifierLastIndex, length[0]).trim()
}
modifierLastIndex = -1
}
if (Utils._.includes(dataType, 'PRIMARY KEY') && needsMultiplePrimaryKeys) {
......@@ -215,7 +222,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements)
},
incrementQuery: function(tableName, attrValueHash, where) {
incrementQuery: function(tableName, attrValueHash, where, options) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>"
......@@ -226,6 +233,12 @@ module.exports = (function() {
values.push(this.quoteIdentifier(key) + "=" + this.quoteIdentifier(key) + "+ " + this.escape(value))
}
options = options || {}
for (var key in options) {
var value = options[key];
values.push(this.quoteIdentifier(key) + "=" + this.escape(value))
}
var replacements = {
table: this.quoteIdentifier(tableName),
values: values.join(","),
......
......@@ -48,7 +48,11 @@ module.exports = (function() {
function(fct) {
fct = bindToProcess(fct);
this.on('error', function(err) { fct(err, null) })
.on('success', function(arg0, arg1) { fct(null, arg0, arg1) })
.on('success', function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(null);
fct.apply(fct, args);
})
return this
}
......
......@@ -75,6 +75,10 @@ module.exports = (function() {
if (options.skipOnError && (self.fails.length > 0)) {
onError('Skipped due to earlier error!')
} else {
if (typeof serial.options === "object" && Object.keys(serial.options).length > 0 && serial.method === "queryAndEmit") {
serial.params.push(serial.options)
}
var emitter = serial.klass[serial.method].apply(serial.klass, serial.params)
emitter.success(function(result) {
......
......@@ -80,6 +80,10 @@ module.exports = (function() {
}
}
options = Utils._.extend({
logging: this.sequelize.options.logging
}, options || {})
return new Utils.CustomEventEmitter(function(emitter) {
// Postgres requires a special SQL command for enums
if (self.sequelize.options.dialect === "postgres") {
......@@ -91,7 +95,7 @@ module.exports = (function() {
for (i = 0; i < keyLen; i++) {
if (attributes[keys[i]].toString().match(/^ENUM\(/)) {
sql = self.QueryGenerator.pgListEnums(getTableName, keys[i], options)
chainer.add(self.sequelize.query(sql, null, { plain: true, raw: true, type: 'SELECT' }))
chainer.add(self.sequelize.query(sql, null, { plain: true, raw: true, type: 'SELECT', logging: options.logging }))
}
}
......@@ -108,7 +112,7 @@ module.exports = (function() {
// If the enum type doesn't exist then create it
if (!results[enumIdx]) {
sql = self.QueryGenerator.pgEnum(getTableName, keys[i], attributes[keys[i]], options)
chainer2.add(self.sequelize.query(sql, null, { raw: true }))
chainer2.add(self.sequelize.query(sql, null, { raw: true, logging: options.logging }))
} else if (!!results[enumIdx] && !!daoTable) {
var enumVals = self.QueryGenerator.fromArray(results[enumIdx].enum_value)
, vals = daoTable.rawAttributes[keys[i]].values
......@@ -138,7 +142,7 @@ module.exports = (function() {
chainer2.run().success(function() {
queryAndEmit
.call(self, sql, 'createTable')
.call(self, sql, 'createTable', options)
.success(function(res) {
self.emit('createTable', null)
emitter.emit('success', res)
......@@ -147,7 +151,9 @@ module.exports = (function() {
self.emit('createTable', err)
emitter.emit('error', err)
})
.on('sql', function(sql) { emitter.emit('sql', sql) })
.on('sql', function(sql) {
emitter.emit('sql', sql)
})
}).error(function(err) {
emitter.emit('error', err)
}).on('sql', function(sql) {
......@@ -158,7 +164,7 @@ module.exports = (function() {
attributes = self.QueryGenerator.attributesToSQL(attributeHashes)
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
queryAndEmit.call(self, sql, 'createTable', emitter).success(function(results) {
queryAndEmit.call(self, sql, 'createTable', options).success(function(results) {
self.emit('createTable', null)
emitter.emit('success', results)
}).error(function(err) {
......@@ -182,7 +188,7 @@ module.exports = (function() {
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'queryAndEmit', [sql])
chainer.add(self, 'queryAndEmit', [sql, 'dropTable'], options)
// Since postgres has a special case for enums, we should drop the related
// enum type within the table and attribute
......@@ -203,7 +209,7 @@ module.exports = (function() {
for (i = 0; i < keyLen; i++) {
if (daoTable.rawAttributes[keys[i]].type && daoTable.rawAttributes[keys[i]].type === "ENUM") {
chainer.add(self.sequelize, 'query', [self.QueryGenerator.pgEnumDrop(getTableName, keys[i]), null, {raw: true}])
chainer.add(self.sequelize, 'query', [self.QueryGenerator.pgEnumDrop(getTableName, keys[i]), null, {logging: options.logging, raw: true}])
}
}
}
......@@ -658,8 +664,8 @@ module.exports = (function() {
}
QueryInterface.prototype.increment = function(dao, tableName, values, identifier, options) {
var sql = this.QueryGenerator.incrementQuery(tableName, values, identifier);
return queryAndEmit.call(this, [sql, dao, options], 'increment');
var sql = this.QueryGenerator.incrementQuery(tableName, values, identifier, options.attributes)
return queryAndEmit.call(this, [sql, dao, options], 'increment')
}
QueryInterface.prototype.rawSelect = function(tableName, options, attributeSelector) {
......@@ -872,7 +878,8 @@ module.exports = (function() {
options = Utils._.extend({
success: function(){},
error: function(){},
transaction: null
transaction: null,
logging: this.sequelize.options.logging
}, options || {})
var execQuery = function(emitter) {
......@@ -884,12 +891,12 @@ module.exports = (function() {
}
if (sqlOrQueryParams.length === 2) {
sqlOrQueryParams.push({})
sqlOrQueryParams.push(typeof options === "object" ? options : {})
}
emitter.query = this.sequelize.query.apply(this.sequelize, sqlOrQueryParams)
} else {
emitter.query = this.sequelize.query(sqlOrQueryParams, null, { })
emitter.query = this.sequelize.query(sqlOrQueryParams, null, options)
}
emitter
......
......@@ -344,6 +344,8 @@ module.exports = (function() {
options = Utils._.extend({}, this.options.sync, options)
}
options.logging = options.logging === undefined ? false : Boolean(options.logging)
var chainer = new Utils.QueryChainer()
// Topologically sort by foreign key constraints to give us an appropriate
......
{
"name": "sequelize",
"description": "Multi dialect ORM for Node.JS",
"version": "1.7.0-beta.0",
"version": "1.7.0-beta.1",
"author": "Sascha Depold <sascha@depold.com>",
"contributors": [
{
......
......@@ -1025,6 +1025,41 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
})
})
})
describe('can safely sync multiple times', function(done) {
it('through the factory', function(done) {
var Enum = this.sequelize.define('Enum', {
state: {
type: Sequelize.ENUM,
values: ['happy', 'sad'],
allowNull: true
}
})
Enum.sync({ force: true }).success(function() {
Enum.sync().success(function() {
Enum.sync({ force: true }).complete(done)
})
})
})
it('through sequelize', function(done) {
var self = this
, Enum = this.sequelize.define('Enum', {
state: {
type: Sequelize.ENUM,
values: ['happy', 'sad'],
allowNull: true
}
})
this.sequelize.sync({ force: true }).success(function() {
self.sequelize.sync().success(function() {
self.sequelize.sync({ force: true }).complete(done)
})
})
})
})
})
})
......
......@@ -312,6 +312,18 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
it('with single field and no value', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.increment('aNumber').complete(function() {
self.User.find(1).complete(function(err, user2) {
expect(user2.aNumber).to.be.equal(1)
done()
})
})
})
})
it('should still work right with other concurrent updates', function(done) {
var self = this
this.User.find(1).complete(function (err, user1) {
......@@ -359,6 +371,26 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
})
it('with timestamps set to true', function (done) {
var User = this.sequelize.define('IncrementUser', {
aNumber: DataTypes.INTEGER
}, { timestamps: true })
User.sync({ force: true }).success(function() {
User.create({aNumber: 1}).success(function (user) {
var oldDate = user.updatedAt
setTimeout(function () {
user.increment('aNumber', 1).success(function() {
User.find(1).success(function (user) {
expect(user.updatedAt).to.be.afterTime(oldDate)
done()
})
})
}, 1000)
})
})
})
})
describe('decrement', function () {
......@@ -412,6 +444,18 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
it('with single field and no value', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.decrement('aNumber').complete(function() {
self.User.find(1).complete(function(err, user2) {
expect(user2.aNumber).to.be.equal(-1)
done()
})
})
})
})
it('should still work right with other concurrent updates', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
......@@ -459,6 +503,26 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
})
it('with timestamps set to true', function (done) {
var User = this.sequelize.define('IncrementUser', {
aNumber: DataTypes.INTEGER
}, { timestamps: true })
User.sync({ force: true }).success(function() {
User.create({aNumber: 1}).success(function (user) {
var oldDate = user.updatedAt
setTimeout(function () {
user.decrement('aNumber', 1).success(function() {
User.find(1).success(function (user) {
expect(user.updatedAt).to.be.afterTime(oldDate)
done()
})
})
}, 1000)
})
})
})
})
describe('reload', function () {
......@@ -1178,6 +1242,25 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
})
})
})
it("returns null for null, undefined, and unset boolean values", function(done) {
var Setting = this.sequelize.define('SettingHelper', {
setting_key: DataTypes.STRING,
bool_value: { type: DataTypes.BOOLEAN, allowNull: true },
bool_value2: { type: DataTypes.BOOLEAN, allowNull: true },
bool_value3: { type: DataTypes.BOOLEAN, allowNull: true }
}, { timestamps: false, logging: false })
Setting.sync({ force: true }).success(function() {
Setting.create({ setting_key: 'test', bool_value: null, bool_value2: undefined }).success(function() {
Setting.find({ where: { setting_key: 'test' } }).success(function(setting) {
expect(setting.bool_value).to.equal(null)
expect(setting.bool_value2).to.equal(null)
expect(setting.bool_value3).to.equal(null)
done()
})
})
})
})
})
describe('equals', function() {
......
......@@ -9,6 +9,7 @@ var chai = require('chai')
, moment = require('moment')
, Transaction = require(__dirname + '/../lib/transaction')
, path = require('path')
, sinon = require('sinon')
chai.Assertion.includeStack = true
......@@ -391,6 +392,37 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
})
})
}
describe("doesn't emit logging when explicitly saying not to", function() {
afterEach(function(done) {
this.sequelize.options.logging = false
done()
})
beforeEach(function(done) {
this.spy = sinon.spy()
var self = this
this.sequelize.options.logging = function() { self.spy() }
this.User = this.sequelize.define('UserTest', { username: DataTypes.STRING })
done()
})
it('through Sequelize.sync()', function(done) {
var self = this
this.sequelize.sync({ force: true, logging: false }).success(function() {
expect(self.spy.notCalled).to.be.true
done()
})
})
it('through DAOFactory.sync()', function(done) {
var self = this
this.User.sync({ force: true, logging: false }).success(function() {
expect(self.spy.notCalled).to.be.true
done()
})
})
})
})
describe('drop should work', function() {
......
......@@ -91,8 +91,8 @@ if (dialect === 'sqlite') {
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));"
},
{
arguments: ['myTable', {title: 'VARCHAR(255) BINARY', number: 'INTEGER(5) UNSIGNED'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR BINARY(255), `number` INTEGER UNSIGNED(5));"
arguments: ['myTable', {title: 'VARCHAR(255) BINARY', number: 'INTEGER(5) UNSIGNED PRIMARY KEY '}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR BINARY(255), `number` INTEGER UNSIGNED(5) PRIMARY KEY);"
},
{
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!