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

Commit cfb657ad by Martin Aspeli

Implementation sketch and query generators

1 parent 2029345b
...@@ -336,23 +336,75 @@ module.exports = (function() { ...@@ -336,23 +336,75 @@ module.exports = (function() {
* *
* The `success` handler is passed a list of newly inserted models. * The `success` handler is passed a list of newly inserted models.
*/ */
DaoFactory.prototype.bulkCreate = function(values, options) { DAOFactory.prototype.bulkCreate = function(values, options) {
var factory = this var factory = this
return this.bulkInsert(values.map(function(attrs) { return this.bulkInsert(values.map(function(attrs) {
return factory.build(attrs, options) return factory.build(attrs, options)
}) }))
} }
/** /**
* Insert multiple instances * Insert multiple instances
* *
* @param {Array} daos List of built DAOs * @param {Array} daos List of built DAOs
* @param {Array} fields Fields to insert (defaults to all fields)
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. * @return {Object} A promise which fires `success`, `error`, `complete` and `sql`.
* *
* The `success` handler is passed a list of newly inserted models. * The `success` handler is passed a list of newly inserted models.
*/ */
DaoFactory.prototype.bulkInsert = function(daos, fields) { DAOFactory.prototype.bulkInsert = function(daos, fields) {
// XXX: TODO var self = this
, records = []
, updatedAtAttr = self.options.underscored ? 'updated_at' : 'updatedAt'
, createdAtAttr = self.options.underscored ? 'created_at' : 'createdAt'
if (fields) {
if (self.options.timestamps) {
if (fields.indexOf(updatedAtAttr) === -1) {
fields.push(updatedAtAttr)
}
if (fields.indexOf(createdAtAttr) === -1) {
fields.push(createdAtAttr)
}
}
daos.forEach(function(dao) {
var values = {};
fields.forEach(function(field) {
if (dao.values[field] !== undefined) {
values[field] = dao.values[field]
}
})
records.push(values);
})
} else {
daos.forEach(function(dao) {
records.push(dao.values)
})
}
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(', '))
}
}
}
if (self.options.timestamps && dao.hasOwnProperty(updatedAtAttr)) {
dao[updatedAtAttr] = values[updatedAtAttr] = Utils.now()
}
})
return self.QueryInterface.bulkInsert(self.tableName, records)
} }
/** /**
...@@ -361,8 +413,15 @@ module.exports = (function() { ...@@ -361,8 +413,15 @@ 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.
* @return {Object} A promise which fires `success`, `error`, `complete` and `sql`. * @return {Object} A promise which fires `success`, `error`, `complete` and `sql`.
*/ */
DaoFactory.prototype.bulkDelete = function(options) { DAOFactory.prototype.bulkDelete = function(options) {
// XXX: TODO if (this.options.timestamps && this.options.paranoid) {
var attr = this.options.underscored ? 'deleted_at' : 'deletedAt'
var attrValueHash = {}
attrValueHash[attr] = new Date()
return this.QueryInterface.bulkUpdate(this.tableName, attrValueHash, options)
} else {
return this.QueryInterface.bulkDelete(this.tableName, options)
}
} }
// private // private
......
...@@ -244,7 +244,24 @@ module.exports = (function() { ...@@ -244,7 +244,24 @@ module.exports = (function() {
}, },
bulkInsertQuery: function(tableName, attrValueHashes) { bulkInsertQuery: function(tableName, attrValueHashes) {
throwMethodUndefined('bulkInsertQuery') var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;"
, 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 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) {
......
...@@ -328,7 +328,26 @@ module.exports = (function() { ...@@ -328,7 +328,26 @@ module.exports = (function() {
}, },
bulkInsertQuery: function(tableName, attrValueHashes) { bulkInsertQuery: function(tableName, attrValueHashes) {
throwMethodUndefined('bulkInsertQuery') var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %> RETURNING *;"
, tuples = []
Utils._.forEach(attrValueHashes, function(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) {
......
...@@ -126,7 +126,24 @@ module.exports = (function() { ...@@ -126,7 +126,24 @@ module.exports = (function() {
}, },
bulkInsertQuery: function(tableName, attrValueHashes) { bulkInsertQuery: function(tableName, attrValueHashes) {
throwMethodUndefined('bulkInsertQuery') 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) {
......
...@@ -272,6 +272,11 @@ module.exports = (function() { ...@@ -272,6 +272,11 @@ module.exports = (function() {
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')
......
...@@ -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}],
......
...@@ -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}],
......
...@@ -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 }],
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!