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

Commit 3b07b657 by Jonathan M. Altman

pulled in upstream master changes to simplify merge/pull request

2 parents c7739be1 6815eb51
...@@ -4,7 +4,6 @@ before_script: ...@@ -4,7 +4,6 @@ before_script:
script: script:
- "make test" - "make test"
- "make binary"
notifications: notifications:
email: email:
...@@ -21,4 +20,5 @@ env: ...@@ -21,4 +20,5 @@ env:
language: node_js language: node_js
node_js: node_js:
- 0.8 - "0.8"
- "0.10"
...@@ -58,6 +58,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -58,6 +58,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
### 1.7.0 ### 1.7.0
- ~~Check if lodash is a proper alternative to current underscore usage.~~ - ~~Check if lodash is a proper alternative to current underscore usage.~~
- Transactions - Transactions
- Associations of not yet saved objects: [#864](https://github.com/sequelize/sequelize/issues/864)
- Support for update of tables without primary key - Support for update of tables without primary key
- MariaDB support - MariaDB support
- ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude - ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude
...@@ -65,7 +66,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -65,7 +66,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy)) - ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy))
- ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango - ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango
- Move validation of enum attribute value to validate method - Move validation of enum attribute value to validate method
- BLOB [#99](https://github.com/sequelize/sequelize/issues/99) - ~~BLOB~~ [#842](https://github.com/sequelize/sequelize/pull/842), thanks to @janmeier
- ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude - ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude
### 1.7.x ### 1.7.x
......
...@@ -31,6 +31,12 @@ ...@@ -31,6 +31,12 @@
- [BUG] Custom primary key (not keys, just singular) should no longer be a problem for models when using any of the data retrievals with just a number or through associations [#771](https://github.com/sequelize/sequelize/pull/771). thanks to sdephold & durango - [BUG] Custom primary key (not keys, just singular) should no longer be a problem for models when using any of the data retrievals with just a number or through associations [#771](https://github.com/sequelize/sequelize/pull/771). thanks to sdephold & durango
- [BUG] Default schemas should now be utilized when describing tables [#812](https://github.com/sequelize/sequelize/pull/812). thanks to durango - [BUG] Default schemas should now be utilized when describing tables [#812](https://github.com/sequelize/sequelize/pull/812). thanks to durango
- [BUG] Fixed eager loading for many-to-many associations. [#834](https://github.com/sequelize/sequelize/pull/834). thanks to lemon-tree - [BUG] Fixed eager loading for many-to-many associations. [#834](https://github.com/sequelize/sequelize/pull/834). thanks to lemon-tree
- [BUG] allowNull: true enums can now be null [#857](https://github.com/sequelize/sequelize/pull/857). thanks to durango
- [BUG] Fixes Postgres' ability to search within arrays. [#879](https://github.com/sequelize/sequelize/pull/879). thanks to durango
- [BUG] Find and finAll would modify the options objects, now the objects are cloned at the start of the method [#884](https://github.com/sequelize/sequelize/pull/884) thanks to janmeier
- [BUG] Add support for typed arrays in SqlString.escape and SqlString.arrayToList [#891](https://github.com/sequelize/sequelize/pull/891). thanks to LJ1102
- [BUG] Postgres requires empty array to be explicitly cast on update [#890](https://github.com/sequelize/sequelize/pull/890). thanks to robraux
- [BUG] Added tests & bugfixes for DAO-Factory.update and array of values in where clause [#880](https://github.com/sequelize/sequelize/pull/880). thanks to domasx2
- [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
...@@ -56,6 +62,11 @@ ...@@ -56,6 +62,11 @@
- [FEATURE] bulkCreate() now has a third argument which gives you the ability to validate each row before attempting to bulkInsert [#797](https://github.com/sequelize/sequelize/pull/797). thanks to durango - [FEATURE] bulkCreate() now has a third argument which gives you the ability to validate each row before attempting to bulkInsert [#797](https://github.com/sequelize/sequelize/pull/797). thanks to durango
- [FEATURE] Added `isDirty` to model instances. [#798](https://github.com/sequelize/sequelize/pull/798). Thanks to mstorgaard - [FEATURE] Added `isDirty` to model instances. [#798](https://github.com/sequelize/sequelize/pull/798). Thanks to mstorgaard
- [FEATURE] Added possibility to use env variable for the database connection. [#784](https://github.com/sequelize/sequelize/pull/784). Thanks to sykopomp. - [FEATURE] Added possibility to use env variable for the database connection. [#784](https://github.com/sequelize/sequelize/pull/784). Thanks to sykopomp.
- [FEATURE] Blob support. janmeier
- [FEATURE] We can now define our own custom timestamp columns [#856](https://github.com/sequelize/sequelize/pull/856). thanks to durango
- [FEATURE] Scopes. [#748](https://github.com/sequelize/sequelize/pull/748). durango
- [FEATURE] Model#find() / Model#findAll() is now working with strings. [#855](https://github.com/sequelize/sequelize/pull/855). Thanks to whito.
- [FEATURE] Shortcut method for getting a defined model. [#868](https://github.com/sequelize/sequelize/pull/868). Thanks to jwilm.
- [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] 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] 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 - [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango
......
...@@ -22,9 +22,11 @@ module.exports = (function() { ...@@ -22,9 +22,11 @@ module.exports = (function() {
// the id is in the source table // the id is in the source table
BelongsTo.prototype.injectAttributes = function() { BelongsTo.prototype.injectAttributes = function() {
var newAttributes = {} var newAttributes = {}
, targetKeys = Object.keys(this.target.primaryKeys)
, keyType = ((this.target.hasPrimaryKeys && targetKeys.length === 1) ? this.target.rawAttributes[targetKeys[0]].type : DataTypes.INTEGER)
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName, this.target.options.language) + "Id", this.source.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName, this.target.options.language) + "Id", this.source.options.underscored)
newAttributes[this.identifier] = { type: this.options.keyType || DataTypes.INTEGER } newAttributes[this.identifier] = { type: this.options.keyType || keyType }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options) Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options)
Utils._.defaults(this.source.rawAttributes, newAttributes) Utils._.defaults(this.source.rawAttributes, newAttributes)
......
...@@ -57,30 +57,32 @@ module.exports = (function() { ...@@ -57,30 +57,32 @@ module.exports = (function() {
var self = this var self = this
, chainer = new Utils.QueryChainer() , chainer = new Utils.QueryChainer()
, association = self.__factory.target.associations[self.__factory.associationAccessor] , association = self.__factory.target.associations[self.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier; , foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier
, sourceKeys = Object.keys(self.__factory.source.primaryKeys)
, targetKeys = Object.keys(self.__factory.target.primaryKeys)
var obsoleteAssociations = oldAssociations.filter(function (old) { var obsoleteAssociations = oldAssociations.filter(function (old) {
// Return only those old associations that are not found in new // Return only those old associations that are not found in new
return !Utils._.find(newAssociations, function (obj) { return !Utils._.find(newAssociations, function (obj) {
return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id) return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
}) })
}) })
, unassociatedObjects = newAssociations.filter(function (obj) {
var unassociatedObjects = newAssociations.filter(function (obj) {
// Return only those associations that are new
return !Utils._.find(oldAssociations, function (old) { return !Utils._.find(oldAssociations, function (old) {
return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id) return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
}) })
}) })
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
var foreignIds = obsoleteAssociations.map(function (associatedObject) { var foreignIds = obsoleteAssociations.map(function (associatedObject) {
return associatedObject.id return ((targetKeys.length === 1) ? associatedObject[targetKeys[0]] : associatedObject.id)
}) })
, primaryKeys = Object.keys(self.__factory.connectorDAO.rawAttributes)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
var where = {} var where = {}
where[self.__factory.identifier] = self.instance[self.__factory.identifier] || self.instance.id where[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[foreignKey] = foreignIds where[foreignIdentifier] = foreignIds
chainer.add(self.__factory.connectorDAO.destroy(where)) chainer.add(self.__factory.connectorDAO.destroy(where))
} }
...@@ -88,8 +90,8 @@ module.exports = (function() { ...@@ -88,8 +90,8 @@ module.exports = (function() {
if (unassociatedObjects.length > 0) { if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) { var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {} var attributes = {}
attributes[self.__factory.identifier] = self.instance[self.__factory.identifier] || self.instance.id attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = unassociatedObject[foreignIdentifier] || unassociatedObject.id attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
return attributes return attributes
}) })
...@@ -109,8 +111,11 @@ module.exports = (function() { ...@@ -109,8 +111,11 @@ module.exports = (function() {
, association = this.__factory.target.associations[this.__factory.associationAccessor] , association = this.__factory.target.associations[this.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier; , foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier;
attributes[this.__factory.identifier] = this.instance[this.__factory.identifier] || this.instance.id var sourceKeys = Object.keys(this.__factory.source.primaryKeys);
attributes[foreignIdentifier] = newAssociation[foreignIdentifier] || newAssociation.id var targetKeys = Object.keys(this.__factory.target.primaryKeys);
attributes[this.__factory.identifier] = ((sourceKeys.length === 1) ? this.instance[sourceKeys[0]] : this.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? newAssociation[targetKeys[0]] : newAssociation.id)
this.__factory.connectorDAO.create(attributes) this.__factory.connectorDAO.create(attributes)
.success(function() { emitterProxy.emit('success', newAssociation) }) .success(function() { emitterProxy.emit('success', newAssociation) })
......
...@@ -54,9 +54,12 @@ module.exports = (function() { ...@@ -54,9 +54,12 @@ module.exports = (function() {
// define a new model, which connects the models // define a new model, which connects the models
var combinedTableAttributes = {} var combinedTableAttributes = {}
var keyType = this.options.keyType || DataTypes.INTEGER var sourceKeys = Object.keys(this.source.primaryKeys);
combinedTableAttributes[this.identifier] = {type: keyType, primaryKey: true} var sourceKeyType = ((!this.source.hasPrimaryKeys || sourceKeys.length !== 1) ? DataTypes.INTEGER : this.source.rawAttributes[sourceKeys[0]].type)
combinedTableAttributes[this.foreignIdentifier] = {type: keyType, primaryKey: true} var targetKeys = Object.keys(this.target.primaryKeys);
var targetKeyType = ((!this.target.hasPrimaryKeys || targetKeys.length !== 1) ? DataTypes.INTEGER : this.target.rawAttributes[targetKeys[0]].type)
combinedTableAttributes[this.identifier] = {type: sourceKeyType, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type: targetKeyType, primaryKey: true}
this.connectorDAO = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options) this.connectorDAO = this.source.daoFactoryManager.sequelize.define(this.combinedName, combinedTableAttributes, this.options)
......
...@@ -27,9 +27,11 @@ module.exports = (function() { ...@@ -27,9 +27,11 @@ module.exports = (function() {
// the id is in the target table // the id is in the target table
HasOne.prototype.injectAttributes = function() { HasOne.prototype.injectAttributes = function() {
var newAttributes = {} var newAttributes = {}
, sourceKeys = Object.keys(this.source.primaryKeys)
, keyType = ((this.source.hasPrimaryKeys && sourceKeys.length === 1) ? this.source.rawAttributes[sourceKeys[0]].type : DataTypes.INTEGER)
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored)
newAttributes[this.identifier] = { type: this.options.keyType || DataTypes.INTEGER } newAttributes[this.identifier] = { type: this.options.keyType || keyType }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options) Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
......
...@@ -28,7 +28,7 @@ module.exports = (function() { ...@@ -28,7 +28,7 @@ module.exports = (function() {
Object.defineProperty(DAO.prototype, 'isDeleted', { Object.defineProperty(DAO.prototype, 'isDeleted', {
get: function() { get: function() {
var result = this.__options.timestamps && this.__options.paranoid var result = this.__options.timestamps && this.__options.paranoid
result = result && this.dataValues[this.__options.underscored ? 'deleted_at' : 'deletedAt'] !== null result = result && this.dataValues[Utils._.underscoredIf(this.__options.deletedAt, this.__options.underscored)] !== null
return result return result
} }
...@@ -99,8 +99,8 @@ module.exports = (function() { ...@@ -99,8 +99,8 @@ module.exports = (function() {
DAO.prototype.save = function(fields, options) { DAO.prototype.save = function(fields, options) {
var self = this var self = this
, values = fields ? {} : this.dataValues , values = fields ? {} : this.dataValues
, updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt' , updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, createdAtAttr = this.__options.underscored ? 'created_at' : 'createdAt' , createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)
if (fields) { if (fields) {
if (self.__options.timestamps) { if (self.__options.timestamps) {
...@@ -122,6 +122,14 @@ module.exports = (function() { ...@@ -122,6 +122,14 @@ module.exports = (function() {
}) })
} }
var errors = this.validate()
if (!!errors) {
return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', errors)
}).run()
}
for (var attrName in this.daoFactory.rawAttributes) { for (var attrName in this.daoFactory.rawAttributes) {
if (this.daoFactory.rawAttributes.hasOwnProperty(attrName)) { if (this.daoFactory.rawAttributes.hasOwnProperty(attrName)) {
var definition = this.daoFactory.rawAttributes[attrName] var definition = this.daoFactory.rawAttributes[attrName]
...@@ -144,7 +152,7 @@ module.exports = (function() { ...@@ -144,7 +152,7 @@ module.exports = (function() {
valueOutOfScope = ((definition.values || []).indexOf(values[attrName]) === -1) valueOutOfScope = ((definition.values || []).indexOf(values[attrName]) === -1)
} }
if (isEnum && hasValue && valueOutOfScope) { if (isEnum && hasValue && valueOutOfScope && !(definition.allowNull === true && values[attrName] === null)) {
throw new Error('Value "' + values[attrName] + '" for ENUM ' + attrName + ' is out of allowed scope. Allowed values: ' + definition.values.join(', ')) throw new Error('Value "' + values[attrName] + '" for ENUM ' + attrName + ' is out of allowed scope. Allowed values: ' + definition.values.join(', '))
} }
...@@ -160,14 +168,7 @@ module.exports = (function() { ...@@ -160,14 +168,7 @@ module.exports = (function() {
this.dataValues[updatedAtAttr] = values[updatedAtAttr] = Utils.now(this.sequelize.options.dialect) this.dataValues[updatedAtAttr] = values[updatedAtAttr] = Utils.now(this.sequelize.options.dialect)
} }
var errors = this.validate() if (this.isNewRecord) {
if (!!errors) {
return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', errors)
}).run()
}
else if (this.isNewRecord) {
this.isDirty = false this.isDirty = false
return this.QueryInterface.insert(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values) return this.QueryInterface.insert(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values)
} else { } else {
...@@ -243,17 +244,17 @@ module.exports = (function() { ...@@ -243,17 +244,17 @@ module.exports = (function() {
readOnlyAttributes.push('id') readOnlyAttributes.push('id')
if (this.isNewRecord !== true) { if (this.isNewRecord !== true) {
readOnlyAttributes.push(this.daoFactory.options.underscored === true ? 'created_at' : 'createdAt') readOnlyAttributes.push(Utils._.underscoredIf(this.daoFactory.options.createdAt, this.daoFactory.options.underscored))
} }
// readOnlyAttributes.push(this.daoFactory.options.underscored === true ? 'updated_at' : 'updatedAt') // readOnlyAttributes.push(this.daoFactory.options.underscored === true ? 'updated_at' : 'updatedAt')
readOnlyAttributes.push(this.daoFactory.options.underscored === true ? 'deleted_at' : 'deletedAt') readOnlyAttributes.push(this.daoFactory.options.deletedAt, this.daoFactory.options.underscored)
var isDirty = this.isDirty var isDirty = this.isDirty
Utils._.each(updates, function(value, attr) { Utils._.each(updates, function(value, attr) {
var updateAllowed = ( var updateAllowed = (
(readOnlyAttributes.indexOf(attr) == -1) && (readOnlyAttributes.indexOf(attr) === -1) &&
(readOnlyAttributes.indexOf(Utils._.underscored(attr)) == -1) && (readOnlyAttributes.indexOf(Utils._.underscored(attr)) === -1) &&
(self.attributes.indexOf(attr) > -1) (self.attributes.indexOf(attr) > -1)
) )
...@@ -269,7 +270,7 @@ module.exports = (function() { ...@@ -269,7 +270,7 @@ module.exports = (function() {
// since we're updating the record, we should be updating the updatedAt column.. // since we're updating the record, we should be updating the updatedAt column..
if (this.daoFactory.options.timestamps === true) { if (this.daoFactory.options.timestamps === true) {
isDirty = true isDirty = true
self[this.daoFactory.options.underscored === true ? 'updated_at' : 'updatedAt'] = new Date() self[Utils._.underscoredIf(this.daoFactory.options.updatedAt, this.daoFactory.options.underscored)] = new Date()
} }
this.isDirty = isDirty this.isDirty = isDirty
...@@ -277,7 +278,7 @@ module.exports = (function() { ...@@ -277,7 +278,7 @@ module.exports = (function() {
DAO.prototype.destroy = function() { DAO.prototype.destroy = function() {
if (this.__options.timestamps && this.__options.paranoid) { if (this.__options.timestamps && this.__options.paranoid) {
var attr = this.__options.underscored ? 'deleted_at' : 'deletedAt' var attr = Utils._.underscoredIf(this.__options.deletedAt, this.__options.underscored)
this.dataValues[attr] = new Date() this.dataValues[attr] = new Date()
return this.save() return this.save()
} else { } else {
...@@ -341,10 +342,6 @@ module.exports = (function() { ...@@ -341,10 +342,6 @@ module.exports = (function() {
} }
DAO.prototype.addAttribute = function(attribute, value) { DAO.prototype.addAttribute = function(attribute, value) {
if (typeof this.dataValues[attribute] !== 'undefined') {
return
}
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 !== undefined) { // transform integer 0,1 into boolean
value = !!value value = !!value
} }
...@@ -374,9 +371,9 @@ module.exports = (function() { ...@@ -374,9 +371,9 @@ module.exports = (function() {
this.__defineSetter__(attribute, has.set || function(v) { this.__defineSetter__(attribute, has.set || function(v) {
if (Utils.hasChanged(this.dataValues[attribute], v)) { if (Utils.hasChanged(this.dataValues[attribute], v)) {
//Only dirty the object if the change is not due to id, touchedAt, createdAt or updatedAt being initiated //Only dirty the object if the change is not due to id, touchedAt, createdAt or updatedAt being initiated
var updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt' var updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, createdAtAttr = this.__options.underscored ? 'created_at' : 'createdAt' , createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)
, touchedAtAttr = this.__options.underscored ? 'touched_at' : 'touchedAt' , touchedAtAttr = Utils._.underscoredIf(this.__options.touchedAt, this.__options.underscored)
if (this.dataValues[attribute] || (attribute != 'id' && attribute != touchedAtAttr && attribute != createdAtAttr && attribute != updatedAtAttr)) { if (this.dataValues[attribute] || (attribute != 'id' && attribute != touchedAtAttr && attribute != createdAtAttr && attribute != updatedAtAttr)) {
this.isDirty = true this.isDirty = true
...@@ -422,11 +419,11 @@ module.exports = (function() { ...@@ -422,11 +419,11 @@ module.exports = (function() {
} }
if (this.__options.timestamps) { if (this.__options.timestamps) {
defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = Utils.now(this.sequelize.options.dialect) defaults[Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)] = Utils.now(this.sequelize.options.dialect)
defaults[this.__options.underscored ? 'updated_at' : 'updatedAt'] = Utils.now(this.sequelize.options.dialect) defaults[Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)] = Utils.now(this.sequelize.options.dialect)
if (this.__options.paranoid) { if (this.__options.paranoid) {
defaults[this.__options.underscored ? 'deleted_at' : 'deletedAt'] = null defaults[Utils._.underscoredIf(this.__options.deletedAt, this.__options.underscored)] = null
} }
} }
} }
......
var STRING = function(length, binary) { var STRING = function(length, binary) {
if (this instanceof STRING) { if (this instanceof STRING) {
this._binary = !!binary; this._binary = !!binary
if (typeof length === 'number') { if (typeof length === 'number') {
this._length = length; this._length = length
} else { } else {
this._length = 255; this._length = 255
} }
} else { } else {
return new STRING(length, binary); return new STRING(length, binary)
} }
}; }
STRING.prototype = { STRING.prototype = {
get BINARY() { get BINARY() {
this._binary = true; this._binary = true
return this; return this
}, },
get type() { get type() {
return this.toString(); return this.toString()
}, },
toString: function() { toString: function() {
return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : ''); return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : '')
} }
}; }
Object.defineProperty(STRING, 'BINARY', { Object.defineProperty(STRING, 'BINARY', {
get: function() { get: function() {
return new STRING(undefined, true); return new STRING(undefined, true)
} }
}); })
var INTEGER = function() { var INTEGER = function() {
return INTEGER.prototype.construct.apply(this, [INTEGER].concat(Array.prototype.slice.apply(arguments))); return INTEGER.prototype.construct.apply(this, [INTEGER].concat(Array.prototype.slice.apply(arguments)))
}; }
var BIGINT = function() { var BIGINT = function() {
return BIGINT.prototype.construct.apply(this, [BIGINT].concat(Array.prototype.slice.apply(arguments))); return BIGINT.prototype.construct.apply(this, [BIGINT].concat(Array.prototype.slice.apply(arguments)))
}; }
var FLOAT = function() { var FLOAT = function() {
return FLOAT.prototype.construct.apply(this, [FLOAT].concat(Array.prototype.slice.apply(arguments))); return FLOAT.prototype.construct.apply(this, [FLOAT].concat(Array.prototype.slice.apply(arguments)))
}; }
FLOAT._type = FLOAT; var BLOB = function() {
FLOAT._typeName = 'FLOAT'; return BLOB.prototype.construct.apply(this, [BLOB].concat(Array.prototype.slice.apply(arguments)))
INTEGER._type = INTEGER; }
INTEGER._typeName = 'INTEGER';
BIGINT._type = BIGINT; FLOAT._type = FLOAT
BIGINT._typeName = 'BIGINT'; FLOAT._typeName = 'FLOAT'
STRING._type = STRING; INTEGER._type = INTEGER
STRING._typeName = 'VARCHAR'; INTEGER._typeName = 'INTEGER'
BIGINT._type = BIGINT
STRING.toString = INTEGER.toString = FLOAT.toString = BIGINT.toString = function() { BIGINT._typeName = 'BIGINT'
return new this._type().toString(); STRING._type = STRING
}; STRING._typeName = 'VARCHAR'
BLOB._type = BLOB
BLOB._typeName = 'BLOB'
BLOB.toString = STRING.toString = INTEGER.toString = FLOAT.toString = BIGINT.toString = function() {
return new this._type().toString()
}
BLOB.prototype = {
construct: function(RealType, length) {
if (this instanceof RealType) {
this._typeName = RealType._typeName
if (typeof length === 'string') {
this._length = length
} else {
this._length = ''
}
} else {
return new RealType(length)
}
},
get type() {
return this.toString()
},
toString: function() {
switch (this._length.toLowerCase()) {
case 'tiny':
return 'TINYBLOB'
case 'medium':
return 'MEDIUMBLOB'
case 'long':
return 'LONGBLOB'
default:
return this._typeName
}
}
}
FLOAT.prototype = BIGINT.prototype = INTEGER.prototype = { FLOAT.prototype = BIGINT.prototype = INTEGER.prototype = {
construct: function(RealType, length, decimals, unsigned, zerofill) { construct: function(RealType, length, decimals, unsigned, zerofill) {
if (this instanceof RealType) { if (this instanceof RealType) {
this._typeName = RealType._typeName; this._typeName = RealType._typeName
this._unsigned = !!unsigned; this._unsigned = !!unsigned
this._zerofill = !!zerofill; this._zerofill = !!zerofill
if (typeof length === 'number') { if (typeof length === 'number') {
this._length = length; this._length = length
} }
if (typeof decimals === 'number') { if (typeof decimals === 'number') {
this._decimals = decimals; this._decimals = decimals
} }
} else { } else {
return new RealType(length, decimals, unsigned, zerofill); return new RealType(length, decimals, unsigned, zerofill)
} }
}, },
get type() { get type() {
return this.toString(); return this.toString()
}, },
get UNSIGNED() { get UNSIGNED() {
this._unsigned = true; this._unsigned = true
return this; return this
}, },
get ZEROFILL() { get ZEROFILL() {
this._zerofill = true; this._zerofill = true
return this; return this
}, },
toString: function() { toString: function() {
var result = this._typeName; var result = this._typeName
if (this._length) { if (this._length) {
result += '(' + this._length; result += '(' + this._length
if (typeof this._decimals === 'number') { if (typeof this._decimals === 'number') {
result += ',' + this._decimals; result += ',' + this._decimals
} }
result += ')'; result += ')'
} }
if (this._unsigned) { if (this._unsigned) {
result += ' UNSIGNED'; result += ' UNSIGNED'
} }
if (this._zerofill) { if (this._zerofill) {
result += ' ZEROFILL'; result += ' ZEROFILL'
} }
return result; return result
} }
}; }
var unsignedDesc = { var unsignedDesc = {
get: function() { get: function() {
return new this._type(undefined, undefined, true); return new this._type(undefined, undefined, true)
} }
}; }
var zerofillDesc = { var zerofillDesc = {
get: function() { get: function() {
return new this._type(undefined, undefined, undefined, true); return new this._type(undefined, undefined, undefined, true)
} }
}; }
var typeDesc = { var typeDesc = {
get: function() { get: function() {
return new this._type().toString(); return new this._type().toString()
} }
}; }
Object.defineProperty(STRING, 'type', typeDesc); Object.defineProperty(STRING, 'type', typeDesc)
Object.defineProperty(INTEGER, 'type', typeDesc); Object.defineProperty(INTEGER, 'type', typeDesc)
Object.defineProperty(BIGINT, 'type', typeDesc); Object.defineProperty(BIGINT, 'type', typeDesc)
Object.defineProperty(FLOAT, 'type', typeDesc); Object.defineProperty(FLOAT, 'type', typeDesc)
Object.defineProperty(BLOB, 'type', typeDesc)
Object.defineProperty(INTEGER, 'UNSIGNED', unsignedDesc); Object.defineProperty(INTEGER, 'UNSIGNED', unsignedDesc)
Object.defineProperty(BIGINT, 'UNSIGNED', unsignedDesc); Object.defineProperty(BIGINT, 'UNSIGNED', unsignedDesc)
Object.defineProperty(FLOAT, 'UNSIGNED', unsignedDesc); Object.defineProperty(FLOAT, 'UNSIGNED', unsignedDesc)
Object.defineProperty(INTEGER, 'ZEROFILL', zerofillDesc); Object.defineProperty(INTEGER, 'ZEROFILL', zerofillDesc)
Object.defineProperty(BIGINT, 'ZEROFILL', zerofillDesc); Object.defineProperty(BIGINT, 'ZEROFILL', zerofillDesc)
Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc); Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc)
module.exports = { module.exports = {
STRING: STRING, STRING: STRING,
...@@ -147,6 +187,7 @@ module.exports = { ...@@ -147,6 +187,7 @@ module.exports = {
BOOLEAN: 'TINYINT(1)', BOOLEAN: 'TINYINT(1)',
FLOAT: FLOAT, FLOAT: FLOAT,
NOW: 'NOW', NOW: 'NOW',
BLOB: BLOB,
get ENUM() { get ENUM() {
var result = function() { var result = function() {
......
...@@ -182,32 +182,37 @@ module.exports = (function() { ...@@ -182,32 +182,37 @@ module.exports = (function() {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes] var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) { options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) { var attributes = include.attributes.map(function(attr) {
return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr) return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr)
}.bind(this)) }.bind(this))
optAttributes = optAttributes.concat(attributes) optAttributes = optAttributes.concat(attributes)
if (!include.association.connectorDAO) {
var table = include.daoFactory.tableName var table = include.daoFactory.tableName
var as = include.as , as = include.as
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var attrLeft = 'id' if (!include.association.connectorDAO) {
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as) var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
var attrRight = include.association.identifier , tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
, attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
, tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
, attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight)
} else { } else {
var table = include.daoFactory.tableName var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
var as = include.as , tableSource = tableName
var tableLeft = tableName , identSource = include.association.identifier
var identLeft = include.association.identifier , attrSource = ((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
var attrLeft = 'id'
var tableRight = include.as var primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
var identRight = include.association.foreignIdentifier , tableTarget = include.as
var attrRight = 'id' , identTarget = include.association.foreignIdentifier
, attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
var tableJunction = include.association.connectorDAO.tableName var tableJunction = include.association.connectorDAO.tableName
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identLeft) joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableSource) + "." + this.quoteIdentifier(attrSource) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identSource)
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identRight) joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableTarget) + "." + this.quoteIdentifier(attrTarget) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identTarget)
} }
}.bind(this)) }.bind(this))
...@@ -467,8 +472,13 @@ module.exports = (function() { ...@@ -467,8 +472,13 @@ module.exports = (function() {
result.push([_key, _value].join("=")) result.push([_key, _value].join("="))
} else { } else {
for (var logic in value) { for (var logic in value) {
var logicResult = this.getWhereLogic(logic) var logicResult = Utils.getWhereLogic(logic)
if (logicResult === "BETWEEN" || logicResult === "NOT BETWEEN") { if (logic === "IN" || logic === "NOT IN") {
var values = Array.isArray(where[i][ii]) ? where[i][ii] : [where[i][ii]]
_where[_where.length] = i + ' ' + logic + ' (' + values.map(function(){ return '?' }).join(',') + ')'
_whereArgs = _whereArgs.concat(values)
}
else if (logicResult === "BETWEEN" || logicResult === "NOT BETWEEN") {
_value = this.escape(value[logic][0]) _value = this.escape(value[logic][0])
var _value2 = this.escape(value[logic][1]) var _value2 = this.escape(value[logic][1])
...@@ -488,33 +498,6 @@ module.exports = (function() { ...@@ -488,33 +498,6 @@ module.exports = (function() {
return result.join(" AND ") return result.join(" AND ")
}, },
getWhereLogic: function(logic) {
switch (logic) {
case 'gte':
return '>='
break
case 'gt':
return '>'
break
case 'lte':
return '<='
break
case 'lt':
return '<'
break
case 'ne':
return '!='
break
case 'between':
return 'BETWEEN'
break
case 'nbetween':
case 'notbetween':
return 'NOT BETWEEN'
break
}
},
attributesToSQL: function(attributes) { attributesToSQL: function(attributes) {
var result = {} var result = {}
......
...@@ -19,8 +19,9 @@ module.exports = (function() { ...@@ -19,8 +19,9 @@ module.exports = (function() {
// set pooling parameters if specified // set pooling parameters if specified
if (this.pooling) { if (this.pooling) {
this.pg.defaults.poolSize = this.config.pool.maxConnections this.pg.defaults.poolSize = this.config.pool.maxConnections || 10
this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime || 30000
this.pg.defaults.reapIntervalMillis = this.config.pool.reapInterval || 1000
} }
this.disconnectTimeoutId = null this.disconnectTimeoutId = null
...@@ -57,9 +58,9 @@ module.exports = (function() { ...@@ -57,9 +58,9 @@ module.exports = (function() {
}) })
.on('success', function(done) { .on('success', function(done) {
var query = new Query(self.client, self.sequelize, callee, options || {}) var query = new Query(self.client, self.sequelize, callee, options || {})
done = done || null
return query.run(sql, done) return query.run(sql)
.complete(function(err) { done && done(err) })
.success(function(results) { self.endQuery.call(self) }) .success(function(results) { self.endQuery.call(self) })
.error(function(err) { self.endQuery.call(self) }) .error(function(err) { self.endQuery.call(self) })
.proxy(emitter) .proxy(emitter)
...@@ -89,6 +90,7 @@ module.exports = (function() { ...@@ -89,6 +90,7 @@ module.exports = (function() {
if (!!err) { if (!!err) {
// release the pool immediately, very important. // release the pool immediately, very important.
done && done(err) done && done(err)
self.client = null
if (err.code) { if (err.code) {
switch(err.code) { switch(err.code) {
...@@ -112,18 +114,18 @@ module.exports = (function() { ...@@ -112,18 +114,18 @@ module.exports = (function() {
emitter.emit('success', done) emitter.emit('success', done)
}) })
} else { } else {
done && done()
self.client = null self.client = null
emitter.emit('success', done) emitter.emit('success')
} }
} }
if (this.pooling) { if (this.pooling) {
// acquire client from pool // acquire client from pool
this.poolIdentifier = this.pg.pools.getOrCreate(uri) this.pg.connect(uri, connectCallback)
this.poolIdentifier.connect(connectCallback)
} else { } else {
if (!!this.client) { if (!!this.client) {
connectCallback(null, this.client); connectCallback(null, this.client)
} else { } else {
//create one-off client //create one-off client
this.client = new this.pg.Client(uri) this.client = new this.pg.Client(uri)
...@@ -140,10 +142,11 @@ module.exports = (function() { ...@@ -140,10 +142,11 @@ module.exports = (function() {
} }
if (this.client) { if (this.client) {
this.client.end() // Closes a client correctly even if we have backed up queries
// https://github.com/brianc/node-postgres/pull/346
this.client.on('drain', this.client.end.bind(this.client))
} }
this.client = null
this.isConnecting = false this.isConnecting = false
this.isConnected = false this.isConnected = false
} }
......
...@@ -68,10 +68,6 @@ module.exports = (function() { ...@@ -68,10 +68,6 @@ module.exports = (function() {
var dataType = this.pgDataTypeMapping(tableName, attr, attributes[attr]) var dataType = this.pgDataTypeMapping(tableName, attr, attributes[attr])
attrStr.push(this.quoteIdentifier(attr) + " " + dataType) attrStr.push(this.quoteIdentifier(attr) + " " + dataType)
if (attributes[attr].match(/^ENUM\(/)) {
query = this.pgEnum(tableName, attr, attributes[attr]) + query
}
} }
var values = { var values = {
...@@ -253,7 +249,7 @@ module.exports = (function() { ...@@ -253,7 +249,7 @@ module.exports = (function() {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes] var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) { options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) { var attributes = include.attributes.map(function(attr) {
return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr, true) return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr, true)
}.bind(this)) }.bind(this))
...@@ -262,20 +258,24 @@ module.exports = (function() { ...@@ -262,20 +258,24 @@ module.exports = (function() {
var joinQuery = ' LEFT OUTER JOIN <%= table %> AS <%= as %> ON <%= tableLeft %>.<%= attrLeft %> = <%= tableRight %>.<%= attrRight %>' var joinQuery = ' LEFT OUTER JOIN <%= table %> AS <%= as %> ON <%= tableLeft %>.<%= attrLeft %> = <%= tableRight %>.<%= attrRight %>'
if (!include.association.connectorDAO) { if (!include.association.connectorDAO) {
var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
query += Utils._.template(joinQuery)({ query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.daoFactory.tableName), table: this.quoteIdentifiers(include.daoFactory.tableName),
as: this.quoteIdentifier(include.as), as: this.quoteIdentifier(include.as),
tableLeft: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? include.as : tableName), tableLeft: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? include.as : tableName),
attrLeft: this.quoteIdentifier('id'), attrLeft: this.quoteIdentifier(((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])),
tableRight: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? tableName : include.as), tableRight: this.quoteIdentifiers((include.association.associationType === 'BelongsTo') ? tableName : include.as),
attrRight: this.quoteIdentifier(include.association.identifier) attrRight: this.quoteIdentifier(include.association.identifier)
}) })
} else { } else {
var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
, primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
query += Utils._.template(joinQuery)({ query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.association.connectorDAO.tableName), table: this.quoteIdentifiers(include.association.connectorDAO.tableName),
as: this.quoteIdentifier(include.association.connectorDAO.tableName), as: this.quoteIdentifier(include.association.connectorDAO.tableName),
tableLeft: this.quoteIdentifiers(tableName), tableLeft: this.quoteIdentifiers(tableName),
attrLeft: this.quoteIdentifier('id'), attrLeft: this.quoteIdentifier(((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])),
tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName), tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName),
attrRight: this.quoteIdentifier(include.association.identifier) attrRight: this.quoteIdentifier(include.association.identifier)
}) })
...@@ -284,7 +284,7 @@ module.exports = (function() { ...@@ -284,7 +284,7 @@ module.exports = (function() {
table: this.quoteIdentifiers(include.daoFactory.tableName), table: this.quoteIdentifiers(include.daoFactory.tableName),
as: this.quoteIdentifier(include.as), as: this.quoteIdentifier(include.as),
tableLeft: this.quoteIdentifiers(include.as), tableLeft: this.quoteIdentifiers(include.as),
attrLeft: this.quoteIdentifier('id'), attrLeft: this.quoteIdentifier(((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])),
tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName), tableRight: this.quoteIdentifiers(include.association.connectorDAO.tableName),
attrRight: this.quoteIdentifier(include.association.foreignIdentifier) attrRight: this.quoteIdentifier(include.association.foreignIdentifier)
}) })
...@@ -382,7 +382,7 @@ module.exports = (function() { ...@@ -382,7 +382,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
updateQuery: function(tableName, attrValueHash, where, options) { updateQuery: function(tableName, attrValueHash, where, options, attributes) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull, options) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull, options)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> RETURNING *" var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> RETURNING *"
...@@ -390,7 +390,7 @@ module.exports = (function() { ...@@ -390,7 +390,7 @@ module.exports = (function() {
for (var key in attrValueHash) { for (var key in attrValueHash) {
var value = attrValueHash[key] var value = attrValueHash[key]
values.push(this.quoteIdentifier(key) + "=" + this.escape(value)) values.push(this.quoteIdentifier(key) + "=" + this.escape(value, (!!attributes && !!attributes[key] ? attributes[key] : undefined)))
} }
var replacements = { var replacements = {
...@@ -402,7 +402,7 @@ module.exports = (function() { ...@@ -402,7 +402,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
deleteQuery: function(tableName, where, options) { deleteQuery: function(tableName, where, options, factory) {
options = options || {} options = options || {}
if (options.truncate === true) { if (options.truncate === true) {
...@@ -415,6 +415,10 @@ module.exports = (function() { ...@@ -415,6 +415,10 @@ module.exports = (function() {
primaryKeys[tableName] = primaryKeys[tableName] || []; primaryKeys[tableName] = primaryKeys[tableName] || [];
if (!!factory && primaryKeys[tableName].length < 1) {
primaryKeys[tableName] = Object.keys(factory.primaryKeys)
}
var query = "DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %> WHERE <%= where %><%= limit %>)" var query = "DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %> WHERE <%= where %><%= limit %>)"
var pks; var pks;
...@@ -526,7 +530,7 @@ module.exports = (function() { ...@@ -526,7 +530,7 @@ module.exports = (function() {
if (Utils.isHash(smth)) { if (Utils.isHash(smth)) {
smth = Utils.prependTableNameToHash(tableName, smth) smth = Utils.prependTableNameToHash(tableName, smth)
result = this.hashToWhereConditions(smth) result = this.hashToWhereConditions(smth, factory)
} else if (typeof smth === 'number') { } else if (typeof smth === 'number') {
var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : [] var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : []
if (primaryKeys.length > 0) { if (primaryKeys.length > 0) {
...@@ -549,7 +553,7 @@ module.exports = (function() { ...@@ -549,7 +553,7 @@ module.exports = (function() {
return result return result
}, },
hashToWhereConditions: function(hash) { hashToWhereConditions: function(hash, factory) {
var result = [] var result = []
for (var key in hash) { for (var key in hash) {
...@@ -561,10 +565,24 @@ module.exports = (function() { ...@@ -561,10 +565,24 @@ module.exports = (function() {
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (value.length === 0) { value = [null] } if (value.length === 0) { value = [null] }
var col = null, coltype = null
// Special conditions for searching within an array column type
var _realKey = key.split('.').pop()
if (!!factory && !!factory.rawAttributes[_realKey]) {
col = factory.rawAttributes[_realKey]
coltype = col.type
if(coltype && !(typeof coltype == 'string')) {
coltype = coltype.toString();
}
}
if ( col && ((!!coltype && coltype.match(/\[\]$/) !== null) || (col.toString().match(/\[\]$/) !== null))) {
_value = 'ARRAY[' + value.map(this.escape).join(',') + ']::' + (!!col.type ? col.type : col.toString())
result.push([_key, _value].join(" && "))
} else {
_value = "(" + value.map(this.escape).join(',') + ")" _value = "(" + value.map(this.escape).join(',') + ")"
result.push([_key, _value].join(" IN ")) result.push([_key, _value].join(" IN "))
} }
}
else if ((value) && (typeof value === "object")) { else if ((value) && (typeof value === "object")) {
if (!!value.join) { if (!!value.join) {
//using as sentinel for join column => value //using as sentinel for join column => value
...@@ -572,8 +590,13 @@ module.exports = (function() { ...@@ -572,8 +590,13 @@ module.exports = (function() {
result.push([_key, _value].join("=")) result.push([_key, _value].join("="))
} else { } else {
for (var logic in value) { for (var logic in value) {
var logicResult = this.getWhereLogic(logic) var logicResult = Utils.getWhereLogic(logic)
if (logicResult === "BETWEEN" || logicResult === "NOT BETWEEN") { if (logic === "IN" || logic === "NOT IN") {
var values = Array.isArray(where[i][ii]) ? where[i][ii] : [where[i][ii]]
_where[_where.length] = i + ' ' + logic + ' (' + values.map(function(){ return '?' }).join(',') + ')'
_whereArgs = _whereArgs.concat(values)
}
else if (logicResult === "BETWEEN" || logicResult === "NOT BETWEEN") {
_value = this.escape(value[logic][0]) _value = this.escape(value[logic][0])
var _value2 = this.escape(value[logic][1]) var _value2 = this.escape(value[logic][1])
...@@ -593,33 +616,6 @@ module.exports = (function() { ...@@ -593,33 +616,6 @@ module.exports = (function() {
return result.join(' AND ') return result.join(' AND ')
}, },
getWhereLogic: function(logic) {
switch (logic) {
case 'gte':
return '>='
break
case 'gt':
return '>'
break
case 'lte':
return '<='
break
case 'lt':
return '<'
break
case 'ne':
return '!='
break
case 'between':
return 'BETWEEN'
break
case 'nbetween':
case 'notbetween':
return 'NOT BETWEEN'
break
}
},
attributesToSQL: function(attributes) { attributesToSQL: function(attributes) {
var result = {} var result = {}
...@@ -893,9 +889,52 @@ module.exports = (function() { ...@@ -893,9 +889,52 @@ module.exports = (function() {
}).join(' OR '); }).join(' OR ');
}, },
pgEnum: function (tableName, attr, dataType) { pgListEnums: function(tableName, attrName, options) {
if (arguments.length === 1) {
options = tableName
tableName = null
}
var enumName = ''
if (!!tableName && !!attrName) {
enumName = ' AND t.typname=' + this.escape("enum_" + tableName + "_" + attrName) + ' '
}
var query = 'SELECT t.typname enum_name, array_agg(e.enumlabel) enum_value FROM pg_type t ' +
'JOIN pg_enum e ON t.oid = e.enumtypid ' +
'JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace ' +
'WHERE n.nspname = \'public\' ' + enumName + ' GROUP BY 1'
return query
},
pgEnum: function (tableName, attr, dataType, options) {
var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
var sql = "CREATE TYPE " + enumName + " AS " + dataType.match(/^ENUM\(.+\)/)[0] + "; "
if (!!options && options.force === true) {
sql = this.pgEnumDrop(tableName, attr) + sql
}
return sql
},
pgEnumAdd: function(tableName, attr, value, options) {
var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
var sql = 'ALTER TYPE ' + enumName + ' ADD VALUE ' + this.escape(value)
if (!!options.before) {
sql += ' BEFORE ' + this.escape(options.before)
}
else if (!!options.after) {
sql += ' AFTER ' + this.escape(options.after)
}
return sql
},
pgEnumDrop: function(tableName, attr) {
var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr) var enumName = this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)
return "DROP TYPE IF EXISTS " + enumName + "; CREATE TYPE " + enumName + " AS " + dataType.match(/^ENUM\(.+\)/)[0] + "; " return 'DROP TYPE IF EXISTS ' + enumName + '; '
}, },
fromArray: function(text) { fromArray: function(text) {
...@@ -943,6 +982,10 @@ module.exports = (function() { ...@@ -943,6 +982,10 @@ module.exports = (function() {
dataType = dataType.replace(/NOT NULL/, '') dataType = dataType.replace(/NOT NULL/, '')
} }
if (dataType.lastIndexOf('BLOB') !== -1) {
dataType = 'bytea'
}
if (dataType.match(/^ENUM\(/)) { if (dataType.match(/^ENUM\(/)) {
dataType = dataType.replace(/^ENUM\(.+\)/, this.pgEscapeAndQuote("enum_" + tableName + "_" + attr)) dataType = dataType.replace(/^ENUM\(.+\)/, this.pgEscapeAndQuote("enum_" + tableName + "_" + attr))
} }
......
...@@ -18,37 +18,36 @@ module.exports = (function() { ...@@ -18,37 +18,36 @@ module.exports = (function() {
} }
Utils.inherit(Query, AbstractQuery) Utils.inherit(Query, AbstractQuery)
Query.prototype.run = function(sql, done) { Query.prototype.run = function(sql) {
this.sql = sql this.sql = sql
var self = this var self = this
, receivedError = false
, query = this.client.query(sql)
, rows = []
if (this.options.logging !== false) { if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing: ' + this.sql)
} }
var receivedError = false
, query = this.client.query(sql)
, rows = []
query.on('row', function(row) { query.on('row', function(row) {
rows.push(row) rows.push(row)
}) })
query.on('error', function(err) { query.on('error', function(err) {
receivedError = true receivedError = true
this.emit('error', err, this.callee) self.emit('error', err, self.callee)
}.bind(this)) })
query.on('end', function() { query.on('end', function() {
done && done() self.emit('sql', self.sql)
this.emit('sql', this.sql)
if (receivedError) { if (receivedError) {
return return
} }
onSuccess.call(this, rows, sql) onSuccess.call(self, rows, sql)
}.bind(this)) })
return this return this
} }
......
...@@ -164,32 +164,37 @@ module.exports = (function() { ...@@ -164,32 +164,37 @@ module.exports = (function() {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes] var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) { options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) { var attributes = include.attributes.map(function(attr) {
return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr) return this.quoteIdentifier(include.as) + "." + this.quoteIdentifier(attr) + " AS " + this.quoteIdentifier(include.as + "." + attr)
}.bind(this)) }.bind(this))
optAttributes = optAttributes.concat(attributes) optAttributes = optAttributes.concat(attributes)
if (!include.association.connectorDAO) {
var table = include.daoFactory.tableName var table = include.daoFactory.tableName
var as = include.as , as = include.as
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var attrLeft = 'id' if (!include.association.connectorDAO) {
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as) var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
var attrRight = include.association.identifier , tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
, attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
, tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
, attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight)
} else { } else {
var table = include.daoFactory.tableName var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
var as = include.as , tableSource = tableName
var tableLeft = tableName , identSource = include.association.identifier
var identLeft = include.association.identifier , attrSource = ((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
var attrLeft = 'id'
var tableRight = include.as var primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
var identRight = include.association.foreignIdentifier , tableTarget = include.as
var attrRight = 'id' , identTarget = include.association.foreignIdentifier
, attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
var tableJunction = include.association.connectorDAO.tableName var tableJunction = include.association.connectorDAO.tableName
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableLeft) + "." + this.quoteIdentifier(attrLeft) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identLeft) joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(tableJunction) + " ON " + this.quoteIdentifier(tableSource) + "." + this.quoteIdentifier(attrSource) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identSource)
joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identRight) joinQuery += " LEFT OUTER JOIN " + this.quoteIdentifier(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableTarget) + "." + this.quoteIdentifier(attrTarget) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identTarget)
} }
}.bind(this)) }.bind(this))
......
...@@ -29,7 +29,7 @@ module.exports = (function() { ...@@ -29,7 +29,7 @@ module.exports = (function() {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing: ' + this.sql)
} }
var columnTypes = {}; var columnTypes = {}
this.database.serialize(function() { this.database.serialize(function() {
var executeSql = function() { var executeSql = function() {
self.database[getDatabaseMethod.call(self)](self.sql, function(err, results) { self.database[getDatabaseMethod.call(self)](self.sql, function(err, results) {
...@@ -48,16 +48,16 @@ module.exports = (function() { ...@@ -48,16 +48,16 @@ module.exports = (function() {
self.database.all("PRAGMA table_info(" + tableName + ")", function(err, results) { self.database.all("PRAGMA table_info(" + tableName + ")", function(err, results) {
if (!err) { if (!err) {
for (var i=0, l=results.length; i<l; i++) { for (var i=0, l=results.length; i<l; i++) {
columnTypes[results[i].name] = results[i].type; columnTypes[results[i].name] = results[i].type
} }
} }
executeSql(); executeSql()
}); });
} else { } else {
executeSql(); executeSql()
} }
} else { } else {
executeSql(); executeSql()
} }
}) })
...@@ -85,15 +85,18 @@ module.exports = (function() { ...@@ -85,15 +85,18 @@ module.exports = (function() {
if (this.sql.indexOf('sqlite_master') !== -1) { if (this.sql.indexOf('sqlite_master') !== -1) {
result = results.map(function(resultSet) { return resultSet.name }) result = results.map(function(resultSet) { return resultSet.name })
} else if (this.send('isSelectQuery')) { } else if (this.send('isSelectQuery')) {
// we need to convert the timestamps into actual date objects
if(!this.options.raw) { if(!this.options.raw) {
results = results.map(function(result) { results = results.map(function(result) {
for (var name in result) { for (var name in result) {
if (result.hasOwnProperty(name) && (metaData.columnTypes[name] === 'DATETIME')) { if (result.hasOwnProperty(name) && metaData.columnTypes[name]) {
var val = result[name]; if (metaData.columnTypes[name] === 'DATETIME') {
if(val !== null) { // we need to convert the timestamps into actual date objects
result[name] = new Date(val+'Z'); // Z means UTC var val = result[name]
if (val !== null) {
result[name] = new Date(val+'Z') // Z means UTC
}
} else if (metaData.columnTypes[name].lastIndexOf('BLOB') !== -1) {
result[name] = new Buffer(result[name])
} }
} }
} }
......
...@@ -64,24 +64,160 @@ module.exports = (function() { ...@@ -64,24 +64,160 @@ module.exports = (function() {
QueryInterface.prototype.createTable = function(tableName, attributes, options) { QueryInterface.prototype.createTable = function(tableName, attributes, options) {
var attributeHashes = {} var attributeHashes = {}
, dataTypeValues = Utils._.values(DataTypes)
Utils._.each(attributes, function(dataTypeOrOptions, attributeName) { , keys = Object.keys(attributes)
if (Utils._.values(DataTypes).indexOf(dataTypeOrOptions) > -1) { , keyLen = keys.length
attributeHashes[attributeName] = { type: dataTypeOrOptions, allowNull: true } , self = this
, sql = ''
, i = 0
for (i = 0; i < keyLen; i++) {
if (dataTypeValues.indexOf(attributes[keys[i]]) > -1) {
attributeHashes[keys[i]] = { type: attributes[keys[i]], allowNull: true }
} else { } else {
attributeHashes[attributeName] = dataTypeOrOptions attributeHashes[keys[i]] = attributes[keys[i]]
}
}
return new Utils.CustomEventEmitter(function(emitter) {
// Postgres requires a special SQL command for enums
if (self.sequelize.options.dialect === "postgres") {
var chainer = new Utils.QueryChainer()
// For backwards-compatibility, public schemas don't need to
// explicitly state their schema when creating a new enum type
, getTableName = (!options || !options.schema || options.schema === "public" ? '' : options.schema + '_') + tableName
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.runSerially().success(function(results) {
var chainer2 = new Utils.QueryChainer()
// Find the table that we're trying to create throgh DAOFactoryManager
, daoTable = self.sequelize.daoFactoryManager.daos.filter(function(dao) { return dao.tableName === tableName })
, enumIdx = 0
daoTable = daoTable.length > 0 ? daoTable[0] : null
for (i = 0; i < keyLen; i++) {
if (attributes[keys[i]].toString().match(/^ENUM\(/)) {
// 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 }))
}
else if (!!results[enumIdx] && !!daoTable) {
var enumVals = self.QueryGenerator.fromArray(results[enumIdx].enum_value)
, vals = daoTable.rawAttributes[keys[i]].values
vals.forEach(function(value, idx) {
// reset out after/before options since it's for every enum value
options.before = null
options.after = null
if (enumVals.indexOf(value) === -1) {
if (!!vals[idx+1]) {
options.before = vals[idx+1]
}
else if (!!vals[idx-1]) {
options.after = vals[idx-1]
}
chainer2.add(self.sequelize.query(self.QueryGenerator.pgEnumAdd(getTableName, keys[i], value, options)))
} }
}) })
}
}
}
attributes = this.QueryGenerator.attributesToSQL(attributeHashes) attributes = self.QueryGenerator.attributesToSQL(attributeHashes)
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
chainer2.run().success(function() {
queryAndEmit.call(self, sql, 'createTable')
.success(function(res) {
self.emit('createTable', null)
emitter.emit('success', res)
})
.error(function(err) {
self.emit('createTable', err)
emitter.emit('error', err)
})
.on('sql', function(sql) { emitter.emit('sql', sql) })
}).error(function(err) {
emitter.emit('error', err)
}).on('sql', function(sql) {
emitter.emit('sql', sql)
})
})
} else {
attributes = self.QueryGenerator.attributesToSQL(attributeHashes)
sql = self.QueryGenerator.createTableQuery(tableName, attributes, options)
var sql = this.QueryGenerator.createTableQuery(tableName, attributes, options) queryAndEmit.call(self, sql, 'createTable', emitter).success(function(results) {
return queryAndEmit.call(this, sql, 'createTable') self.emit('createTable', null)
emitter.emit('success', results)
}).error(function(err) {
self.emit('createTable', err)
emitter.emit('error', err)
}).on('sql', function(sql) {
emitter.emit('sql', sql)
})
}
}).run()
} }
QueryInterface.prototype.dropTable = function(tableName, options) { QueryInterface.prototype.dropTable = function(tableName, options) {
// if we're forcing we should be cascading unless explicitly stated otherwise
options = options || {}
options.cascade = options.cascade || options.force || false
var sql = this.QueryGenerator.dropTableQuery(tableName, options) var sql = this.QueryGenerator.dropTableQuery(tableName, options)
return queryAndEmit.call(this, sql, 'dropTable') , self = this
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'queryAndEmit', [sql])
// Since postgres has a special case for enums, we should drop the related
// enum type within the table and attribute
if (self.sequelize.options.dialect === "postgres") {
// Find the table that we're trying to drop
daoTable = self.sequelize.daoFactoryManager.daos.filter(function(dao) {
return dao.tableName === tableName
})
// Just in case if we're trying to drop a non-existing table
daoTable = daoTable.length > 0 ? daoTable[0] : null
if (!!daoTable) {
var getTableName = (!options || !options.schema || options.schema === "public" ? '' : options.schema + '_') + tableName
var keys = Object.keys(daoTable.rawAttributes)
, keyLen = keys.length
, i = 0
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.runSerially().success(function(results) {
emitter.emit('success', results[0])
self.emit('dropTable', null)
}).error(function(err) {
emitter.emit('error', err)
self.emit('dropTable', err)
}).on('sql', function(sql) {
emitter.emit('sql', sql)
})
}).run()
} }
QueryInterface.prototype.dropAllTables = function() { QueryInterface.prototype.dropAllTables = function() {
...@@ -156,7 +292,8 @@ module.exports = (function() { ...@@ -156,7 +292,8 @@ module.exports = (function() {
if (self.QueryGenerator.describeTableQuery) { if (self.QueryGenerator.describeTableQuery) {
sql = self.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter) sql = self.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter)
} else { } else {
sql = 'DESCRIBE ' + self.QueryGenerator.addSchema({tableName: tableName, options: {schema: schema, schemaDelimiter: schemaDelimiter}}) + ';' var table = self.QueryGenerator.quoteIdentifier(self.QueryGenerator.addSchema({tableName: tableName, options: {schema: schema, schemaDelimiter: schemaDelimiter}}), self.QueryGenerator.options.quoteIdentifiers)
sql = 'DESCRIBE ' + table + ';'
} }
self.sequelize.query(sql, null, { raw: true }).success(function(data) { self.sequelize.query(sql, null, { raw: true }).success(function(data) {
...@@ -280,7 +417,7 @@ module.exports = (function() { ...@@ -280,7 +417,7 @@ module.exports = (function() {
QueryInterface.prototype.update = function(dao, tableName, values, identifier, options) { QueryInterface.prototype.update = function(dao, tableName, values, identifier, options) {
var self = this var self = this
, restrict = false , restrict = false
, sql = self.QueryGenerator.updateQuery(tableName, values, identifier, options) , sql = self.QueryGenerator.updateQuery(tableName, values, identifier, options, dao.daoFactory.rawAttributes)
// Check for a restrict field // Check for a restrict field
if (!!dao.daoFactory && !!dao.daoFactory.associations) { if (!!dao.daoFactory && !!dao.daoFactory.associations) {
...@@ -344,7 +481,7 @@ module.exports = (function() { ...@@ -344,7 +481,7 @@ module.exports = (function() {
QueryInterface.prototype.delete = function(dao, tableName, identifier) { QueryInterface.prototype.delete = function(dao, tableName, identifier) {
var self = this var self = this
, restrict = false , restrict = false
, sql = self.QueryGenerator.deleteQuery(tableName, identifier) , sql = self.QueryGenerator.deleteQuery(tableName, identifier, null, dao.daoFactory)
// Check for a restrict field // Check for a restrict field
if (!!dao.daoFactory && !!dao.daoFactory.associations) { if (!!dao.daoFactory && !!dao.daoFactory.associations) {
...@@ -405,6 +542,22 @@ module.exports = (function() { ...@@ -405,6 +542,22 @@ module.exports = (function() {
QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) { QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) {
options = options || {} options = options || {}
// See if we need to merge options and factory.scopeObj
// we're doing this on the QueryInterface level because it's a bridge between
// sequelize and the databases
if (Object.keys(factory.scopeObj).length > 0) {
if (!!options) {
Utils.injectScope.call(factory, options, true)
}
var scopeObj = buildScope.call(factory)
Object.keys(scopeObj).forEach(function(method) {
if (typeof scopeObj[method] === "number" || !Utils._.isEmpty(scopeObj[method])) {
options[method] = scopeObj[method]
}
})
}
var sql = this.QueryGenerator.selectQuery(tableName, options, factory) var sql = this.QueryGenerator.selectQuery(tableName, options, factory)
queryOptions = Utils._.extend({}, queryOptions, { include: options.include }) queryOptions = Utils._.extend({}, queryOptions, { include: options.include })
return queryAndEmit.call(this, [sql, factory, queryOptions], 'select') return queryAndEmit.call(this, [sql, factory, queryOptions], 'select')
...@@ -576,6 +729,17 @@ module.exports = (function() { ...@@ -576,6 +729,17 @@ module.exports = (function() {
return this.QueryGenerator.escape(value) return this.QueryGenerator.escape(value)
} }
// private
var buildScope = function() {
var smart
// Use smartWhere to convert several {where} objects into a single where object
smart = Utils.smartWhere(this.scopeObj.where || [], this.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(this, smart, this.daoFactoryManager.sequelize.options.dialect)
return {limit: this.scopeObj.limit || null, offset: this.scopeObj.offset || null, where: smart, order: (this.scopeObj.order || []).join(', ')}
}
var queryAndEmit = QueryInterface.prototype.queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) { var queryAndEmit = QueryInterface.prototype.queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) {
options = Utils._.extend({ options = Utils._.extend({
success: function(){}, success: function(){},
......
...@@ -195,6 +195,20 @@ module.exports = (function() { ...@@ -195,6 +195,20 @@ module.exports = (function() {
return factory return factory
} }
/**
Fetch a DAO factory
@param {String} daoName The name of a model defined with Sequelize.define
@returns {DAOFactory} The DAOFactory for daoName
*/
Sequelize.prototype.model = function(daoName) {
if(!this.isDefined(daoName)) {
throw new Error(daoName + ' has not been defined')
}
return this.daoFactoryManager.getDAO(daoName)
}
Sequelize.prototype.isDefined = function(daoName) { Sequelize.prototype.isDefined = function(daoName) {
var daos = this.daoFactoryManager.daos var daos = this.daoFactoryManager.daos
return (daos.filter(function(dao) { return dao.name === daoName }).length !== 0) return (daos.filter(function(dao) { return dao.name === daoName }).length !== 0)
......
var moment = require("moment") var moment = require("moment")
, isArrayBufferView
, SqlString = exports; , SqlString = exports;
if (typeof ArrayBufferView === 'function') {
isArrayBufferView = function(object) { return object && (object instanceof ArrayBufferView) }
} else {
var arrayBufferViews = [
Int8Array, Uint8Array, Int16Array, Uint16Array,
Int32Array, Uint32Array, Float32Array, Float64Array
]
isArrayBufferView = function(object) {
for (var i=0; i<8; i++) {
if (object instanceof arrayBufferViews[i]) {
return true
}
}
return false
};
}
SqlString.escapeId = function (val, forbidQualified) { SqlString.escapeId = function (val, forbidQualified) {
if (forbidQualified) { if (forbidQualified) {
return '`' + val.replace(/`/g, '``') + '`'; return '`' + val.replace(/`/g, '``') + '`'
} }
return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`'; return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`'
}; }
SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
if (arguments.length === 1 && typeof arguments[0] === "object") { if (arguments.length === 1 && typeof arguments[0] === "object") {
...@@ -37,29 +56,28 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { ...@@ -37,29 +56,28 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
} }
if (val instanceof Date) { if (val instanceof Date) {
val = SqlString.dateToString(val, timeZone || "Z", dialect); val = SqlString.dateToString(val, timeZone || "Z", dialect)
} }
if (Buffer.isBuffer(val)) { if (Buffer.isBuffer(val)) {
return SqlString.bufferToString(val); return SqlString.bufferToString(val, dialect)
} }
if (Array.isArray(val) || isArrayBufferView(val)) {
if (Array.isArray(val)) { return SqlString.arrayToList(val, timeZone, dialect, field)
return SqlString.arrayToList(val, timeZone, dialect, field);
} }
if (typeof val === 'object') { if (typeof val === 'object') {
if (stringifyObjects) { if (stringifyObjects) {
val = val.toString(); val = val.toString()
} else { } else {
return SqlString.objectToValues(val, timeZone); return SqlString.objectToValues(val, timeZone)
} }
} }
if (dialect === 'postgres' || dialect === 'sqlite') { if (dialect === 'postgres' || dialect === 'sqlite') {
// http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
// http://stackoverflow.com/q/603572/130598 // http://stackoverflow.com/q/603572/130598
val = val.replace(/'/g, "''"); val = val.replace(/'/g, "''")
} else { } else {
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
switch(s) { switch(s) {
...@@ -71,111 +89,134 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { ...@@ -71,111 +89,134 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
case "\x1a": return "\\Z"; case "\x1a": return "\\Z";
default: return "\\"+s; default: return "\\"+s;
} }
}); })
} }
return "'"+val+"'"; return "'"+val+"'"
}; }
SqlString.arrayToList = function(array, timeZone, dialect, field) { SqlString.arrayToList = function(array, timeZone, dialect, field) {
if (dialect === 'postgres') { if (dialect === 'postgres') {
var ret = 'ARRAY[' + array.map(function(v) { if (array.map) {
return SqlString.escape(v, true, timeZone, dialect, field); var valstr = array.map(function(v) {
}).join(',') + ']'; return SqlString.escape(v, true, timeZone, dialect, field)
}).join(',')
} else {
var valstr = ""
for (var i = 0; i < array.length; i++) {
valstr += SqlString.escape(array[i], true, timeZone, dialect, field) + ','
}
valstr = valstr.slice(0,-1)
}
var ret = 'ARRAY[' + valstr + ']'
if (!!field && !!field.type) { if (!!field && !!field.type) {
ret += '::' + field.type.replace(/\(\d+\)/g, ''); ret += '::' + field.type.replace(/\(\d+\)/g, '')
} }
return ret; return ret
} else { } else {
if (array.map) {
return array.map(function(v) { return array.map(function(v) {
if (Array.isArray(v)) if (Array.isArray(v)) {
return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')'; return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')'
return SqlString.escape(v, true, timeZone, dialect); }
}).join(', '); return SqlString.escape(v, true, timeZone, dialect)
}).join(', ')
} else {
var valstr = ""
for (var i = 0; i < array.length; i++) {
valstr += SqlString.escape(array[i], true, timeZone, dialect) + ', '
}
return valstr.slice(0, -2)
} }
}; }
}
SqlString.format = function(sql, values, timeZone, dialect) { SqlString.format = function(sql, values, timeZone, dialect) {
values = [].concat(values); values = [].concat(values);
return sql.replace(/\?/g, function(match) { return sql.replace(/\?/g, function(match) {
if (!values.length) { if (!values.length) {
return match; return match
} }
return SqlString.escape(values.shift(), false, timeZone, dialect); return SqlString.escape(values.shift(), false, timeZone, dialect)
}); })
}; }
SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) { SqlString.formatNamedParameters = function(sql, values, timeZone, dialect) {
return sql.replace(/\:(\w+)/g, function (value, key) { return sql.replace(/\:(\w+)/g, function (value, key) {
if (values.hasOwnProperty(key)) { if (values.hasOwnProperty(key)) {
return SqlString.escape(values[key], false, timeZone, dialect); return SqlString.escape(values[key], false, timeZone, dialect)
} } else {
else { throw new Error('Named parameter "' + value + '" has no value in the given object.')
throw new Error('Named parameter "' + value + '" has no value in the given object.');
} }
}); })
}; }
SqlString.dateToString = function(date, timeZone, dialect) { SqlString.dateToString = function(date, timeZone, dialect) {
var dt = new Date(date); var dt = new Date(date)
// TODO: Ideally all dialects would work a bit more like this // TODO: Ideally all dialects would work a bit more like this
if (dialect === "postgres") { if (dialect === "postgres") {
return moment(dt).zone('+00:00').format("YYYY-MM-DD HH:mm:ss.SSS Z"); return moment(dt).zone('+00:00').format("YYYY-MM-DD HH:mm:ss.SSS Z")
} }
if (timeZone !== 'local') { if (timeZone !== 'local') {
var tz = convertTimezone(timeZone); var tz = convertTimezone(timeZone)
dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000)); dt.setTime(dt.getTime() + (dt.getTimezoneOffset() * 60000))
if (tz !== false) { if (tz !== false) {
dt.setTime(dt.getTime() + (tz * 60000)); dt.setTime(dt.getTime() + (tz * 60000))
} }
} }
return moment(dt).format("YYYY-MM-DD HH:mm:ss"); return moment(dt).format("YYYY-MM-DD HH:mm:ss")
}; }
SqlString.bufferToString = function(buffer) { SqlString.bufferToString = function(buffer, dialect) {
var hex = ''; var hex = ''
try { try {
hex = buffer.toString('hex'); hex = buffer.toString('hex')
} catch (err) { } catch (err) {
// node v0.4.x does not support hex / throws unknown encoding error // node v0.4.x does not support hex / throws unknown encoding error
for (var i = 0; i < buffer.length; i++) { for (var i = 0; i < buffer.length; i++) {
var byte = buffer[i]; var byte = buffer[i]
hex += zeroPad(byte.toString(16)); hex += zeroPad(byte.toString(16))
} }
} }
return "X'" + hex+ "'"; if (dialect === 'postgres') {
}; // bytea hex format http://www.postgresql.org/docs/current/static/datatype-binary.html
return "E'\\\\x" + hex+ "'"
}
return "X'" + hex+ "'"
}
SqlString.objectToValues = function(object, timeZone) { SqlString.objectToValues = function(object, timeZone) {
var values = []; var values = []
for (var key in object) { for (var key in object) {
var value = object[key]; var value = object[key]
if(typeof value === 'function') { if(typeof value === 'function') {
continue; continue;
} }
values.push(this.escapeId(key) + ' = ' + SqlString.escape(value, true, timeZone)); values.push(this.escapeId(key) + ' = ' + SqlString.escape(value, true, timeZone))
} }
return values.join(', '); return values.join(', ')
}; }
function zeroPad(number) { function zeroPad(number) {
return (number < 10) ? '0' + number : number; return (number < 10) ? '0' + number : number
} }
function convertTimezone(tz) { function convertTimezone(tz) {
if (tz == "Z") return 0; if (tz == "Z") {
return 0
}
var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/); var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/)
if (m) { if (m) {
return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60; return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60
} }
return false; return false
} }
...@@ -61,6 +61,68 @@ var Utils = module.exports = { ...@@ -61,6 +61,68 @@ var Utils = module.exports = {
var timeZone = null; var timeZone = null;
return SqlString.formatNamedParameters(sql, parameters, timeZone, dialect) return SqlString.formatNamedParameters(sql, parameters, timeZone, dialect)
}, },
injectScope: function(scope, merge) {
var self = this
scope = scope || {}
self.scopeObj = self.scopeObj || {}
if (Array.isArray(scope.where)) {
self.scopeObj.where = self.scopeObj.where || []
self.scopeObj.where.push(scope.where)
return true
}
if (typeof scope.order === "string") {
self.scopeObj.order = self.scopeObj.order || []
self.scopeObj.order[self.scopeObj.order.length] = scope.order
}
// Limit and offset are *always* merged.
if (!!scope.limit) {
self.scopeObj.limit = scope.limit
}
if (!!scope.offset) {
self.scopeObj.offset = scope.offset
}
// Where objects are a mixed variable. Possible values are arrays, strings, and objects
if (!!scope.where) {
// Begin building our scopeObj
self.scopeObj.where = self.scopeObj.where || []
// Reset if we're merging!
if (merge === true && !!scope.where && !!self.scopeObj.where) {
var scopeKeys = Object.keys(scope.where)
self.scopeObj.where = self.scopeObj.where.map(function(scopeObj) {
if (!Array.isArray(scopeObj) && typeof scopeObj === "object") {
return lodash.omit.apply(undefined, [scopeObj].concat(scopeKeys))
} else {
return scopeObj
}
}).filter(function(scopeObj) {
return !lodash.isEmpty(scopeObj)
})
self.scopeObj.where = self.scopeObj.where.concat(scope.where)
}
if (Array.isArray(scope.where)) {
self.scopeObj.where.push(scope.where)
}
else if (typeof scope.where === "object") {
Object.keys(scope.where).forEach(function(){
self.scopeObj.where.push(scope.where)
})
} else { // Assume the value is a string
self.scopeObj.where.push([scope.where])
}
}
if (!!self.scopeObj.where) {
self.scopeObj.where = lodash.uniq(self.scopeObj.where)
}
},
// smartWhere can accept an array of {where} objects, or a single {where} object. // smartWhere can accept an array of {where} objects, or a single {where} object.
// The smartWhere function breaks down the collection of where objects into a more // The smartWhere function breaks down the collection of where objects into a more
// centralized object for each column so we can avoid duplicates // centralized object for each column so we can avoid duplicates
...@@ -225,45 +287,32 @@ var Utils = module.exports = { ...@@ -225,45 +287,32 @@ var Utils = module.exports = {
return 'JOIN' return 'JOIN'
case 'gte': case 'gte':
return '>=' return '>='
break
case 'gt': case 'gt':
return '>' return '>'
break
case 'lte': case 'lte':
return '<=' return '<='
break
case 'lt': case 'lt':
return '<' return '<'
break
case 'eq': case 'eq':
case 'join':
return '=' return '='
break
case 'ne': case 'ne':
return '!=' return '!='
break
case 'between': case 'between':
case '..': case '..':
return 'BETWEEN' return 'BETWEEN'
break
case 'nbetween': case 'nbetween':
case 'notbetween': case 'notbetween':
case '!..': case '!..':
return 'NOT BETWEEN' return 'NOT BETWEEN'
break
case 'in': case 'in':
return 'IN' return 'IN'
break
case 'not': case 'not':
return 'NOT IN' return 'NOT IN'
break
case 'like': case 'like':
return 'LIKE' return 'LIKE'
break
case 'nlike': case 'nlike':
case 'notlike': case 'notlike':
return 'NOT LIKE' return 'NOT LIKE'
break
default: default:
return '' return ''
} }
......
{ {
"name": "sequelize", "name": "sequelize",
"description": "Multi dialect ORM for Node.JS", "description": "Multi dialect ORM for Node.JS",
"version": "1.7.0-alpha3", "version": "1.7.0-beta.0",
"author": "Sascha Depold <sascha@depold.com>", "author": "Sascha Depold <sascha@depold.com>",
"contributors": [ "contributors": [
{ {
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
"lodash": "~1.3.1", "lodash": "~1.3.1",
"underscore.string": "~2.3.0", "underscore.string": "~2.3.0",
"lingo": "~0.0.5", "lingo": "~0.0.5",
"validator": "~1.4.0", "validator": "~1.5.0",
"moment": "~2.1.0", "moment": "~2.1.0",
"commander": "~2.0.0", "commander": "~2.0.0",
"dottie": "0.0.8-0", "dottie": "0.0.8-0",
...@@ -51,12 +51,12 @@ ...@@ -51,12 +51,12 @@
"devDependencies": { "devDependencies": {
"sqlite3": "~2.1.12", "sqlite3": "~2.1.12",
"mysql": "~2.0.0-alpha8", "mysql": "~2.0.0-alpha8",
"pg": "~2.3.1", "pg": "~2.6.0",
"watchr": "~2.4.3", "watchr": "~2.4.3",
"yuidocjs": "~0.3.36", "yuidocjs": "~0.3.36",
"chai": "~1.7.2", "chai": "~1.7.2",
"mocha": "~1.12.0", "mocha": "~1.12.0",
"chai-datetime": "~1.0.0", "chai-datetime": "~1.1.1",
"sinon": "~1.7.3" "sinon": "~1.7.3"
}, },
"keywords": [ "keywords": [
......
...@@ -192,6 +192,31 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() { ...@@ -192,6 +192,31 @@ describe(Support.getTestDialectTeaser("BelongsTo"), function() {
}) })
}) })
describe("Association column", function() {
it('has correct type for non-id primary keys with non-integer type', function(done) {
var User = this.sequelize.define('UserPKBT', {
username: {
type: DataTypes.STRING
}
})
, self = this
var Group = this.sequelize.define('GroupPKBT', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
})
User.belongsTo(Group)
self.sequelize.sync({ force: true }).success(function() {
expect(User.rawAttributes.GroupPKBTId.type.toString()).to.equal(DataTypes.STRING.toString())
done()
})
})
})
describe("Association options", function() { describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) { it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }) var User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING })
......
...@@ -229,6 +229,31 @@ describe(Support.getTestDialectTeaser("HasOne"), function() { ...@@ -229,6 +229,31 @@ describe(Support.getTestDialectTeaser("HasOne"), function() {
}) })
describe("Association column", function() {
it('has correct type for non-id primary keys with non-integer type', function(done) {
var User = this.sequelize.define('UserPKBT', {
username: {
type: Sequelize.STRING
}
})
, self = this
var Group = this.sequelize.define('GroupPKBT', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
})
Group.hasOne(User)
self.sequelize.sync({ force: true }).success(function() {
expect(User.rawAttributes.GroupPKBTId.type.toString()).to.equal(Sequelize.STRING.toString())
done()
})
})
})
describe("Association options", function() { describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) { it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }) var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
......
...@@ -1000,6 +1000,34 @@ describe(Support.getTestDialectTeaser("DAO"), function () { ...@@ -1000,6 +1000,34 @@ describe(Support.getTestDialectTeaser("DAO"), function () {
}) })
}) })
it('destroys a record with a primary key of something other than id', function(done) {
var UserDestroy = this.sequelize.define('UserDestroy', {
newId: {
type: DataTypes.STRING,
primaryKey: true
},
email: DataTypes.STRING
})
UserDestroy.sync().success(function() {
UserDestroy.create({newId: '123ABC', email: 'hello'}).success(function() {
UserDestroy.find({where: {email: 'hello'}}).success(function(user) {
user.destroy().on('sql', function(sql) {
if (dialect === "postgres" || dialect === "postgres-native") {
expect(sql).to.equal('DELETE FROM "UserDestroys" WHERE "newId" IN (SELECT "newId" FROM "UserDestroys" WHERE "newId"=\'123ABC\' LIMIT 1)')
}
else if (dialect === "mysql") {
expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC' LIMIT 1")
} else {
expect(sql).to.equal("DELETE FROM `UserDestroys` WHERE `newId`='123ABC'")
}
done()
})
})
})
})
})
it("sets deletedAt property to a specific date when deleting an instance", function(done) { it("sets deletedAt property to a specific date when deleting an instance", function(done) {
var self = this var self = this
this.ParanoidUser.create({ username: 'fnord' }).success(function() { this.ParanoidUser.create({ username: 'fnord' }).success(function() {
......
...@@ -253,6 +253,78 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -253,6 +253,78 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
} }
} }
describe('#update', function() {
it('should allow us to update specific columns without tripping the validations', function(done) {
var User = this.sequelize.define('model', {
username: Sequelize.STRING,
email: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isEmail: {
msg: 'You must enter a valid email address'
}
}
}
})
User.sync({ force: true }).success(function() {
User.create({username: 'bob', email: 'hello@world.com'}).success(function(user) {
User.update({username: 'toni'}, {id: user.id})
.error(function(err) { console.log(err) })
.success(function() {
User.find(1).success(function(user) {
expect(user.username).to.equal('toni')
done()
})
})
})
})
})
it('should be able to emit an error upon updating when a validation has failed from an instance', function(done) {
var Model = this.sequelize.define('model', {
name: {
type: Sequelize.STRING,
validate: {
notNull: true, // won't allow null
notEmpty: true // don't allow empty strings
}
}
})
Model.sync({ force: true }).success(function() {
Model.create({name: 'World'}).success(function(model) {
model.updateAttributes({name: ''}).error(function(err) {
expect(err).to.deep.equal({ name: [ 'String is empty: name', 'String is empty: name' ] })
done()
})
})
})
})
it('should be able to emit an error upon updating when a validation has failed from the factory', function(done) {
var Model = this.sequelize.define('model', {
name: {
type: Sequelize.STRING,
validate: {
notNull: true, // won't allow null
notEmpty: true // don't allow empty strings
}
}
})
Model.sync({ force: true }).success(function() {
Model.create({name: 'World'}).success(function(model) {
Model.update({name: ''}, {id: 1}).error(function(err) {
expect(err).to.deep.equal({ name: [ 'String is empty: name', 'String is empty: name' ] })
done()
})
})
})
})
})
describe('#create', function() { describe('#create', function() {
describe('generic', function() { describe('generic', function() {
beforeEach(function(done) { beforeEach(function(done) {
...@@ -301,7 +373,7 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -301,7 +373,7 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
}) })
}) })
describe('explicitly validating id/primary/auto incremented columns', function() { describe('explicitly validating primary/auto incremented columns', function() {
it('should emit an error when we try to enter in a string for the id key without validation arguments', function(done) { it('should emit an error when we try to enter in a string for the id key without validation arguments', function(done) {
var User = this.sequelize.define('UserId', { var User = this.sequelize.define('UserId', {
id: { id: {
...@@ -322,41 +394,63 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -322,41 +394,63 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
}) })
}) })
it('should emit an error when we try to enter in a string for the id key with validation arguments', function(done) { it('should emit an error when we try to enter in a string for an auto increment key (not named id)', function(done) {
var User = this.sequelize.define('UserId', { var User = this.sequelize.define('UserId', {
id: { username: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
autoIncrement: true, autoIncrement: true,
primaryKey: true, primaryKey: true,
validate: { validate: {
isInt: { args: true, msg: 'ID must be an integer!' } isInt: { args: true, msg: 'Username must be an integer!' }
} }
} }
}) })
User.sync({ force: true }).success(function() { User.sync({ force: true }).success(function() {
User.create({id: 'helloworld'}).error(function(err) { User.create({username: 'helloworldhelloworld'}).error(function(err) {
expect(err).to.deep.equal({id: ['ID must be an integer!']}) expect(err).to.deep.equal({username: ['Username must be an integer!']})
done() done()
}) })
}) })
}) })
it('should emit an error when we try to enter in a string for an auto increment key (not named id)', function(done) { describe("primaryKey with the name as id with arguments for it's validation", function() {
var User = this.sequelize.define('UserId', { beforeEach(function(done) {
username: { this.User = this.sequelize.define('UserId', {
id: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
autoIncrement: true, autoIncrement: true,
primaryKey: true, primaryKey: true,
validate: { validate: {
isInt: { args: true, msg: 'Username must be an integer!' } isInt: { args: true, msg: 'ID must be an integer!' }
} }
} }
}) })
User.sync({ force: true }).success(function() { this.User.sync({ force: true }).success(function() {
User.create({username: 'helloworldhelloworld'}).error(function(err) { done()
expect(err).to.deep.equal({username: ['Username must be an integer!']}) })
})
it('should emit an error when we try to enter in a string for the id key with validation arguments', function(done) {
this.User.create({id: 'helloworld'}).error(function(err) {
expect(err).to.deep.equal({id: ['ID must be an integer!']})
done()
})
})
it('should emit an error when we try to enter in a string for an auto increment key through .build().validate()', function(done) {
var user = this.User.build({id: 'helloworld'})
, errors = user.validate()
expect(errors).to.deep.equal({ id: [ 'ID must be an integer!' ] })
done()
})
it('should emit an error when we try to .save()', function(done) {
var user = this.User.build({id: 'helloworld'})
user.save().error(function(err) {
expect(err).to.deep.equal({ id: [ 'ID must be an integer!' ] })
done() done()
}) })
}) })
...@@ -446,5 +540,26 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -446,5 +540,26 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
expect(successfulFoo.validate()).to.be.null expect(successfulFoo.validate()).to.be.null
done() done()
}) })
it('validates enums', function() {
var values = ['value1', 'value2']
var Bar = this.sequelize.define('Bar' + config.rand(), {
field: {
type: Sequelize.ENUM,
values: values,
validate: {
isIn: [values]
}
}
})
var failingBar = Bar.build({ field: 'value3' })
, errors = failingBar.validate()
expect(errors).not.to.be.null
expect(errors.field).to.have.length(1)
expect(errors.field[0]).to.equal("Unexpected value or invalid argument: field")
})
}) })
}) })
...@@ -27,6 +27,11 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -27,6 +27,11 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
[Sequelize.NOW, 'NOW', 'NOW'], [Sequelize.NOW, 'NOW', 'NOW'],
[Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'], [Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'],
[Sequelize.BLOB, 'BLOB', 'BLOB'],
[Sequelize.BLOB('tiny'), 'BLOB(\'tiny\')', 'TINYBLOB'],
[Sequelize.BLOB('medium'), 'BLOB(\'medium\')', 'MEDIUMBLOB'],
[Sequelize.BLOB('long'), 'BLOB(\'long\')', 'LONGBLOB'],
[Sequelize.INTEGER, 'INTEGER', 'INTEGER'], [Sequelize.INTEGER, 'INTEGER', 'INTEGER'],
[Sequelize.INTEGER.UNSIGNED, 'INTEGER.UNSIGNED', 'INTEGER UNSIGNED'], [Sequelize.INTEGER.UNSIGNED, 'INTEGER.UNSIGNED', 'INTEGER UNSIGNED'],
[Sequelize.INTEGER(11), 'INTEGER(11)','INTEGER(11)'], [Sequelize.INTEGER(11), 'INTEGER(11)','INTEGER(11)'],
......
...@@ -85,6 +85,14 @@ if (dialect.match(/^mysql/)) { ...@@ -85,6 +85,14 @@ if (dialect.match(/^mysql/)) {
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` INTEGER) COMMENT 'I\\'m a comment!' ENGINE=InnoDB;" expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` INTEGER) COMMENT 'I\\'m a comment!' ENGINE=InnoDB;"
}, },
{ {
arguments: ['myTable', {data: "BLOB"}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`data` BLOB) ENGINE=InnoDB;"
},
{
arguments: ['myTable', {data: "LONGBLOB"}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`data` LONGBLOB) ENGINE=InnoDB;"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {engine: 'MyISAM'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {engine: 'MyISAM'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=MyISAM;" expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=MyISAM;"
}, },
...@@ -220,6 +228,9 @@ if (dialect.match(/^mysql/)) { ...@@ -220,6 +228,9 @@ if (dialect.match(/^mysql/)) {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);" expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);"
}, { }, {
arguments: ['myTable', {data: new Buffer('Sequelize') }],
expectation: "INSERT INTO `myTable` (`data`) VALUES (X'53657175656c697a65');"
}, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);" expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);"
}, { }, {
......
...@@ -3,6 +3,7 @@ var chai = require('chai') ...@@ -3,6 +3,7 @@ var chai = require('chai')
, Support = require(__dirname + '/../support') , Support = require(__dirname + '/../support')
, dialect = Support.getTestDialect() , dialect = Support.getTestDialect()
, DataTypes = require(__dirname + "/../../lib/data-types") , DataTypes = require(__dirname + "/../../lib/data-types")
, _ = require('lodash')
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
...@@ -25,6 +26,28 @@ if (dialect.match(/^postgres/)) { ...@@ -25,6 +26,28 @@ if (dialect.match(/^postgres/)) {
done() done()
}) })
it('should be able to search within an array', function(done) {
this.User.all({where: {email: ['hello', 'world']}}).on('sql', function(sql) {
expect(sql).to.equal('SELECT * FROM "Users" WHERE "Users"."email" && ARRAY[\'hello\',\'world\']::TEXT[];')
done()
})
})
it('should be able to find a record while searching in an array', function(done) {
var self = this
this.User.bulkCreate([
{username: 'bob', email: ['myemail@email.com']},
{username: 'tony', email: ['wrongemail@email.com']}
]).success(function() {
self.User.all({where: {email: ['myemail@email.com']}}).success(function(user) {
expect(user).to.be.instanceof(Array)
expect(user).to.have.length(1)
expect(user[0].username).to.equal('bob')
done()
})
})
})
it('describeTable should tell me that a column is hstore and not USER-DEFINED', function(done) { it('describeTable should tell me that a column is hstore and not USER-DEFINED', function(done) {
this.sequelize.queryInterface.describeTable('Users').success(function(table) { this.sequelize.queryInterface.describeTable('Users').success(function(table) {
expect(table.document.type).to.equal('HSTORE') expect(table.document.type).to.equal('HSTORE')
...@@ -32,6 +55,67 @@ if (dialect.match(/^postgres/)) { ...@@ -32,6 +55,67 @@ if (dialect.match(/^postgres/)) {
}) })
}) })
describe('enums', function() {
it('should be able to ignore enum types that already exist', function(done) {
var User = this.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('happy', 'sad', 'meh')
})
User.sync({ force: true }).success(function() {
User.sync().success(function() {
done()
})
})
})
it('should be able to create/drop enums multiple times', function(done) {
var User = this.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('happy', 'sad', 'meh')
})
User.sync({ force: true }).success(function() {
User.sync({ force: true }).success(function() {
done()
})
})
})
it('should be able to add enum types', function(done) {
var self = this
, User = this.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('happy', 'sad', 'meh')
})
var _done = _.after(4, function() {
done()
})
User.sync({ force: true }).success(function() {
User = self.sequelize.define('UserEnums', {
mood: DataTypes.ENUM('neutral', 'happy', 'sad', 'ecstatic', 'meh', 'joyful')
})
User.sync().success(function() {
expect(User.rawAttributes.mood.values).to.deep.equal(['neutral', 'happy', 'sad', 'ecstatic', 'meh', 'joyful'])
_done()
}).on('sql', function(sql) {
if (sql.indexOf('neutral') > -1) {
expect(sql).to.equal("ALTER TYPE \"enum_UserEnums_mood\" ADD VALUE 'neutral' BEFORE 'happy'")
_done()
}
else if (sql.indexOf('ecstatic') > -1) {
expect(sql).to.equal("ALTER TYPE \"enum_UserEnums_mood\" ADD VALUE 'ecstatic' BEFORE 'meh'")
_done()
}
else if (sql.indexOf('joyful') > -1) {
expect(sql).to.equal("ALTER TYPE \"enum_UserEnums_mood\" ADD VALUE 'joyful' AFTER 'meh'")
_done()
}
})
})
})
})
describe('integers', function() { describe('integers', function() {
describe('integer', function() { describe('integer', function() {
beforeEach(function(done) { beforeEach(function(done) {
......
...@@ -17,6 +17,7 @@ if (dialect.match(/^postgres/)) { ...@@ -17,6 +17,7 @@ if (dialect.match(/^postgres/)) {
this.User = this.sequelize.define('User', { this.User = this.sequelize.define('User', {
username: DataTypes.STRING, username: DataTypes.STRING,
email: {type: DataTypes.ARRAY(DataTypes.TEXT)}, email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
numbers: {type: DataTypes.ARRAY(DataTypes.FLOAT)},
document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'} document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'}
}) })
this.User.sync({ force: true }).success(function() { this.User.sync({ force: true }).success(function() {
...@@ -126,12 +127,20 @@ if (dialect.match(/^postgres/)) { ...@@ -126,12 +127,20 @@ if (dialect.match(/^postgres/)) {
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" INTEGER); COMMENT ON TABLE \"myTable\" IS 'I''m a comment!';", expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" INTEGER); COMMENT ON TABLE \"myTable\" IS 'I''m a comment!';",
}, },
{ {
arguments: ['myTable', {data: "BLOB"}],
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"data\" bytea);"
},
{
arguments: ['myTable', {data: "LONGBLOB"}],
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"data\" bytea);"
},
{
arguments: ['mySchema.myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], arguments: ['mySchema.myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS \"mySchema\".\"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));" expectation: "CREATE TABLE IF NOT EXISTS \"mySchema\".\"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));"
}, },
{ {
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
expectation: "DROP TYPE IF EXISTS \"enum_myTable_title\"; CREATE TYPE \"enum_myTable_title\" AS ENUM(\"A\", \"B\", \"C\"); CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" \"enum_myTable_title\", \"name\" VARCHAR(255));" expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" \"enum_myTable_title\", \"name\" VARCHAR(255));"
}, },
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}],
...@@ -155,7 +164,7 @@ if (dialect.match(/^postgres/)) { ...@@ -155,7 +164,7 @@ if (dialect.match(/^postgres/)) {
}, },
{ {
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
expectation: "DROP TYPE IF EXISTS enum_myTable_title; CREATE TYPE enum_myTable_title AS ENUM(\"A\", \"B\", \"C\"); CREATE TABLE IF NOT EXISTS myTable (title enum_myTable_title, name VARCHAR(255));", expectation: "CREATE TABLE IF NOT EXISTS myTable (title enum_myTable_title, name VARCHAR(255));",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
...@@ -350,6 +359,12 @@ if (dialect.match(/^postgres/)) { ...@@ -350,6 +359,12 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}], arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;"
}, { }, {
arguments: ['myTable', {data: new Buffer('Sequelize') }],
expectation: "INSERT INTO \"myTable\" (\"data\") VALUES (E'\\\\x53657175656c697a65') RETURNING *;"
}, {
arguments: ['myTable', {name: 'foo', numbers: new Uint8Array([1,2,3])}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"numbers\") VALUES ('foo',ARRAY[1,2,3]) RETURNING *;"
}, {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1) RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1) RETURNING *;"
}, { }, {
...@@ -392,6 +407,10 @@ if (dialect.match(/^postgres/)) { ...@@ -392,6 +407,10 @@ if (dialect.match(/^postgres/)) {
expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;", expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', numbers: new Uint8Array([1,2,3])}],
expectation: "INSERT INTO myTable (name,numbers) VALUES ('foo',ARRAY[1,2,3]) RETURNING *;",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1) RETURNING *;", expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1) RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
...@@ -525,6 +544,9 @@ if (dialect.match(/^postgres/)) { ...@@ -525,6 +544,9 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {bar: 2}, {name: 'foo'}], arguments: ['myTable', {bar: 2}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *" expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *"
}, { }, {
arguments: ['myTable', {numbers: new Uint8Array([1,2,3])}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"numbers\"=ARRAY[1,2,3] WHERE \"name\"='foo' RETURNING *"
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo' RETURNING *" expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo' RETURNING *"
}, { }, {
...@@ -564,6 +586,10 @@ if (dialect.match(/^postgres/)) { ...@@ -564,6 +586,10 @@ if (dialect.match(/^postgres/)) {
expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *", expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {numbers: new Uint8Array([1,2,3])}, {name: 'foo'}],
expectation: "UPDATE myTable SET numbers=ARRAY[1,2,3] WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "UPDATE myTable SET name='foo'';DROP TABLE myTable;' WHERE name='foo' RETURNING *", expectation: "UPDATE myTable SET name='foo'';DROP TABLE myTable;' WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
......
...@@ -144,17 +144,17 @@ describe(Support.getTestDialectTeaser("Executable"), function() { ...@@ -144,17 +144,17 @@ describe(Support.getTestDialectTeaser("Executable"), function() {
;(function(flags) { ;(function(flags) {
flags.forEach(function(flag) { flags.forEach(function(flag) {
var prepare = function(callback) { var prepare = function(callback) {
exec("rm -rf ./*", { cwd: __dirname + '/tmp' }, function() { exec("rm -rf ./*", { cwd: __dirname + '/tmp' }, function(error, stdout) {
exec("../../bin/sequelize --init", { cwd: __dirname + '/tmp' }, function() { exec("../../bin/sequelize --init", { cwd: __dirname + '/tmp' }, function(error, stdout) {
exec("cp ../assets/migrations/*-createPerson.js ./migrations/", { cwd: __dirname + '/tmp' }, function() { exec("cp ../assets/migrations/*-createPerson.js ./migrations/", { cwd: __dirname + '/tmp' }, function(error, stdout) {
exec("cat ../support.js|sed s,/../,/../../, > ./support.js", { cwd: __dirname + '/tmp' }, function() { exec("cat ../support.js|sed s,/../,/../../, > ./support.js", { cwd: __dirname + '/tmp' }, function(error, stdout) {
var dialect = Support.getTestDialect() var dialect = Support.getTestDialect()
, config = require(__dirname + '/config/config.js') , config = require(__dirname + '/config/config.js')
config.sqlite.storage = __dirname + "/tmp/test.sqlite" config.sqlite.storage = __dirname + "/tmp/test.sqlite"
config = _.extend(config, config[dialect], { dialect: dialect }) config = _.extend(config, config[dialect], { dialect: dialect })
exec("echo '" + JSON.stringify(config) + "' > config/config.json", { cwd: __dirname + '/tmp' }, function() { exec("echo '" + JSON.stringify(config) + "' > config/config.json", { cwd: __dirname + '/tmp' }, function(error, stdout) {
exec("../../bin/sequelize " + flag, { cwd: __dirname + "/tmp" }, callback) exec("../../bin/sequelize " + flag, { cwd: __dirname + "/tmp" }, callback)
}) })
}) })
......
...@@ -51,6 +51,23 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () { ...@@ -51,6 +51,23 @@ describe(Support.getTestDialectTeaser("Sequelize"), function () {
}) })
}) })
describe('model', function() {
it('throws an error if the dao being accessed is undefined', function() {
var self = this
expect(function() {
self.sequelize.model('Project')
}).to.throw(/project has not been defined/i)
})
it('returns the dao factory defined by daoName', function() {
var project = this.sequelize.define('Project', {
name: DataTypes.STRING
})
expect(this.sequelize.model('Project')).to.equal(project)
})
})
describe('query', function() { describe('query', function() {
afterEach(function(done) { afterEach(function(done) {
this.sequelize.options.quoteIdentifiers = true this.sequelize.options.quoteIdentifiers = true
......
...@@ -79,6 +79,14 @@ if (dialect === 'sqlite') { ...@@ -79,6 +79,14 @@ if (dialect === 'sqlite') {
createTableQuery: [ createTableQuery: [
{ {
arguments: ['myTable', {data: "BLOB"}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`data` BLOB);"
},
{
arguments: ['myTable', {data: "LONGBLOB"}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`data` LONGBLOB);"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));" expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));"
}, },
...@@ -104,6 +112,9 @@ if (dialect === 'sqlite') { ...@@ -104,6 +112,9 @@ if (dialect === 'sqlite') {
arguments: ['myTable', { name: "'bar'" }], arguments: ['myTable', { name: "'bar'" }],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('''bar''');" expectation: "INSERT INTO `myTable` (`name`) VALUES ('''bar''');"
}, { }, {
arguments: ['myTable', {data: new Buffer('Sequelize') }],
expectation: "INSERT INTO `myTable` (`data`) VALUES (X'53657175656c697a65');"
}, {
arguments: ['myTable', { name: "bar", value: null }], arguments: ['myTable', { name: "bar", value: null }],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL);" expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL);"
}, { }, {
......
File mode changed
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!