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

Commit b3b9cc13 by Sascha Depold

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

2 parents 7e9d13de e772a0cc
...@@ -19,5 +19,4 @@ env: ...@@ -19,5 +19,4 @@ env:
language: node_js language: node_js
node_js: node_js:
- 0.8 - 0.8
\ No newline at end of file
\ No newline at end of file
...@@ -38,7 +38,7 @@ Also make sure to take a look at the examples in the repository. The website wil ...@@ -38,7 +38,7 @@ Also make sure to take a look at the examples in the repository. The website wil
- [Documentation](http://www.sequelizejs.com) - [Documentation](http://www.sequelizejs.com)
- [Twitter](http://twitter.com/sdepold) - [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) - [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) - [XING](https://www.xing.com/net/priec1b5cx/sequelize) (pretty much inactive, but you might want to name it on your profile)
...@@ -51,10 +51,11 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -51,10 +51,11 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- Transactions - Transactions
- Support for update of tables without primary key - Support for update of tables without primary key
- MariaDB support - MariaDB support
- Support for update and delete calls for whole tables without previous loading of instances - ~~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) - 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.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango
- Move validation of enum attribute value to validate method
- BLOB [#99](https://github.com/sequelize/sequelize/issues/99) - BLOB [#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 - ~~Support for foreign keys~~ Implemented in [#595](https://github.com/sequelize/sequelize/pull/595), thanks to @optilude
...@@ -70,6 +71,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -70,6 +71,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
### 2.0.0 ### 2.0.0
- ~~save datetimes in UTC~~ - ~~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
- add proper error message everywhere
## Collaboration 2.0 ## ## Collaboration 2.0 ##
......
# v1.7.0 # # v1.7.0 #
- [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). 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] 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] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics
...@@ -6,6 +8,8 @@ ...@@ -6,6 +8,8 @@
- [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] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude - [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
- [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
# 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
......
...@@ -156,11 +156,11 @@ module.exports = (function() { ...@@ -156,11 +156,11 @@ module.exports = (function() {
} }
// alias for findAll // alias for findAll
DAOFactory.prototype.all = function(options) { DAOFactory.prototype.all = function(options, queryOptions) {
return this.findAll(options) return this.findAll(options, queryOptions)
} }
DAOFactory.prototype.findAll = function(options) { DAOFactory.prototype.findAll = function(options, queryOptions) {
var hasJoin = false var hasJoin = false
var options = Utils._.clone(options) var options = Utils._.clone(options)
...@@ -177,10 +177,10 @@ module.exports = (function() { ...@@ -177,10 +177,10 @@ module.exports = (function() {
this.options.whereCollection = options.where || null this.options.whereCollection = options.where || null
} }
return this.QueryInterface.select(this, this.tableName, options, { return this.QueryInterface.select(this, this.tableName, options, Utils._.defaults({
type: 'SELECT', type: 'SELECT',
hasJoin: hasJoin hasJoin: hasJoin
}) }, queryOptions))
} }
//right now, the caller (has-many-double-linked) is in charge of the where clause //right now, the caller (has-many-double-linked) is in charge of the where clause
...@@ -199,9 +199,10 @@ module.exports = (function() { ...@@ -199,9 +199,10 @@ module.exports = (function() {
* *
* @param {Object} options Options to describe the scope of the search. * @param {Object} options Options to describe the scope of the search.
* @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 {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`. * @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 var hasJoin = false
// no options defined? // no options defined?
...@@ -253,11 +254,11 @@ module.exports = (function() { ...@@ -253,11 +254,11 @@ module.exports = (function() {
options.limit = 1 options.limit = 1
return this.QueryInterface.select(this, this.getTableName(), options, { return this.QueryInterface.select(this, this.getTableName(), options, Utils._.defaults({
plain: true, plain: true,
type: 'SELECT', type: 'SELECT',
hasJoin: hasJoin hasJoin: hasJoin
}) }, queryOptions))
} }
DAOFactory.prototype.count = function(options) { DAOFactory.prototype.count = function(options) {
...@@ -328,6 +329,110 @@ module.exports = (function() { ...@@ -328,6 +329,110 @@ module.exports = (function() {
}).run() }).run()
} }
/**
* 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 // private
var query = function() { var query = function() {
......
...@@ -150,7 +150,14 @@ module.exports = (function() { ...@@ -150,7 +150,14 @@ module.exports = (function() {
this[updatedAtAttr] = values[updatedAtAttr] = Utils.now() this[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) return this.QueryInterface.insert(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values)
} else { } else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : this.id; var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : this.id;
...@@ -211,7 +218,9 @@ module.exports = (function() { ...@@ -211,7 +218,9 @@ module.exports = (function() {
Utils._.each(self.values, function(value, field) { Utils._.each(self.values, function(value, field) {
// if field has validators // 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 // for each validator
Utils._.each(self.validators[field], function(details, validatorType) { Utils._.each(self.validators[field], function(details, validatorType) {
......
...@@ -243,6 +243,25 @@ module.exports = (function() { ...@@ -243,6 +243,25 @@ module.exports = (function() {
return query return query
}, },
bulkInsertQuery: function(tableName, attrValueHashes) {
var tuples = []
Utils._.forEach(attrValueHashes, function(attrValueHash) {
tuples.push("(" +
Utils._.values(attrValueHash).map(function(value){
return Utils.escape((value instanceof Date) ? Utils.toSqlDate(value) : value)
}).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) { updateQuery: function(tableName, attrValueHash, where) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
...@@ -264,13 +283,31 @@ module.exports = (function() { ...@@ -264,13 +283,31 @@ module.exports = (function() {
deleteQuery: function(tableName, where, options) { deleteQuery: function(tableName, where, options) {
options = options || {} options = options || {}
options.limit = options.limit || 1
var table = QueryGenerator.addQuotes(tableName) var table = QueryGenerator.addQuotes(tableName)
var where = QueryGenerator.getWhereConditions(where) var where = QueryGenerator.getWhereConditions(where)
var limit = Utils.escape(options.limit) var limit = ""
if(Utils._.isUndefined(options.limit)) {
options.limit = 1;
}
if(!!options.limit) {
limit = " LIMIT " + Utils.escape(options.limit)
}
var query = "DELETE FROM " + table + " WHERE " + where + " LIMIT " + limit var query = "DELETE FROM " + table + " WHERE " + where + limit
return query
},
bulkDeleteQuery: function(tableName, where, options) {
options = options || {}
var table = QueryGenerator.addQuotes(tableName)
var where = QueryGenerator.getWhereConditions(where)
var query = "DELETE FROM " + table + " WHERE " + where
return query return query
}, },
...@@ -286,7 +323,7 @@ module.exports = (function() { ...@@ -286,7 +323,7 @@ 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 table = QueryGenerator.addQuotes(tableName) var table = QueryGenerator.addQuotes(tableName)
var values = values.join(",") var values = values.join(",")
var where = QueryGenerator.getWhereConditions(where) var where = QueryGenerator.getWhereConditions(where)
......
...@@ -301,18 +301,7 @@ module.exports = (function() { ...@@ -301,18 +301,7 @@ module.exports = (function() {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;" 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 = { var replacements = {
table: QueryGenerator.addQuotes(tableName) table: QueryGenerator.addQuotes(tableName)
...@@ -327,6 +316,30 @@ module.exports = (function() { ...@@ -327,6 +316,30 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
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) { updateQuery: function(tableName, attrValueHash, where) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
...@@ -349,11 +362,14 @@ module.exports = (function() { ...@@ -349,11 +362,14 @@ module.exports = (function() {
deleteQuery: function(tableName, where, options) { deleteQuery: function(tableName, where, options) {
options = options || {} options = options || {}
options.limit = options.limit || 1
if(Utils._.isUndefined(options.limit)) {
options.limit = 1;
}
primaryKeys[tableName] = primaryKeys[tableName] || []; 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 %>)"
var pks; var pks;
if (primaryKeys[tableName] && primaryKeys[tableName].length > 0) { if (primaryKeys[tableName] && primaryKeys[tableName].length > 0) {
...@@ -367,7 +383,7 @@ module.exports = (function() { ...@@ -367,7 +383,7 @@ module.exports = (function() {
var replacements = { var replacements = {
table: QueryGenerator.addQuotes(tableName), table: QueryGenerator.addQuotes(tableName),
where: QueryGenerator.getWhereConditions(where), where: QueryGenerator.getWhereConditions(where),
limit: QueryGenerator.pgEscape(options.limit), limit: !!options.limit? " LIMIT " + QueryGenerator.pgEscape(options.limit) : "",
primaryKeys: primaryKeys[tableName].length > 1 ? '(' + pks + ')' : pks, primaryKeys: primaryKeys[tableName].length > 1 ? '(' + pks + ')' : pks,
primaryKeysSelection: pks primaryKeysSelection: pks
} }
...@@ -721,5 +737,22 @@ module.exports = (function() { ...@@ -721,5 +737,22 @@ module.exports = (function() {
} }
} }
// 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) return Utils._.extend(Utils._.clone(require("../query-generator")), QueryGenerator)
})() })()
...@@ -108,27 +108,29 @@ module.exports = (function() { ...@@ -108,27 +108,29 @@ module.exports = (function() {
} else if (this.send('isShowOrDescribeQuery')) { } else if (this.send('isShowOrDescribeQuery')) {
this.emit('success', results) this.emit('success', results)
} else if (this.send('isInsertQuery')) { } else if (this.send('isInsertQuery')) {
for (var key in rows[0]) { if(this.callee !== null) { // may happen for bulk inserts
if (rows[0].hasOwnProperty(key)) { for (var key in rows[0]) {
var record = rows[0][key] if (rows[0].hasOwnProperty(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) { var record = rows[0][key]
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record) 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
} }
} }
this.emit('success', this.callee) this.emit('success', this.callee)
} else if (this.send('isUpdateQuery')) { } else if (this.send('isUpdateQuery')) {
for (var key in rows[0]) { if(this.callee !== null) { // may happen for bulk updates
if (rows[0].hasOwnProperty(key)) { for (var key in rows[0]) {
var record = rows[0][key] if (rows[0].hasOwnProperty(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) { var record = rows[0][key]
record = this.callee.daoFactory.daoFactoryManager.sequelize.queryInterface.QueryGenerator.toHstore(record) 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
} }
} }
......
...@@ -119,6 +119,14 @@ module.exports = (function() { ...@@ -119,6 +119,14 @@ 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. Returns an update query.
Parameters: Parameters:
- tableName -> Name of the table - tableName -> Name of the table
...@@ -148,6 +156,19 @@ module.exports = (function() { ...@@ -148,6 +156,19 @@ module.exports = (function() {
}, },
/* /*
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. Returns an update query.
Parameters: Parameters:
- tableName -> Name of the table - tableName -> Name of the table
......
...@@ -125,6 +125,27 @@ module.exports = (function() { ...@@ -125,6 +125,27 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
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)
},
updateQuery: function(tableName, attrValueHash, where) { updateQuery: function(tableName, attrValueHash, where) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
...@@ -151,8 +172,7 @@ module.exports = (function() { ...@@ -151,8 +172,7 @@ module.exports = (function() {
var query = "DELETE FROM <%= table %> WHERE <%= where %>" var query = "DELETE FROM <%= table %> WHERE <%= where %>"
var replacements = { var replacements = {
table: Utils.addTicks(tableName), table: Utils.addTicks(tableName),
where: this.getWhereConditions(where), where: MySqlQueryGenerator.getWhereConditions(where)
limit: Utils.escape(options.limit)
} }
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
......
...@@ -260,16 +260,31 @@ module.exports = (function() { ...@@ -260,16 +260,31 @@ module.exports = (function() {
}) })
} }
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) { QueryInterface.prototype.update = function(dao, tableName, values, identifier) {
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier) var sql = this.QueryGenerator.updateQuery(tableName, values, identifier)
return queryAndEmit.call(this, [sql, dao], 'update') return queryAndEmit.call(this, [sql, dao], 'update')
} }
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) { QueryInterface.prototype.delete = function(dao, tableName, identifier) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier) var sql = this.QueryGenerator.deleteQuery(tableName, identifier)
return queryAndEmit.call(this, [sql, dao], 'delete') return queryAndEmit.call(this, [sql, dao], 'delete')
} }
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) { QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) {
options = options || {} options = options || {}
......
...@@ -23,16 +23,16 @@ ...@@ -23,16 +23,16 @@
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/sdepold/sequelize.git" "url": "https://github.com/sequelize/sequelize.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/sdepold/sequelize/issues" "url": "https://github.com/sequelize/sequelize/issues"
}, },
"dependencies": { "dependencies": {
"lodash": "~1.2.1", "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": "1.1.1",
"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",
......
...@@ -200,6 +200,37 @@ describe('QueryGenerator', function() { ...@@ -200,6 +200,37 @@ describe('QueryGenerator', function() {
} }
], ],
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
}
],
updateQuery: [ updateQuery: [
{ {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}],
...@@ -240,6 +271,9 @@ describe('QueryGenerator', function() { ...@@ -240,6 +271,9 @@ describe('QueryGenerator', function() {
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;' LIMIT 10" 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'"
} }
], ],
......
...@@ -208,6 +208,46 @@ describe('QueryGenerator', function() { ...@@ -208,6 +208,46 @@ describe('QueryGenerator', function() {
} }
], ],
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: [ updateQuery: [
{ {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}],
...@@ -264,6 +304,9 @@ describe('QueryGenerator', function() { ...@@ -264,6 +304,9 @@ describe('QueryGenerator', function() {
}, { }, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {limit: 10}], arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {limit: 10}],
expectation: "DELETE FROM \"mySchema\".\"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo'';DROP TABLE mySchema.myTable;' LIMIT 10)" 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')"
} }
], ],
......
...@@ -121,6 +121,43 @@ describe('QueryGenerator', function() { ...@@ -121,6 +121,43 @@ describe('QueryGenerator', function() {
} }
], ],
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: [ updateQuery: [
{ {
arguments: ['myTable', { name: 'foo' }, { id: 2 }], arguments: ['myTable', { name: 'foo' }, { id: 2 }],
...@@ -152,6 +189,25 @@ describe('QueryGenerator', function() { ...@@ -152,6 +189,25 @@ describe('QueryGenerator', function() {
expectation: "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'", expectation: "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'",
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
} }
],
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'"
}
] ]
}; };
......
...@@ -20,6 +20,18 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -20,6 +20,18 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER }, aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER }, 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: { dateAllowNullTrue: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: true allowNull: true
...@@ -378,6 +390,44 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -378,6 +390,44 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
}) })
describe('save', function() { 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) { it('takes zero into account', function(done) {
this.User.build({ aNumber: 0 }).save([ 'aNumber' ]).success(function(user) { this.User.build({ aNumber: 0 }).save([ 'aNumber' ]).success(function(user) {
expect(user.aNumber).toEqual(0) expect(user.aNumber).toEqual(0)
......
...@@ -42,6 +42,10 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -42,6 +42,10 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
fail: "abc", fail: "abc",
pass: "129.89.23.1" pass: "129.89.23.1"
} }
, isIPv6 : {
fail: '1111:2222:3333::5555:',
pass: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156'
}
, isAlpha : { , isAlpha : {
fail: "012", fail: "012",
pass: "abc" pass: "abc"
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!