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

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:
script:
- "make test"
- "make binary"
notifications:
email:
......@@ -21,4 +20,5 @@ env:
language: 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
### 1.7.0
- ~~Check if lodash is a proper alternative to current underscore usage.~~
- Transactions
- Associations of not yet saved objects: [#864](https://github.com/sequelize/sequelize/issues/864)
- Support for update of tables without primary key
- 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
......@@ -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))
- ~~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
- 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
### 1.7.x
......
......@@ -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] 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] 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] 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
......@@ -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] 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] 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] 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
......
......@@ -22,9 +22,11 @@ module.exports = (function() {
// the id is in the source table
BelongsTo.prototype.injectAttributes = function() {
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)
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)
Utils._.defaults(this.source.rawAttributes, newAttributes)
......
......@@ -57,30 +57,32 @@ module.exports = (function() {
var self = this
, chainer = new Utils.QueryChainer()
, 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) {
// Return only those old associations that are not found in new
return !Utils._.find(newAssociations, function (obj) {
return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id)
})
})
, unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) {
return (!!obj[foreignIdentifier] && !!old[foreignIdentifier] ? obj[foreignIdentifier] === old[foreignIdentifier] : obj.id === old.id)
})
})
// Return only those old associations that are not found in new
return !Utils._.find(newAssociations, function (obj) {
return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
})
})
var unassociatedObjects = newAssociations.filter(function (obj) {
// Return only those associations that are new
return !Utils._.find(oldAssociations, function (old) {
return ((targetKeys.length === 1) ? obj[targetKeys[0]] === old[targetKeys[0]] : obj.id === old.id)
})
})
if (obsoleteAssociations.length > 0) {
var foreignIds = obsoleteAssociations.map(function (associatedObject) {
return associatedObject.id
})
, primaryKeys = Object.keys(self.__factory.connectorDAO.rawAttributes)
, foreignKey = primaryKeys.filter(function(pk) { return pk != self.__factory.identifier })[0]
return ((targetKeys.length === 1) ? associatedObject[targetKeys[0]] : associatedObject.id)
})
var where = {}
where[self.__factory.identifier] = self.instance[self.__factory.identifier] || self.instance.id
where[foreignKey] = foreignIds
where[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
where[foreignIdentifier] = foreignIds
chainer.add(self.__factory.connectorDAO.destroy(where))
}
......@@ -88,8 +90,8 @@ module.exports = (function() {
if (unassociatedObjects.length > 0) {
var bulk = unassociatedObjects.map(function(unassociatedObject) {
var attributes = {}
attributes[self.__factory.identifier] = self.instance[self.__factory.identifier] || self.instance.id
attributes[foreignIdentifier] = unassociatedObject[foreignIdentifier] || unassociatedObject.id
attributes[self.__factory.identifier] = ((sourceKeys.length === 1) ? self.instance[sourceKeys[0]] : self.instance.id)
attributes[foreignIdentifier] = ((targetKeys.length === 1) ? unassociatedObject[targetKeys[0]] : unassociatedObject.id)
return attributes
})
......@@ -109,8 +111,11 @@ module.exports = (function() {
, association = this.__factory.target.associations[this.__factory.associationAccessor]
, foreignIdentifier = association.isSelfAssociation ? association.foreignIdentifier : association.identifier;
attributes[this.__factory.identifier] = this.instance[this.__factory.identifier] || this.instance.id
attributes[foreignIdentifier] = newAssociation[foreignIdentifier] || newAssociation.id
var sourceKeys = Object.keys(this.__factory.source.primaryKeys);
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)
.success(function() { emitterProxy.emit('success', newAssociation) })
......
......@@ -54,9 +54,12 @@ module.exports = (function() {
// define a new model, which connects the models
var combinedTableAttributes = {}
var keyType = this.options.keyType || DataTypes.INTEGER
combinedTableAttributes[this.identifier] = {type: keyType, primaryKey: true}
combinedTableAttributes[this.foreignIdentifier] = {type: keyType, primaryKey: true}
var sourceKeys = Object.keys(this.source.primaryKeys);
var sourceKeyType = ((!this.source.hasPrimaryKeys || sourceKeys.length !== 1) ? DataTypes.INTEGER : this.source.rawAttributes[sourceKeys[0]].type)
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)
......
......@@ -27,9 +27,11 @@ module.exports = (function() {
// the id is in the target table
HasOne.prototype.injectAttributes = function() {
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)
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)
Utils._.defaults(this.target.rawAttributes, newAttributes)
......
......@@ -36,7 +36,7 @@ var validateModel = function() {
}
var validateAttributes = function() {
var self = this
var self = this
, errors = {}
// for each field and value
......
......@@ -28,7 +28,7 @@ module.exports = (function() {
Object.defineProperty(DAO.prototype, 'isDeleted', {
get: function() {
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
}
......@@ -99,8 +99,8 @@ module.exports = (function() {
DAO.prototype.save = function(fields, options) {
var self = this
, values = fields ? {} : this.dataValues
, updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt'
, createdAtAttr = this.__options.underscored ? 'created_at' : 'createdAt'
, updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)
if (fields) {
if (self.__options.timestamps) {
......@@ -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) {
if (this.daoFactory.rawAttributes.hasOwnProperty(attrName)) {
var definition = this.daoFactory.rawAttributes[attrName]
......@@ -144,7 +152,7 @@ module.exports = (function() {
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(', '))
}
......@@ -160,14 +168,7 @@ module.exports = (function() {
this.dataValues[updatedAtAttr] = values[updatedAtAttr] = Utils.now(this.sequelize.options.dialect)
}
var errors = this.validate()
if (!!errors) {
return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', errors)
}).run()
}
else if (this.isNewRecord) {
if (this.isNewRecord) {
this.isDirty = false
return this.QueryInterface.insert(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values)
} else {
......@@ -243,17 +244,17 @@ module.exports = (function() {
readOnlyAttributes.push('id')
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 ? 'deleted_at' : 'deletedAt')
readOnlyAttributes.push(this.daoFactory.options.deletedAt, this.daoFactory.options.underscored)
var isDirty = this.isDirty
Utils._.each(updates, function(value, attr) {
var updateAllowed = (
(readOnlyAttributes.indexOf(attr) == -1) &&
(readOnlyAttributes.indexOf(Utils._.underscored(attr)) == -1) &&
(readOnlyAttributes.indexOf(attr) === -1) &&
(readOnlyAttributes.indexOf(Utils._.underscored(attr)) === -1) &&
(self.attributes.indexOf(attr) > -1)
)
......@@ -269,7 +270,7 @@ module.exports = (function() {
// since we're updating the record, we should be updating the updatedAt column..
if (this.daoFactory.options.timestamps === 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
......@@ -277,7 +278,7 @@ module.exports = (function() {
DAO.prototype.destroy = function() {
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()
return this.save()
} else {
......@@ -341,10 +342,6 @@ module.exports = (function() {
}
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
value = !!value
}
......@@ -374,9 +371,9 @@ module.exports = (function() {
this.__defineSetter__(attribute, has.set || function(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
var updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt'
, createdAtAttr = this.__options.underscored ? 'created_at' : 'createdAt'
, touchedAtAttr = this.__options.underscored ? 'touched_at' : 'touchedAt'
var updatedAtAttr = Utils._.underscoredIf(this.__options.updatedAt, this.__options.underscored)
, createdAtAttr = Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)
, touchedAtAttr = Utils._.underscoredIf(this.__options.touchedAt, this.__options.underscored)
if (this.dataValues[attribute] || (attribute != 'id' && attribute != touchedAtAttr && attribute != createdAtAttr && attribute != updatedAtAttr)) {
this.isDirty = true
......@@ -422,11 +419,11 @@ module.exports = (function() {
}
if (this.__options.timestamps) {
defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = Utils.now(this.sequelize.options.dialect)
defaults[this.__options.underscored ? 'updated_at' : 'updatedAt'] = Utils.now(this.sequelize.options.dialect)
defaults[Utils._.underscoredIf(this.__options.createdAt, this.__options.underscored)] = 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) {
defaults[this.__options.underscored ? 'deleted_at' : 'deletedAt'] = null
defaults[Utils._.underscoredIf(this.__options.deletedAt, this.__options.underscored)] = null
}
}
}
......
var STRING = function(length, binary) {
if (this instanceof STRING) {
this._binary = !!binary;
this._binary = !!binary
if (typeof length === 'number') {
this._length = length;
this._length = length
} else {
this._length = 255;
this._length = 255
}
} else {
return new STRING(length, binary);
return new STRING(length, binary)
}
};
}
STRING.prototype = {
get BINARY() {
this._binary = true;
return this;
this._binary = true
return this
},
get type() {
return this.toString();
return this.toString()
},
toString: function() {
return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : '');
return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : '')
}
};
}
Object.defineProperty(STRING, 'BINARY', {
get: function() {
return new STRING(undefined, true);
return new STRING(undefined, true)
}
});
})
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() {
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() {
return FLOAT.prototype.construct.apply(this, [FLOAT].concat(Array.prototype.slice.apply(arguments)));
};
FLOAT._type = FLOAT;
FLOAT._typeName = 'FLOAT';
INTEGER._type = INTEGER;
INTEGER._typeName = 'INTEGER';
BIGINT._type = BIGINT;
BIGINT._typeName = 'BIGINT';
STRING._type = STRING;
STRING._typeName = 'VARCHAR';
STRING.toString = INTEGER.toString = FLOAT.toString = BIGINT.toString = function() {
return new this._type().toString();
};
return FLOAT.prototype.construct.apply(this, [FLOAT].concat(Array.prototype.slice.apply(arguments)))
}
var BLOB = function() {
return BLOB.prototype.construct.apply(this, [BLOB].concat(Array.prototype.slice.apply(arguments)))
}
FLOAT._type = FLOAT
FLOAT._typeName = 'FLOAT'
INTEGER._type = INTEGER
INTEGER._typeName = 'INTEGER'
BIGINT._type = BIGINT
BIGINT._typeName = 'BIGINT'
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 = {
construct: function(RealType, length, decimals, unsigned, zerofill) {
if (this instanceof RealType) {
this._typeName = RealType._typeName;
this._unsigned = !!unsigned;
this._zerofill = !!zerofill;
this._typeName = RealType._typeName
this._unsigned = !!unsigned
this._zerofill = !!zerofill
if (typeof length === 'number') {
this._length = length;
this._length = length
}
if (typeof decimals === 'number') {
this._decimals = decimals;
this._decimals = decimals
}
} else {
return new RealType(length, decimals, unsigned, zerofill);
return new RealType(length, decimals, unsigned, zerofill)
}
},
get type() {
return this.toString();
return this.toString()
},
get UNSIGNED() {
this._unsigned = true;
return this;
this._unsigned = true
return this
},
get ZEROFILL() {
this._zerofill = true;
return this;
this._zerofill = true
return this
},
toString: function() {
var result = this._typeName;
var result = this._typeName
if (this._length) {
result += '(' + this._length;
result += '(' + this._length
if (typeof this._decimals === 'number') {
result += ',' + this._decimals;
result += ',' + this._decimals
}
result += ')';
result += ')'
}
if (this._unsigned) {
result += ' UNSIGNED';
result += ' UNSIGNED'
}
if (this._zerofill) {
result += ' ZEROFILL';
result += ' ZEROFILL'
}
return result;
return result
}
};
}
var unsignedDesc = {
get: function() {
return new this._type(undefined, undefined, true);
return new this._type(undefined, undefined, true)
}
};
}
var zerofillDesc = {
get: function() {
return new this._type(undefined, undefined, undefined, true);
return new this._type(undefined, undefined, undefined, true)
}
};
}
var typeDesc = {
get: function() {
return new this._type().toString();
return new this._type().toString()
}
};
}
Object.defineProperty(STRING, 'type', typeDesc);
Object.defineProperty(INTEGER, 'type', typeDesc);
Object.defineProperty(BIGINT, 'type', typeDesc);
Object.defineProperty(FLOAT, 'type', typeDesc);
Object.defineProperty(STRING, 'type', typeDesc)
Object.defineProperty(INTEGER, 'type', typeDesc)
Object.defineProperty(BIGINT, 'type', typeDesc)
Object.defineProperty(FLOAT, 'type', typeDesc)
Object.defineProperty(BLOB, 'type', typeDesc)
Object.defineProperty(INTEGER, 'UNSIGNED', unsignedDesc);
Object.defineProperty(BIGINT, 'UNSIGNED', unsignedDesc);
Object.defineProperty(FLOAT, 'UNSIGNED', unsignedDesc);
Object.defineProperty(INTEGER, 'UNSIGNED', unsignedDesc)
Object.defineProperty(BIGINT, 'UNSIGNED', unsignedDesc)
Object.defineProperty(FLOAT, 'UNSIGNED', unsignedDesc)
Object.defineProperty(INTEGER, 'ZEROFILL', zerofillDesc);
Object.defineProperty(BIGINT, 'ZEROFILL', zerofillDesc);
Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc);
Object.defineProperty(INTEGER, 'ZEROFILL', zerofillDesc)
Object.defineProperty(BIGINT, 'ZEROFILL', zerofillDesc)
Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc)
module.exports = {
STRING: STRING,
......@@ -147,6 +187,7 @@ module.exports = {
BOOLEAN: 'TINYINT(1)',
FLOAT: FLOAT,
NOW: 'NOW',
BLOB: BLOB,
get ENUM() {
var result = function() {
......
......@@ -182,32 +182,37 @@ module.exports = (function() {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
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)
}.bind(this))
optAttributes = optAttributes.concat(attributes)
var table = include.daoFactory.tableName
, as = include.as
if (!include.association.connectorDAO) {
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var attrLeft = 'id'
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
var attrRight = include.association.identifier
var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
, 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)
} else {
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = tableName
var identLeft = include.association.identifier
var attrLeft = 'id'
var tableRight = include.as
var identRight = include.association.foreignIdentifier
var attrRight = 'id'
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(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identRight)
var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
, tableSource = tableName
, identSource = include.association.identifier
, attrSource = ((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
var primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
, tableTarget = include.as
, identTarget = include.association.foreignIdentifier
, attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
var tableJunction = include.association.connectorDAO.tableName
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(tableTarget) + "." + this.quoteIdentifier(attrTarget) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identTarget)
}
}.bind(this))
......@@ -467,8 +472,13 @@ module.exports = (function() {
result.push([_key, _value].join("="))
} else {
for (var logic in value) {
var logicResult = this.getWhereLogic(logic)
if (logicResult === "BETWEEN" || logicResult === "NOT BETWEEN") {
var logicResult = Utils.getWhereLogic(logic)
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])
var _value2 = this.escape(value[logic][1])
......@@ -488,33 +498,6 @@ module.exports = (function() {
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) {
var result = {}
......
......@@ -19,8 +19,9 @@ module.exports = (function() {
// set pooling parameters if specified
if (this.pooling) {
this.pg.defaults.poolSize = this.config.pool.maxConnections
this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime
this.pg.defaults.poolSize = this.config.pool.maxConnections || 10
this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime || 30000
this.pg.defaults.reapIntervalMillis = this.config.pool.reapInterval || 1000
}
this.disconnectTimeoutId = null
......@@ -57,9 +58,9 @@ module.exports = (function() {
})
.on('success', function(done) {
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) })
.error(function(err) { self.endQuery.call(self) })
.proxy(emitter)
......@@ -89,6 +90,7 @@ module.exports = (function() {
if (!!err) {
// release the pool immediately, very important.
done && done(err)
self.client = null
if (err.code) {
switch(err.code) {
......@@ -112,18 +114,18 @@ module.exports = (function() {
emitter.emit('success', done)
})
} else {
done && done()
self.client = null
emitter.emit('success', done)
emitter.emit('success')
}
}
if (this.pooling) {
// acquire client from pool
this.poolIdentifier = this.pg.pools.getOrCreate(uri)
this.poolIdentifier.connect(connectCallback)
this.pg.connect(uri, connectCallback)
} else {
if (!!this.client) {
connectCallback(null, this.client);
connectCallback(null, this.client)
} else {
//create one-off client
this.client = new this.pg.Client(uri)
......@@ -140,10 +142,11 @@ module.exports = (function() {
}
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.isConnected = false
}
......
......@@ -68,10 +68,6 @@ module.exports = (function() {
var dataType = this.pgDataTypeMapping(tableName, attr, attributes[attr])
attrStr.push(this.quoteIdentifier(attr) + " " + dataType)
if (attributes[attr].match(/^ENUM\(/)) {
query = this.pgEnum(tableName, attr, attributes[attr]) + query
}
}
var values = {
......@@ -253,7 +249,7 @@ module.exports = (function() {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
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)
}.bind(this))
......@@ -262,20 +258,24 @@ module.exports = (function() {
var joinQuery = ' LEFT OUTER JOIN <%= table %> AS <%= as %> ON <%= tableLeft %>.<%= attrLeft %> = <%= tableRight %>.<%= attrRight %>'
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)({
table: this.quoteIdentifiers(include.daoFactory.tableName),
as: this.quoteIdentifier(include.as),
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),
attrRight: this.quoteIdentifier(include.association.identifier)
})
} else {
var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
, primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
query += Utils._.template(joinQuery)({
table: this.quoteIdentifiers(include.association.connectorDAO.tableName),
as: this.quoteIdentifier(include.association.connectorDAO.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),
attrRight: this.quoteIdentifier(include.association.identifier)
})
......@@ -284,7 +284,7 @@ module.exports = (function() {
table: this.quoteIdentifiers(include.daoFactory.tableName),
as: this.quoteIdentifier(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),
attrRight: this.quoteIdentifier(include.association.foreignIdentifier)
})
......@@ -382,7 +382,7 @@ module.exports = (function() {
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)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> RETURNING *"
......@@ -390,7 +390,7 @@ module.exports = (function() {
for (var key in attrValueHash) {
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 = {
......@@ -402,7 +402,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements)
},
deleteQuery: function(tableName, where, options) {
deleteQuery: function(tableName, where, options, factory) {
options = options || {}
if (options.truncate === true) {
......@@ -415,6 +415,10 @@ module.exports = (function() {
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 pks;
......@@ -526,7 +530,7 @@ module.exports = (function() {
if (Utils.isHash(smth)) {
smth = Utils.prependTableNameToHash(tableName, smth)
result = this.hashToWhereConditions(smth)
result = this.hashToWhereConditions(smth, factory)
} else if (typeof smth === 'number') {
var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : []
if (primaryKeys.length > 0) {
......@@ -549,7 +553,7 @@ module.exports = (function() {
return result
},
hashToWhereConditions: function(hash) {
hashToWhereConditions: function(hash, factory) {
var result = []
for (var key in hash) {
......@@ -561,9 +565,23 @@ module.exports = (function() {
if (Array.isArray(value)) {
if (value.length === 0) { value = [null] }
_value = "(" + value.map(this.escape).join(',') + ")"
result.push([_key, _value].join(" IN "))
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(',') + ")"
result.push([_key, _value].join(" IN "))
}
}
else if ((value) && (typeof value === "object")) {
if (!!value.join) {
......@@ -572,8 +590,13 @@ module.exports = (function() {
result.push([_key, _value].join("="))
} else {
for (var logic in value) {
var logicResult = this.getWhereLogic(logic)
if (logicResult === "BETWEEN" || logicResult === "NOT BETWEEN") {
var logicResult = Utils.getWhereLogic(logic)
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])
var _value2 = this.escape(value[logic][1])
......@@ -593,33 +616,6 @@ module.exports = (function() {
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) {
var result = {}
......@@ -893,9 +889,52 @@ module.exports = (function() {
}).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)
return "DROP TYPE IF EXISTS " + enumName + "; CREATE TYPE " + enumName + " AS " + dataType.match(/^ENUM\(.+\)/)[0] + "; "
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)
return 'DROP TYPE IF EXISTS ' + enumName + '; '
},
fromArray: function(text) {
......@@ -943,6 +982,10 @@ module.exports = (function() {
dataType = dataType.replace(/NOT NULL/, '')
}
if (dataType.lastIndexOf('BLOB') !== -1) {
dataType = 'bytea'
}
if (dataType.match(/^ENUM\(/)) {
dataType = dataType.replace(/^ENUM\(.+\)/, this.pgEscapeAndQuote("enum_" + tableName + "_" + attr))
}
......
......@@ -18,37 +18,36 @@ module.exports = (function() {
}
Utils.inherit(Query, AbstractQuery)
Query.prototype.run = function(sql, done) {
Query.prototype.run = function(sql) {
this.sql = sql
var self = this
, receivedError = false
, query = this.client.query(sql)
, rows = []
if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql)
}
var receivedError = false
, query = this.client.query(sql)
, rows = []
query.on('row', function(row) {
rows.push(row)
})
query.on('error', function(err) {
receivedError = true
this.emit('error', err, this.callee)
}.bind(this))
self.emit('error', err, self.callee)
})
query.on('end', function() {
done && done()
this.emit('sql', this.sql)
self.emit('sql', self.sql)
if (receivedError) {
return
}
onSuccess.call(this, rows, sql)
}.bind(this))
onSuccess.call(self, rows, sql)
})
return this
}
......
......@@ -164,32 +164,37 @@ module.exports = (function() {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
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)
}.bind(this))
optAttributes = optAttributes.concat(attributes)
var table = include.daoFactory.tableName
, as = include.as
if (!include.association.connectorDAO) {
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var attrLeft = 'id'
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
var attrRight = include.association.identifier
var primaryKeysLeft = ((include.association.associationType === 'BelongsTo') ? Object.keys(include.association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
, 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)
} else {
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = tableName
var identLeft = include.association.identifier
var attrLeft = 'id'
var tableRight = include.as
var identRight = include.association.foreignIdentifier
var attrRight = 'id'
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(table) + " AS " + this.quoteIdentifier(as) + " ON " + this.quoteIdentifier(tableRight) + "." + this.quoteIdentifier(attrRight) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identRight)
var primaryKeysSource = Object.keys(include.association.source.primaryKeys)
, tableSource = tableName
, identSource = include.association.identifier
, attrSource = ((!include.association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
var primaryKeysTarget = Object.keys(include.association.target.primaryKeys)
, tableTarget = include.as
, identTarget = include.association.foreignIdentifier
, attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
var tableJunction = include.association.connectorDAO.tableName
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(tableTarget) + "." + this.quoteIdentifier(attrTarget) + " = " + this.quoteIdentifier(tableJunction) + "." + this.quoteIdentifier(identTarget)
}
}.bind(this))
......
......@@ -29,7 +29,7 @@ module.exports = (function() {
this.options.logging('Executing: ' + this.sql)
}
var columnTypes = {};
var columnTypes = {}
this.database.serialize(function() {
var executeSql = function() {
self.database[getDatabaseMethod.call(self)](self.sql, function(err, results) {
......@@ -48,16 +48,16 @@ module.exports = (function() {
self.database.all("PRAGMA table_info(" + tableName + ")", function(err, results) {
if (!err) {
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 {
executeSql();
executeSql()
}
} else {
executeSql();
executeSql()
}
})
......@@ -85,15 +85,18 @@ module.exports = (function() {
if (this.sql.indexOf('sqlite_master') !== -1) {
result = results.map(function(resultSet) { return resultSet.name })
} else if (this.send('isSelectQuery')) {
// we need to convert the timestamps into actual date objects
if(!this.options.raw) {
results = results.map(function(result) {
for (var name in result) {
if (result.hasOwnProperty(name) && (metaData.columnTypes[name] === 'DATETIME')) {
var val = result[name];
if(val !== null) {
result[name] = new Date(val+'Z'); // Z means UTC
if (result.hasOwnProperty(name) && metaData.columnTypes[name]) {
if (metaData.columnTypes[name] === 'DATETIME') {
// we need to convert the timestamps into actual date objects
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])
}
}
}
......
......@@ -195,6 +195,20 @@ module.exports = (function() {
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) {
var daos = this.daoFactoryManager.daos
return (daos.filter(function(dao) { return dao.name === daoName }).length !== 0)
......
var moment = require("moment")
, isArrayBufferView
, 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) {
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) {
if (arguments.length === 1 && typeof arguments[0] === "object") {
......@@ -37,29 +56,28 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
}
if (val instanceof Date) {
val = SqlString.dateToString(val, timeZone || "Z", dialect);
val = SqlString.dateToString(val, timeZone || "Z", dialect)
}
if (Buffer.isBuffer(val)) {
return SqlString.bufferToString(val);
return SqlString.bufferToString(val, dialect)
}
if (Array.isArray(val)) {
return SqlString.arrayToList(val, timeZone, dialect, field);
if (Array.isArray(val) || isArrayBufferView(val)) {
return SqlString.arrayToList(val, timeZone, dialect, field)
}
if (typeof val === 'object') {
if (stringifyObjects) {
val = val.toString();
val = val.toString()
} else {
return SqlString.objectToValues(val, timeZone);
return SqlString.objectToValues(val, timeZone)
}
}
if (dialect === 'postgres' || dialect === 'sqlite') {
// http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
// http://stackoverflow.com/q/603572/130598
val = val.replace(/'/g, "''");
val = val.replace(/'/g, "''")
} else {
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
switch(s) {
......@@ -71,111 +89,134 @@ SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
case "\x1a": return "\\Z";
default: return "\\"+s;
}
});
})
}
return "'"+val+"'";
};
return "'"+val+"'"
}
SqlString.arrayToList = function(array, timeZone, dialect, field) {
if (dialect === 'postgres') {
var ret = 'ARRAY[' + array.map(function(v) {
return SqlString.escape(v, true, timeZone, dialect, field);
}).join(',') + ']';
if (array.map) {
var valstr = array.map(function(v) {
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) {
ret += '::' + field.type.replace(/\(\d+\)/g, '');
ret += '::' + field.type.replace(/\(\d+\)/g, '')
}
return ret;
return ret
} else {
return array.map(function(v) {
if (Array.isArray(v))
return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')';
return SqlString.escape(v, true, timeZone, dialect);
}).join(', ');
if (array.map) {
return array.map(function(v) {
if (Array.isArray(v)) {
return '(' + SqlString.arrayToList(v, timeZone, dialect) + ')'
}
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) {
values = [].concat(values);
return sql.replace(/\?/g, function(match) {
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) {
return sql.replace(/\:(\w+)/g, function (value, key) {
if (values.hasOwnProperty(key)) {
return SqlString.escape(values[key], false, timeZone, dialect);
}
else {
throw new Error('Named parameter "' + value + '" has no value in the given object.');
return SqlString.escape(values[key], false, timeZone, dialect)
} else {
throw new Error('Named parameter "' + value + '" has no value in the given object.')
}
});
};
})
}
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
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') {
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) {
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) {
var hex = '';
SqlString.bufferToString = function(buffer, dialect) {
var hex = ''
try {
hex = buffer.toString('hex');
hex = buffer.toString('hex')
} catch (err) {
// node v0.4.x does not support hex / throws unknown encoding error
for (var i = 0; i < buffer.length; i++) {
var byte = buffer[i];
hex += zeroPad(byte.toString(16));
var byte = buffer[i]
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) {
var values = [];
var values = []
for (var key in object) {
var value = object[key];
var value = object[key]
if(typeof value === 'function') {
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) {
return (number < 10) ? '0' + number : number;
return (number < 10) ? '0' + number : number
}
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) {
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 = {
var timeZone = null;
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.
// The smartWhere function breaks down the collection of where objects into a more
// centralized object for each column so we can avoid duplicates
......@@ -225,45 +287,32 @@ var Utils = module.exports = {
return 'JOIN'
case 'gte':
return '>='
break
case 'gt':
return '>'
break
case 'lte':
return '<='
break
case 'lt':
return '<'
break
case 'eq':
case 'join':
return '='
break
case 'ne':
return '!='
break
case 'between':
case '..':
return 'BETWEEN'
break
case 'nbetween':
case 'notbetween':
case '!..':
return 'NOT BETWEEN'
break
case 'in':
return 'IN'
break
case 'not':
return 'NOT IN'
break
case 'like':
return 'LIKE'
break
case 'nlike':
case 'notlike':
return 'NOT LIKE'
break
default:
return ''
}
......
{
"name": "sequelize",
"description": "Multi dialect ORM for Node.JS",
"version": "1.7.0-alpha3",
"version": "1.7.0-beta.0",
"author": "Sascha Depold <sascha@depold.com>",
"contributors": [
{
......@@ -39,7 +39,7 @@
"lodash": "~1.3.1",
"underscore.string": "~2.3.0",
"lingo": "~0.0.5",
"validator": "~1.4.0",
"validator": "~1.5.0",
"moment": "~2.1.0",
"commander": "~2.0.0",
"dottie": "0.0.8-0",
......@@ -51,12 +51,12 @@
"devDependencies": {
"sqlite3": "~2.1.12",
"mysql": "~2.0.0-alpha8",
"pg": "~2.3.1",
"pg": "~2.6.0",
"watchr": "~2.4.3",
"yuidocjs": "~0.3.36",
"chai": "~1.7.2",
"mocha": "~1.12.0",
"chai-datetime": "~1.0.0",
"chai-datetime": "~1.1.1",
"sinon": "~1.7.3"
},
"keywords": [
......@@ -77,4 +77,4 @@
"node": ">=0.4.6"
},
"license": "MIT"
}
}
\ No newline at end of file
......@@ -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() {
it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING })
......
......@@ -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() {
it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
......
......@@ -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) {
var self = this
this.ParanoidUser.create({ username: 'fnord' }).success(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('generic', function() {
beforeEach(function(done) {
......@@ -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) {
var User = this.sequelize.define('UserId', {
id: {
......@@ -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', {
id: {
username: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
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.create({id: 'helloworld'}).error(function(err) {
expect(err).to.deep.equal({id: ['ID must be an integer!']})
User.create({username: 'helloworldhelloworld'}).error(function(err) {
expect(err).to.deep.equal({username: ['Username must be an integer!']})
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', {
username: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
validate: {
isInt: { args: true, msg: 'Username must be an integer!' }
describe("primaryKey with the name as id with arguments for it's validation", function() {
beforeEach(function(done) {
this.User = this.sequelize.define('UserId', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
validate: {
isInt: { args: true, msg: 'ID must be an integer!' }
}
}
}
})
this.User.sync({ force: true }).success(function() {
done()
})
})
User.sync({ force: true }).success(function() {
User.create({username: 'helloworldhelloworld'}).error(function(err) {
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()
})
})
......@@ -446,5 +540,26 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
expect(successfulFoo.validate()).to.be.null
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() {
[Sequelize.NOW, 'NOW', 'NOW'],
[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.UNSIGNED, 'INTEGER.UNSIGNED', 'INTEGER UNSIGNED'],
[Sequelize.INTEGER(11), 'INTEGER(11)','INTEGER(11)'],
......
......@@ -85,6 +85,14 @@ if (dialect.match(/^mysql/)) {
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'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=MyISAM;"
},
......@@ -220,6 +228,9 @@ if (dialect.match(/^mysql/)) {
arguments: ['myTable', {name: 'foo', 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}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);"
}, {
......
......@@ -3,6 +3,7 @@ var chai = require('chai')
, Support = require(__dirname + '/../support')
, dialect = Support.getTestDialect()
, DataTypes = require(__dirname + "/../../lib/data-types")
, _ = require('lodash')
chai.Assertion.includeStack = true
......@@ -25,6 +26,28 @@ if (dialect.match(/^postgres/)) {
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) {
this.sequelize.queryInterface.describeTable('Users').success(function(table) {
expect(table.document.type).to.equal('HSTORE')
......@@ -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('integer', function() {
beforeEach(function(done) {
......
......@@ -17,6 +17,7 @@ if (dialect.match(/^postgres/)) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
numbers: {type: DataTypes.ARRAY(DataTypes.FLOAT)},
document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'}
})
this.User.sync({ force: true }).success(function() {
......@@ -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!';",
},
{
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)'}],
expectation: "CREATE TABLE IF NOT EXISTS \"mySchema\".\"myTable\" (\"title\" VARCHAR(255), \"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'}],
......@@ -155,7 +164,7 @@ if (dialect.match(/^postgres/)) {
},
{
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}}
},
{
......@@ -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()}],
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}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1) RETURNING *;"
}, {
......@@ -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 *;",
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}],
expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1) RETURNING *;",
context: {options: {quoteIdentifiers: false}}
......@@ -525,6 +544,9 @@ if (dialect.match(/^postgres/)) {
arguments: ['myTable', {bar: 2}, {name: 'foo'}],
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'}],
expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo' RETURNING *"
}, {
......@@ -564,6 +586,10 @@ if (dialect.match(/^postgres/)) {
expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *",
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'}],
expectation: "UPDATE myTable SET name='foo'';DROP TABLE myTable;' WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}}
......
......@@ -144,17 +144,17 @@ describe(Support.getTestDialectTeaser("Executable"), function() {
;(function(flags) {
flags.forEach(function(flag) {
var prepare = function(callback) {
exec("rm -rf ./*", { cwd: __dirname + '/tmp' }, function() {
exec("../../bin/sequelize --init", { cwd: __dirname + '/tmp' }, function() {
exec("cp ../assets/migrations/*-createPerson.js ./migrations/", { cwd: __dirname + '/tmp' }, function() {
exec("cat ../support.js|sed s,/../,/../../, > ./support.js", { cwd: __dirname + '/tmp' }, function() {
exec("rm -rf ./*", { cwd: __dirname + '/tmp' }, function(error, stdout) {
exec("../../bin/sequelize --init", { cwd: __dirname + '/tmp' }, function(error, stdout) {
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(error, stdout) {
var dialect = Support.getTestDialect()
, config = require(__dirname + '/config/config.js')
config.sqlite.storage = __dirname + "/tmp/test.sqlite"
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)
})
})
......
......@@ -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() {
afterEach(function(done) {
this.sequelize.options.quoteIdentifiers = true
......
......@@ -79,6 +79,14 @@ if (dialect === 'sqlite') {
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)'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));"
},
......@@ -104,6 +112,9 @@ if (dialect === 'sqlite') {
arguments: ['myTable', { name: "'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 }],
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!