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

Commit 7e9d13de by Sascha Depold

Merge branch 'master' of github.com:sequelize/sequelize

2 parents b2184b24 067e97c4
...@@ -46,12 +46,8 @@ Also make sure to take a look at the examples in the repository. The website wil ...@@ -46,12 +46,8 @@ Also make sure to take a look at the examples in the repository. The website wil
A very basic roadmap. Chances aren't too bad, that not mentioned things are implemented as well. Don't panic :) A very basic roadmap. Chances aren't too bad, that not mentioned things are implemented as well. Don't panic :)
### 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 ### 1.7.0
- Check if lodash is a proper alternative to current underscore usage. - ~~Check if lodash is a proper alternative to current underscore usage.~~
- Transactions - Transactions
- Support for update of tables without primary key - Support for update of tables without primary key
- MariaDB support - MariaDB support
...@@ -59,8 +55,8 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -59,8 +55,8 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099) - Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099)
- Model#delete - Model#delete
- Validate a model before it gets saved. (Move validation of enum attribute value to validate method) - 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) - BLOB [#99](https://github.com/sequelize/sequelize/issues/99)
- Support for foreign keys - ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude
### 1.7.x ### 1.7.x
- Complete support for non-id primary keys - Complete support for non-id primary keys
......
# v1.7.0 # # v1.7.0 #
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). 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] Null dates don't break SQLite anymore. [#572](https://github.com/sequelize/sequelize/pull/572). thanks to mweibel
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
# v1.6.0 # # v1.6.0 #
- [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work - [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work
......
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types') , DataTypes = require('./../data-types')
, Helpers = require('./helpers')
module.exports = (function() { module.exports = (function() {
var BelongsTo = function(srcDAO, targetDAO, options) { var BelongsTo = function(srcDAO, targetDAO, options) {
...@@ -24,6 +25,7 @@ module.exports = (function() { ...@@ -24,6 +25,7 @@ module.exports = (function() {
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName) + "Id", this.source.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.target.tableName) + "Id", this.source.options.underscored)
newAttributes[this.identifier] = { type: DataTypes.INTEGER } newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.target, this.source, this.options)
Utils._.defaults(this.source.rawAttributes, newAttributes) Utils._.defaults(this.source.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added // Sync attributes to DAO proto each time a new assoc is added
......
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types') , DataTypes = require('./../data-types')
, Helpers = require('./helpers')
var HasManySingleLinked = require("./has-many-single-linked") var HasManySingleLinked = require("./has-many-single-linked")
, HasManyMultiLinked = require("./has-many-double-linked") , HasManyMultiLinked = require("./has-many-double-linked")
...@@ -65,6 +66,7 @@ module.exports = (function() { ...@@ -65,6 +66,7 @@ module.exports = (function() {
} else { } else {
var newAttributes = {} var newAttributes = {}
newAttributes[this.identifier] = { type: DataTypes.INTEGER } newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
} }
......
var Utils = require("./../utils") var Utils = require("./../utils")
, DataTypes = require('./../data-types') , DataTypes = require('./../data-types')
, Helpers = require("./helpers")
module.exports = (function() { module.exports = (function() {
var HasOne = function(srcDAO, targetDAO, options) { var HasOne = function(srcDAO, targetDAO, options) {
...@@ -29,6 +30,7 @@ module.exports = (function() { ...@@ -29,6 +30,7 @@ module.exports = (function() {
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName) + "Id", this.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName) + "Id", this.options.underscored)
newAttributes[this.identifier] = { type: DataTypes.INTEGER } newAttributes[this.identifier] = { type: DataTypes.INTEGER }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
// Sync attributes to DAO proto each time a new assoc is added // Sync attributes to DAO proto each time a new assoc is added
......
var Utils = require("./../utils")
module.exports = {
addForeignKeyConstraints: function(newAttribute, source, target, options) {
// FK constraints are opt-in: users must either rset `foreignKeyConstraints`
// on the association, or request an `onDelete` or `onUpdate` behaviour
if(options.foreignKeyConstraint || options.onDelete || options.onUpdate) {
// Find primary keys: composite keys not supported with this approach
var primaryKeys = Utils._.filter(Utils._.keys(source.rawAttributes), function(key) {
return source.rawAttributes[key].primaryKey
})
if(primaryKeys.length == 1) {
newAttribute.references = source.tableName,
newAttribute.referencesKey = primaryKeys[0]
newAttribute.onDelete = options.onDelete,
newAttribute.onUpdate = options.onUpdate
}
}
}
}
var Toposort = require('toposort-class')
module.exports = (function() { module.exports = (function() {
var DAOFactoryManager = function(sequelize) { var DAOFactoryManager = function(sequelize) {
this.daos = [] this.daos = []
...@@ -31,5 +33,34 @@ module.exports = (function() { ...@@ -31,5 +33,34 @@ module.exports = (function() {
return this.daos return this.daos
}) })
/**
* 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 return DAOFactoryManager
})() })()
...@@ -271,14 +271,14 @@ module.exports = (function() { ...@@ -271,14 +271,14 @@ module.exports = (function() {
DAOFactory.prototype.max = function(field, options) { DAOFactory.prototype.max = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {}) options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['max(' + field + ')', 'max']) options.attributes.push(['max(' + field + ')', 'max'])
options.parseInt = true options.parseFloat = true
return this.QueryInterface.rawSelect(this.getTableName(), options, 'max') return this.QueryInterface.rawSelect(this.getTableName(), options, 'max')
} }
DAOFactory.prototype.min = function(field, options) { DAOFactory.prototype.min = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {}) options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['min(' + field + ')', 'min']) options.attributes.push(['min(' + field + ')', 'min'])
options.parseInt = true options.parseFloat = true
return this.QueryInterface.rawSelect(this.getTableName(), options, 'min') return this.QueryInterface.rawSelect(this.getTableName(), options, 'min')
} }
......
...@@ -45,6 +45,7 @@ module.exports = (function() { ...@@ -45,6 +45,7 @@ module.exports = (function() {
var query = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>) ENGINE=<%= engine %> <%= charset %>" var query = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>) ENGINE=<%= engine %> <%= charset %>"
, primaryKeys = [] , primaryKeys = []
, foreignKeys = {}
, attrStr = [] , attrStr = []
for (var attr in attributes) { for (var attr in attributes) {
...@@ -54,6 +55,11 @@ module.exports = (function() { ...@@ -54,6 +55,11 @@ module.exports = (function() {
if (Utils._.includes(dataType, 'PRIMARY KEY')) { if (Utils._.includes(dataType, 'PRIMARY KEY')) {
primaryKeys.push(attr) primaryKeys.push(attr)
attrStr.push(QueryGenerator.addQuotes(attr) + " " + dataType.replace(/PRIMARY KEY/, '')) 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 { } else {
attrStr.push(QueryGenerator.addQuotes(attr) + " " + dataType) attrStr.push(QueryGenerator.addQuotes(attr) + " " + dataType)
} }
...@@ -72,6 +78,12 @@ module.exports = (function() { ...@@ -72,6 +78,12 @@ module.exports = (function() {
values.attributes += ", PRIMARY KEY (" + pkString + ")" values.attributes += ", PRIMARY KEY (" + pkString + ")"
} }
for (var fkey in foreignKeys) {
if(foreignKeys.hasOwnProperty(fkey)) {
values.attributes += ", FOREIGN KEY (" + QueryGenerator.addQuotes(fkey) + ") " + foreignKeys[fkey]
}
}
return Utils._.template(query)(values).trim() + ";" return Utils._.template(query)(values).trim() + ";"
}, },
...@@ -149,8 +161,8 @@ module.exports = (function() { ...@@ -149,8 +161,8 @@ module.exports = (function() {
}, },
selectQuery: function(tableName, options) { selectQuery: function(tableName, options) {
var query = "SELECT <%= attributes %> FROM <%= table %>" var table = null,
, table = null joinQuery = ""
options = options || {} options = options || {}
options.table = table = Array.isArray(tableName) ? tableName.map(function(tbl){ return QueryGenerator.addQuotes(tbl) }).join(", ") : QueryGenerator.addQuotes(tableName) options.table = table = Array.isArray(tableName) ? tableName.map(function(tbl){ return QueryGenerator.addQuotes(tbl) }).join(", ") : QueryGenerator.addQuotes(tableName)
...@@ -168,75 +180,73 @@ module.exports = (function() { ...@@ -168,75 +180,73 @@ module.exports = (function() {
options.include.forEach(function(include) { options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) { var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
var template = Utils._.template("`<%= as %>`.`<%= attr %>` AS `<%= as %>.<%= attr %>`") return "`" + include.as + "`.`" + attr + "` AS `" + include.as + "." + attr + "`"
return template({ as: include.as, attr: attr })
}) })
optAttributes = optAttributes.concat(attributes) optAttributes = optAttributes.concat(attributes)
var joinQuery = " LEFT OUTER JOIN `<%= table %>` AS `<%= as %>` ON `<%= tableLeft %>`.`<%= attrLeft %>` = `<%= tableRight %>`.`<%= attrRight %>`" var table = include.daoFactory.tableName
query += Utils._.template(joinQuery)({ var as = include.as
table: include.daoFactory.tableName, var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
as: include.as, var attrLeft = 'id'
tableLeft: ((include.association.associationType === 'BelongsTo') ? include.as : tableName), var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
attrLeft: 'id', var attrRight = include.association.identifier
tableRight: ((include.association.associationType === 'BelongsTo') ? tableName : include.as), joinQuery += " LEFT OUTER JOIN `" + table + "` AS `" + as + "` ON `" + tableLeft + "`.`" + attrLeft + "` = `" + tableRight + "`.`" + attrRight + "`"
attrRight: include.association.identifier
})
}) })
options.attributes = optAttributes.join(', ') options.attributes = optAttributes.join(', ')
} }
var query = "SELECT " + options.attributes + " FROM " + options.table
query += joinQuery
if (options.hasOwnProperty('where')) { if (options.hasOwnProperty('where')) {
options.where = this.getWhereConditions(options.where, tableName) options.where = this.getWhereConditions(options.where, tableName)
query += " WHERE <%= where %>" query += " WHERE " + options.where
} }
if (options.group) { if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(grp){return QueryGenerator.addQuotes(grp)}).join(', ') : QueryGenerator.addQuotes(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 <%= group %>" query += " GROUP BY " + options.group
} }
if (options.order) { if (options.order) {
query += " ORDER BY <%= order %>" query += " ORDER BY " + options.order
} }
if (options.limit && !(options.include && (options.limit === 1))) { if (options.limit && !(options.include && (options.limit === 1))) {
if (options.offset) { if (options.offset) {
query += " LIMIT <%= offset %>, <%= limit %>" query += " LIMIT " + options.offset + ", " + options.limit
} else { } else {
query += " LIMIT <%= limit %>" query += " LIMIT " + options.limit
} }
} }
query += ";" query += ";"
return Utils._.template(query)(options) return query
}, },
insertQuery: function(tableName, attrValueHash) { insertQuery: function(tableName, attrValueHash) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
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 replacements = { var values = Utils._.values(attrValueHash).map(function(value){
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) return Utils.escape((value instanceof Date) ? Utils.toSqlDate(value) : value)
}).join(",") }).join(",")
}
return Utils._.template(query)(replacements) var query = "INSERT INTO " + table + " (" + attributes + ") VALUES (" + values + ");"
return query
}, },
updateQuery: function(tableName, attrValueHash, where) { updateQuery: function(tableName, attrValueHash, where) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>" var values = []
, values = []
for (var key in attrValueHash) { for (var key in attrValueHash) {
var value = attrValueHash[key] var value = attrValueHash[key]
...@@ -245,34 +255,30 @@ module.exports = (function() { ...@@ -245,34 +255,30 @@ module.exports = (function() {
values.push(QueryGenerator.addQuotes(key) + "=" + Utils.escape(_value)) values.push(QueryGenerator.addQuotes(key) + "=" + Utils.escape(_value))
} }
var replacements = { var query = "UPDATE " + QueryGenerator.addQuotes(tableName) +
table: QueryGenerator.addQuotes(tableName), " SET " + values.join(",") +
values: values.join(","), " WHERE " + QueryGenerator.getWhereConditions(where)
where: QueryGenerator.getWhereConditions(where)
}
return Utils._.template(query)(replacements) return query
}, },
deleteQuery: function(tableName, where, options) { deleteQuery: function(tableName, where, options) {
options = options || {} options = options || {}
options.limit = options.limit || 1 options.limit = options.limit || 1
var query = "DELETE FROM <%= table %> WHERE <%= where %> LIMIT <%= limit %>" var table = QueryGenerator.addQuotes(tableName)
var replacements = { var where = QueryGenerator.getWhereConditions(where)
table: QueryGenerator.addQuotes(tableName), var limit = Utils.escape(options.limit)
where: QueryGenerator.getWhereConditions(where),
limit: Utils.escape(options.limit) var query = "DELETE FROM " + table + " WHERE " + where + " LIMIT " + limit
}
return Utils._.template(query)(replacements) return query
}, },
incrementQuery: function (tableName, attrValueHash, where) { incrementQuery: function (tableName, attrValueHash, where) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %> " var values = []
, values = []
for (var key in attrValueHash) { for (var key in attrValueHash) {
var value = attrValueHash[key] var value = attrValueHash[key]
...@@ -281,13 +287,13 @@ module.exports = (function() { ...@@ -281,13 +287,13 @@ module.exports = (function() {
values.push(QueryGenerator.addQuotes(key) + "=" + QueryGenerator.addQuotes(key) + " + " +Utils.escape(_value)) values.push(QueryGenerator.addQuotes(key) + "=" + QueryGenerator.addQuotes(key) + " + " +Utils.escape(_value))
} }
var replacements = { var table = QueryGenerator.addQuotes(tableName)
table: QueryGenerator.addQuotes(tableName), var values = values.join(",")
values: values.join(","), var where = QueryGenerator.getWhereConditions(where)
where: QueryGenerator.getWhereConditions(where)
}
return Utils._.template(query)(replacements) var query = "UPDATE " + table + " SET " + values + " WHERE " + where
return query
}, },
addIndexQuery: function(tableName, attributes, options) { addIndexQuery: function(tableName, attributes, options) {
...@@ -410,17 +416,18 @@ module.exports = (function() { ...@@ -410,17 +416,18 @@ module.exports = (function() {
var dataType = attributes[name] var dataType = attributes[name]
if (Utils.isHash(dataType)) { if (Utils.isHash(dataType)) {
var template = "<%= type %>" var template
, replacements = { type: dataType.type }
if (dataType.type.toString() === DataTypes.ENUM.toString()) { if (dataType.type.toString() === DataTypes.ENUM.toString()) {
if (Array.isArray(dataType.values) && (dataType.values.length > 0)) { 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) return Utils.escape(value)
}).join(", ") + ")" }).join(", ") + ")"
} else { } else {
throw new Error('Values for ENUM haven\'t been defined.') throw new Error('Values for ENUM haven\'t been defined.')
} }
} else {
template = dataType.type.toString();
} }
if (dataType.hasOwnProperty('allowNull') && (!dataType.allowNull)) { if (dataType.hasOwnProperty('allowNull') && (!dataType.allowNull)) {
...@@ -432,8 +439,7 @@ module.exports = (function() { ...@@ -432,8 +439,7 @@ module.exports = (function() {
} }
if ((dataType.defaultValue != undefined) && (dataType.defaultValue != DataTypes.NOW)) { if ((dataType.defaultValue != undefined) && (dataType.defaultValue != DataTypes.NOW)) {
template += " DEFAULT <%= defaultValue %>" template += " DEFAULT " + Utils.escape(dataType.defaultValue)
replacements.defaultValue = Utils.escape(dataType.defaultValue)
} }
if (dataType.unique) { if (dataType.unique) {
...@@ -444,7 +450,27 @@ module.exports = (function() { ...@@ -444,7 +450,27 @@ module.exports = (function() {
template += " PRIMARY KEY" template += " PRIMARY KEY"
} }
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 { } else {
result[name] = dataType result[name] = dataType
} }
...@@ -469,6 +495,16 @@ module.exports = (function() { ...@@ -469,6 +495,16 @@ module.exports = (function() {
return fields return fields
}, },
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) { addQuotes: function(s, quoteChar) {
return Utils.addTicks(s, quoteChar) return Utils.addTicks(s, quoteChar)
}, },
......
...@@ -62,7 +62,7 @@ module.exports = (function() { ...@@ -62,7 +62,7 @@ module.exports = (function() {
var values = { var values = {
table: QueryGenerator.addQuotes(tableName), table: QueryGenerator.addQuotes(tableName),
attributes: attrStr.join(", "), attributes: attrStr.join(", ")
} }
var pks = primaryKeys[tableName].map(function(pk){ var pks = primaryKeys[tableName].map(function(pk){
...@@ -78,9 +78,10 @@ module.exports = (function() { ...@@ -78,9 +78,10 @@ module.exports = (function() {
dropTableQuery: function(tableName, options) { dropTableQuery: function(tableName, options) {
options = options || {} options = options || {}
var query = "DROP TABLE IF EXISTS <%= table %>;" var query = "DROP TABLE IF EXISTS <%= table %><%= cascade %>;"
return Utils._.template(query)({ return Utils._.template(query)({
table: QueryGenerator.addQuotes(tableName) table: QueryGenerator.addQuotes(tableName),
cascade: options.cascade? " CASCADE" : ""
}) })
}, },
...@@ -262,13 +263,6 @@ module.exports = (function() { ...@@ -262,13 +263,6 @@ module.exports = (function() {
query += " WHERE <%= where %>" 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) { if(options.group) {
if (Array.isArray(options.group)) { if (Array.isArray(options.group)) {
options.group = options.group.map(function(grp){ options.group = options.group.map(function(grp){
...@@ -281,6 +275,13 @@ module.exports = (function() { ...@@ -281,6 +275,13 @@ module.exports = (function() {
query += " GROUP BY <%= group %>" query += " GROUP BY <%= 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))) { if (!(options.include && (options.limit === 1))) {
if (options.limit) { if (options.limit) {
query += " LIMIT <%= limit %>" query += " LIMIT <%= limit %>"
...@@ -556,6 +557,28 @@ module.exports = (function() { ...@@ -556,6 +557,28 @@ module.exports = (function() {
template += " PRIMARY KEY" template += " PRIMARY KEY"
} }
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) result[name] = Utils._.template(template)(replacements)
} else { } else {
result[name] = dataType result[name] = dataType
...@@ -579,8 +602,16 @@ module.exports = (function() { ...@@ -579,8 +602,16 @@ module.exports = (function() {
return fields return fields
}, },
enableForeignKeyConstraintsQuery: function() {
return false // not supported by dialect
},
disableForeignKeyConstraintsQuery: function() {
return false // not supported by dialect
},
databaseConnectionUri: function(config) { databaseConnectionUri: function(config) {
var template = '<%= protocol %>://<%= user %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %>'; var template = '<%= protocol %>://<%= user %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %>'
return Utils._.template(template)({ return Utils._.template(template)({
user: encodeURIComponent(config.username), user: encodeURIComponent(config.username),
......
...@@ -229,7 +229,22 @@ module.exports = (function() { ...@@ -229,7 +229,22 @@ module.exports = (function() {
*/ */
findAutoIncrementField: function(factory) { findAutoIncrementField: function(factory) {
throwMethodUndefined('findAutoIncrementField') throwMethodUndefined('findAutoIncrementField')
},
/*
Globally enable foreign key constraints
*/
enableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('enableForeignKeyConstraintsQuery')
},
/*
Globally disable foreign key constraints
*/
disableForeignKeyConstraintsQuery: function() {
throwMethodUndefined('disableForeignKeyConstraintsQuery')
} }
} }
var throwMethodUndefined = function(methodName) { var throwMethodUndefined = function(methodName) {
......
...@@ -5,7 +5,13 @@ var Utils = require("../../utils") ...@@ -5,7 +5,13 @@ var Utils = require("../../utils")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize) { var ConnectorManager = function(sequelize) {
this.sequelize = sequelize 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')
}
})
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype) Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
......
...@@ -217,6 +217,28 @@ module.exports = (function() { ...@@ -217,6 +217,28 @@ module.exports = (function() {
} }
} }
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) result[name] = Utils._.template(template)(replacements)
} else { } else {
result[name] = dataType result[name] = dataType
...@@ -242,6 +264,16 @@ module.exports = (function() { ...@@ -242,6 +264,16 @@ module.exports = (function() {
return fields return fields
}, },
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) { hashToWhereConditions: function(hash) {
for (var key in hash) { for (var key in hash) {
if (hash.hasOwnProperty(key)) { if (hash.hasOwnProperty(key)) {
......
...@@ -79,8 +79,8 @@ module.exports = (function() { ...@@ -79,8 +79,8 @@ module.exports = (function() {
return queryAndEmit.call(this, sql, 'createTable') return queryAndEmit.call(this, sql, 'createTable')
} }
QueryInterface.prototype.dropTable = function(tableName) { QueryInterface.prototype.dropTable = function(tableName, options) {
var sql = this.QueryGenerator.dropTableQuery(tableName) var sql = this.QueryGenerator.dropTableQuery(tableName, options)
return queryAndEmit.call(this, sql, 'dropTable') return queryAndEmit.call(this, sql, 'dropTable')
} }
...@@ -91,11 +91,17 @@ module.exports = (function() { ...@@ -91,11 +91,17 @@ module.exports = (function() {
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
self.showAllTables().success(function(tableNames) { self.showAllTables().success(function(tableNames) {
chainer.add(self, 'disableForeignKeyConstraints', [])
tableNames.forEach(function(tableName) { tableNames.forEach(function(tableName) {
chainer.add(self.dropTable(tableName)) chainer.add(self, 'dropTable', [tableName, {cascade: true}])
}) })
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer chainer
.run() .runSerially()
.success(function() { .success(function() {
self.emit('dropAllTables', null) self.emit('dropAllTables', null)
emitter.emit('success', null) emitter.emit('success', null)
...@@ -296,6 +302,10 @@ module.exports = (function() { ...@@ -296,6 +302,10 @@ module.exports = (function() {
result = parseInt(result) result = parseInt(result)
} }
if (options && options.parseFloat) {
result = parseFloat(result)
}
self.emit('rawSelect', null) self.emit('rawSelect', null)
emitter.emit('success', result) emitter.emit('success', result)
}) })
...@@ -309,6 +319,30 @@ module.exports = (function() { ...@@ -309,6 +319,30 @@ module.exports = (function() {
}).run() }).run()
} }
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 // private
var queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) { var queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) {
......
...@@ -202,7 +202,7 @@ module.exports = (function() { ...@@ -202,7 +202,7 @@ module.exports = (function() {
Sequelize.prototype.query = function(sql, callee, options, replacements) { Sequelize.prototype.query = function(sql, callee, options, replacements) {
if (arguments.length === 4) { if (arguments.length === 4) {
sql = Utils.format([sql].concat(replacements)) sql = Utils.format([sql].concat(replacements), this.options.dialect)
} else if (arguments.length === 3) { } else if (arguments.length === 3) {
options = options options = options
} else if (arguments.length === 2) { } else if (arguments.length === 2) {
...@@ -261,11 +261,14 @@ module.exports = (function() { ...@@ -261,11 +261,14 @@ module.exports = (function() {
var chainer = new Utils.QueryChainer() var chainer = new Utils.QueryChainer()
this.daoFactoryManager.daos.forEach(function(dao) { // Topologically sort by foreign key constraints to give us an appropriate
chainer.add(dao.sync(options)) // creation order
this.daoFactoryManager.forEachDAO(function(dao) {
chainer.add(dao, 'sync', [options])
}) })
return chainer.run() return chainer.runSerially()
} }
Sequelize.prototype.drop = function() { Sequelize.prototype.drop = function() {
......
...@@ -7,7 +7,7 @@ SqlString.escapeId = function (val, forbidQualified) { ...@@ -7,7 +7,7 @@ SqlString.escapeId = function (val, forbidQualified) {
return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`'; return '`' + val.replace(/`/g, '``').replace(/\./g, '`.`') + '`';
}; };
SqlString.escape = function(val, stringifyObjects, timeZone) { SqlString.escape = function(val, stringifyObjects, timeZone, dialect) {
if (val === undefined || val === null) { if (val === undefined || val === null) {
return 'NULL'; return 'NULL';
} }
...@@ -37,6 +37,10 @@ SqlString.escape = function(val, stringifyObjects, timeZone) { ...@@ -37,6 +37,10 @@ SqlString.escape = function(val, stringifyObjects, timeZone) {
} }
} }
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) { val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
switch(s) { switch(s) {
case "\0": return "\\0"; case "\0": return "\\0";
...@@ -48,6 +52,7 @@ SqlString.escape = function(val, stringifyObjects, timeZone) { ...@@ -48,6 +52,7 @@ SqlString.escape = function(val, stringifyObjects, timeZone) {
default: return "\\"+s; default: return "\\"+s;
} }
}); });
}
return "'"+val+"'"; return "'"+val+"'";
}; };
...@@ -58,7 +63,7 @@ SqlString.arrayToList = function(array, timeZone) { ...@@ -58,7 +63,7 @@ SqlString.arrayToList = function(array, timeZone) {
}).join(', '); }).join(', ');
}; };
SqlString.format = function(sql, values, timeZone) { SqlString.format = function(sql, values, timeZone, dialect) {
values = [].concat(values); values = [].concat(values);
return sql.replace(/\?/g, function(match) { return sql.replace(/\?/g, function(match) {
...@@ -66,7 +71,7 @@ SqlString.format = function(sql, values, timeZone) { ...@@ -66,7 +71,7 @@ SqlString.format = function(sql, values, timeZone) {
return match; return match;
} }
return SqlString.escape(values.shift(), false, timeZone); return SqlString.escape(values.shift(), false, timeZone, dialect);
}); });
}; };
......
...@@ -4,7 +4,7 @@ var util = require("util") ...@@ -4,7 +4,7 @@ var util = require("util")
var Utils = module.exports = { var Utils = module.exports = {
_: (function() { _: (function() {
var _ = require("underscore") var _ = require("lodash")
, _s = require('underscore.string') , _s = require('underscore.string')
_.mixin(_s.exports()) _.mixin(_s.exports())
...@@ -47,8 +47,9 @@ var Utils = module.exports = { ...@@ -47,8 +47,9 @@ var Utils = module.exports = {
escape: function(s) { escape: function(s) {
return SqlString.escape(s, true, "local").replace(/\\"/g, '"') return SqlString.escape(s, true, "local").replace(/\\"/g, '"')
}, },
format: function(arr) { format: function(arr, dialect) {
return SqlString.format(arr.shift(), arr) var timeZone = null;
return SqlString.format(arr.shift(), arr, timeZone, dialect)
}, },
isHash: function(obj) { isHash: function(obj) {
return Utils._.isObject(obj) && !Array.isArray(obj); return Utils._.isObject(obj) && !Array.isArray(obj);
......
...@@ -29,14 +29,15 @@ ...@@ -29,14 +29,15 @@
"url": "https://github.com/sdepold/sequelize/issues" "url": "https://github.com/sdepold/sequelize/issues"
}, },
"dependencies": { "dependencies": {
"underscore": "~1.4.0", "lodash": "~1.2.1",
"underscore.string": "~2.3.0", "underscore.string": "~2.3.0",
"lingo": "~0.0.5", "lingo": "~0.0.5",
"validator": "0.4.x", "validator": "0.4.x",
"moment": "~1.7.0", "moment": "~1.7.0",
"commander": "~0.6.0", "commander": "~0.6.0",
"generic-pool": "1.0.9", "generic-pool": "1.0.9",
"dottie": "0.0.6-1" "dottie": "0.0.6-1",
"toposort-class": "0.1.4"
}, },
"devDependencies": { "devDependencies": {
"jasmine-node": "1.5.0", "jasmine-node": "1.5.0",
......
...@@ -90,25 +90,6 @@ describe('DAOFactory', function() { ...@@ -90,25 +90,6 @@ describe('DAOFactory', function() {
}) })
}) })
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() { it('allows sql logging of update statements', function() {
Helpers.async(function(done) { Helpers.async(function(done) {
User = sequelize.define('User', { User = sequelize.define('User', {
......
...@@ -10,6 +10,62 @@ describe('QueryGenerator', function() { ...@@ -10,6 +10,62 @@ describe('QueryGenerator', function() {
afterEach(function() { Helpers.drop() }) afterEach(function() { Helpers.drop() })
var suites = { 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: [ createTableQuery: [
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
...@@ -26,6 +82,14 @@ describe('QueryGenerator', function() { ...@@ -26,6 +82,14 @@ describe('QueryGenerator', function() {
{ {
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}, {charset: 'latin1'}], arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}, {charset: 'latin1'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM(\"A\", \"B\", \"C\"), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;" 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;"
} }
], ],
......
...@@ -14,6 +14,62 @@ describe('QueryGenerator', function() { ...@@ -14,6 +14,62 @@ describe('QueryGenerator', function() {
afterEach(function() { Helpers.drop() }) afterEach(function() { Helpers.drop() })
var suites = { 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: [ createTableQuery: [
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
...@@ -26,6 +82,14 @@ describe('QueryGenerator', function() { ...@@ -26,6 +82,14 @@ describe('QueryGenerator', function() {
{ {
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
expectation: "DROP TYPE IF EXISTS \"enum_myTable_title\"; CREATE TYPE \"enum_myTable_title\" AS ENUM(\"A\", \"B\", \"C\"); CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" \"enum_myTable_title\", \"name\" VARCHAR(255));" expectation: "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);"
} }
], ],
...@@ -37,6 +101,14 @@ describe('QueryGenerator', function() { ...@@ -37,6 +101,14 @@ describe('QueryGenerator', function() {
{ {
arguments: ['mySchema.myTable'], arguments: ['mySchema.myTable'],
expectation: "DROP TABLE IF EXISTS \"mySchema\".\"myTable\";" 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;"
} }
], ],
......
...@@ -9,6 +9,81 @@ describe('QueryGenerator', function() { ...@@ -9,6 +9,81 @@ describe('QueryGenerator', function() {
afterEach(function() { Helpers.drop() }) afterEach(function() { Helpers.drop() })
var suites = { 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: [ insertQuery: [
{ {
arguments: ['myTable', { name: 'foo' }], arguments: ['myTable', { name: 'foo' }],
......
...@@ -47,4 +47,136 @@ describe(Helpers.getTestDialectTeaser("BelongsTo"), function() { ...@@ -47,4 +47,136 @@ describe(Helpers.getTestDialectTeaser("BelongsTo"), 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()
})
})
})
})
})
})
})
})
}) })
...@@ -288,7 +288,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -288,7 +288,7 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() {
var add = this.spy() var add = this.spy()
this.stub(Sequelize.Utils, 'QueryChainer').returns({ add: add, run: function(){} }) this.stub(Sequelize.Utils, 'QueryChainer').returns({ add: add, runSerially: function(){} })
this.sequelize.sync({ force: true }) this.sequelize.sync({ force: true })
expect(add).toHaveBeenCalledThrice() expect(add).toHaveBeenCalledThrice()
...@@ -323,4 +323,135 @@ describe(Helpers.getTestDialectTeaser("HasMany"), function() { ...@@ -323,4 +323,135 @@ describe(Helpers.getTestDialectTeaser("HasMany"), 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.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()
})
})
})
})
})
})
})
})
}) })
...@@ -47,4 +47,136 @@ describe(Helpers.getTestDialectTeaser("HasOne"), function() { ...@@ -47,4 +47,136 @@ describe(Helpers.getTestDialectTeaser("HasOne"), 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()
})
})
})
})
})
})
})
})
}) })
...@@ -2,7 +2,7 @@ if(typeof require === 'function') { ...@@ -2,7 +2,7 @@ if(typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, Sequelize = require("../index") , Sequelize = require("../index")
, Helpers = require('./buster-helpers') , Helpers = require('./buster-helpers')
, _ = require('underscore') , _ = require('lodash')
, dialect = Helpers.getTestDialect() , dialect = Helpers.getTestDialect()
} }
...@@ -1031,7 +1031,13 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1031,7 +1031,13 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
age: Sequelize.INTEGER age: Sequelize.INTEGER
}) })
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))
}) })
it("should return the min value", function(done) { it("should return the min value", function(done) {
...@@ -1052,6 +1058,17 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1052,6 +1058,17 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
done() done()
}) })
}) })
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 }) //- describe: min
describe('max', function() { describe('max', function() {
...@@ -1060,7 +1077,13 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1060,7 +1077,13 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
age: Sequelize.INTEGER age: Sequelize.INTEGER
}) })
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))
}) })
it("should return the max value", function(done) { it("should return the max value", function(done) {
...@@ -1074,6 +1097,17 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1074,6 +1097,17 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
}.bind(this)) }.bind(this))
}) })
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) { it('allows sql logging', function(done) {
this.UserWithAge.max('age').on('sql', function(sql) { this.UserWithAge.max('age').on('sql', function(sql) {
expect(sql).toBeDefined() expect(sql).toBeDefined()
......
...@@ -2,7 +2,7 @@ if (typeof require === 'function') { ...@@ -2,7 +2,7 @@ if (typeof require === 'function') {
const buster = require("buster") const buster = require("buster")
, Helpers = require('./buster-helpers') , Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect() , dialect = Helpers.getTestDialect()
, _ = require('underscore') , _ = require('lodash')
} }
buster.spec.expose() buster.spec.expose()
...@@ -31,10 +31,20 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -31,10 +31,20 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
aNumber: { type: DataTypes.INTEGER }, aNumber: { type: DataTypes.INTEGER },
aRandomId: { type: DataTypes.INTEGER } aRandomId: { type: DataTypes.INTEGER }
}) })
self.ParanoidUser = sequelize.define('ParanoidUser', {
username: { type: DataTypes.STRING }
}, {
paranoid: true
})
self.ParanoidUser.hasOne( self.ParanoidUser )
}, },
onComplete: function() { onComplete: function() {
self.User.sync({ force: true }).success(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)
})
}) })
} }
}) })
...@@ -494,6 +504,51 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -494,6 +504,51 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
}.bind(this)) }.bind(this))
}) })
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) { it("can reuse query option objects", function(done) {
this.User.create({ username: 'fnord' }).success(function() { this.User.create({ username: 'fnord' }).success(function() {
var query = { where: { username: 'fnord' }} var query = { where: { username: 'fnord' }}
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!