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

Commit 18818c7d by Sascha Depold

Merge branch 'master' into milestones/2.0.0

2 parents ffcee629 6a36ca11
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
- [BUG] Correctly handle booleans in MySQL. [#608](https://github.com/sequelize/sequelize/pull/608). Thanks to terraflubb - [BUG] Correctly handle booleans in MySQL. [#608](https://github.com/sequelize/sequelize/pull/608). Thanks to terraflubb
- [BUG] Fixed empty where conditions in MySQL. [#619](https://github.com/sequelize/sequelize/pull/619). Thanks to terraflubb - [BUG] Fixed empty where conditions in MySQL. [#619](https://github.com/sequelize/sequelize/pull/619). Thanks to terraflubb
- [BUG] Allow overriding of default columns. [#635](https://github.com/sequelize/sequelize/pull/635). Thanks to sevastos - [BUG] Allow overriding of default columns. [#635](https://github.com/sequelize/sequelize/pull/635). Thanks to sevastos
- [BUG] Fix where params for belongsTo [#658](https://github.com/sequelize/sequelize/pull/658). Thanks to mweibel
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to 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] 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 - [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
......
...@@ -42,8 +42,10 @@ module.exports = (function() { ...@@ -42,8 +42,10 @@ module.exports = (function() {
var id = this[self.identifier] var id = this[self.identifier]
if (!Utils._.isUndefined(params)) { if (!Utils._.isUndefined(params)) {
if (!Utils._.isUndefined(params.attributes)) { if (!Utils._.isUndefined(params.where)) {
params = Utils._.extend({where: {id:id}}, params) params.where = Utils._.extend({id:id}, params.where)
} else {
params.where = {id: id}
} }
} else { } else {
params = id params = id
......
...@@ -213,7 +213,7 @@ module.exports = (function() { ...@@ -213,7 +213,7 @@ module.exports = (function() {
//right now, the caller (has-many-double-linked) is in charge of the where clause //right now, the caller (has-many-double-linked) is in charge of the where clause
DAOFactory.prototype.findAllJoin = function(joinTableName, options) { DAOFactory.prototype.findAllJoin = function(joinTableName, options) {
var optcpy = Utils._.clone(options) var optcpy = Utils._.clone(options)
optcpy.attributes = optcpy.attributes || [Utils.addTicks(this.tableName)+".*"] optcpy.attributes = optcpy.attributes || [this.QueryInterface.quoteIdentifier(this.tableName)+".*"]
// whereCollection is used for non-primary key updates // whereCollection is used for non-primary key updates
this.options.whereCollection = optcpy.where || null; this.options.whereCollection = optcpy.where || null;
......
...@@ -136,7 +136,7 @@ module.exports = (function() { ...@@ -136,7 +136,7 @@ module.exports = (function() {
throw new Error('Value for HSTORE must be a string or number.') throw new Error('Value for HSTORE must be a string or number.')
} }
text.push(this.QueryInterface.QueryGenerator.addQuotes(key) + '=>' + (typeof value === "string" ? this.QueryInterface.QueryGenerator.addQuotes(value) : value)) text.push(this.QueryInterface.quoteIdentifier(key) + '=>' + (typeof value === "string" ? this.QueryInterface.quoteIdentifier(value) : value))
}.bind(this)) }.bind(this))
values[attrName] = text.join(',') values[attrName] = text.join(',')
} }
...@@ -180,7 +180,7 @@ module.exports = (function() { ...@@ -180,7 +180,7 @@ module.exports = (function() {
*/ */
DAO.prototype.reload = function() { DAO.prototype.reload = function() {
var where = [ var where = [
this.QueryInterface.QueryGenerator.addQuotes(this.__factory.tableName) + '.' + this.QueryInterface.QueryGenerator.addQuotes('id')+'=?', this.QueryInterface.quoteIdentifier(this.__factory.tableName) + '.' + this.QueryInterface.quoteIdentifier('id')+'=?',
this.id this.id
] ]
......
...@@ -108,6 +108,20 @@ module.exports = (function() { ...@@ -108,6 +108,20 @@ module.exports = (function() {
this.emit('success', result) this.emit('success', result)
} else { } else {
// Postgres will treat tables as case-insensitive, so fix the case
// of the returned values to match attributes
if(this.sequelize.options.quoteIdentifiers == false) {
var attrsMap = Utils._.reduce(this.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m}, {})
rows.forEach(function(row) {
Utils._.keys(row).forEach(function(key) {
var targetAttr = attrsMap[key]
if(targetAttr != key) {
row[targetAttr] = row[key]
delete row[key]
}
})
})
}
this.emit('success', this.send('handleSelectQuery', rows)) this.emit('success', this.send('handleSelectQuery', rows))
} }
} else if (this.send('isShowOrDescribeQuery')) { } else if (this.send('isShowOrDescribeQuery')) {
......
...@@ -268,7 +268,28 @@ module.exports = (function() { ...@@ -268,7 +268,28 @@ module.exports = (function() {
Globally disable foreign key constraints Globally disable foreign key constraints
*/ */
disableForeignKeyConstraintsQuery: function() { disableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('disableForeignKeyConstraintsQuery') throwMethodUndefined('disableForeignKeyConstraintsQuery')
},
/*
Escape an identifier (e.g. a table or attribute name)
*/
quoteIdentifier: function(identifier, force) {
throwMethodUndefined('quoteIdentifier')
},
/*
Split an identifier into .-separated tokens and quote each part
*/
quoteIdentifiers: function(identifiers, force) {
throwMethodUndefined('quoteIdentifiers')
},
/*
Escape a value (e.g. a string, number or date)
*/
escape: function(value) {
throwMethodUndefined('quoteIdentifier')
} }
} }
......
...@@ -358,6 +358,33 @@ module.exports = (function() { ...@@ -358,6 +358,33 @@ module.exports = (function() {
} }
} }
// Helper methods useful for querying
/**
* Escape an identifier (e.g. a table or attribute name). If force is true,
* the identifier will be quoted even if the `quoteIdentifiers` option is
* false.
*/
QueryInterface.prototype.quoteIdentifier = function(identifier, force) {
return this.QueryGenerator.quoteIdentifier(identifier, force)
}
/**
* Split an identifier into .-separated tokens and quote each part.
* If force is true, the identifier will be quoted even if the
* `quoteIdentifiers` option is false.
*/
QueryInterface.prototype.quoteIdentifiers = function(identifiers, force) {
return this.QueryGenerator.quoteIdentifiers(identifiers, force)
}
/**
* Escape a value (e.g. a string, number or date)
*/
QueryInterface.prototype.escape = function(value) {
return this.QueryGenerator.escape(value)
}
// private // private
var queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) { var queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) {
......
...@@ -26,6 +26,7 @@ module.exports = (function() { ...@@ -26,6 +26,7 @@ module.exports = (function() {
@param {Boolean} [options.native=false] A flag that defines if native library shall be used or not. @param {Boolean} [options.native=false] A flag that defines if native library shall be used or not.
@param {Boolean} [options.replication=false] I have absolutely no idea. @param {Boolean} [options.replication=false] I have absolutely no idea.
@param {Object} [options.pool={}] Something. @param {Object} [options.pool={}] Something.
@param {Boolean} [options.quoteIdentifiers=true] Set to `false` to make table names and attributes case-insensitive on Postgres and skip double quoting of them.
@example @example
// without password and options // without password and options
...@@ -78,7 +79,8 @@ module.exports = (function() { ...@@ -78,7 +79,8 @@ module.exports = (function() {
queue: true, queue: true,
native: false, native: false,
replication: false, replication: false,
pool: {} pool: {},
quoteIdentifiers: true
}, options || {}) }, options || {})
if (this.options.logging === true) { if (this.options.logging === true) {
......
...@@ -35,18 +35,6 @@ var Utils = module.exports = { ...@@ -35,18 +35,6 @@ var Utils = module.exports = {
addEventEmitter: function(_class) { addEventEmitter: function(_class) {
util.inherits(_class, require('events').EventEmitter) util.inherits(_class, require('events').EventEmitter)
}, },
TICK_CHAR: '`',
addTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return tickChar + Utils.removeTicks(s, tickChar) + tickChar
},
removeTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return s.replace(new RegExp(tickChar, 'g'), "")
},
escape: function(s) {
return SqlString.escape(s, true, "local").replace(/\\"/g, '"')
},
format: function(arr, dialect) { format: function(arr, dialect) {
var timeZone = null; var timeZone = null;
return SqlString.format(arr.shift(), arr, timeZone, dialect) return SqlString.format(arr.shift(), arr, timeZone, dialect)
...@@ -180,6 +168,22 @@ var Utils = module.exports = { ...@@ -180,6 +168,22 @@ var Utils = module.exports = {
var now = new Date() var now = new Date()
now.setMilliseconds(0) now.setMilliseconds(0)
return now return now
},
// Note: Use the `quoteIdentifier()` and `escape()` methods on the
// `QueryInterface` instead for more portable code.
TICK_CHAR: '`',
addTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return tickChar + Utils.removeTicks(s, tickChar) + tickChar
},
removeTicks: function(s, tickChar) {
tickChar = tickChar || Utils.TICK_CHAR
return s.replace(new RegExp(tickChar, 'g'), "")
},
escape: function(s) {
return SqlString.escape(s, true, "local").replace(/\\"/g, '"')
} }
} }
......
...@@ -8,7 +8,10 @@ describe('BelongsTo', function() { ...@@ -8,7 +8,10 @@ describe('BelongsTo', function() {
, Task = null , Task = null
var setup = function() { var setup = function() {
User = sequelize.define('User', { username: Sequelize.STRING }) User = sequelize.define('User', { username: Sequelize.STRING, enabled: {
type: Sequelize.BOOLEAN,
defaultValue: true
}})
Task = sequelize.define('Task', { title: Sequelize.STRING }) Task = sequelize.define('Task', { title: Sequelize.STRING })
} }
...@@ -88,6 +91,29 @@ describe('BelongsTo', function() { ...@@ -88,6 +91,29 @@ describe('BelongsTo', function() {
}) })
}) })
it('extends the id where param with the supplied where params', function() {
Task.belongsTo(User, {as: 'User'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'asd', enabled: false}).success(function(u) {
Task.create({title: 'a task'}).success(function(t) {
t.setUser(u).success(function() {
t.getUser({where: {enabled: true}}).success(function(user) {
expect(user).toEqual(null)
done()
})
})
})
})
})
})
it("handles self associations", function() { it("handles self associations", function() {
Helpers.async(function(done) { Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING }) var Person = sequelize.define('Person', { name: Sequelize.STRING })
......
...@@ -398,7 +398,8 @@ describe('QueryGenerator', function() { ...@@ -398,7 +398,8 @@ describe('QueryGenerator', function() {
it(title, function() { it(title, function() {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}}; var context = test.context || {options: {}};
var conditions = QueryGenerator[suiteTitle].apply(context, test.arguments) QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).toEqual(test.expectation) expect(conditions).toEqual(test.expectation)
}) })
......
...@@ -206,7 +206,7 @@ describe('QueryGenerator', function() { ...@@ -206,7 +206,7 @@ describe('QueryGenerator', function() {
expectation: "DELETE FROM `myTable` WHERE `id`=1" expectation: "DELETE FROM `myTable` WHERE `id`=1"
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;'" expectation: "DELETE FROM `myTable` WHERE `name`='foo'';DROP TABLE myTable;'"
}, { }, {
arguments: ['myTable', {name: 'foo'}, {limit: null}], arguments: ['myTable', {name: 'foo'}, {limit: null}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo'" expectation: "DELETE FROM `myTable` WHERE `name`='foo'"
...@@ -221,8 +221,8 @@ describe('QueryGenerator', function() { ...@@ -221,8 +221,8 @@ describe('QueryGenerator', function() {
it(title, function() { it(title, function() {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}}; var context = test.context || {options: {}};
QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(context, test.arguments) var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).toEqual(test.expectation) expect(conditions).toEqual(test.expectation)
}) })
}) })
......
...@@ -9,6 +9,7 @@ buster.spec.expose() ...@@ -9,6 +9,7 @@ buster.spec.expose()
buster.testRunner.timeout = 500 buster.testRunner.timeout = 500
describe(Helpers.getTestDialectTeaser("HasMany"), function() { describe(Helpers.getTestDialectTeaser("HasMany"), function() {
before(function(done) { before(function(done) {
var self = this var self = this
......
...@@ -65,4 +65,60 @@ if (dialect.match(/^postgres/)) { ...@@ -65,4 +65,60 @@ if (dialect.match(/^postgres/)) {
}) })
}) })
}) })
describe('[POSTGRES] Unquoted identifiers', function() {
before(function(done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize, DataTypes) {
self.sequelize = sequelize
self.sequelize.options.quoteIdentifiers = false
self.User = sequelize.define('User', {
username: DataTypes.STRING,
fullName: DataTypes.STRING // Note mixed case
})
},
onComplete: function() {
// We can create a table with non-quoted identifiers
self.User.sync({ force: true }).success(done)
}
})
})
it("can insert and select", function(done) {
var self = this
self.User
.create({ username: 'user', fullName: "John Smith" })
.success(function(user) {
// We can insert into a table with non-quoted identifiers
expect(user.id).toBeDefined()
expect(user.id).not.toBeNull()
expect(user.username).toEqual('user')
expect(user.fullName).toEqual('John Smith')
// We can query by non-quoted identifiers
self.User.find({
where: {fullName: "John Smith"}
})
.success(function(user2) {
// We can map values back to non-quoted identifiers
expect(user2.id).toEqual(user.id)
expect(user2.username).toEqual('user')
expect(user2.fullName).toEqual('John Smith')
done();
})
.error(function(err) {
console.log(err)
})
})
.error(function(err) {
console.log(err)
})
})
})
} }
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!