sequelize
Advanced tools
Comparing version 1.7.0-alpha1 to 1.7.0-alpha2
# v1.7.0 # | ||
- [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango | ||
- [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). thanks to durango | ||
- [BUG] Fix string escape with postgresql on raw SQL queries. [#586](https://github.com/sequelize/sequelize/pull/586). thanks to zanamixx | ||
- [BUG] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics | ||
- [BUG] Added decimal support for min/max. [#583](https://github.com/sequelize/sequelize/pull/583). thanks to durango | ||
- [BUG] Null dates don't break SQLite anymore. [#572](https://github.com/sequelize/sequelize/pull/572). thanks to mweibel | ||
- [BUG] Correctly handle booleans in MySQL. [#608](https://github.com/sequelize/sequelize/pull/608). Thanks to terraflubb | ||
- [BUG] Fixed empty where conditions in MySQL. [#619](https://github.com/sequelize/sequelize/pull/619). Thanks to terraflubb | ||
- [BUG] Allow overriding of default columns. [#635](https://github.com/sequelize/sequelize/pull/635). Thanks to sevastos | ||
- [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 | ||
- [BUG] Null dates don't break SQLite anymore. [#572](https://github.com/sequelize/sequelize/pull/572). thanks to mweibel | ||
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude | ||
- [FEATURE] Support for bulk insert (`<DAOFactory>.bulkCreate()`, update (`<DAOFactory>.update()`) and delete (`<DAOFactory>.destroy()`) [#569](https://github.com/sequelize/sequelize/pull/569). thanks to optilude | ||
- [FEATURE] Add an extra `queryOptions` parameter to `DAOFactory.find` and `DAOFactory.findAll`. This allows a user to specify `{ raw: true }`, meaning that the raw result should be returned, instead of built DAOs. Usefull for queries returning large datasets, see [#611](https://github.com/sequelize/sequelize/pull/611) janmeier | ||
- [FEATURE] Added convenient data types. [#616](https://github.com/sequelize/sequelize/pull/616). Thanks to Costent | ||
- [FEATURE] Binary is more verbose now. [#612](https://github.com/sequelize/sequelize/pull/612). Thanks to terraflubb | ||
- [FEATURE] Promises/A support. [#626](https://github.com/sequelize/sequelize/pull/626). Thanks to kevinbeaty | ||
- [FEATURE] Added Getters/Setters method for DAO. [#538](https://github.com/sequelize/sequelize/pull/538). Thanks to iamjochem | ||
- [FEATURE] Added model wide validations. [#640](https://github.com/sequelize/sequelize/pull/640). Thanks to tremby | ||
@@ -5,0 +22,0 @@ # v1.6.0 # |
@@ -8,7 +8,7 @@ /* | ||
The last association has the type many-to-one and is defined by the function hasManyAndBelongsTo. | ||
The rest of the example is about setting and getting the associated data. | ||
The rest of the example is about setting and getting the associated data. | ||
*/ | ||
var Sequelize = require(__dirname + "/../../index") | ||
, config = require(__dirname + "/../../test/config") | ||
, config = require(__dirname + "/../../spec/config/config") | ||
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) | ||
@@ -40,9 +40,9 @@ , Person = sequelize.define('Person', { name: Sequelize.STRING }) | ||
.add(pet.save()) | ||
chainer.run().on('success', function() { | ||
person.setMother(mother).on('success', function() { person.getMother().on('success', function(mom) { | ||
console.log('my mom: ', mom.name) | ||
console.log('my mom: ', mom.name) | ||
})}) | ||
person.setFather(father).on('success', function() { person.getFather().on('success', function(dad) { | ||
console.log('my dad: ', dad.name) | ||
console.log('my dad: ', dad.name) | ||
})}) | ||
@@ -49,0 +49,0 @@ person.setBrothers([brother]).on('success', function() { person.getBrothers().on('success', function(brothers) { |
var Sequelize = require(__dirname + "/../../index") | ||
, config = require(__dirname + "/../../test/config") | ||
, config = require(__dirname + "/../../spec/config/config") | ||
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) | ||
@@ -11,8 +11,8 @@ | ||
queries = [] | ||
for(var i = 0; i < count; i++) | ||
chainer.add(Person.create({name: 'someone' + (i % 3)})) | ||
console.log("Begin to save " + count + " items!") | ||
chainer.run().on('success', function() { | ||
@@ -19,0 +19,0 @@ console.log("finished") |
@@ -1,21 +0,28 @@ | ||
var fs = require("fs") | ||
, Sequelize = require("sequelize") | ||
, sequelize = new Sequelize('sequelize_test', 'root', null, {logging: false}) | ||
, Image = sequelize.define('Image', { data: Sequelize.TEXT }) | ||
/* | ||
Title: Default values | ||
Image.sync({force: true}).on('success', function() { | ||
console.log("reading image") | ||
var image = fs.readFileSync(__dirname + '/source.png').toString("base64") | ||
console.log("done\n") | ||
console.log("creating database entry") | ||
Image.create({data: image}).on('success', function(img) { | ||
console.log("done\n") | ||
console.log("writing file") | ||
fs.writeFileSync(__dirname + '/target.png', img.data, "base64") | ||
console.log("done\n") | ||
console.log("you might open the file ./target.png") | ||
This example demonstrates the use of default values for defined model fields. Instead of just specifying the datatype, | ||
you have to pass a hash with a type and a default. You also might want to specify either an attribute can be null or not! | ||
*/ | ||
var Sequelize = require(__dirname + "/../../index") | ||
, config = require(__dirname + "/../../spec/config/config") | ||
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) | ||
var User = sequelize.define('User', { | ||
name: { type: Sequelize.STRING, allowNull: false}, | ||
isAdmin: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false } | ||
}) | ||
, user = User.build({ name: 'Someone' }) | ||
sequelize.sync({force: true}).on('success', function() { | ||
user.save().on('success', function(user) { | ||
console.log("user.isAdmin should be the default value (false): ", user.isAdmin) | ||
user.updateAttributes({ isAdmin: true }).on('success', function(user) { | ||
console.log("user.isAdmin was overwritten to true: " + user.isAdmin) | ||
}) | ||
}) | ||
}).on('failure', function(err) { | ||
console.log(err) | ||
}) |
/* | ||
Title: Defining class and instance methods | ||
This example shows the usage of the classMethods and instanceMethods option for Models. | ||
@@ -8,6 +8,6 @@ */ | ||
var Sequelize = require(__dirname + "/../../index") | ||
, config = require(__dirname + "/../../test/config") | ||
, config = require(__dirname + "/../../spec/config/config") | ||
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) | ||
// model definition | ||
// model definition | ||
var Task = sequelize.define("Task", { | ||
@@ -55,3 +55,3 @@ name: Sequelize.STRING, | ||
console.log("should be 10: " + task1.importance) | ||
Task.setImportance(30, function() { | ||
@@ -58,0 +58,0 @@ Task.findAll().on('success', function(tasks) { |
var Sequelize = require(__dirname + "/../../index") | ||
, config = require(__dirname + "/../../test/config") | ||
, config = require(__dirname + "/../../spec/config/config") | ||
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) | ||
var Person = sequelize.define('Person', | ||
var Person = sequelize.define('Person', | ||
{ name: Sequelize.STRING, | ||
@@ -15,8 +15,8 @@ age : Sequelize.INTEGER | ||
queries = [] | ||
for(var i = 0; i < count; i++) | ||
chainer.add(Person.create({name: 'someone' + (i % 3), age : i+5})) | ||
console.log("Begin to save " + count + " items!") | ||
chainer.run().on('success', function() { | ||
@@ -23,0 +23,0 @@ console.log("finished") |
var Sequelize = require(__dirname + "/../../index") | ||
, config = require("../../test/config") | ||
, config = require(__dirname + "/../../spec/config/config") | ||
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, host: config.host}) | ||
@@ -13,3 +13,3 @@ , QueryChainer = Sequelize.Utils.QueryChainer | ||
, done = 0 | ||
var createPerson = function() { | ||
@@ -20,6 +20,6 @@ Person.create({name: 'someone'}).on('success', function() { | ||
console.log("\nFinished creation of " + count + " people. Took: " + duration + "ms (avg: " + (duration/count) + "ms)") | ||
start = Date.now() | ||
console.log("Will now read them from the database:") | ||
Person.findAll().on('success', function(people) { | ||
@@ -40,5 +40,5 @@ console.log("Reading " + people.length + " items took: " + (Date.now() - start) + "ms") | ||
} | ||
}).on('failure', function(err) { | ||
console.log(err) | ||
}) |
var Sequelize = require(__dirname + "/../../index") | ||
, config = require(__dirname + "/../../test/config") | ||
, config = require(__dirname + "/../../spec/config/config") | ||
, sequelize = new Sequelize(config.database, config.username, config.password, { | ||
@@ -7,3 +7,3 @@ // use other database server or port | ||
port: 12345, | ||
// disable logging | ||
@@ -10,0 +10,0 @@ logging: false |
var Sequelize = require(__dirname + "/../../index") | ||
, config = require(__dirname + "/../../test/config") | ||
, config = require(__dirname + "/../../spec/config/config") | ||
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) | ||
, Project = sequelize.import(__dirname + "/Project") | ||
, Task = sequelize.import(__dirname + "/Task") | ||
Project.hasMany(Task) | ||
Task.belongsTo(Project) | ||
sequelize.sync({force: true}).on('success', function() { | ||
@@ -11,0 +11,0 @@ Project |
var Utils = require("./../utils") | ||
, DataTypes = require('./../data-types') | ||
, Helpers = require('./helpers') | ||
@@ -27,2 +28,3 @@ module.exports = (function() { | ||
newAttributes[this.identifier] = { type: DataTypes.INTEGER } | ||
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options) | ||
Utils._.defaults(this.source.rawAttributes, newAttributes) | ||
@@ -29,0 +31,0 @@ |
var Utils = require("./../utils") | ||
, DataTypes = require('./../data-types') | ||
, Helpers = require('./helpers') | ||
@@ -68,2 +69,3 @@ var HasManySingleLinked = require("./has-many-single-linked") | ||
newAttributes[this.identifier] = { type: DataTypes.INTEGER } | ||
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options) | ||
Utils._.defaults(this.target.rawAttributes, newAttributes) | ||
@@ -70,0 +72,0 @@ } |
var Utils = require("./../utils") | ||
, DataTypes = require('./../data-types') | ||
, Helpers = require("./helpers") | ||
@@ -32,2 +33,3 @@ module.exports = (function() { | ||
newAttributes[this.identifier] = { type: DataTypes.INTEGER } | ||
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options) | ||
Utils._.defaults(this.target.rawAttributes, newAttributes) | ||
@@ -34,0 +36,0 @@ |
@@ -0,1 +1,3 @@ | ||
var Toposort = require('toposort-class') | ||
module.exports = (function() { | ||
@@ -34,3 +36,32 @@ var DAOFactoryManager = function(sequelize) { | ||
/** | ||
* Iterate over DAOs in an order suitable for e.g. creating tables. Will | ||
* take foreign key constraints into account so that dependencies are visited | ||
* before dependents. | ||
*/ | ||
DAOFactoryManager.prototype.forEachDAO = function(iterator) { | ||
var daos = {} | ||
, sorter = new Toposort() | ||
this.daos.forEach(function(dao) { | ||
daos[dao.tableName] = dao | ||
var deps = [] | ||
for(var attrName in dao.rawAttributes) { | ||
if(dao.rawAttributes.hasOwnProperty(attrName)) { | ||
if(dao.rawAttributes[attrName].references) { | ||
deps.push(dao.rawAttributes[attrName].references) | ||
} | ||
} | ||
} | ||
sorter.add(dao.tableName, deps) | ||
}) | ||
sorter.sort().reverse().forEach(function(name) { | ||
iterator(daos[name]) | ||
}) | ||
} | ||
return DAOFactoryManager | ||
})() |
@@ -22,4 +22,12 @@ var Utils = require("./utils") | ||
schemaDelimiter: '' | ||
}, options || {}) | ||
}, options || {}) | ||
// error check options | ||
Utils._.each(options.validate, function(validator, validatorType) { | ||
if (Utils._.contains(Utils._.keys(attributes), validatorType)) | ||
throw new Error("A model validator function must not have the same name as a field. Model: " + name + ", field/validation name: " + validatorType) | ||
if (!Utils._.isFunction(validator)) | ||
throw new Error("Members of the validate option must be functions. Model: " + name + ", error with validate member " + validatorType) | ||
}) | ||
this.name = name | ||
@@ -34,5 +42,2 @@ if (!this.options.tableName) { | ||
this.associations = {} | ||
// extract validation | ||
this.validate = this.options.validate || {} | ||
} | ||
@@ -80,2 +85,3 @@ | ||
this.DAO.prototype.rawAttributes = this.rawAttributes; | ||
if (this.options.instanceMethods) { | ||
@@ -86,2 +92,23 @@ Utils._.each(this.options.instanceMethods, function(fct, name) { | ||
} | ||
Utils._.each(['Get', 'Set'], function(type) { | ||
var prop = type.toLowerCase(), | ||
opt = prop + 'terMethods', | ||
meth = '__define' + type + 'ter__', | ||
funcs = Utils._.isObject(self.options[opt]) ? self.options[opt] : {} | ||
; | ||
Utils._.each(self.rawAttributes, function(attr, name) { | ||
if (attr.hasOwnProperty(prop)) | ||
funcs[name] = attr[prop] | ||
}); | ||
Utils._.each(funcs, function(fct, name) { | ||
if (!Utils._.isFunction(fct)) | ||
throw new Error(type + 'ter for "' + name + '" is not a function.') | ||
self.DAO.prototype[meth](name, fct); | ||
}) | ||
}) | ||
this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes); | ||
@@ -163,7 +190,7 @@ | ||
// alias for findAll | ||
DAOFactory.prototype.all = function(options) { | ||
return this.findAll(options) | ||
DAOFactory.prototype.all = function(options, queryOptions) { | ||
return this.findAll(options, queryOptions) | ||
} | ||
DAOFactory.prototype.findAll = function(options) { | ||
DAOFactory.prototype.findAll = function(options, queryOptions) { | ||
var hasJoin = false | ||
@@ -185,6 +212,6 @@ var options = Utils._.clone(options) | ||
return this.QueryInterface.select(this, this.tableName, options, { | ||
return this.QueryInterface.select(this, this.tableName, options, Utils._.defaults({ | ||
type: 'SELECT', | ||
hasJoin: hasJoin | ||
}) | ||
}, queryOptions)) | ||
} | ||
@@ -208,5 +235,6 @@ | ||
* @param {Array} include A list of associations which shall get eagerly loaded. Supported is either { include: [ DaoFactory1, DaoFactory2, ...] } or { include: [ { daoFactory: DaoFactory1, as: 'Alias' } ] }. | ||
* @param {Object} set the query options, e.g. raw, specifying that you want raw data instead of built DAOs | ||
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. | ||
*/ | ||
DAOFactory.prototype.find = function(options) { | ||
DAOFactory.prototype.find = function(options, queryOptions) { | ||
var hasJoin = false | ||
@@ -263,7 +291,7 @@ | ||
return this.QueryInterface.select(this, this.getTableName(), options, { | ||
return this.QueryInterface.select(this, this.getTableName(), options, Utils._.defaults({ | ||
plain: true, | ||
type: 'SELECT', | ||
hasJoin: hasJoin | ||
}) | ||
}, queryOptions)) | ||
} | ||
@@ -282,3 +310,3 @@ | ||
options.attributes.push(['max(' + field + ')', 'max']) | ||
options.parseInt = true | ||
options.parseFloat = true | ||
@@ -290,3 +318,3 @@ return this.QueryInterface.rawSelect(this.getTableName(), options, 'max') | ||
options.attributes.push(['min(' + field + ')', 'min']) | ||
options.parseInt = true | ||
options.parseFloat = true | ||
@@ -341,2 +369,106 @@ return this.QueryInterface.rawSelect(this.getTableName(), options, 'min') | ||
/** | ||
* Create and insert multiple instances | ||
* | ||
* @param {Array} records List of objects (key/value pairs) to create instances from | ||
* @param {Array} fields Fields to insert (defaults to all fields) | ||
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. | ||
* | ||
* Note: the `success` handler is not passed any arguments. To obtain DAOs for | ||
* the newly created values, you will need to query for them again. This is | ||
* because MySQL and SQLite do not make it easy to obtain back automatically | ||
* generated IDs and other default values in a way that can be mapped to | ||
* multiple records | ||
*/ | ||
DAOFactory.prototype.bulkCreate = function(records, fields) { | ||
var self = this | ||
, daos = records.map(function(v) { return self.build(v) }) | ||
, updatedAtAttr = self.options.underscored ? 'updated_at' : 'updatedAt' | ||
, createdAtAttr = self.options.underscored ? 'created_at' : 'createdAt' | ||
// we will re-create from DAOs, which may have set up default attributes | ||
records = [] | ||
if (fields) { | ||
// Always insert updated and created time stamps | ||
if (self.options.timestamps) { | ||
if (fields.indexOf(updatedAtAttr) === -1) { | ||
fields.push(updatedAtAttr) | ||
} | ||
if (fields.indexOf(createdAtAttr) === -1) { | ||
fields.push(createdAtAttr) | ||
} | ||
} | ||
// Build records for the fields we know about | ||
daos.forEach(function(dao) { | ||
var values = {}; | ||
fields.forEach(function(field) { | ||
values[field] = dao.values[field] | ||
}) | ||
if (self.options.timestamps) { | ||
values[updatedAtAttr] = Utils.now() | ||
} | ||
records.push(values); | ||
}) | ||
} else { | ||
daos.forEach(function(dao) { | ||
records.push(dao.values) | ||
}) | ||
} | ||
// Validate enums | ||
records.forEach(function(values) { | ||
for (var attrName in self.rawAttributes) { | ||
if (self.rawAttributes.hasOwnProperty(attrName)) { | ||
var definition = self.rawAttributes[attrName] | ||
, isEnum = (definition.type && (definition.type.toString() === DataTypes.ENUM.toString())) | ||
, hasValue = (typeof values[attrName] !== 'undefined') | ||
, valueOutOfScope = ((definition.values || []).indexOf(values[attrName]) === -1) | ||
if (isEnum && hasValue && valueOutOfScope) { | ||
throw new Error('Value "' + values[attrName] + '" for ENUM ' + attrName + ' is out of allowed scope. Allowed values: ' + definition.values.join(', ')) | ||
} | ||
} | ||
} | ||
}) | ||
return self.QueryInterface.bulkInsert(self.tableName, records) | ||
} | ||
/** | ||
* Delete multiple instances | ||
* | ||
* @param {Object} where Options to describe the scope of the search. | ||
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. | ||
*/ | ||
DAOFactory.prototype.destroy = function(where) { | ||
if (this.options.timestamps && this.options.paranoid) { | ||
var attr = this.options.underscored ? 'deleted_at' : 'deletedAt' | ||
var attrValueHash = {} | ||
attrValueHash[attr] = Utils.now() | ||
return this.QueryInterface.bulkUpdate(this.tableName, attrValueHash, where) | ||
} else { | ||
return this.QueryInterface.bulkDelete(this.tableName, where) | ||
} | ||
} | ||
/** | ||
* Update multiple instances | ||
* | ||
* @param {Object} attrValueHash A hash of fields to change and their new values | ||
* @param {Object} where Options to describe the scope of the search. | ||
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. | ||
*/ | ||
DAOFactory.prototype.update = function(attrValueHash, where) { | ||
if(this.options.timestamps) { | ||
var attr = this.options.underscored ? 'updated_at' : 'updatedAt' | ||
attrValueHash[attr] = Utils.now() | ||
} | ||
return this.QueryInterface.bulkUpdate(this.tableName, attrValueHash, where) | ||
} | ||
// private | ||
@@ -390,3 +522,5 @@ | ||
Utils._.each(defaultAttributes, function(value, attr) { | ||
self.rawAttributes[attr] = value | ||
if (Utils._.isUndefined(self.rawAttributes[attr])) { | ||
self.rawAttributes[attr] = value | ||
} | ||
}) | ||
@@ -393,0 +527,0 @@ } |
142
lib/dao.js
@@ -10,2 +10,3 @@ var Utils = require("./utils") | ||
this.dataValues = {} | ||
this.__options = options | ||
@@ -17,18 +18,4 @@ this.hasPrimaryKeys = options.hasPrimaryKeys | ||
initAttributes.call(this, values, isNewRecord) | ||
} | ||
if (this.hasDefaultValues) { | ||
Utils._.each(this.defaultValues, function (value, name) { | ||
if (typeof self[name] === 'undefined') { | ||
self.addAttribute(name, value()); | ||
} | ||
}) | ||
} | ||
if (this.booleanValues.length) { | ||
this.booleanValues.forEach(function (name) { | ||
//transform integer 0,1 into boolean | ||
self[name] = !!self[name]; | ||
}); | ||
} | ||
} | ||
Utils._.extend(DAO.prototype, Mixin.prototype) | ||
@@ -47,3 +34,3 @@ | ||
var result = this.__options.timestamps && this.__options.paranoid | ||
result = result && this[this.__options.underscored ? 'deleted_at' : 'deletedAt'] !== null | ||
result = result && this.dataValues[this.__options.underscored ? 'deleted_at' : 'deletedAt'] !== null | ||
@@ -60,3 +47,6 @@ return result | ||
this.attributes.concat(this.__eagerlyLoadedAssociations).forEach(function(attr) { | ||
result[attr] = self[attr] | ||
result[attr] = self.dataValues.hasOwnProperty(attr) | ||
? self.dataValues[attr] | ||
: self[attr] | ||
; | ||
}) | ||
@@ -74,3 +64,3 @@ | ||
Utils._.each(this.__factory.primaryKeys, function(_, attr) { | ||
result[attr] = self[attr] | ||
result[attr] = self.dataValues[attr] | ||
}) | ||
@@ -93,3 +83,3 @@ | ||
primaryKeys.forEach(function(identifier) { | ||
result[identifier] = self[identifier] | ||
result[identifier] = self.dataValues[identifier] | ||
}) | ||
@@ -101,2 +91,10 @@ | ||
DAO.prototype.getDataValue = function(name) { | ||
return this.dataValues && this.dataValues.hasOwnProperty(name) ? this.dataValues[name] : this[name] | ||
} | ||
DAO.prototype.setDataValue = function(name, value) { | ||
this.dataValues[name] = value | ||
} | ||
// if an array with field names is passed to save() | ||
@@ -121,5 +119,7 @@ // only those fields will be updated | ||
var tmpVals = self.values | ||
fields.forEach(function(field) { | ||
if (self.values[field] !== undefined) { | ||
values[field] = self.values[field] | ||
if (tmpVals[field] !== undefined) { | ||
values[field] = tmpVals[field] | ||
} | ||
@@ -157,7 +157,14 @@ }) | ||
if (this.__options.timestamps && this.hasOwnProperty(updatedAtAttr)) { | ||
this[updatedAtAttr] = values[updatedAtAttr] = Utils.now() | ||
if (this.__options.timestamps && this.dataValues.hasOwnProperty(updatedAtAttr)) { | ||
this.dataValues[updatedAtAttr] = values[updatedAtAttr] = Utils.now() | ||
} | ||
if (this.isNewRecord) { | ||
var errors = this.validate() | ||
if (!!errors) { | ||
return new Utils.CustomEventEmitter(function(emitter) { | ||
emitter.emit('error', errors) | ||
}).run() | ||
} | ||
else if (this.isNewRecord) { | ||
return this.QueryInterface.insert(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values) | ||
@@ -223,3 +230,5 @@ } else { | ||
// if field has validators | ||
if (self.validators.hasOwnProperty(field)) { | ||
var hasAllowedNull = (self.rawAttributes[field].allowNull && self.rawAttributes[field].allowNull === true && (value === null || value === undefined)); | ||
if (self.validators.hasOwnProperty(field) && !hasAllowedNull) { | ||
// for each validator | ||
@@ -273,2 +282,11 @@ Utils._.each(self.validators[field], function(details, validatorType) { | ||
// for each model validator for this DAO | ||
Utils._.each(self.__options.validate, function(validator, validatorType) { | ||
try { | ||
validator.apply(self) | ||
} catch (err) { | ||
failures[validatorType] = [err.message] // TODO: data structure needs to change for 2.0 | ||
} | ||
}) | ||
return (Utils._.isEmpty(failures) ? null : failures) | ||
@@ -306,3 +324,3 @@ } | ||
var attr = this.__options.underscored ? 'deleted_at' : 'deletedAt' | ||
this[attr] = new Date() | ||
this.dataValues[attr] = new Date() | ||
return this.save() | ||
@@ -368,3 +386,33 @@ } else { | ||
DAO.prototype.addAttribute = function(attribute, value) { | ||
this[attribute] = value | ||
if (typeof this.dataValues[attribute] !== 'undefined') | ||
return; | ||
if (this.booleanValues.length && this.booleanValues.indexOf(attribute) !== -1) // transform integer 0,1 into boolean | ||
value = !!value; | ||
var has = (function(o) { | ||
var predef = Object.getOwnPropertyDescriptor(o, attribute); | ||
if (predef && predef.hasOwnProperty('value')) | ||
return true; // true here means 'this property exist as a simple value property, do not place setters or getters at all' | ||
return { | ||
get: (predef && predef.hasOwnProperty('get') ? predef.get : null) || o.__lookupGetter__(attribute), | ||
set: (predef && predef.hasOwnProperty('set') ? predef.set : null) || o.__lookupSetter__(attribute) | ||
}; | ||
})(this); | ||
// @ node-v0.8.19: | ||
// calling __defineGetter__ destroys any previously defined setters for the attribute in | ||
// question *if* that property setter was defined on the object's prototype (which is what | ||
// we do in dao-factory) ... therefore we need to [re]define both the setter and getter | ||
// here with either the function that already existed OR the default/automatic definition | ||
// | ||
// (the same is true for __defineSetter and 'prototype' getters) | ||
if (has !== true) { | ||
this.__defineGetter__(attribute, has.get || function() { return this.dataValues[attribute]; }); | ||
this.__defineSetter__(attribute, has.set || function(v) { this.dataValues[attribute] = v; }); | ||
} | ||
this[attribute] = value; | ||
} | ||
@@ -383,4 +431,9 @@ | ||
var initAttributes = function(values, isNewRecord) { | ||
// set id to null if not passed as value, a newly created dao has no id | ||
var defaults = this.hasPrimaryKeys ? {} : { id: null }, | ||
attrs = {}, | ||
key; | ||
// add all passed values to the dao and store the attribute names in this.attributes | ||
for (var key in values) { | ||
for (key in values) { | ||
if (values.hasOwnProperty(key)) { | ||
@@ -395,6 +448,2 @@ if (typeof values[key] === "string" && !!this.__factory && !!this.__factory.rawAttributes[key] && !!this.__factory.rawAttributes[key].type && !!this.__factory.rawAttributes[key].type.type && this.__factory.rawAttributes[key].type.type === DataTypes.HSTORE.type) { | ||
// set id to null if not passed as value | ||
// a newly created dao has no id | ||
var defaults = this.hasPrimaryKeys ? {} : { id: null } | ||
if (this.__options.timestamps && isNewRecord) { | ||
@@ -407,13 +456,34 @@ defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = Utils.now() | ||
} | ||
if (this.hasDefaultValues) { | ||
Utils._.each(this.defaultValues, function(valueFn, key) { | ||
if (!defaults.hasOwnProperty(key)) | ||
defaults[key] = valueFn() | ||
}) | ||
} | ||
} | ||
if (Utils._.size(defaults)) { | ||
for (var attr in defaults) { | ||
var value = defaults[attr] | ||
for (key in defaults) { | ||
attrs[key] = Utils.toDefaultValue(defaults[key]) | ||
} | ||
} | ||
if (!this.hasOwnProperty(attr)) { | ||
this.addAttribute(attr, Utils.toDefaultValue(value)) | ||
Utils._.each(this.attributes, function(key) { | ||
if (!attrs.hasOwnProperty(key)) { | ||
attrs[key] = undefined | ||
} | ||
}) | ||
if (values) { | ||
for (key in values) { | ||
if (values.hasOwnProperty(key)) { | ||
attrs[key] = values[key] | ||
} | ||
} | ||
} | ||
for (key in attrs) { | ||
this.addAttribute(key, attrs[key]) | ||
} | ||
} | ||
@@ -420,0 +490,0 @@ |
@@ -0,9 +1,149 @@ | ||
var STRING = function(length, binary) { | ||
if (this instanceof STRING) { | ||
this._binary = !!binary; | ||
if (typeof length === 'number') { | ||
this._length = length; | ||
} else { | ||
this._length = 255; | ||
} | ||
} else { | ||
return new STRING(length, binary); | ||
} | ||
}; | ||
STRING.prototype = { | ||
get BINARY() { | ||
this._binary = true; | ||
return this; | ||
}, | ||
get type() { | ||
return this.toString(); | ||
}, | ||
toString: function() { | ||
return 'VARCHAR(' + this._length + ')' + ((this._binary) ? ' BINARY' : ''); | ||
} | ||
}; | ||
Object.defineProperty(STRING, 'BINARY', { | ||
get: function() { | ||
return new STRING(undefined, true); | ||
} | ||
}); | ||
var INTEGER = function() { | ||
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))); | ||
}; | ||
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(); | ||
}; | ||
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; | ||
if (typeof length === 'number') { | ||
this._length = length; | ||
} | ||
if (typeof decimals === 'number') { | ||
this._decimals = decimals; | ||
} | ||
} else { | ||
return new RealType(length, decimals, unsigned, zerofill); | ||
} | ||
}, | ||
get type() { | ||
return this.toString(); | ||
}, | ||
get UNSIGNED() { | ||
this._unsigned = true; | ||
return this; | ||
}, | ||
get ZEROFILL() { | ||
this._zerofill = true; | ||
return this; | ||
}, | ||
toString: function() { | ||
var result = this._typeName; | ||
if (this._length) { | ||
result += '(' + this._length; | ||
if (typeof this._decimals === 'number') { | ||
result += ',' + this._decimals; | ||
} | ||
result += ')'; | ||
} | ||
if (this._unsigned) { | ||
result += ' UNSIGNED'; | ||
} | ||
if (this._zerofill) { | ||
result += ' ZEROFILL'; | ||
} | ||
return result; | ||
} | ||
}; | ||
var unsignedDesc = { | ||
get: function() { | ||
return new this._type(undefined, undefined, true); | ||
} | ||
}; | ||
var zerofillDesc = { | ||
get: function() { | ||
return new this._type(undefined, undefined, undefined, true); | ||
} | ||
}; | ||
var typeDesc = { | ||
get: function() { | ||
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(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); | ||
module.exports = { | ||
STRING: 'VARCHAR(255)', | ||
STRING: STRING, | ||
TEXT: 'TEXT', | ||
INTEGER: 'INTEGER', | ||
BIGINT: 'BIGINT', | ||
INTEGER: INTEGER, | ||
BIGINT: BIGINT, | ||
DATE: 'DATETIME', | ||
BOOLEAN: 'TINYINT(1)', | ||
FLOAT: 'FLOAT', | ||
FLOAT: FLOAT, | ||
NOW: 'NOW', | ||
@@ -10,0 +150,0 @@ |
@@ -22,3 +22,5 @@ var mysql | ||
minConnections: 0, | ||
maxIdleTime: 1000 | ||
maxIdleTime: 1000, | ||
handleDisconnects: false, | ||
validate: validateConnection | ||
}); | ||
@@ -87,2 +89,3 @@ this.pendingQueries = 0; | ||
}, | ||
validate: self.poolCfg.validate, | ||
max: self.poolCfg.maxConnections, | ||
@@ -103,2 +106,3 @@ min: self.poolCfg.minConnections, | ||
}, | ||
validate: self.poolCfg.validate, | ||
max: self.poolCfg.maxConnections, | ||
@@ -121,2 +125,3 @@ min: self.poolCfg.minConnections, | ||
min: self.poolCfg.minConnections, | ||
validate: self.poolCfg.validate, | ||
idleTimeoutMillis: self.poolCfg.maxIdleTime | ||
@@ -254,6 +259,21 @@ }) | ||
this.isConnecting = false | ||
if (config.pool.handleDisconnects) { | ||
handleDisconnect(this.pool, connection) | ||
} | ||
done(null, connection) | ||
} | ||
var handleDisconnect = function(pool, client) { | ||
client.on('error', function(err) { | ||
if (err.code !== 'PROTOCOL_CONNECTION_LOST') { | ||
throw err | ||
} | ||
pool.destroy(client) | ||
}) | ||
} | ||
var validateConnection = function(client) { | ||
return client && client.state != 'disconnected' | ||
} | ||
var enqueue = function(queueItem, options) { | ||
@@ -344,2 +364,2 @@ options = options || {} | ||
return ConnectorManager | ||
})() | ||
})() |
@@ -5,6 +5,16 @@ var Utils = require("../../utils") | ||
var processAndEscapeValue = function(value) { | ||
var processedValue = value | ||
if (value instanceof Date) { | ||
processedValue = Utils.toSqlDate(value) | ||
} else if (typeof value === 'boolean') { | ||
processedValue = value ? 1 : 0 | ||
} | ||
return Utils.escape(processedValue) | ||
} | ||
module.exports = (function() { | ||
var QueryGenerator = { | ||
addSchema: function(opts) { | ||
var tableName = undefined | ||
var tableName | ||
var schema = (!!opts && !!opts.options && !!opts.options.schema ? opts.options.schema : undefined) | ||
@@ -49,2 +59,3 @@ var schemaDelimiter = (!!opts && !!opts.options && !!opts.options.schemaDelimiter ? opts.options.schemaDelimiter : undefined) | ||
, primaryKeys = [] | ||
, foreignKeys = {} | ||
, attrStr = [] | ||
@@ -59,2 +70,7 @@ | ||
attrStr.push(QueryGenerator.addQuotes(attr) + " " + dataType.replace(/PRIMARY KEY/, '')) | ||
} else if (Utils._.includes(dataType, 'REFERENCES')) { | ||
// MySQL doesn't support inline REFERENCES declarations: move to the end | ||
var m = dataType.match(/^(.+) (REFERENCES.*)$/) | ||
attrStr.push(QueryGenerator.addQuotes(attr) + " " + m[1]) | ||
foreignKeys[attr] = m[2] | ||
} else { | ||
@@ -78,2 +94,8 @@ attrStr.push(QueryGenerator.addQuotes(attr) + " " + dataType) | ||
for (var fkey in foreignKeys) { | ||
if(foreignKeys.hasOwnProperty(fkey)) { | ||
values.attributes += ", FOREIGN KEY (" + QueryGenerator.addQuotes(fkey) + ") " + foreignKeys[fkey] | ||
} | ||
} | ||
return Utils._.template(query)(values).trim() + ";" | ||
@@ -126,3 +148,3 @@ }, | ||
for (attrName in attributes) { | ||
for (var attrName in attributes) { | ||
var definition = attributes[attrName] | ||
@@ -157,4 +179,4 @@ | ||
selectQuery: function(tableName, options) { | ||
var query = "SELECT <%= attributes %> FROM <%= table %>" | ||
, table = null | ||
var table = null, | ||
joinQuery = "" | ||
@@ -173,8 +195,7 @@ options = options || {} | ||
if (options.include) { | ||
var optAttributes = [options.table + '.*'] | ||
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes] | ||
options.include.forEach(function(include) { | ||
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) { | ||
var template = Utils._.template("`<%= as %>`.`<%= attr %>` AS `<%= as %>.<%= attr %>`") | ||
return template({ as: include.as, attr: attr }) | ||
return "`" + include.as + "`.`" + attr + "` AS `" + include.as + "." + attr + "`" | ||
}) | ||
@@ -184,11 +205,10 @@ | ||
var joinQuery = " LEFT OUTER JOIN `<%= table %>` AS `<%= as %>` ON `<%= tableLeft %>`.`<%= attrLeft %>` = `<%= tableRight %>`.`<%= attrRight %>`" | ||
query += Utils._.template(joinQuery)({ | ||
table: include.daoFactory.tableName, | ||
as: include.as, | ||
tableLeft: ((include.association.associationType === 'BelongsTo') ? include.as : tableName), | ||
attrLeft: 'id', | ||
tableRight: ((include.association.associationType === 'BelongsTo') ? tableName : include.as), | ||
attrRight: include.association.identifier | ||
}) | ||
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 | ||
joinQuery += " LEFT OUTER JOIN `" + table + "` AS `" + as + "` ON `" + tableLeft + "`.`" + attrLeft + "` = `" + tableRight + "`.`" + attrRight + "`" | ||
}) | ||
@@ -199,5 +219,8 @@ | ||
var query = "SELECT " + options.attributes + " FROM " + options.table | ||
query += joinQuery | ||
if (options.hasOwnProperty('where')) { | ||
options.where = this.getWhereConditions(options.where, tableName) | ||
query += " WHERE <%= where %>" | ||
query += " WHERE " + options.where | ||
} | ||
@@ -207,7 +230,7 @@ | ||
options.group = Array.isArray(options.group) ? options.group.map(function(grp){return QueryGenerator.addQuotes(grp)}).join(', ') : QueryGenerator.addQuotes(options.group) | ||
query += " GROUP BY <%= group %>" | ||
query += " GROUP BY " + options.group | ||
} | ||
if (options.order) { | ||
query += " ORDER BY <%= order %>" | ||
query += " ORDER BY " + options.order | ||
} | ||
@@ -218,5 +241,5 @@ | ||
if (options.offset) { | ||
query += " LIMIT <%= offset %>, <%= limit %>" | ||
query += " LIMIT " + options.offset + ", " + options.limit | ||
} else { | ||
query += " LIMIT <%= limit %>" | ||
query += " LIMIT " + options.limit | ||
} | ||
@@ -227,3 +250,3 @@ } | ||
return Utils._.template(query)(options) | ||
return query | ||
}, | ||
@@ -234,35 +257,45 @@ | ||
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>);" | ||
var table = QueryGenerator.addQuotes(tableName) | ||
var attributes = Object.keys(attrValueHash).map(function(attr){return QueryGenerator.addQuotes(attr)}).join(",") | ||
var values = Utils._.values(attrValueHash).map(processAndEscapeValue).join(",") | ||
var replacements = { | ||
table: QueryGenerator.addQuotes(tableName), | ||
attributes: Object.keys(attrValueHash).map(function(attr){return QueryGenerator.addQuotes(attr)}).join(","), | ||
values: Utils._.values(attrValueHash).map(function(value){ | ||
return Utils.escape((value instanceof Date) ? Utils.toSqlDate(value) : value) | ||
}).join(",") | ||
} | ||
var query = "INSERT INTO " + table + " (" + attributes + ") VALUES (" + values + ");" | ||
return Utils._.template(query)(replacements) | ||
return query | ||
}, | ||
bulkInsertQuery: function(tableName, attrValueHashes) { | ||
var tuples = [] | ||
Utils._.forEach(attrValueHashes, function(attrValueHash) { | ||
tuples.push("(" + | ||
Utils._.values(attrValueHash).map(processAndEscapeValue).join(",") + | ||
")") | ||
}) | ||
var table = QueryGenerator.addQuotes(tableName) | ||
var attributes = Object.keys(attrValueHashes[0]).map(function(attr){return QueryGenerator.addQuotes(attr)}).join(",") | ||
var query = "INSERT INTO " + table + " (" + attributes + ") VALUES " + tuples.join(",") + ";" | ||
return query | ||
}, | ||
updateQuery: function(tableName, attrValueHash, where) { | ||
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) | ||
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>" | ||
, values = [] | ||
var values = [] | ||
for (var key in attrValueHash) { | ||
var value = attrValueHash[key] | ||
, _value = (value instanceof Date) ? Utils.toSqlDate(value) : value | ||
, _value = processAndEscapeValue(value) | ||
values.push(QueryGenerator.addQuotes(key) + "=" + Utils.escape(_value)) | ||
values.push(QueryGenerator.addQuotes(key) + "=" + _value) | ||
} | ||
var replacements = { | ||
table: QueryGenerator.addQuotes(tableName), | ||
values: values.join(","), | ||
where: QueryGenerator.getWhereConditions(where) | ||
} | ||
var query = "UPDATE " + QueryGenerator.addQuotes(tableName) + | ||
" SET " + values.join(",") + | ||
" WHERE " + QueryGenerator.getWhereConditions(where) | ||
return Utils._.template(query)(replacements) | ||
return query | ||
}, | ||
@@ -272,34 +305,50 @@ | ||
options = options || {} | ||
options.limit = options.limit || 1 | ||
var query = "DELETE FROM <%= table %> WHERE <%= where %> LIMIT <%= limit %>" | ||
var replacements = { | ||
table: QueryGenerator.addQuotes(tableName), | ||
where: QueryGenerator.getWhereConditions(where), | ||
limit: Utils.escape(options.limit) | ||
var table = QueryGenerator.addQuotes(tableName) | ||
where = QueryGenerator.getWhereConditions(where) | ||
var limit = "" | ||
if(Utils._.isUndefined(options.limit)) { | ||
options.limit = 1; | ||
} | ||
return Utils._.template(query)(replacements) | ||
if(!!options.limit) { | ||
limit = " LIMIT " + Utils.escape(options.limit) | ||
} | ||
var query = "DELETE FROM " + table + " WHERE " + where + limit | ||
return query | ||
}, | ||
bulkDeleteQuery: function(tableName, where, options) { | ||
options = options || {} | ||
var table = QueryGenerator.addQuotes(tableName) | ||
where = QueryGenerator.getWhereConditions(where) | ||
var query = "DELETE FROM " + table + " WHERE " + where | ||
return query | ||
}, | ||
incrementQuery: function (tableName, attrValueHash, where) { | ||
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) | ||
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> " | ||
, values = [] | ||
var values = [] | ||
for (var key in attrValueHash) { | ||
var value = attrValueHash[key] | ||
, _value = (value instanceof Date) ? Utils.toSqlDate(value) : value | ||
, _value = processAndEscapeValue(value) | ||
values.push(QueryGenerator.addQuotes(key) + "=" + QueryGenerator.addQuotes(key) + " + " +Utils.escape(_value)) | ||
values.push(QueryGenerator.addQuotes(key) + "=" + QueryGenerator.addQuotes(key) + " + " + _value) | ||
} | ||
var replacements = { | ||
table: QueryGenerator.addQuotes(tableName), | ||
values: values.join(","), | ||
where: QueryGenerator.getWhereConditions(where) | ||
} | ||
var table = QueryGenerator.addQuotes(tableName) | ||
values = values.join(",") | ||
where = QueryGenerator.getWhereConditions(where) | ||
return Utils._.template(query)(replacements) | ||
var query = "UPDATE " + table + " SET " + values + " WHERE " + where | ||
return query | ||
}, | ||
@@ -384,3 +433,3 @@ | ||
return result | ||
return result ? result : '1=1' | ||
}, | ||
@@ -400,9 +449,7 @@ | ||
// is value an array? | ||
if (value.length == 0) { value = [null] } | ||
_value = "(" + value.map(function(subValue) { | ||
return Utils.escape(subValue); | ||
}).join(',') + ")" | ||
if (value.length === 0) { value = [null] } | ||
_value = "(" + value.map(processAndEscapeValue).join(',') + ")" | ||
result.push([_key, _value].join(" IN ")) | ||
} else if ((value) && (typeof value == 'object')) { | ||
} else if ((value) && (typeof value == 'object') && !(value instanceof Date)) { | ||
// is value an object? | ||
@@ -414,3 +461,3 @@ | ||
} else { | ||
_value = Utils.escape(value) | ||
_value = processAndEscapeValue(value) | ||
result.push((_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("=")) | ||
@@ -430,8 +477,7 @@ } | ||
if (Utils.isHash(dataType)) { | ||
var template = "<%= type %>" | ||
, replacements = { type: dataType.type } | ||
var template | ||
if (dataType.type.toString() === DataTypes.ENUM.toString()) { | ||
if (Array.isArray(dataType.values) && (dataType.values.length > 0)) { | ||
replacements.type = "ENUM(" + Utils._.map(dataType.values, function(value) { | ||
template = "ENUM(" + Utils._.map(dataType.values, function(value) { | ||
return Utils.escape(value) | ||
@@ -442,2 +488,4 @@ }).join(", ") + ")" | ||
} | ||
} else { | ||
template = dataType.type.toString(); | ||
} | ||
@@ -453,5 +501,4 @@ | ||
if ((dataType.defaultValue != undefined) && (dataType.defaultValue != DataTypes.NOW)) { | ||
template += " DEFAULT <%= defaultValue %>" | ||
replacements.defaultValue = Utils.escape(dataType.defaultValue) | ||
if ((dataType.defaultValue !== undefined) && (dataType.defaultValue != DataTypes.NOW)) { | ||
template += " DEFAULT " + Utils.escape(dataType.defaultValue) | ||
} | ||
@@ -467,3 +514,23 @@ | ||
result[name] = Utils._.template(template)(replacements) | ||
if(dataType.references) { | ||
template += " REFERENCES " + Utils.addTicks(dataType.references) | ||
if(dataType.referencesKey) { | ||
template += " (" + Utils.addTicks(dataType.referencesKey) + ")" | ||
} else { | ||
template += " (" + Utils.addTicks('id') + ")" | ||
} | ||
if(dataType.onDelete) { | ||
template += " ON DELETE " + dataType.onDelete.toUpperCase() | ||
} | ||
if(dataType.onUpdate) { | ||
template += " ON UPDATE " + dataType.onUpdate.toUpperCase() | ||
} | ||
} | ||
result[name] = template | ||
} else { | ||
@@ -493,2 +560,12 @@ result[name] = dataType | ||
enableForeignKeyConstraintsQuery: function() { | ||
var sql = "SET FOREIGN_KEY_CHECKS = 1;" | ||
return Utils._.template(sql, {}) | ||
}, | ||
disableForeignKeyConstraintsQuery: function() { | ||
var sql = "SET FOREIGN_KEY_CHECKS = 0;" | ||
return Utils._.template(sql, {}) | ||
}, | ||
addQuotes: function(s, quoteChar) { | ||
@@ -495,0 +572,0 @@ return Utils.addTicks(s, quoteChar) |
@@ -65,3 +65,3 @@ var Utils = require("../../utils") | ||
table: QueryGenerator.addQuotes(tableName), | ||
attributes: attrStr.join(", "), | ||
attributes: attrStr.join(", ") | ||
} | ||
@@ -82,5 +82,6 @@ | ||
options = options || {} | ||
var query = "DROP TABLE IF EXISTS <%= table %>;" | ||
var query = "DROP TABLE IF EXISTS <%= table %><%= cascade %>;" | ||
return Utils._.template(query)({ | ||
table: QueryGenerator.addQuotes(tableName) | ||
table: QueryGenerator.addQuotes(tableName), | ||
cascade: options.cascade? " CASCADE" : "" | ||
}) | ||
@@ -197,3 +198,3 @@ }, | ||
before: QueryGenerator.addQuotes(attrBefore), | ||
after: QueryGenerator.addQuotes(attributeName), | ||
after: QueryGenerator.addQuotes(attributeName) | ||
})) | ||
@@ -238,3 +239,3 @@ } | ||
if (options.include) { | ||
var optAttributes = [options.table + '.*'] | ||
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes] | ||
@@ -265,13 +266,6 @@ options.include.forEach(function(include) { | ||
if(options.hasOwnProperty('where')) { | ||
options.where = QueryGenerator.getWhereConditions(options.where) | ||
options.where = QueryGenerator.getWhereConditions(options.where, tableName) | ||
query += " WHERE <%= where %>" | ||
} | ||
if(options.order) { | ||
options.order = options.order.replace(/([^ ]+)(.*)/, function(m, g1, g2) { | ||
return QueryGenerator.addQuotes(g1) + g2 | ||
}) | ||
query += " ORDER BY <%= order %>" | ||
} | ||
if(options.group) { | ||
@@ -289,2 +283,9 @@ if (Array.isArray(options.group)) { | ||
if(options.order) { | ||
options.order = options.order.replace(/([^ ]+)(.*)/, function(m, g1, g2) { | ||
return QueryGenerator.addQuotes(g1) + g2 | ||
}) | ||
query += " ORDER BY <%= order %>" | ||
} | ||
if (!(options.include && (options.limit === 1))) { | ||
@@ -309,15 +310,4 @@ if (options.limit) { | ||
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;" | ||
, returning = [] | ||
, returning = removeSerialsFromHash(tableName, attrValueHash) | ||
Utils._.forEach(attrValueHash, function(value, key, hash) { | ||
if (tables[tableName] && tables[tableName][key]) { | ||
switch (tables[tableName][key]) { | ||
case 'serial': | ||
delete hash[key] | ||
returning.push(key) | ||
break | ||
} | ||
} | ||
}); | ||
var replacements = { | ||
@@ -336,2 +326,26 @@ table: QueryGenerator.addQuotes(tableName) | ||
bulkInsertQuery: function(tableName, attrValueHashes) { | ||
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %> RETURNING *;" | ||
, tuples = [] | ||
Utils._.forEach(attrValueHashes, function(attrValueHash) { | ||
removeSerialsFromHash(tableName, attrValueHash) | ||
tuples.push("(" + | ||
Utils._.values(attrValueHash).map(function(value){ | ||
return QueryGenerator.pgEscape(value) | ||
}).join(",") + | ||
")") | ||
}) | ||
var replacements = { | ||
table: QueryGenerator.addQuotes(tableName) | ||
, attributes: Object.keys(attrValueHashes[0]).map(function(attr){ | ||
return QueryGenerator.addQuotes(attr) | ||
}).join(",") | ||
, tuples: tuples.join(",") | ||
} | ||
return Utils._.template(query)(replacements) | ||
}, | ||
updateQuery: function(tableName, attrValueHash, where) { | ||
@@ -359,7 +373,10 @@ attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) | ||
options = options || {} | ||
options.limit = options.limit || 1 | ||
if(Utils._.isUndefined(options.limit)) { | ||
options.limit = 1; | ||
} | ||
primaryKeys[tableName] = primaryKeys[tableName] || []; | ||
var query = "DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %> WHERE <%= where %> LIMIT <%= limit %>)" | ||
var query = "DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %> WHERE <%= where %><%= limit %>)" | ||
@@ -378,3 +395,3 @@ var pks; | ||
where: QueryGenerator.getWhereConditions(where), | ||
limit: QueryGenerator.pgEscape(options.limit), | ||
limit: !!options.limit? " LIMIT " + QueryGenerator.pgEscape(options.limit) : "", | ||
primaryKeys: primaryKeys[tableName].length > 1 ? '(' + pks + ')' : pks, | ||
@@ -470,10 +487,12 @@ primaryKeysSelection: pks | ||
getWhereConditions: function(smth) { | ||
getWhereConditions: function(smth, tableName) { | ||
var result = null | ||
if (Utils.isHash(smth)) { | ||
smth = Utils.prependTableNameToHash(tableName, smth) | ||
result = QueryGenerator.hashToWhereConditions(smth) | ||
} | ||
else if (typeof smth === "number") { | ||
result = '\"id\"' + "=" + QueryGenerator.pgEscape(smth) | ||
smth = Utils.prependTableNameToHash(tableName, { id: smth }) | ||
result = QueryGenerator.hashToWhereConditions(smth) | ||
} | ||
@@ -570,2 +589,24 @@ else if (typeof smth === "string") { | ||
if(dataType.references) { | ||
template += " REFERENCES <%= referencesTable %> (<%= referencesKey %>)" | ||
replacements.referencesTable = QueryGenerator.addQuotes(dataType.references) | ||
if(dataType.referencesKey) { | ||
replacements.referencesKey = QueryGenerator.addQuotes(dataType.referencesKey) | ||
} else { | ||
replacements.referencesKey = QueryGenerator.addQuotes('id') | ||
} | ||
if(dataType.onDelete) { | ||
template += " ON DELETE <%= onDeleteAction %>" | ||
replacements.onDeleteAction = dataType.onDelete.toUpperCase() | ||
} | ||
if(dataType.onUpdate) { | ||
template += " ON UPDATE <%= onUpdateAction %>" | ||
replacements.onUpdateAction = dataType.onUpdate.toUpperCase() | ||
} | ||
} | ||
result[name] = Utils._.template(template)(replacements) | ||
@@ -594,4 +635,12 @@ } else { | ||
enableForeignKeyConstraintsQuery: function() { | ||
return false // not supported by dialect | ||
}, | ||
disableForeignKeyConstraintsQuery: function() { | ||
return false // not supported by dialect | ||
}, | ||
databaseConnectionUri: function(config) { | ||
var template = '<%= protocol %>://<%= user %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %>'; | ||
var template = '<%= protocol %>://<%= user %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %>' | ||
@@ -693,5 +742,11 @@ return Utils._.template(template)({ | ||
if (Utils._.includes(dataType, 'SERIAL')) { | ||
dataType = dataType.replace(/INTEGER/, '') | ||
if (Utils._.includes(dataType, 'BIGINT')) { | ||
dataType = dataType.replace(/SERIAL/, 'BIGSERIAL') | ||
dataType = dataType.replace(/BIGINT/, '') | ||
tables[tableName][attr] = 'bigserial' | ||
} else { | ||
dataType = dataType.replace(/INTEGER/, '') | ||
tables[tableName][attr] = 'serial' | ||
} | ||
dataType = dataType.replace(/NOT NULL/, '') | ||
tables[tableName][attr] = 'serial' | ||
} | ||
@@ -707,3 +762,20 @@ | ||
// Private | ||
var removeSerialsFromHash = function(tableName, attrValueHash) { | ||
var returning = []; | ||
Utils._.forEach(attrValueHash, function(value, key, hash) { | ||
if (tables[tableName] && tables[tableName][key]) { | ||
switch (tables[tableName][key]) { | ||
case 'serial': | ||
delete hash[key] | ||
returning.push(key) | ||
break | ||
} | ||
} | ||
}); | ||
return returning; | ||
} | ||
return Utils._.extend(Utils._.clone(require("../query-generator")), QueryGenerator) | ||
})() |
@@ -111,10 +111,11 @@ var Utils = require("../../utils") | ||
} else if (this.send('isInsertQuery')) { | ||
for (var key in rows[0]) { | ||
if (rows[0].hasOwnProperty(key)) { | ||
var record = rows[0][key] | ||
if (!!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) { | ||
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record) | ||
if(this.callee !== null) { // may happen for bulk inserts | ||
for (var key in rows[0]) { | ||
if (rows[0].hasOwnProperty(key)) { | ||
var record = rows[0][key] | ||
if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) { | ||
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record) | ||
} | ||
this.callee[key] = record | ||
} | ||
this.callee[key] = record | ||
} | ||
@@ -125,10 +126,11 @@ } | ||
} else if (this.send('isUpdateQuery')) { | ||
for (var key in rows[0]) { | ||
if (rows[0].hasOwnProperty(key)) { | ||
var record = rows[0][key] | ||
if (!!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) { | ||
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record) | ||
if(this.callee !== null) { // may happen for bulk updates | ||
for (var key in rows[0]) { | ||
if (rows[0].hasOwnProperty(key)) { | ||
var record = rows[0][key] | ||
if (!!this.callee.daoFactory && !!this.callee.daoFactory.rawAttributes && !!this.callee.daoFactory.rawAttributes[key] && !!this.callee.daoFactory.rawAttributes[key].type && !!this.callee.daoFactory.rawAttributes[key].type.type && this.callee.daoFactory.rawAttributes[key].type.type === DataTypes.HSTORE.type) { | ||
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record) | ||
} | ||
this.callee[key] = record | ||
} | ||
this.callee[key] = record | ||
} | ||
@@ -135,0 +137,0 @@ } |
@@ -122,2 +122,10 @@ module.exports = (function() { | ||
/* | ||
Returns an insert into command for multiple values. | ||
Parameters: table name + list of hashes of attribute-value-pairs. | ||
*/ | ||
bulkInsertQuery: function(tableName, attrValueHashes) { | ||
throwMethodUndefined('bulkInsertQuery') | ||
}, | ||
/* | ||
Returns an update query. | ||
@@ -152,2 +160,15 @@ Parameters: | ||
/* | ||
Returns a bulk deletion query. | ||
Parameters: | ||
- tableName -> Name of the table | ||
- where -> A hash with conditions (e.g. {name: 'foo'}) | ||
OR an ID as integer | ||
OR a string with conditions (e.g. 'name="foo"'). | ||
If you use a string, you have to escape it on your own. | ||
*/ | ||
bulkDeleteQuery: function(tableName, where, options) { | ||
throwMethodUndefined('bulkDeleteQuery') | ||
}, | ||
/* | ||
Returns an update query. | ||
@@ -234,3 +255,18 @@ Parameters: | ||
throwMethodUndefined('findAutoIncrementField') | ||
}, | ||
/* | ||
Globally enable foreign key constraints | ||
*/ | ||
enableForeignKeyConstraintsQuery: function() { | ||
throwMethodUndefined('enableForeignKeyConstraintsQuery') | ||
}, | ||
/* | ||
Globally disable foreign key constraints | ||
*/ | ||
disableForeignKeyConstraintsQuery: function() { | ||
throwMethodUndefined('disableForeignKeyConstraintsQuery') | ||
} | ||
} | ||
@@ -237,0 +273,0 @@ |
@@ -8,3 +8,9 @@ var Utils = require("../../utils") | ||
this.sequelize = sequelize | ||
this.database = new sqlite3.Database(sequelize.options.storage || ':memory:') | ||
this.database = db = new sqlite3.Database(sequelize.options.storage || ':memory:', function(err) { | ||
if(!err && sequelize.options.foreignKeys !== false) { | ||
// Make it possible to define and use foreign key constraints unless | ||
// explicitly disallowed. It's still opt-in per relation | ||
db.run('PRAGMA FOREIGN_KEYS=ON') | ||
} | ||
}) | ||
} | ||
@@ -11,0 +17,0 @@ Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype) |
@@ -27,6 +27,15 @@ var Utils = require("../../utils") | ||
addQuotes: function(s, quoteChar) { | ||
return Utils.addTicks(s, quoteChar) | ||
removeQuotes: function (s, quoteChar) { | ||
quoteChar = quoteChar || '`' | ||
return s.replace(new RegExp(quoteChar, 'g'), '') | ||
}, | ||
addQuotes: function (s, quoteChar) { | ||
quoteChar = quoteChar || '`' | ||
return QueryGenerator.removeQuotes(s, quoteChar) | ||
.split('.') | ||
.map(function(e) { return quoteChar + String(e) + quoteChar }) | ||
.join('.') | ||
}, | ||
addSchema: function(opts) { | ||
@@ -80,2 +89,6 @@ var tableName = undefined | ||
if (Utils._.includes(dataType, 'AUTOINCREMENT')) { | ||
dataType = dataType.replace(/BIGINT/, 'INTEGER') | ||
} | ||
if (Utils._.includes(dataType, 'PRIMARY KEY') && needsMultiplePrimaryKeys) { | ||
@@ -130,2 +143,91 @@ primaryKeys.push(attr) | ||
bulkInsertQuery: function(tableName, attrValueHashes) { | ||
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;" | ||
, tuples = [] | ||
Utils._.forEach(attrValueHashes, function(attrValueHash) { | ||
tuples.push("(" + | ||
Utils._.values(attrValueHash).map(function(value){ | ||
return escape((value instanceof Date) ? Utils.toSqlDate(value) : value) | ||
}).join(",") + | ||
")") | ||
}) | ||
var replacements = { | ||
table: Utils.addTicks(tableName), | ||
attributes: Object.keys(attrValueHashes[0]).map(function(attr){return Utils.addTicks(attr)}).join(","), | ||
tuples: tuples | ||
} | ||
return Utils._.template(query)(replacements) | ||
}, | ||
selectQuery: function(tableName, options) { | ||
var table = null, | ||
joinQuery = "" | ||
options = options || {} | ||
options.table = table = Array.isArray(tableName) ? tableName.map(function(tbl){ return QueryGenerator.addQuotes(tbl) }).join(", ") : QueryGenerator.addQuotes(tableName) | ||
options.attributes = options.attributes && options.attributes.map(function(attr){ | ||
if(Array.isArray(attr) && attr.length == 2) { | ||
return [attr[0], QueryGenerator.addQuotes(attr[1])].join(' as ') | ||
} else { | ||
return attr.indexOf(Utils.TICK_CHAR) < 0 ? QueryGenerator.addQuotes(attr) : attr | ||
} | ||
}).join(", ") | ||
options.attributes = options.attributes || '*' | ||
if (options.include) { | ||
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes] | ||
options.include.forEach(function(include) { | ||
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) { | ||
return "`" + include.as + "`.`" + attr + "` AS `" + include.as + "." + attr + "`" | ||
}) | ||
optAttributes = optAttributes.concat(attributes) | ||
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 | ||
joinQuery += " LEFT OUTER JOIN `" + table + "` AS `" + as + "` ON `" + tableLeft + "`.`" + attrLeft + "` = `" + tableRight + "`.`" + attrRight + "`" | ||
}) | ||
options.attributes = optAttributes.join(', ') | ||
} | ||
var query = "SELECT " + options.attributes + " FROM " + options.table | ||
query += joinQuery | ||
if (options.hasOwnProperty('where')) { | ||
options.where = this.getWhereConditions(options.where, tableName) | ||
query += " WHERE " + options.where | ||
} | ||
if (options.group) { | ||
options.group = Array.isArray(options.group) ? options.group.map(function(grp){return QueryGenerator.addQuotes(grp)}).join(', ') : QueryGenerator.addQuotes(options.group) | ||
query += " GROUP BY " + options.group | ||
} | ||
if (options.order) { | ||
query += " ORDER BY " + options.order | ||
} | ||
if (options.limit && !(options.include && (options.limit === 1))) { | ||
if (options.offset) { | ||
query += " LIMIT " + options.offset + ", " + options.limit | ||
} else { | ||
query += " LIMIT " + options.limit | ||
} | ||
} | ||
query += ";" | ||
return query | ||
}, | ||
updateQuery: function(tableName, attrValueHash, where) { | ||
@@ -157,4 +259,3 @@ attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) | ||
table: Utils.addTicks(tableName), | ||
where: this.getWhereConditions(where), | ||
limit: Utils.escape(options.limit) | ||
where: MySqlQueryGenerator.getWhereConditions(where) | ||
} | ||
@@ -224,2 +325,24 @@ | ||
if(dataType.references) { | ||
template += " REFERENCES <%= referencesTable %> (<%= referencesKey %>)" | ||
replacements.referencesTable = Utils.addTicks(dataType.references) | ||
if(dataType.referencesKey) { | ||
replacements.referencesKey = Utils.addTicks(dataType.referencesKey) | ||
} else { | ||
replacements.referencesKey = Utils.addTicks('id') | ||
} | ||
if(dataType.onDelete) { | ||
template += " ON DELETE <%= onDeleteAction %>" | ||
replacements.onDeleteAction = dataType.onDelete.toUpperCase() | ||
} | ||
if(dataType.onUpdate) { | ||
template += " ON UPDATE <%= onUpdateAction %>" | ||
replacements.onUpdateAction = dataType.onUpdate.toUpperCase() | ||
} | ||
} | ||
result[name] = Utils._.template(template)(replacements) | ||
@@ -250,2 +373,12 @@ } else { | ||
enableForeignKeyConstraintsQuery: function() { | ||
var sql = "PRAGMA foreign_keys = ON;" | ||
return Utils._.template(sql, {}) | ||
}, | ||
disableForeignKeyConstraintsQuery: function() { | ||
var sql = "PRAGMA foreign_keys = OFF;" | ||
return Utils._.template(sql, {}) | ||
}, | ||
hashToWhereConditions: function(hash) { | ||
@@ -252,0 +385,0 @@ for (var key in hash) { |
var util = require("util") | ||
, EventEmitter = require("events").EventEmitter | ||
, Promise = require("promise") | ||
, proxyEventKeys = ['success', 'error', 'sql'] | ||
var bindToProcess = function(fct) { | ||
if (fct) { | ||
if(process.domain) { | ||
return process.domain.bind(fct); | ||
} | ||
} | ||
return fct; | ||
}; | ||
module.exports = (function() { | ||
var CustomEventEmitter = function(fct) { | ||
this.fct = fct | ||
this.fct = bindToProcess(fct); | ||
} | ||
@@ -17,3 +29,3 @@ util.inherits(CustomEventEmitter, EventEmitter) | ||
}.bind(this)) | ||
return this | ||
@@ -25,3 +37,3 @@ } | ||
function(fct) { | ||
this.on('success', fct) | ||
this.on('success', bindToProcess(fct)) | ||
return this | ||
@@ -34,3 +46,3 @@ } | ||
function(fct) { | ||
this.on('error', fct) | ||
this.on('error', bindToProcess(fct)) | ||
return this; | ||
@@ -42,2 +54,3 @@ } | ||
function(fct) { | ||
fct = bindToProcess(fct); | ||
this.on('error', function(err) { fct(err, null) }) | ||
@@ -56,4 +69,16 @@ .on('success', function(result) { fct(null, result) }) | ||
CustomEventEmitter.prototype.then = | ||
function (onFulfilled, onRejected) { | ||
var self = this | ||
onFulfilled = bindToProcess(onFulfilled) | ||
onRejected = bindToProcess(onRejected) | ||
return new Promise(function (resolve, reject) { | ||
self.on('error', reject) | ||
.on('success', resolve); | ||
}).then(onFulfilled, onRejected) | ||
} | ||
return CustomEventEmitter; | ||
})() | ||
@@ -56,12 +56,22 @@ const fs = require("fs") | ||
if (migrations.length === 0) { | ||
self.options.logging("There are no pending migrations.") | ||
} else { | ||
self.options.logging("Running migrations...") | ||
} | ||
migrations.forEach(function(migration) { | ||
var migrationTime | ||
chainer.add(migration, 'execute', [options], { | ||
before: function(migration) { | ||
if (self.options.logging !== false) { | ||
self.options.logging('Executing migration: ' + migration.filename) | ||
self.options.logging(migration.filename) | ||
} | ||
migrationTime = process.hrtime() | ||
}, | ||
after: function(migration) { | ||
migrationTime = process.hrtime(migrationTime) | ||
migrationTime = Math.round( (migrationTime[0] * 1000) + (migrationTime[1] / 1000000)); | ||
if (self.options.logging !== false) { | ||
self.options.logging('Executed migration: ' + migration.filename) | ||
self.options.logging('Completed in ' + migrationTime + 'ms') | ||
} | ||
@@ -68,0 +78,0 @@ }, |
@@ -82,4 +82,4 @@ var Utils = require('./utils') | ||
QueryInterface.prototype.dropTable = function(tableName) { | ||
var sql = this.QueryGenerator.dropTableQuery(tableName) | ||
QueryInterface.prototype.dropTable = function(tableName, options) { | ||
var sql = this.QueryGenerator.dropTableQuery(tableName, options) | ||
return queryAndEmit.call(this, sql, 'dropTable') | ||
@@ -95,7 +95,13 @@ } | ||
self.showAllTables().success(function(tableNames) { | ||
chainer.add(self, 'disableForeignKeyConstraints', []) | ||
tableNames.forEach(function(tableName) { | ||
chainer.add(self.dropTable(tableName)) | ||
chainer.add(self, 'dropTable', [tableName, {cascade: true}]) | ||
}) | ||
chainer.add(self, 'enableForeignKeyConstraints', []) | ||
chainer | ||
.run() | ||
.runSerially() | ||
.success(function() { | ||
@@ -259,2 +265,7 @@ self.emit('dropAllTables', null) | ||
QueryInterface.prototype.bulkInsert = function(tableName, records) { | ||
var sql = this.QueryGenerator.bulkInsertQuery(tableName, records) | ||
return queryAndEmit.call(this, sql, 'bulkInsert') | ||
} | ||
QueryInterface.prototype.update = function(dao, tableName, values, identifier) { | ||
@@ -265,2 +276,7 @@ var sql = this.QueryGenerator.updateQuery(tableName, values, identifier) | ||
QueryInterface.prototype.bulkUpdate = function(tableName, values, identifier) { | ||
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier) | ||
return queryAndEmit.call(this, sql, 'bulkUpdate') | ||
} | ||
QueryInterface.prototype.delete = function(dao, tableName, identifier) { | ||
@@ -271,2 +287,7 @@ var sql = this.QueryGenerator.deleteQuery(tableName, identifier) | ||
QueryInterface.prototype.bulkDelete = function(tableName, identifier) { | ||
var sql = this.QueryGenerator.deleteQuery(tableName, identifier, {limit: null}) | ||
return queryAndEmit.call(this, sql, 'bulkDelete') | ||
} | ||
QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) { | ||
@@ -304,2 +325,6 @@ options = options || {} | ||
if (options && options.parseFloat) { | ||
result = parseFloat(result) | ||
} | ||
self.emit('rawSelect', null) | ||
@@ -318,2 +343,26 @@ emitter.emit('success', result) | ||
QueryInterface.prototype.enableForeignKeyConstraints = function() { | ||
var sql = this.QueryGenerator.enableForeignKeyConstraintsQuery() | ||
if(sql) { | ||
return queryAndEmit.call(this, sql, 'enableForeignKeyConstraints') | ||
} else { | ||
return new Utils.CustomEventEmitter(function(emitter) { | ||
this.emit('enableForeignKeyConstraints', null) | ||
emitter.emit('success') | ||
}).run() | ||
} | ||
} | ||
QueryInterface.prototype.disableForeignKeyConstraints = function() { | ||
var sql = this.QueryGenerator.disableForeignKeyConstraintsQuery() | ||
if(sql){ | ||
return queryAndEmit.call(this, sql, 'disableForeignKeyConstraints') | ||
} else { | ||
return new Utils.CustomEventEmitter(function(emitter) { | ||
this.emit('disableForeignKeyConstraints', null) | ||
emitter.emit('success') | ||
}).run() | ||
} | ||
} | ||
// private | ||
@@ -320,0 +369,0 @@ |
@@ -50,3 +50,4 @@ var url = require("url") | ||
if (arguments.length === 1) { | ||
if (arguments.length === 1 || (arguments.length === 2 && typeof username === 'object')) { | ||
options = username || {} | ||
urlParts = url.parse(arguments[0]) | ||
@@ -206,3 +207,3 @@ database = urlParts.path.replace(/^\//, '') | ||
if (arguments.length === 4) { | ||
sql = Utils.format([sql].concat(replacements)) | ||
sql = Utils.format([sql].concat(replacements), this.options.dialect) | ||
} else if (arguments.length === 3) { | ||
@@ -266,7 +267,10 @@ options = options | ||
this.daoFactoryManager.daos.forEach(function(dao) { | ||
chainer.add(dao.sync(options)) | ||
// Topologically sort by foreign key constraints to give us an appropriate | ||
// creation order | ||
this.daoFactoryManager.forEachDAO(function(dao) { | ||
chainer.add(dao, 'sync', [options]) | ||
}) | ||
return chainer.run() | ||
return chainer.runSerially() | ||
} | ||
@@ -273,0 +277,0 @@ |
@@ -10,3 +10,3 @@ var SqlString = exports; | ||
SqlString.escape = function(val, stringifyObjects, timeZone) { | ||
SqlString.escape = function(val, stringifyObjects, timeZone, dialect) { | ||
if (val === undefined || val === null) { | ||
@@ -41,13 +41,18 @@ return 'NULL'; | ||
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { | ||
switch(s) { | ||
case "\0": return "\\0"; | ||
case "\n": return "\\n"; | ||
case "\r": return "\\r"; | ||
case "\b": return "\\b"; | ||
case "\t": return "\\t"; | ||
case "\x1a": return "\\Z"; | ||
default: return "\\"+s; | ||
} | ||
}); | ||
if (dialect == "postgres") { | ||
// http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS | ||
val = val.replace(/'/g, "''"); | ||
} else { | ||
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) { | ||
switch(s) { | ||
case "\0": return "\\0"; | ||
case "\n": return "\\n"; | ||
case "\r": return "\\r"; | ||
case "\b": return "\\b"; | ||
case "\t": return "\\t"; | ||
case "\x1a": return "\\Z"; | ||
default: return "\\"+s; | ||
} | ||
}); | ||
} | ||
return "'"+val+"'"; | ||
@@ -63,3 +68,3 @@ }; | ||
SqlString.format = function(sql, values, timeZone) { | ||
SqlString.format = function(sql, values, timeZone, dialect) { | ||
values = [].concat(values); | ||
@@ -72,3 +77,3 @@ | ||
return SqlString.escape(values.shift(), false, timeZone); | ||
return SqlString.escape(values.shift(), false, timeZone, dialect); | ||
}); | ||
@@ -75,0 +80,0 @@ }; |
@@ -7,3 +7,3 @@ var util = require("util") | ||
_: (function() { | ||
var _ = require("underscore") | ||
var _ = require("lodash") | ||
, _s = require('underscore.string') | ||
@@ -51,4 +51,5 @@ | ||
}, | ||
format: function(arr) { | ||
return SqlString.format(arr.shift(), arr) | ||
format: function(arr, dialect) { | ||
var timeZone = null; | ||
return SqlString.format(arr.shift(), arr, timeZone, dialect) | ||
}, | ||
@@ -55,0 +56,0 @@ isHash: function(obj) { |
{ | ||
"name": "sequelize", | ||
"description": "Multi dialect ORM for Node.JS", | ||
"version": "1.7.0-alpha1", | ||
"version": "1.7.0-alpha2", | ||
"author": "Sascha Depold <sascha@depold.com>", | ||
@@ -26,16 +26,18 @@ "contributors": [ | ||
"type": "git", | ||
"url": "https://github.com/sdepold/sequelize.git" | ||
"url": "https://github.com/sequelize/sequelize.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/sdepold/sequelize/issues" | ||
"url": "https://github.com/sequelize/sequelize/issues" | ||
}, | ||
"dependencies": { | ||
"underscore": "~1.4.0", | ||
"lodash": "~1.2.1", | ||
"underscore.string": "~2.3.0", | ||
"lingo": "~0.0.5", | ||
"validator": "0.4.x", | ||
"validator": "1.1.1", | ||
"moment": "~1.7.0", | ||
"commander": "~0.6.0", | ||
"generic-pool": "1.0.9", | ||
"dottie": "0.0.6-1" | ||
"dottie": "0.0.6-1", | ||
"toposort-class": "0.1.4", | ||
"generic-pool": "2.0.3", | ||
"promise": "~3.0.0" | ||
}, | ||
@@ -47,3 +49,3 @@ "devDependencies": { | ||
"pg": "~0.10.2", | ||
"buster": "~0.6.0", | ||
"buster": "~0.6.3", | ||
"watchr": "~2.2.0", | ||
@@ -50,0 +52,0 @@ "yuidocjs": "~0.3.36" |
@@ -33,3 +33,3 @@ # Sequelize # | ||
## Documentation, Examples and Updates ## | ||
## Documentation and Updates ## | ||
@@ -42,6 +42,11 @@ You can find the documentation and announcements of updates on the [project's website](http://www.sequelizejs.com). | ||
- [Twitter](http://twitter.com/sdepold) | ||
- [IRC](irc://irc.freenode.net/sequelizejs) | ||
- [IRC](http://webchat.freenode.net?channels=sequelizejs) | ||
- [Google Groups](https://groups.google.com/forum/#!forum/sequelize) | ||
- [XING](https://www.xing.com/net/priec1b5cx/sequelize) (pretty much inactive, but you might want to name it on your profile) | ||
## Running Examples | ||
Instructions for running samples are located in the [example directory](https://github.com/sequelize/sequelize/tree/master/examples). Try these samples in a live sandbox environment: | ||
<a href="https://runnable.com/sequelize" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png"></a> | ||
## Roadmap | ||
@@ -51,17 +56,14 @@ | ||
### 1.6.0 (ToDo) | ||
- ~~Fix last issues with eager loading of associated data~~ | ||
- ~~Find out why Person.belongsTo(House) would add person_id to house. It should add house_id to person~~ | ||
### 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 | ||
- Support for update of tables without primary key | ||
- MariaDB support | ||
- Support for update and delete calls for whole tables without previous loading of instances | ||
- ~~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 | ||
- Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099) | ||
- Model#delete | ||
- Validate a model before it gets saved. (Move validation of enum attribute value to validate method) | ||
- BLOB [#99](https://github.com/sdepold/sequelize/issues/99) | ||
- Support for foreign keys | ||
- ~~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) | ||
- ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude | ||
@@ -79,3 +81,8 @@ ### 1.7.x | ||
- ~~save datetimes in UTC~~ | ||
- encapsulate attributes if a dao inside the attributes property + add getters and setters | ||
- encapsulate attributes if a dao inside the attributes property | ||
- ~~add getters and setters for dao~~ Implemented in [#538](https://github.com/sequelize/sequelize/pull/538), thanks to iamjochem | ||
- add proper error message everywhere | ||
- refactor validate() output data structure, separating field-specific errors | ||
from general model validator errors (i.e. | ||
`{fields: {field1: ['field1error1']}, model: ['modelError1']}` or similar) | ||
@@ -82,0 +89,0 @@ |
@@ -93,21 +93,2 @@ var config = require("./config/config") | ||
it('marks the database entry as deleted if dao is paranoid', function() { | ||
Helpers.async(function(done) { | ||
User = sequelize.define('User', { | ||
name: Sequelize.STRING, bio: Sequelize.TEXT | ||
}, { paranoid:true }) | ||
User.sync({ force: true }).success(done) | ||
}) | ||
Helpers.async(function(done) { | ||
User.create({ name: 'asd', bio: 'asd' }).success(function(u) { | ||
expect(u.deletedAt).toBeNull() | ||
u.destroy().success(function(u) { | ||
expect(u.deletedAt).toBeTruthy() | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
it('allows sql logging of update statements', function() { | ||
@@ -114,0 +95,0 @@ Helpers.async(function(done) { |
@@ -13,2 +13,58 @@ var config = require("../config/config") | ||
var suites = { | ||
attributesToSQL: [ | ||
{ | ||
arguments: [{id: 'INTEGER'}], | ||
expectation: {id: 'INTEGER'} | ||
}, | ||
{ | ||
arguments: [{id: 'INTEGER', foo: 'VARCHAR(255)'}], | ||
expectation: {id: 'INTEGER', foo: 'VARCHAR(255)'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER'}}], | ||
expectation: {id: 'INTEGER'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', allowNull: false}}], | ||
expectation: {id: 'INTEGER NOT NULL'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', allowNull: true}}], | ||
expectation: {id: 'INTEGER'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', primaryKey: true, autoIncrement: true}}], | ||
expectation: {id: 'INTEGER auto_increment PRIMARY KEY'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', defaultValue: 0}}], | ||
expectation: {id: 'INTEGER DEFAULT 0'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', unique: true}}], | ||
expectation: {id: 'INTEGER UNIQUE'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar'}}], | ||
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`)'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}], | ||
expectation: {id: 'INTEGER REFERENCES `Bar` (`pk`)'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}], | ||
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}], | ||
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', allowNull: false, autoIncrement: true, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}], | ||
expectation: {id: 'INTEGER NOT NULL auto_increment DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT'} | ||
}, | ||
], | ||
createTableQuery: [ | ||
@@ -30,2 +86,10 @@ { | ||
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM(\"A\", \"B\", \"C\"), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;" | ||
}, | ||
{ | ||
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}], | ||
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `id` INTEGER , PRIMARY KEY (`id`)) ENGINE=InnoDB;" | ||
}, | ||
{ | ||
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION'}], | ||
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `otherId` INTEGER, FOREIGN KEY (`otherId`) REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION) ENGINE=InnoDB;" | ||
} | ||
@@ -107,2 +171,22 @@ ], | ||
context: QueryGenerator | ||
}, { | ||
title: 'multiple where arguments', | ||
arguments: ['myTable', {where: {boat: 'canoe', weather: 'cold'}}], | ||
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`boat`='canoe' AND `myTable`.`weather`='cold';", | ||
context: QueryGenerator | ||
}, { | ||
title: 'no where arguments (object)', | ||
arguments: ['myTable', {where: {}}], | ||
expectation: "SELECT * FROM `myTable` WHERE 1=1;", | ||
context: QueryGenerator | ||
}, { | ||
title: 'no where arguments (string)', | ||
arguments: ['myTable', {where: ''}], | ||
expectation: "SELECT * FROM `myTable` WHERE 1=1;", | ||
context: QueryGenerator | ||
}, { | ||
title: 'no where arguments (null)', | ||
arguments: ['myTable', {where: null}], | ||
expectation: "SELECT * FROM `myTable` WHERE 1=1;", | ||
context: QueryGenerator | ||
} | ||
@@ -139,5 +223,45 @@ ], | ||
context: {options: {omitNull: true}} | ||
}, { | ||
arguments: ['myTable', {foo: false}], | ||
expectation: "INSERT INTO `myTable` (`foo`) VALUES (0);" | ||
}, { | ||
arguments: ['myTable', {foo: true}], | ||
expectation: "INSERT INTO `myTable` (`foo`) VALUES (1);" | ||
} | ||
], | ||
bulkInsertQuery: [ | ||
{ | ||
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]], | ||
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');" | ||
}, { | ||
arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]], | ||
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;'),('bar');" | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55))}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55'),('bar','2012-03-27 10:01:55');" | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1),('bar',2);" | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', nullValue: null}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',NULL);" | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", | ||
context: {options: {omitNull: false}} | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", | ||
context: {options: {omitNull: true}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: undefined}, {name: 'bar', foo: 2, undefinedValue: undefined}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", | ||
context: {options: {omitNull: true}} // Note: As above | ||
}, { | ||
arguments: ['myTable', [{name: "foo", value: true}, {name: 'bar', value: false}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',1),('bar',0);" | ||
} | ||
], | ||
updateQuery: [ | ||
@@ -167,2 +291,8 @@ { | ||
context: {options: {omitNull: true}} | ||
}, { | ||
arguments: ['myTable', {bar: false}, {name: 'foo'}], | ||
expectation: "UPDATE `myTable` SET `bar`=0 WHERE `name`='foo'" | ||
}, { | ||
arguments: ['myTable', {bar: true}, {name: 'foo'}], | ||
expectation: "UPDATE `myTable` SET `bar`=1 WHERE `name`='foo'" | ||
} | ||
@@ -184,2 +314,5 @@ ], | ||
expectation: "DELETE FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;' LIMIT 10" | ||
}, { | ||
arguments: ['myTable', {name: 'foo'}, {limit: null}], | ||
expectation: "DELETE FROM `myTable` WHERE `name`='foo'" | ||
} | ||
@@ -236,2 +369,23 @@ ], | ||
expectation: "`id` IN (NULL)" | ||
}, | ||
{ | ||
arguments: [{ maple: false, bacon: true }], | ||
expectation: "`maple`=0 AND `bacon`=1" | ||
}, | ||
{ | ||
arguments: [{ beaver: [false, true] }], | ||
expectation: "`beaver` IN (0,1)" | ||
}, | ||
{ | ||
arguments: [{birthday: new Date(Date.UTC(2011, 6, 1, 10, 1, 55))}], | ||
expectation: "`birthday`='2011-07-01 10:01:55'" | ||
}, | ||
{ | ||
arguments: [{ birthday: new Date(Date.UTC(2011, 6, 1, 10, 1, 55)), | ||
otherday: new Date(Date.UTC(2013, 6, 2, 10, 1, 22)) }], | ||
expectation: "`birthday`='2011-07-01 10:01:55' AND `otherday`='2013-07-02 10:01:22'" | ||
}, | ||
{ | ||
arguments: [{ birthday: [new Date(Date.UTC(2011, 6, 1, 10, 1, 55)), new Date(Date.UTC(2013, 6, 2, 10, 1, 22))] }], | ||
expectation: "`birthday` IN ('2011-07-01 10:01:55','2013-07-02 10:01:22')" | ||
} | ||
@@ -238,0 +392,0 @@ ] |
@@ -17,2 +17,58 @@ var config = require("../config/config") | ||
var suites = { | ||
attributesToSQL: [ | ||
{ | ||
arguments: [{id: 'INTEGER'}], | ||
expectation: {id: 'INTEGER'} | ||
}, | ||
{ | ||
arguments: [{id: 'INTEGER', foo: 'VARCHAR(255)'}], | ||
expectation: {id: 'INTEGER', foo: 'VARCHAR(255)'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER'}}], | ||
expectation: {id: 'INTEGER'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', allowNull: false}}], | ||
expectation: {id: 'INTEGER NOT NULL'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', allowNull: true}}], | ||
expectation: {id: 'INTEGER'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', primaryKey: true, autoIncrement: true}}], | ||
expectation: {id: 'INTEGER SERIAL PRIMARY KEY'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', defaultValue: 0}}], | ||
expectation: {id: 'INTEGER DEFAULT 0'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', unique: true}}], | ||
expectation: {id: 'INTEGER UNIQUE'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar'}}], | ||
expectation: {id: 'INTEGER REFERENCES "Bar" ("id")'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}], | ||
expectation: {id: 'INTEGER REFERENCES "Bar" ("pk")'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}], | ||
expectation: {id: 'INTEGER REFERENCES "Bar" ("id") ON DELETE CASCADE'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}], | ||
expectation: {id: 'INTEGER REFERENCES "Bar" ("id") ON UPDATE RESTRICT'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', allowNull: false, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}], | ||
expectation: {id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES "Bar" ("id") ON DELETE CASCADE ON UPDATE RESTRICT'} | ||
}, | ||
], | ||
createTableQuery: [ | ||
@@ -30,2 +86,10 @@ { | ||
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));" | ||
}, | ||
{ | ||
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}], | ||
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255), \"id\" INTEGER , PRIMARY KEY (\"id\"));" | ||
}, | ||
{ | ||
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES "otherTable" ("id") ON DELETE CASCADE ON UPDATE NO ACTION'}], | ||
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255), \"otherId\" INTEGER REFERENCES \"otherTable\" (\"id\") ON DELETE CASCADE ON UPDATE NO ACTION);" | ||
} | ||
@@ -42,2 +106,10 @@ ], | ||
expectation: "DROP TABLE IF EXISTS \"mySchema\".\"myTable\";" | ||
}, | ||
{ | ||
arguments: ['myTable', {cascade: true}], | ||
expectation: "DROP TABLE IF EXISTS \"myTable\" CASCADE;" | ||
}, | ||
{ | ||
arguments: ['mySchema.myTable', {cascade: true}], | ||
expectation: "DROP TABLE IF EXISTS \"mySchema\".\"myTable\" CASCADE;" | ||
} | ||
@@ -55,12 +127,12 @@ ], | ||
arguments: ['myTable', {where: {id: 2}}], | ||
expectation: "SELECT * FROM \"myTable\" WHERE \"id\"=2;" | ||
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"id\"=2;" | ||
}, { | ||
arguments: ['myTable', {where: {name: 'foo'}}], | ||
expectation: "SELECT * FROM \"myTable\" WHERE \"name\"='foo';" | ||
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\"='foo';" | ||
}, { | ||
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}], | ||
expectation: "SELECT * FROM \"myTable\" WHERE \"name\"='foo'';DROP TABLE myTable;';" | ||
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\"='foo'';DROP TABLE myTable;';" | ||
}, { | ||
arguments: ['myTable', {where: 2}], | ||
expectation: "SELECT * FROM \"myTable\" WHERE \"id\"=2;" | ||
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"id\"=2;" | ||
}, { | ||
@@ -99,3 +171,3 @@ arguments: ['foo', { attributes: [['count(*)', 'count']] }], | ||
arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}], | ||
expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo'';DROP TABLE mySchema.myTable;';" | ||
expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"mySchema\".\"myTable\".\"name\"='foo'';DROP TABLE mySchema.myTable;';" | ||
} | ||
@@ -144,2 +216,42 @@ ], | ||
bulkInsertQuery: [ | ||
{ | ||
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]], | ||
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING *;" | ||
}, { | ||
arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]], | ||
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar') RETURNING *;" | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55))}]], | ||
expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.0Z'),('bar','2012-03-27 10:01:55.0Z') RETURNING *;" | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]], | ||
expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1),('bar',2) RETURNING *;" | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], | ||
expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;" | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], | ||
expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;", | ||
context: {options: {omitNull: false}} | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], | ||
expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;", | ||
context: {options: {omitNull: true}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', nullValue: undefined}, {name: 'bar', nullValue: undefined}]], | ||
expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;", | ||
context: {options: {omitNull: true}} // Note: As above | ||
}, { | ||
arguments: ['mySchema.myTable', [{name: 'foo'}, {name: 'bar'}]], | ||
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING *;" | ||
}, { | ||
arguments: ['mySchema.myTable', [{name: JSON.stringify({info: 'Look ma a " quote'})}, {name: JSON.stringify({info: 'Look ma another " quote'})}]], | ||
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('{\"info\":\"Look ma a \\\" quote\"}'),('{\"info\":\"Look ma another \\\" quote\"}') RETURNING *;" | ||
}, { | ||
arguments: ['mySchema.myTable', [{name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'bar'}]], | ||
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar') RETURNING *;" | ||
} | ||
], | ||
updateQuery: [ | ||
@@ -201,2 +313,5 @@ { | ||
expectation: "DELETE FROM \"mySchema\".\"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo'';DROP TABLE mySchema.myTable;' LIMIT 10)" | ||
}, { | ||
arguments: ['myTable', {name: 'foo'}, {limit: null}], | ||
expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"name\"='foo')" | ||
} | ||
@@ -203,0 +318,0 @@ ], |
@@ -12,2 +12,77 @@ var Sequelize = require("../../index") | ||
var suites = { | ||
attributesToSQL: [ | ||
{ | ||
arguments: [{id: 'INTEGER'}], | ||
expectation: {id: 'INTEGER'} | ||
}, | ||
{ | ||
arguments: [{id: 'INTEGER', foo: 'VARCHAR(255)'}], | ||
expectation: {id: 'INTEGER', foo: 'VARCHAR(255)'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER'}}], | ||
expectation: {id: 'INTEGER'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', allowNull: false}}], | ||
expectation: {id: 'INTEGER NOT NULL'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', allowNull: true}}], | ||
expectation: {id: 'INTEGER'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', primaryKey: true, autoIncrement: true}}], | ||
expectation: {id: 'INTEGER PRIMARY KEY AUTOINCREMENT'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', defaultValue: 0}}], | ||
expectation: {id: 'INTEGER DEFAULT 0'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', unique: true}}], | ||
expectation: {id: 'INTEGER UNIQUE'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar'}}], | ||
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`)'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}], | ||
expectation: {id: 'INTEGER REFERENCES `Bar` (`pk`)'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}], | ||
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}], | ||
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT'} | ||
}, | ||
{ | ||
arguments: [{id: {type: 'INTEGER', allowNull: false, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}], | ||
expectation: {id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT'} | ||
}, | ||
], | ||
createTableQuery: [ | ||
{ | ||
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], | ||
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));" | ||
}, | ||
{ | ||
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}], | ||
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM(\"A\", \"B\", \"C\"), `name` VARCHAR(255));" | ||
}, | ||
{ | ||
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}], | ||
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `id` INTEGER PRIMARY KEY);" | ||
}, | ||
{ | ||
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION'}], | ||
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `otherId` INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION);" | ||
} | ||
], | ||
insertQuery: [ | ||
@@ -50,2 +125,39 @@ { | ||
bulkInsertQuery: [ | ||
{ | ||
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]], | ||
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');" | ||
}, { | ||
arguments: ['myTable', [{name: "'bar'"}, {name: 'foo'}]], | ||
expectation: "INSERT INTO `myTable` (`name`) VALUES ('''bar'''),('foo');" | ||
}, { | ||
arguments: ['myTable', [{name: "bar", value: null}, {name: 'foo', value: 1}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('foo',1);" | ||
}, { | ||
arguments: ['myTable', [{name: "bar", value: undefined}, {name: 'bar', value: 2}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('bar',2);" | ||
}, { | ||
arguments: ['myTable', [{name: "foo", value: true}, {name: 'bar', value: false}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',1),('bar',0);" | ||
}, { | ||
arguments: ['myTable', [{name: "foo", value: false}, {name: 'bar', value: false}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',0),('bar',0);" | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);" | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", | ||
context: {options: {omitNull: false}} | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", | ||
context: {options: {omitNull: true}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not | ||
}, { | ||
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], | ||
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", | ||
context: {options: {omitNull: true}} // Note: As above | ||
} | ||
], | ||
updateQuery: [ | ||
@@ -82,2 +194,21 @@ { | ||
} | ||
], | ||
deleteQuery: [ | ||
{ | ||
arguments: ['myTable', {name: 'foo'}], | ||
expectation: "DELETE FROM `myTable` WHERE `name`='foo'" | ||
}, { | ||
arguments: ['myTable', 1], | ||
expectation: "DELETE FROM `myTable` WHERE `id`=1" | ||
}, { | ||
arguments: ['myTable', 1, {limit: 10}], | ||
expectation: "DELETE FROM `myTable` WHERE `id`=1" | ||
}, { | ||
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}], | ||
expectation: "DELETE FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;'" | ||
}, { | ||
arguments: ['myTable', {name: 'foo'}, {limit: null}], | ||
expectation: "DELETE FROM `myTable` WHERE `name`='foo'" | ||
} | ||
] | ||
@@ -84,0 +215,0 @@ }; |
@@ -50,2 +50,134 @@ if (typeof require === 'function') { | ||
}) | ||
describe("Foreign key constraints", function() { | ||
it("are not enabled by default", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
Task.belongsTo(User) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
task.setUser(user).success(function() { | ||
user.destroy().success(function() { | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can cascade deletes", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
Task.belongsTo(User, {onDelete: 'cascade'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
task.setUser(user).success(function() { | ||
user.destroy().success(function() { | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(0) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can restrict deletes", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
Task.belongsTo(User, {onDelete: 'restrict'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
task.setUser(user).success(function() { | ||
user.destroy().error(function() { | ||
// Should fail due to FK restriction | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can cascade updates", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
Task.belongsTo(User, {onUpdate: 'cascade'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
task.setUser(user).success(function() { | ||
// Changing the id of a DAO requires a little dance since | ||
// the `UPDATE` query generated by `save()` uses `id` in the | ||
// `WHERE` clause | ||
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory) | ||
user.QueryInterface.update(user, tableName, {id: 999}, user.id) | ||
.success(function() { | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
expect(tasks[0].UserId).toEqual(999) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can restrict updates", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
Task.belongsTo(User, {onUpdate: 'restrict'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
task.setUser(user).success(function() { | ||
// Changing the id of a DAO requires a little dance since | ||
// the `UPDATE` query generated by `save()` uses `id` in the | ||
// `WHERE` clause | ||
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory) | ||
user.QueryInterface.update(user, tableName, {id: 999}, user.id) | ||
.error(function() { | ||
// Should fail due to FK restriction | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
@@ -291,3 +291,3 @@ if (typeof require === 'function') { | ||
this.stub(Sequelize.Utils, 'QueryChainer').returns({ add: add, run: function(){} }) | ||
this.stub(Sequelize.Utils, 'QueryChainer').returns({ add: add, runSerially: function(){} }) | ||
@@ -327,2 +327,133 @@ this.sequelize.sync({ force: true }) | ||
}) | ||
describe("Foreign key constraints", function() { | ||
it("are not enabled by default", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
User.hasMany(Task) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
user.setTasks([task]).success(function() { | ||
user.destroy().success(function() { | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can cascade deletes", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
User.hasMany(Task, {onDelete: 'cascade'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
user.setTasks([task]).success(function() { | ||
user.destroy().success(function() { | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(0) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can restrict deletes", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
User.hasMany(Task, {onDelete: 'restrict'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
user.setTasks([task]).success(function() { | ||
user.destroy().error(function() { | ||
// Should fail due to FK restriction | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can cascade updates", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
User.hasMany(Task, {onUpdate: 'cascade'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
user.setTasks([task]).success(function() { | ||
// Changing the id of a DAO requires a little dance since | ||
// the `UPDATE` query generated by `save()` uses `id` in the | ||
// `WHERE` clause | ||
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory) | ||
user.QueryInterface.update(user, tableName, {id: 999}, user.id) | ||
.success(function() { | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
expect(tasks[0].UserId).toEqual(999) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can restrict updates", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
User.hasMany(Task, {onUpdate: 'restrict'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
user.setTasks([task]).success(function() { | ||
// Changing the id of a DAO requires a little dance since | ||
// the `UPDATE` query generated by `save()` uses `id` in the | ||
// `WHERE` clause | ||
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory) | ||
user.QueryInterface.update(user, tableName, {id: 999}, user.id) | ||
.error(function() { | ||
// Should fail due to FK restriction | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
@@ -50,2 +50,134 @@ if (typeof require === 'function') { | ||
}) | ||
describe("Foreign key constraints", function() { | ||
it("are not enabled by default", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
User.hasOne(Task) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
user.setTask(task).success(function() { | ||
user.destroy().success(function() { | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can cascade deletes", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
User.hasOne(Task, {onDelete: 'cascade'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
user.setTask(task).success(function() { | ||
user.destroy().success(function() { | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(0) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can restrict deletes", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
User.hasOne(Task, {onDelete: 'restrict'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
user.setTask(task).success(function() { | ||
user.destroy().error(function() { | ||
// Should fail due to FK restriction | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can cascade updates", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
User.hasOne(Task, {onUpdate: 'cascade'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
user.setTask(task).success(function() { | ||
// Changing the id of a DAO requires a little dance since | ||
// the `UPDATE` query generated by `save()` uses `id` in the | ||
// `WHERE` clause | ||
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory) | ||
user.QueryInterface.update(user, tableName, {id: 999}, user.id) | ||
.success(function() { | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
expect(tasks[0].UserId).toEqual(999) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
it("can restrict updates", function(done) { | ||
var Task = this.sequelize.define('Task', { title: Sequelize.STRING }) | ||
, User = this.sequelize.define('User', { username: Sequelize.STRING }) | ||
User.hasOne(Task, {onUpdate: 'restrict'}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
User.create({ username: 'foo' }).success(function(user) { | ||
Task.create({ title: 'task' }).success(function(task) { | ||
user.setTask(task).success(function() { | ||
// Changing the id of a DAO requires a little dance since | ||
// the `UPDATE` query generated by `save()` uses `id` in the | ||
// `WHERE` clause | ||
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory) | ||
user.QueryInterface.update(user, tableName, {id: 999}, user.id) | ||
.error(function() { | ||
// Should fail due to FK restriction | ||
Task.findAll().success(function(tasks) { | ||
expect(tasks.length).toEqual(1) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
@@ -5,3 +5,3 @@ if(typeof require === 'function') { | ||
, Helpers = require('./buster-helpers') | ||
, _ = require('underscore') | ||
, _ = require('lodash') | ||
, dialect = Helpers.getTestDialect() | ||
@@ -74,2 +74,30 @@ } | ||
}) | ||
it('throws an error if a custom model-wide validation is not a function', function() { | ||
Helpers.assertException(function() { | ||
this.sequelize.define('Foo', { | ||
field: { | ||
type: Sequelize.INTEGER | ||
} | ||
}, { | ||
validate: { | ||
notFunction: 33 | ||
} | ||
}) | ||
}.bind(this), 'Members of the validate option must be functions. Model: Foo, error with validate member notFunction') | ||
}) | ||
it('throws an error if a custom model-wide validation has the same name as a field', function() { | ||
Helpers.assertException(function() { | ||
this.sequelize.define('Foo', { | ||
field: { | ||
type: Sequelize.INTEGER | ||
} | ||
}, { | ||
validate: { | ||
field: function() {} | ||
} | ||
}) | ||
}.bind(this), 'A model validator function must not have the same name as a field. Model: Foo, field/validation name: field') | ||
}) | ||
}) | ||
@@ -105,2 +133,78 @@ | ||
}) | ||
it("attaches getter and setter methods from attribute definition", function() { | ||
var Product = this.sequelize.define('ProductWithSettersAndGetters1', { | ||
price: { | ||
type: Sequelize.INTEGER, | ||
get : function() { | ||
return 'answer = ' + this.getDataValue('price'); | ||
}, | ||
set : function(v) { | ||
return this.setDataValue('price', v + 42); | ||
} | ||
} | ||
},{ | ||
}); | ||
expect(Product.build({price: 42}).price).toEqual('answer = 84'); | ||
var p = Product.build({price: 1}); | ||
expect(p.price).toEqual('answer = 43'); | ||
p.price = 0; | ||
expect(p.price).toEqual('answer = 42'); // ah finally the right answer :-) | ||
}) | ||
it("attaches getter and setter methods from options", function() { | ||
var Product = this.sequelize.define('ProductWithSettersAndGetters2', { | ||
priceInCents: { | ||
type: Sequelize.INTEGER | ||
} | ||
},{ | ||
setterMethods: { | ||
price: function(value) { | ||
this.dataValues.priceInCents = value * 100; | ||
} | ||
}, | ||
getterMethods: { | ||
price: function() { | ||
return '$' + (this.getDataValue('priceInCents') / 100); | ||
}, | ||
priceInCents: function() { | ||
return this.dataValues.priceInCents; | ||
} | ||
} | ||
}); | ||
expect(Product.build({price: 20}).priceInCents).toEqual(20 * 100); | ||
expect(Product.build({priceInCents: 30 * 100}).price).toEqual('$' + 30); | ||
}) | ||
it("attaches getter and setter methods from options only if not defined in attribute", function() { | ||
var Product = this.sequelize.define('ProductWithSettersAndGetters3', { | ||
price1: { | ||
type: Sequelize.INTEGER, | ||
set : function(v) { this.setDataValue('price1', v * 10); } | ||
}, | ||
price2: { | ||
type: Sequelize.INTEGER, | ||
get : function(v) { return this.getDataValue('price2') * 10; } | ||
} | ||
},{ | ||
setterMethods: { | ||
price1: function(v) { this.setDataValue('price1', v * 100); } | ||
}, | ||
getterMethods: { | ||
price2: function() { return '$' + this.getDataValue('price2'); } | ||
} | ||
}); | ||
var p = Product.build({ price1: 1, price2: 2 }); | ||
expect(p.price1).toEqual(10); | ||
expect(p.price2).toEqual(20); | ||
}) | ||
}) | ||
@@ -393,2 +497,272 @@ | ||
describe('bulkCreate', function() { | ||
it('inserts multiple values respecting the white list', function(done) { | ||
var self = this | ||
, data = [{ username: 'Peter', secretValue: '42' }, | ||
{ username: 'Paul', secretValue: '23'}] | ||
this.User.bulkCreate(data, ['username']).success(function() { | ||
self.User.findAll({order: 'id'}).success(function(users) { | ||
expect(users.length).toEqual(2) | ||
expect(users[0].username).toEqual("Peter") | ||
expect(users[0].secretValue).toBeNull(); | ||
expect(users[1].username).toEqual("Paul") | ||
expect(users[1].secretValue).toBeNull(); | ||
done() | ||
}) | ||
}) | ||
}) | ||
it('should store all values if no whitelist is specified', function(done) { | ||
var self = this | ||
, data = [{ username: 'Peter', secretValue: '42' }, | ||
{ username: 'Paul', secretValue: '23'}] | ||
this.User.bulkCreate(data).success(function() { | ||
self.User.findAll({order: 'id'}).success(function(users) { | ||
expect(users.length).toEqual(2) | ||
expect(users[0].username).toEqual("Peter") | ||
expect(users[0].secretValue).toEqual('42') | ||
expect(users[1].username).toEqual("Paul") | ||
expect(users[1].secretValue).toEqual('23') | ||
done() | ||
}) | ||
}) | ||
}) | ||
it('saves data with single quote', function(done) { | ||
var self = this | ||
, quote = "Single'Quote" | ||
, data = [{ username: 'Peter', data: quote}, | ||
{ username: 'Paul', data: quote}] | ||
this.User.bulkCreate(data).success(function() { | ||
self.User.findAll({order: 'id'}).success(function(users) { | ||
expect(users.length).toEqual(2) | ||
expect(users[0].username).toEqual("Peter") | ||
expect(users[0].data).toEqual(quote) | ||
expect(users[1].username).toEqual("Paul") | ||
expect(users[1].data).toEqual(quote) | ||
done() | ||
}) | ||
}) | ||
}) | ||
it('saves data with double quote', function(done) { | ||
var self = this | ||
, quote = 'Double"Quote' | ||
, data = [{ username: 'Peter', data: quote}, | ||
{ username: 'Paul', data: quote}] | ||
this.User.bulkCreate(data).success(function() { | ||
self.User.findAll({order: 'id'}).success(function(users) { | ||
expect(users.length).toEqual(2) | ||
expect(users[0].username).toEqual("Peter") | ||
expect(users[0].data).toEqual(quote) | ||
expect(users[1].username).toEqual("Paul") | ||
expect(users[1].data).toEqual(quote) | ||
done() | ||
}) | ||
}) | ||
}) | ||
it('saves stringified JSON data', function(done) { | ||
var self = this | ||
, json = JSON.stringify({ key: 'value' }) | ||
, data = [{ username: 'Peter', data: json}, | ||
{ username: 'Paul', data: json}] | ||
this.User.bulkCreate(data).success(function() { | ||
self.User.findAll({order: 'id'}).success(function(users) { | ||
expect(users.length).toEqual(2) | ||
expect(users[0].username).toEqual("Peter") | ||
expect(users[0].data).toEqual(json) | ||
expect(users[1].username).toEqual("Paul") | ||
expect(users[1].data).toEqual(json) | ||
done() | ||
}) | ||
}) | ||
}) | ||
it('stores the current date in createdAt', function(done) { | ||
var self = this | ||
, data = [{ username: 'Peter'}, | ||
{ username: 'Paul'}] | ||
this.User.bulkCreate(data).success(function() { | ||
self.User.findAll({order: 'id'}).success(function(users) { | ||
expect(users.length).toEqual(2) | ||
expect(users[0].username).toEqual("Peter") | ||
expect(parseInt(+users[0].createdAt/5000)).toEqual(parseInt(+new Date()/5000)) | ||
expect(users[1].username).toEqual("Paul") | ||
expect(parseInt(+users[1].createdAt/5000)).toEqual(parseInt(+new Date()/5000)) | ||
done() | ||
}) | ||
}) | ||
}) | ||
describe('enums', function() { | ||
before(function(done) { | ||
this.Item = this.sequelize.define('Item', { | ||
state: { type: Helpers.Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] }, | ||
name: Sequelize.STRING | ||
}) | ||
this.sequelize.sync({ force: true }).success(function() { | ||
this.Item.bulkCreate([{state: 'in_cart', name: 'A'}, { state: 'available', name: 'B'}]).success(function() { | ||
done() | ||
}.bind(this)) | ||
}.bind(this)) | ||
}) | ||
it('correctly restores enum values', function(done) { | ||
this.Item.find({ where: { state: 'available' }}).success(function(item) { | ||
expect(item.name).toEqual('B') | ||
done() | ||
}.bind(this)) | ||
}) | ||
}) | ||
}) // - bulkCreate | ||
describe('update', function() { | ||
it('updates only values that match filter', function(done) { | ||
var self = this | ||
, data = [{ username: 'Peter', secretValue: '42' }, | ||
{ username: 'Paul', secretValue: '42' }, | ||
{ username: 'Bob', secretValue: '43' }] | ||
this.User.bulkCreate(data).success(function() { | ||
self.User.update({username: 'Bill'}, {secretValue: '42'}) | ||
.success(function() { | ||
self.User.findAll({order: 'id'}).success(function(users) { | ||
expect(users.length).toEqual(3) | ||
users.forEach(function (user) { | ||
if (user.secretValue == '42') { | ||
expect(user.username).toEqual("Bill") | ||
} else { | ||
expect(user.username).toEqual("Bob") | ||
} | ||
}) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
it('sets updatedAt to the current timestamp', function(done) { | ||
var self = this | ||
, data = [{ username: 'Peter', secretValue: '42' }, | ||
{ username: 'Paul', secretValue: '42' }, | ||
{ username: 'Bob', secretValue: '43' }] | ||
this.User.bulkCreate(data).success(function() { | ||
self.User.update({username: 'Bill'}, {secretValue: '42'}) | ||
.success(function() { | ||
self.User.findAll({order: 'id'}).success(function(users) { | ||
expect(users.length).toEqual(3) | ||
expect(users[0].username).toEqual("Bill") | ||
expect(users[1].username).toEqual("Bill") | ||
expect(users[2].username).toEqual("Bob") | ||
expect(parseInt(+users[0].updatedAt/5000)).toEqual(parseInt(+new Date()/5000)) | ||
expect(parseInt(+users[1].updatedAt/5000)).toEqual(parseInt(+new Date()/5000)) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) // - update | ||
describe('destroy', function() { | ||
it('deletes values that match filter', function(done) { | ||
var self = this | ||
, data = [{ username: 'Peter', secretValue: '42' }, | ||
{ username: 'Paul', secretValue: '42' }, | ||
{ username: 'Bob', secretValue: '43' }] | ||
this.User.bulkCreate(data).success(function() { | ||
self.User.destroy({secretValue: '42'}) | ||
.success(function() { | ||
self.User.findAll({order: 'id'}).success(function(users) { | ||
expect(users.length).toEqual(1) | ||
expect(users[0].username).toEqual("Bob") | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
it('sets deletedAt to the current timestamp if paranoid is true', function(done) { | ||
var self = this | ||
, User = this.sequelize.define('ParanoidUser', { | ||
username: Sequelize.STRING, | ||
secretValue: Sequelize.STRING, | ||
data: Sequelize.STRING | ||
}, { | ||
paranoid: true | ||
}) | ||
, data = [{ username: 'Peter', secretValue: '42' }, | ||
{ username: 'Paul', secretValue: '42' }, | ||
{ username: 'Bob', secretValue: '43' }] | ||
User.sync({ force: true }).success(function() { | ||
User.bulkCreate(data).success(function() { | ||
User.destroy({secretValue: '42'}) | ||
.success(function() { | ||
User.findAll({order: 'id'}).success(function(users) { | ||
expect(users.length).toEqual(3) | ||
expect(users[0].username).toEqual("Peter") | ||
expect(users[1].username).toEqual("Paul") | ||
expect(users[2].username).toEqual("Bob") | ||
expect(parseInt(+users[0].deletedAt/5000)).toEqual(parseInt(+new Date()/5000)) | ||
expect(parseInt(+users[1].deletedAt/5000)).toEqual(parseInt(+new Date()/5000)) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) // - destroy | ||
describe('find', function find() { | ||
@@ -500,2 +874,33 @@ before(function(done) { | ||
it('returns the selected fields and all fields of the included table as instance.selectedValues', function(done) { | ||
this.Mission = this.sequelize.define('Mission', { | ||
title: {type: Sequelize.STRING, defaultValue: 'a mission!!'}, | ||
foo: {type: Sequelize.INTEGER, defaultValue: 2}, | ||
}) | ||
this.Mission.belongsTo(this.User) | ||
this.User.hasMany(this.Mission) | ||
this.sequelize.sync({ force: true }).complete(function() { | ||
this.Mission.create() | ||
.success(function(mission) { | ||
this.User.create({ | ||
username: 'John DOE' | ||
}).success(function(user) { | ||
mission.setUser(user) | ||
.success(function() { | ||
this.User.find({ | ||
where: { username: 'John DOE' }, | ||
attributes: ['username'], | ||
include: [this.Mission] | ||
}).success(function(user) { | ||
expect(user.selectedValues).toEqual({ username: 'John DOE' }) | ||
done() | ||
}) | ||
}.bind(this)) | ||
}.bind(this)) | ||
}.bind(this)) | ||
}.bind(this)) | ||
}) | ||
it('always honors ZERO as primary key', function(_done) { | ||
@@ -798,2 +1203,38 @@ var permutations = [ | ||
}) | ||
describe('queryOptions', function() { | ||
before(function(done) { | ||
this.User.create({ | ||
username: 'barfooz' | ||
}).success(function(user) { | ||
this.user = user | ||
done() | ||
}.bind(this)) | ||
}) | ||
it("should return a DAO when queryOptions are not set", function (done) { | ||
this.User.find({ where: { username: 'barfooz'}}).done(function (err, user) { | ||
expect(user).toHavePrototype(this.User.DAO.prototype) | ||
done(); | ||
}.bind(this)) | ||
}) | ||
it("should return a DAO when raw is false", function (done) { | ||
this.User.find({ where: { username: 'barfooz'}}, { raw: false }).done(function (err, user) { | ||
expect(user).toHavePrototype(this.User.DAO.prototype) | ||
done(); | ||
}.bind(this)) | ||
}) | ||
it("should return raw data when raw is true", function (done) { | ||
this.User.find({ where: { username: 'barfooz'}}, { raw: true }).done(function (err, user) { | ||
expect(user).not.toHavePrototype(this.User.DAO.prototype) | ||
expect(user).toBeObject() | ||
done(); | ||
}.bind(this)) | ||
}) | ||
}) // - describe: queryOptions | ||
}) //- describe: find | ||
@@ -1031,2 +1472,45 @@ | ||
}) | ||
describe('queryOptions', function() { | ||
before(function(done) { | ||
this.User.create({ | ||
username: 'barfooz' | ||
}).success(function(user) { | ||
this.user = user | ||
done() | ||
}.bind(this)) | ||
}) | ||
it("should return a DAO when queryOptions are not set", function (done) { | ||
this.User.findAll({ where: { username: 'barfooz'}}).done(function (err, users) { | ||
users.forEach(function (user) { | ||
expect(user).toHavePrototype(this.User.DAO.prototype) | ||
}, this) | ||
done(); | ||
}.bind(this)) | ||
}) | ||
it("should return a DAO when raw is false", function (done) { | ||
this.User.findAll({ where: { username: 'barfooz'}}, { raw: false }).done(function (err, users) { | ||
users.forEach(function (user) { | ||
expect(user).toHavePrototype(this.User.DAO.prototype) | ||
}, this) | ||
done(); | ||
}.bind(this)) | ||
}) | ||
it("should return raw data when raw is true", function (done) { | ||
this.User.findAll({ where: { username: 'barfooz'}}, { raw: true }).done(function (err, users) { | ||
users.forEach(function (user) { | ||
expect(user).not.toHavePrototype(this.User.DAO.prototype) | ||
expect(users[0]).toBeObject() | ||
}, this) | ||
done(); | ||
}.bind(this)) | ||
}) | ||
}) // - describe: queryOptions | ||
}) | ||
@@ -1041,3 +1525,9 @@ }) //- describe: findAll | ||
this.UserWithAge.sync({ force: true }).success(done) | ||
this.UserWithDec = this.sequelize.define('UserWithDec', { | ||
value: Sequelize.DECIMAL(10, 3) | ||
}) | ||
this.UserWithAge.sync({ force: true }).success(function(){ | ||
this.UserWithDec.sync({ force: true }).success(done) | ||
}.bind(this)) | ||
}) | ||
@@ -1063,2 +1553,13 @@ | ||
}) | ||
it("should allow decimals in min", function(done){ | ||
this.UserWithDec.create({value: 3.5}).success(function(){ | ||
this.UserWithDec.create({ value: 5.5 }).success(function(){ | ||
this.UserWithDec.min('value').success(function(min){ | ||
expect(min).toEqual(3.5) | ||
done() | ||
}) | ||
}.bind(this)) | ||
}.bind(this)) | ||
}) | ||
}) //- describe: min | ||
@@ -1072,3 +1573,9 @@ | ||
this.UserWithAge.sync({ force: true }).success(done) | ||
this.UserWithDec = this.sequelize.define('UserWithDec', { | ||
value: Sequelize.DECIMAL(10, 3) | ||
}) | ||
this.UserWithAge.sync({ force: true }).success(function(){ | ||
this.UserWithDec.sync({ force: true }).success(done) | ||
}.bind(this)) | ||
}) | ||
@@ -1087,2 +1594,13 @@ | ||
it("should allow decimals in max", function(done){ | ||
this.UserWithDec.create({value: 3.5}).success(function(){ | ||
this.UserWithDec.create({ value: 5.5 }).success(function(){ | ||
this.UserWithDec.max('value').success(function(max){ | ||
expect(max).toEqual(5.5) | ||
done() | ||
}) | ||
}.bind(this)) | ||
}.bind(this)) | ||
}) | ||
it('allows sql logging', function(done) { | ||
@@ -1163,2 +1681,6 @@ this.UserWithAge.max('age').on('sql', function(sql) { | ||
expect(UserPublic.indexOf('INSERT INTO "UserPublics"')).toBeGreaterThan(-1) | ||
} else if (dialect === "sqlite") { | ||
expect(self.UserSpecialSync.getTableName()).toEqual('`special`.`UserSpecials`'); | ||
expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).toBeGreaterThan(-1) | ||
expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).toBeGreaterThan(-1) | ||
} else { | ||
@@ -1165,0 +1687,0 @@ expect(self.UserSpecialSync.getTableName()).toEqual('`special.UserSpecials`'); |
@@ -5,3 +5,3 @@ if (typeof require === 'function') { | ||
, dialect = Helpers.getTestDialect() | ||
, _ = require('underscore') | ||
, _ = require('lodash') | ||
} | ||
@@ -24,2 +24,14 @@ | ||
bNumber: { type: DataTypes.INTEGER }, | ||
validateTest: { | ||
type: DataTypes.INTEGER, | ||
allowNull: true, | ||
validate: {isInt: true} | ||
}, | ||
validateCustom: { | ||
type: DataTypes.STRING, | ||
allowNull: true, | ||
validate: {len: {msg: 'Length failed.', args: [1,20]}} | ||
}, | ||
dateAllowNullTrue: { | ||
@@ -36,6 +48,16 @@ type: DataTypes.DATE, | ||
}) | ||
self.ParanoidUser = sequelize.define('ParanoidUser', { | ||
username: { type: DataTypes.STRING } | ||
}, { | ||
paranoid: true | ||
}) | ||
self.ParanoidUser.hasOne( self.ParanoidUser ) | ||
}, | ||
onComplete: function() { | ||
self.User.sync({ force: true }).success(function(){ | ||
self.HistoryLog.sync({ force: true }).success(done) | ||
self.HistoryLog.sync({ force: true }).success(function(){ | ||
self.ParanoidUser.sync({force: true }).success(done) | ||
}) | ||
}) | ||
@@ -374,2 +396,40 @@ } | ||
describe('save', function() { | ||
it('should fail a validation upon creating', function(done){ | ||
this.User.create({aNumber: 0, validateTest: 'hello'}).error(function(err){ | ||
expect(err).toBeDefined() | ||
expect(err).toBeObject() | ||
expect(err.validateTest).toBeArray() | ||
expect(err.validateTest[0]).toBeDefined() | ||
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1); | ||
done(); | ||
}); | ||
}) | ||
it('should fail a validation upon building', function(done){ | ||
this.User.build({aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa'}).save() | ||
.error(function(err){ | ||
expect(err).toBeDefined() | ||
expect(err).toBeObject() | ||
expect(err.validateCustom).toBeDefined() | ||
expect(err.validateCustom).toBeArray() | ||
expect(err.validateCustom[0]).toBeDefined() | ||
expect(err.validateCustom[0]).toEqual('Length failed.') | ||
done() | ||
}) | ||
}) | ||
it('should fail a validation when updating', function(done){ | ||
this.User.create({aNumber: 0}).success(function(user){ | ||
user.updateAttributes({validateTest: 'hello'}).error(function(err){ | ||
expect(err).toBeDefined() | ||
expect(err).toBeObject() | ||
expect(err.validateTest).toBeDefined() | ||
expect(err.validateTest).toBeArray() | ||
expect(err.validateTest[0]).toBeDefined() | ||
expect(err.validateTest[0].indexOf('Invalid integer:')).toBeGreaterThan(-1) | ||
done() | ||
}) | ||
}) | ||
}) | ||
it('takes zero into account', function(done) { | ||
@@ -501,2 +561,47 @@ this.User.build({ aNumber: 0 }).save([ 'aNumber' ]).success(function(user) { | ||
it("creates the deletedAt property, when defining paranoid as true", function(done) { | ||
this.ParanoidUser.create({ username: 'fnord' }).success(function() { | ||
this.ParanoidUser.findAll().success(function(users) { | ||
expect(users[0].deletedAt).toBeDefined() | ||
expect(users[0].deletedAt).toBe(null) | ||
done() | ||
}.bind(this)) | ||
}.bind(this)) | ||
}) | ||
it("sets deletedAt property to a specific date when deleting an instance", function(done) { | ||
this.ParanoidUser.create({ username: 'fnord' }).success(function() { | ||
this.ParanoidUser.findAll().success(function(users) { | ||
users[0].destroy().success(function(user) { | ||
expect(user.deletedAt.getMonth).toBeDefined() | ||
done() | ||
}.bind(this)) | ||
}.bind(this)) | ||
}.bind(this)) | ||
}) | ||
it("keeps the deletedAt-attribute with value null, when running updateAttributes", function(done) { | ||
this.ParanoidUser.create({ username: 'fnord' }).success(function() { | ||
this.ParanoidUser.findAll().success(function(users) { | ||
users[0].updateAttributes({username: 'newFnord'}).success(function(user) { | ||
expect(user.deletedAt).toBe(null) | ||
done() | ||
}.bind(this)) | ||
}.bind(this)) | ||
}.bind(this)) | ||
}) | ||
it("keeps the deletedAt-attribute with value null, when updating associations", function(done) { | ||
this.ParanoidUser.create({ username: 'fnord' }).success(function() { | ||
this.ParanoidUser.findAll().success(function(users) { | ||
this.ParanoidUser.create({ username: 'linkedFnord' }).success(function( linkedUser ) { | ||
users[0].setParanoidUser( linkedUser ).success(function(user) { | ||
expect(user.deletedAt).toBe(null) | ||
done() | ||
}.bind(this)) | ||
}.bind(this)) | ||
}.bind(this)) | ||
}.bind(this)) | ||
}) | ||
it("can reuse query option objects", function(done) { | ||
@@ -547,2 +652,42 @@ this.User.create({ username: 'fnord' }).success(function() { | ||
}) | ||
describe('updateAttributes', function() { | ||
it('stores and restores null values', function(done) { | ||
var Download = this.sequelize.define('download', { | ||
startedAt: Helpers.Sequelize.DATE, | ||
canceledAt: Helpers.Sequelize.DATE, | ||
finishedAt: Helpers.Sequelize.DATE | ||
}) | ||
Download.sync({ force: true }).success(function() { | ||
Download.create({ | ||
startedAt: new Date() | ||
}).success(function(download) { | ||
expect(download.startedAt instanceof Date).toBeTrue() | ||
expect(download.canceledAt).toBeFalsy() | ||
expect(download.finishedAt).toBeFalsy() | ||
download.updateAttributes({ | ||
canceledAt: new Date() | ||
}).success(function(download) { | ||
expect(download.startedAt instanceof Date).toBeTrue() | ||
expect(download.canceledAt instanceof Date).toBeTrue() | ||
expect(download.finishedAt).toBeFalsy() | ||
Download.all({ | ||
where: (dialect === 'postgres' ? '"finishedAt" IS NULL' : "`finishedAt` IS NULL") | ||
}).success(function(downloads) { | ||
downloads.forEach(function(download) { | ||
expect(download.startedAt instanceof Date).toBeTrue() | ||
expect(download.canceledAt instanceof Date).toBeTrue() | ||
expect(download.finishedAt).toBeFalsy() | ||
}) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
@@ -45,2 +45,6 @@ if(typeof require === 'function') { | ||
} | ||
, isIPv6 : { | ||
fail: '1111:2222:3333::5555:', | ||
pass: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156' | ||
} | ||
, isAlpha : { | ||
@@ -282,3 +286,56 @@ fail: "012", | ||
}) | ||
it('skips other validations if allowNull is true and the value is null', function() { | ||
var User = this.sequelize.define('User' + Math.random(), { | ||
age: { | ||
type: Sequelize.INTEGER, | ||
allowNull: true, | ||
validate: { | ||
min: { args: 0, msg: 'must be positive' } | ||
} | ||
} | ||
}) | ||
var failingUser = User.build({ age: -1 }) | ||
, errors = failingUser.validate() | ||
expect(errors).not.toBeNull(null) | ||
expect(errors).toEqual({ age: ['must be positive'] }) | ||
var successfulUser1 = User.build({ age: null }) | ||
expect(successfulUser1.validate()).toBeNull() | ||
var successfulUser2 = User.build({ age: 1 }) | ||
expect(successfulUser2.validate()).toBeNull() | ||
}) | ||
it('validates a model with custom model-wide validation methods', function() { | ||
var Foo = this.sequelize.define('Foo' + Math.random(), { | ||
field1: { | ||
type: Sequelize.INTEGER, | ||
allowNull: true | ||
}, | ||
field2: { | ||
type: Sequelize.INTEGER, | ||
allowNull: true | ||
} | ||
}, { | ||
validate: { | ||
xnor: function() { | ||
if ((this.field1 === null) === (this.field2 === null)) { | ||
throw new Error('xnor failed'); | ||
} | ||
} | ||
} | ||
}) | ||
var failingFoo = Foo.build({ field1: null, field2: null }) | ||
, errors = failingFoo.validate() | ||
expect(errors).not.toBeNull() | ||
expect(errors).toEqual({ 'xnor': ['xnor failed'] }) | ||
var successfulFoo = Foo.build({ field1: 33, field2: null }) | ||
expect(successfulFoo.validate()).toBeNull() | ||
}) | ||
}) | ||
}) |
@@ -10,3 +10,3 @@ if(typeof require === 'function') { | ||
describe(Helpers.getTestDialectTeaser('Data types'), function() { | ||
describe(Helpers.getTestDialectTeaser('DataTypes'), function() { | ||
it('should return DECIMAL for the default decimal type', function() { | ||
@@ -19,2 +19,50 @@ expect(Sequelize.DECIMAL).toEqual('DECIMAL'); | ||
}); | ||
}); | ||
var tests = [ | ||
[Sequelize.STRING, 'STRING', 'VARCHAR(255)'], | ||
[Sequelize.STRING(1234), 'STRING(1234)', 'VARCHAR(1234)'], | ||
[Sequelize.STRING(1234).BINARY, 'STRING(1234).BINARY', 'VARCHAR(1234) BINARY'], | ||
[Sequelize.STRING.BINARY, 'STRING.BINARY', 'VARCHAR(255) BINARY'], | ||
[Sequelize.TEXT, 'TEXT', 'TEXT'], | ||
[Sequelize.DATE, 'DATE', 'DATETIME'], | ||
[Sequelize.NOW, 'NOW', 'NOW'], | ||
[Sequelize.BOOLEAN, 'BOOLEAN', 'TINYINT(1)'], | ||
[Sequelize.INTEGER, 'INTEGER', 'INTEGER'], | ||
[Sequelize.INTEGER.UNSIGNED, 'INTEGER.UNSIGNED', 'INTEGER UNSIGNED'], | ||
[Sequelize.INTEGER(11), 'INTEGER(11)','INTEGER(11)'], | ||
[Sequelize.INTEGER(11).UNSIGNED, 'INTEGER(11).UNSIGNED', 'INTEGER(11) UNSIGNED'], | ||
[Sequelize.INTEGER(11).UNSIGNED.ZEROFILL,'INTEGER(11).UNSIGNED.ZEROFILL','INTEGER(11) UNSIGNED ZEROFILL'], | ||
[Sequelize.INTEGER(11).ZEROFILL,'INTEGER(11).ZEROFILL', 'INTEGER(11) ZEROFILL'], | ||
[Sequelize.INTEGER(11).ZEROFILL.UNSIGNED,'INTEGER(11).ZEROFILL.UNSIGNED', 'INTEGER(11) UNSIGNED ZEROFILL'], | ||
[Sequelize.BIGINT, 'BIGINT', 'BIGINT'], | ||
[Sequelize.BIGINT.UNSIGNED, 'BIGINT.UNSIGNED', 'BIGINT UNSIGNED'], | ||
[Sequelize.BIGINT(11), 'BIGINT(11)','BIGINT(11)'], | ||
[Sequelize.BIGINT(11).UNSIGNED, 'BIGINT(11).UNSIGNED', 'BIGINT(11) UNSIGNED'], | ||
[Sequelize.BIGINT(11).UNSIGNED.ZEROFILL, 'BIGINT(11).UNSIGNED.ZEROFILL','BIGINT(11) UNSIGNED ZEROFILL'], | ||
[Sequelize.BIGINT(11).ZEROFILL, 'BIGINT(11).ZEROFILL', 'BIGINT(11) ZEROFILL'], | ||
[Sequelize.BIGINT(11).ZEROFILL.UNSIGNED, 'BIGINT(11).ZEROFILL.UNSIGNED', 'BIGINT(11) UNSIGNED ZEROFILL'], | ||
[Sequelize.FLOAT, 'FLOAT', 'FLOAT'], | ||
[Sequelize.FLOAT.UNSIGNED, 'FLOAT.UNSIGNED', 'FLOAT UNSIGNED'], | ||
[Sequelize.FLOAT(11), 'FLOAT(11)','FLOAT(11)'], | ||
[Sequelize.FLOAT(11).UNSIGNED, 'FLOAT(11).UNSIGNED', 'FLOAT(11) UNSIGNED'], | ||
[Sequelize.FLOAT(11).UNSIGNED.ZEROFILL,'FLOAT(11).UNSIGNED.ZEROFILL','FLOAT(11) UNSIGNED ZEROFILL'], | ||
[Sequelize.FLOAT(11).ZEROFILL,'FLOAT(11).ZEROFILL', 'FLOAT(11) ZEROFILL'], | ||
[Sequelize.FLOAT(11).ZEROFILL.UNSIGNED,'FLOAT(11).ZEROFILL.UNSIGNED', 'FLOAT(11) UNSIGNED ZEROFILL'], | ||
[Sequelize.FLOAT(11, 12), 'FLOAT(11,12)','FLOAT(11,12)'], | ||
[Sequelize.FLOAT(11, 12).UNSIGNED, 'FLOAT(11,12).UNSIGNED', 'FLOAT(11,12) UNSIGNED'], | ||
[Sequelize.FLOAT(11, 12).UNSIGNED.ZEROFILL,'FLOAT(11,12).UNSIGNED.ZEROFILL','FLOAT(11,12) UNSIGNED ZEROFILL'], | ||
[Sequelize.FLOAT(11, 12).ZEROFILL,'FLOAT(11,12).ZEROFILL', 'FLOAT(11,12) ZEROFILL'], | ||
[Sequelize.FLOAT(11, 12).ZEROFILL.UNSIGNED,'FLOAT(11,12).ZEROFILL.UNSIGNED', 'FLOAT(11,12) UNSIGNED ZEROFILL'] | ||
] | ||
tests.forEach(function(test) { | ||
it('transforms "' + test[1] + '" to "' + test[2] + '"', function() { | ||
expect(test[0]).toEqual(test[2]) | ||
}) | ||
}) | ||
}) |
@@ -17,4 +17,4 @@ if(typeof require === 'function') { | ||
options = Helpers.Sequelize.Utils._.extend({ | ||
path: __dirname + '/assets/migrations', | ||
logging: false | ||
path: __dirname + '/assets/migrations', | ||
logging: function(){} | ||
}, options || {}) | ||
@@ -21,0 +21,0 @@ |
@@ -197,3 +197,27 @@ if(typeof require === 'function') { | ||
}) | ||
describe('table', function() { | ||
[ | ||
{ id: { type: Helpers.Sequelize.BIGINT } }, | ||
{ id: { type: Helpers.Sequelize.STRING, allowNull: true } }, | ||
{ id: { type: Helpers.Sequelize.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true } } | ||
].forEach(function(customAttributes) { | ||
it('should be able to override options on the default attributes', function(done) { | ||
var Picture = this.sequelize.define('picture', Helpers.Sequelize.Utils._.cloneDeep(customAttributes)) | ||
Picture.sync({ force: true }).success(function() { | ||
Object.keys(customAttributes).forEach(function(attribute) { | ||
Object.keys(customAttributes[attribute]).forEach(function(option) { | ||
var optionValue = customAttributes[attribute][option]; | ||
expect(Picture.rawAttributes[attribute][option]).toBe(optionValue) | ||
}); | ||
}) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1027986
144
15519
249
10
32
+ Addedlodash@~1.2.1
+ Addedpromise@~3.0.0
+ Addedtoposort-class@0.1.4
+ Addedgeneric-pool@2.0.3(transitive)
+ Addedlodash@1.2.1(transitive)
+ Addedpromise@3.0.1(transitive)
+ Addedtoposort-class@0.1.4(transitive)
+ Addedvalidator@1.1.1(transitive)
- Removedunderscore@~1.4.0
- Removedgeneric-pool@1.0.9(transitive)
- Removedunderscore@1.4.4(transitive)
- Removedvalidator@0.4.28(transitive)
Updatedgeneric-pool@2.0.3
Updatedvalidator@1.1.1