diff --git a/lib/data-types.js b/lib/data-types.js index 45516b0b8ae8..be98f83aa5e1 100644 --- a/lib/data-types.js +++ b/lib/data-types.js @@ -398,7 +398,13 @@ module.exports = { UUIDV4: 'UUIDV4', /** - * A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model + * A key / value column. Only available in postgres. + * @property HSTORE + */ + HSTORE: 'HSTORE', + + /** + * A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model * that is returned to the user but not stored in the DB. * * You could also use it to validate a value before permuting and storing it. Checking password length before hashing it for example: @@ -453,22 +459,5 @@ module.exports = { * An array of `type`, e.g. `DataTypes.ARRAY(DataTypes.DECIMAL)`. Only available in postgres. * @property ARRAY */ - ARRAY: function(type) { return type + '[]'; }, - - /** - * A key / value column. Only available in postgres. - * @property HSTORE - */ - get HSTORE() { - var result = function() { - return { - type: 'HSTORE' - }; - }; - - result.type = 'HSTORE'; - result.toString = result.valueOf = function() { return 'HSTORE'; }; - - return result; - } + ARRAY: function(type) { return type + '[]'; } }; diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index e8b576cc7da6..f2d5e9e2eadc 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -143,7 +143,9 @@ module.exports = (function() { /* Returns an insert into command. Parameters: table name + hash of attribute-value-pairs. */ - insertQuery: function(table, valueHash, modelAttributes) { + insertQuery: function(table, valueHash, modelAttributes, options) { + options = options || {}; + var query , valueQuery = 'INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>)' , emptyQuery = 'INSERT INTO <%= table %>' @@ -168,7 +170,7 @@ module.exports = (function() { emptyQuery += ' VALUES ()'; } - if (this._dialect.supports['RETURNING']) { + if (this._dialect.supports['RETURNING'] && options.returning) { valueQuery += ' RETURNING *'; emptyQuery += ' RETURNING *'; } @@ -235,7 +237,7 @@ module.exports = (function() { query += ' LIMIT ' + this.escape(options.limit) + ' '; } - if (this._dialect.supports['RETURNING'] && (options.returning || options.returning === undefined)) { + if (this._dialect.supports['RETURNING'] && options.returning) { query += ' RETURNING *'; } diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index ad09e560705d..cad904dda224 100644 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -255,11 +255,17 @@ module.exports = (function() { }, bulkInsertQuery: function(tableName, attrValueHashes, options, modelAttributes) { - var query = 'INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %> RETURNING *;' + options = options || {}; + + var query = 'INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>' , tuples = [] , serials = [] , allAttributes = []; + if (this._dialect.supports['RETURNING'] && options.returning) { + query += ' RETURNING *'; + } + Utils._.forEach(attrValueHashes, function(attrValueHash) { Utils._.forOwn(attrValueHash, function(value, key) { if (allAttributes.indexOf(key) === -1) { @@ -291,6 +297,8 @@ module.exports = (function() { , tuples: tuples.join(',') }; + query = query + ';'; + return Utils._.template(query)(replacements); }, @@ -815,7 +823,7 @@ module.exports = (function() { if (value && value._isSequelizeMethod) { return value.toString(this); } else { - if (field && field.type && field.type.toString() === DataTypes.HSTORE.type && Utils._.isObject(value)) { + if (Utils._.isObject(value) && field && (field === DataTypes.HSTORE || field.type === DataTypes.HSTORE)) { value = hstore.stringify(value); } diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index 53304474cda8..875ba46b7412 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -7,6 +7,16 @@ var Utils = require('../../utils') , QueryTypes = require('../../query-types') , Promise = require('../../promise'); +// Parses hstore fields if the model has any hstore fields. +// This cannot be done in the 'pg' lib because hstore is a UDT. +var parseHstoreFields = function(model, row) { + Utils._.keys(row).forEach(function(key) { + if (model._isHstoreAttribute(key)) { + row[key] = hstore.parse(row[key]); + } + }); +}; + module.exports = (function() { var Query = function(client, sequelize, callee, options) { this.client = client; @@ -125,15 +135,9 @@ module.exports = (function() { }); } - // Parse hstore fields if the model has any hstore fields. - // This cannot be done in the 'pg' lib because hstore is a UDT. if (!!self.callee && !!self.callee._hasHstoreAttributes) { rows.forEach(function(row) { - Utils._.keys(row).forEach(function(key) { - if (self.callee._isHstoreAttribute(key)) { - row[key] = hstore.parse(row[key]); - } - }); + parseHstoreFields(self.callee, row); }); } @@ -141,20 +145,34 @@ module.exports = (function() { } } else if (self.send('isShowOrDescribeQuery')) { return results; - } else if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].indexOf(self.options.type) !== -1) { + } else if (QueryTypes.BULKUPDATE === self.options.type) { + if (!self.options.returning) { + return result.rowCount; + } + + if (!!self.callee && !!self.callee._hasHstoreAttributes) { + rows.forEach(function(row) { + parseHstoreFields(self.callee, row); + }); + } + + return self.send('handleSelectQuery', rows); + } else if (QueryTypes.BULKDELETE === self.options.type) { return result.rowCount; } else if (self.send('isInsertQuery') || self.send('isUpdateQuery')) { - if (self.callee !== null) { // may happen for bulk inserts or bulk updates + if (!!self.callee && self.callee.dataValues) { + if (!!self.callee.Model && !!self.callee.Model._hasHstoreAttributes) { + parseHstoreFields(self.callee.Model, rows[0]); + } + for (var key in rows[0]) { if (rows[0].hasOwnProperty(key)) { var record = rows[0][key]; - if (!!self.callee.Model && !!self.callee.Model.rawAttributes && !!self.callee.Model.rawAttributes[key] && !!self.callee.Model.rawAttributes[key].type && self.callee.Model.rawAttributes[key].type.toString() === DataTypes.HSTORE.toString()) { - record = hstore.parse(record); - } var attr = Utils._.find(self.callee.Model.rawAttributes, function (attribute) { return attribute.fieldName === key || attribute.field === key; }); + self.callee.dataValues[attr && attr.fieldName || key] = record; } } diff --git a/lib/model.js b/lib/model.js index e1b5e05dc11b..eb9da97f0f1a 100644 --- a/lib/model.js +++ b/lib/model.js @@ -262,12 +262,13 @@ module.exports = (function() { this.Instance.prototype.validators = {}; Utils._.each(this.rawAttributes, function(definition, name) { - var type = definition.originalType || definition.type || definition; + var type = definition.originalType || definition.type || definition; + if (type === DataTypes.BOOLEAN) { self._booleanAttributes.push(name); } else if (type === DataTypes.DATE) { self._dateAttributes.push(name); - } else if (type === DataTypes.HSTORE.type) { + } else if (type === DataTypes.HSTORE) { self._hstoreAttributes.push(name); } else if (type === DataTypes.VIRTUAL) { self._virtualAttributes.push(name); @@ -1341,6 +1342,7 @@ module.exports = (function() { * @param {Boolean} [options.validate=true] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation * @param {Boolean} [options.hooks=true] Run before / after bulk update hooks? * @param {Boolean} [options.individualHooks=false] Run before / after update hooks? + * @param {Boolean} [options.returning=false] Return the affected rows (only for postgres) * @param {Number} [options.limit] How many rows to update (only for mysql and mariadb) * @deprecated The syntax is due for change, in order to make `where` more consistent with the rest of the API * @@ -1353,6 +1355,7 @@ module.exports = (function() { validate: true, hooks: true, individualHooks: false, + returning: false, force: false }, options || {}); @@ -1458,6 +1461,10 @@ module.exports = (function() { // Run query to update all rows return self.QueryInterface.bulkUpdate(self.getTableName(), attrValueHashUse, where, options, self.tableAttributes).then(function(affectedRows) { + if (options.returning) { + return [affectedRows.length, affectedRows]; + } + return [affectedRows]; }); }).tap(function(result) { diff --git a/lib/query-interface.js b/lib/query-interface.js index cde8f9091236..bec527cc6fd5 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -414,7 +414,7 @@ module.exports = (function() { }; QueryInterface.prototype.insert = function(dao, tableName, values, options) { - var sql = this.QueryGenerator.insertQuery(tableName, values, dao.Model.rawAttributes); + var sql = this.QueryGenerator.insertQuery(tableName, values, dao.Model.rawAttributes, options); return this.sequelize.query(sql, dao, options).then(function(result) { result.isNewRecord = false; @@ -448,9 +448,11 @@ module.exports = (function() { }; QueryInterface.prototype.bulkUpdate = function(tableName, values, identifier, options, attributes) { - var sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, attributes); + var sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, attributes) + , table = Utils._.isObject(tableName) ? tableName : { tableName: tableName } + , daoTable = Utils._.find(this.sequelize.daoFactoryManager.daos, { tableName: table.tableName }); - return this.sequelize.query(sql, null, options); + return this.sequelize.query(sql, daoTable, options); }; QueryInterface.prototype.delete = function(dao, tableName, identifier, options) { diff --git a/test/dao-factory.test.js b/test/dao-factory.test.js index 7b1d86d0ec2c..ecda6fbdc1c0 100644 --- a/test/dao-factory.test.js +++ b/test/dao-factory.test.js @@ -805,6 +805,32 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { }) }) + if (dialect === "postgres") { + it('returns the affected rows if `options.returning` is true', function(_done) { + var self = this + , data = [{ username: 'Peter', secretValue: '42' }, + { username: 'Paul', secretValue: '42' }, + { username: 'Bob', secretValue: '43' }] + , done = _.after(2, _done) + + this.User.bulkCreate(data).success(function() { + self.User.update({ username: 'Bill' }, { secretValue: '42' }, { returning: true }).spread(function(count, rows) { + expect(count).to.equal(2) + expect(rows).to.have.length(2) + + done() + }) + + self.User.update({ username: 'Bill'}, { secretValue: '44' }, { returning: true }).spread(function(count, rows) { + expect(count).to.equal(0) + expect(rows).to.have.length(0) + + done() + }) + }) + }) + } + if(Support.dialectIsMySQL()) { it('supports limit clause', function (done) { var self = this diff --git a/test/postgres/dao.test.js b/test/postgres/dao.test.js index 14a40bbbeea3..198c7dfbe062 100644 --- a/test/postgres/dao.test.js +++ b/test/postgres/dao.test.js @@ -260,7 +260,7 @@ if (dialect.match(/^postgres/)) { .create({ username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}}) .success(function(newUser) { // Check to see if the default value for an hstore field works - expect(newUser.document).to.deep.equal({default: 'value'}) + expect(newUser.document).to.deep.equal({ default: 'value' }) expect(newUser.settings).to.deep.equal({ created: { test: '"value"' }}) // Check to see if updating an hstore field works @@ -295,6 +295,22 @@ if (dialect.match(/^postgres/)) { .error(console.log) }) + it("should update hstore correctly and return the affected rows", function(done) { + var self = this + + this.User + .create({ username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}}) + .success(function(oldUser) { + // Update the user and check that the returned object's fields have been parsed by the hstore library + self.User.update({settings: {should: 'update', to: 'this', first: 'place'}}, oldUser.identifiers, { returning: true }).spread(function(count, users) { + expect(count).to.equal(1); + expect(users[0].settings).to.deep.equal({should: 'update', to: 'this', first: 'place'}) + done() + }) + }) + .error(console.log) + }) + it("should read hstore correctly", function(done) { var self = this var data = { username: 'user', email: ['foo@bar.com'], settings: { created: { test: '"value"' }}} diff --git a/test/postgres/query-generator.test.js b/test/postgres/query-generator.test.js index 99c82a1072c0..e1baddb181f8 100644 --- a/test/postgres/query-generator.test.js +++ b/test/postgres/query-generator.test.js @@ -454,108 +454,111 @@ if (dialect.match(/^postgres/)) { insertQuery: [ { arguments: ['myTable', {}], - expectation: "INSERT INTO \"myTable\" DEFAULT VALUES RETURNING *;" + expectation: "INSERT INTO \"myTable\" DEFAULT VALUES;" }, { arguments: ['myTable', {name: 'foo'}], - expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;" + expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo');" + }, { + arguments: ['myTable', {name: 'foo'}, {}, { returning: true }], + expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;", }, { arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}], - expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;') RETURNING *;" + expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;');" }, { arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}], - expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;" + expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00');" }, { arguments: ['myTable', {data: new Buffer('Sequelize') }], - expectation: "INSERT INTO \"myTable\" (\"data\") VALUES (E'\\\\x53657175656c697a65') RETURNING *;" + expectation: "INSERT INTO \"myTable\" (\"data\") VALUES (E'\\\\x53657175656c697a65');" }, { arguments: ['myTable', {name: 'foo', numbers: new Uint8Array([1,2,3])}], - expectation: "INSERT INTO \"myTable\" (\"name\",\"numbers\") VALUES ('foo',ARRAY[1,2,3]) RETURNING *;" + expectation: "INSERT INTO \"myTable\" (\"name\",\"numbers\") VALUES ('foo',ARRAY[1,2,3]);" }, { arguments: ['myTable', {name: 'foo', foo: 1}], - expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1) RETURNING *;" + expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1);" }, { arguments: ['myTable', {name: 'foo', nullValue: null}], - expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL) RETURNING *;" + expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL);" }, { arguments: ['myTable', {name: 'foo', nullValue: null}], - expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL) RETURNING *;", + expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL);", context: {options: {omitNull: false}} }, { arguments: ['myTable', {name: 'foo', nullValue: null}], - expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;", + expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo');", context: {options: {omitNull: true}} }, { arguments: ['myTable', {name: 'foo', nullValue: undefined}], - expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;", + expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo');", context: {options: {omitNull: true}} }, { arguments: [{tableName: 'myTable', schema: 'mySchema'}, {name: 'foo'}], - expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo') RETURNING *;" + expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo');" }, { arguments: [{tableName: 'myTable', schema: 'mySchema'}, {name: JSON.stringify({info: 'Look ma a " quote'})}], - expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('{\"info\":\"Look ma a \\\" quote\"}') RETURNING *;" + expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('{\"info\":\"Look ma a \\\" quote\"}');" }, { arguments: [{tableName: 'myTable', schema: 'mySchema'}, {name: "foo';DROP TABLE mySchema.myTable;"}], - expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;') RETURNING *;" + expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;');" }, { arguments: ['myTable', function (sequelize) { return { foo: sequelize.fn('NOW') } }], - expectation: "INSERT INTO \"myTable\" (\"foo\") VALUES (NOW()) RETURNING *;", + expectation: "INSERT INTO \"myTable\" (\"foo\") VALUES (NOW());", needsSequelize: true }, // Variants when quoteIdentifiers is false { arguments: ['myTable', {name: 'foo'}], - expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;", + expectation: "INSERT INTO myTable (name) VALUES ('foo');", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}], - expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;') RETURNING *;", + expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;');", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}], - expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;", + expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00');", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {name: 'foo', numbers: new Uint8Array([1,2,3])}], - expectation: "INSERT INTO myTable (name,numbers) VALUES ('foo',ARRAY[1,2,3]) RETURNING *;", + expectation: "INSERT INTO myTable (name,numbers) VALUES ('foo',ARRAY[1,2,3]);", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {name: 'foo', foo: 1}], - expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1) RETURNING *;", + expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1);", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {name: 'foo', nullValue: null}], - expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL) RETURNING *;", + expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL);", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {name: 'foo', nullValue: null}], - expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL) RETURNING *;", + expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL);", context: {options: {omitNull: false, quoteIdentifiers: false}} }, { arguments: ['myTable', {name: 'foo', nullValue: null}], - expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;", + expectation: "INSERT INTO myTable (name) VALUES ('foo');", context: {options: {omitNull: true, quoteIdentifiers: false}} }, { arguments: ['myTable', {name: 'foo', nullValue: undefined}], - expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;", + expectation: "INSERT INTO myTable (name) VALUES ('foo');", context: {options: {omitNull: true, quoteIdentifiers: false}} }, { arguments: [{tableName: 'myTable', schema: 'mySchema'}, {name: 'foo'}], - expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo') RETURNING *;", + expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo');", context: {options: {quoteIdentifiers: false}} }, { arguments: [{tableName: 'myTable', schema: 'mySchema'}, {name: JSON.stringify({info: 'Look ma a " quote'})}], - expectation: "INSERT INTO mySchema.myTable (name) VALUES ('{\"info\":\"Look ma a \\\" quote\"}') RETURNING *;", + expectation: "INSERT INTO mySchema.myTable (name) VALUES ('{\"info\":\"Look ma a \\\" quote\"}');", context: {options: {quoteIdentifiers: false}} }, { arguments: [{tableName: 'myTable', schema: 'mySchema'}, {name: "foo';DROP TABLE mySchema.myTable;"}], - expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;') RETURNING *;", + expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;');", context: {options: {quoteIdentifiers: false}} } @@ -564,86 +567,89 @@ if (dialect.match(/^postgres/)) { bulkInsertQuery: [ { arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]], - expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING *;" + expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar');" + }, { + arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}], { returning: true }], + 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 *;" + expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar');" }, { arguments: ['myTable', [{name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {name: 'bar', birthday: moment("2012-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}]], - expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00') RETURNING *;" + expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00');" }, { arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]], - expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1),('bar',2) RETURNING *;" + expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1),('bar',2);" }, { arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], - expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;" + expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL);" }, { arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], - expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;", + expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL);", 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 *;", + expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',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', nullValue: undefined}, {name: 'bar', nullValue: undefined}]], - expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;", + expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL);", context: {options: {omitNull: true}} // Note: As above }, { arguments: [{schema: 'mySchema', tableName: 'myTable'}, [{name: 'foo'}, {name: 'bar'}]], - expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING *;" + expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'),('bar');" }, { arguments: [{schema: 'mySchema', tableName: '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 *;" + expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('{\"info\":\"Look ma a \\\" quote\"}'),('{\"info\":\"Look ma another \\\" quote\"}');" }, { arguments: [{schema: 'mySchema', tableName: 'myTable'}, [{name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'bar'}]], - expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar') RETURNING *;" + expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar');" }, // Variants when quoteIdentifiers is false { arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]], - expectation: "INSERT INTO myTable (name) VALUES ('foo'),('bar') RETURNING *;", + expectation: "INSERT INTO myTable (name) VALUES ('foo'),('bar');", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]], - expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;'),('bar') RETURNING *;", + expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;'),('bar');", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', [{name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {name: 'bar', birthday: moment("2012-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}]], - expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00') RETURNING *;", + expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00');", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]], - expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1),('bar',2) RETURNING *;", + expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1),('bar',2);", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], - expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;", + expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], - expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;", + expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);", context: {options: {quoteIdentifiers: false, omitNull: false}}, }, { arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], - expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;", + expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);", context: {options: {omitNull: true, quoteIdentifiers: false}} // 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 *;", + expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL);", context: {options: {omitNull: true, quoteIdentifiers: false}} // Note: As above }, { arguments: [{schema: 'mySchema', tableName: 'myTable'}, [{name: 'foo'}, {name: 'bar'}]], - expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'),('bar') RETURNING *;", + expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'),('bar');", context: {options: {quoteIdentifiers: false}} }, { arguments: [{schema: 'mySchema', tableName: '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 *;", + expectation: "INSERT INTO mySchema.myTable (name) VALUES ('{\"info\":\"Look ma a \\\" quote\"}'),('{\"info\":\"Look ma another \\\" quote\"}');", context: {options: {quoteIdentifiers: false}} }, { arguments: [{schema: 'mySchema', tableName: 'myTable'}, [{name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'bar'}]], - expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar') RETURNING *;", + expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar');", context: {options: {quoteIdentifiers: false}} } ], @@ -651,47 +657,50 @@ if (dialect.match(/^postgres/)) { updateQuery: [ { arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {id: 2}], - expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *" + expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2" }, { arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, 2], - expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *" + expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2" }, { arguments: ['myTable', {bar: 2}, {name: 'foo'}], - expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *" + expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo'" + }, { + arguments: ['myTable', {bar: 2}, {name: 'foo'}, { returning: true }], + expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *", }, { arguments: ['myTable', {numbers: new Uint8Array([1,2,3])}, {name: 'foo'}], - expectation: "UPDATE \"myTable\" SET \"numbers\"=ARRAY[1,2,3] WHERE \"name\"='foo' RETURNING *" + expectation: "UPDATE \"myTable\" SET \"numbers\"=ARRAY[1,2,3] WHERE \"name\"='foo'" }, { arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}], - expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo' RETURNING *" + expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo'" }, { arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], - expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo' RETURNING *" + expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo'" }, { arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], - expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo' RETURNING *", + expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo'", context: {options: {omitNull: false}} }, { arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], - expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *", + expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo'", context: {options: {omitNull: true}} }, { arguments: ['myTable', {bar: 2, nullValue: undefined}, {name: 'foo'}], - expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *", + expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo'", context: {options: {omitNull: true}} }, { arguments: [{tableName: 'myTable', schema: 'mySchema'}, {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {id: 2}], - expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *" + expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2" }, { arguments: [{tableName: 'myTable', schema: 'mySchema'}, {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}], - expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo'';DROP TABLE mySchema.myTable;' WHERE \"name\"='foo' RETURNING *" + expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo'';DROP TABLE mySchema.myTable;' WHERE \"name\"='foo'" }, { arguments: ['myTable', function (sequelize) { return { bar: sequelize.fn('NOW') } }, {name: 'foo'}], - expectation: "UPDATE \"myTable\" SET \"bar\"=NOW() WHERE \"name\"='foo' RETURNING *", + expectation: "UPDATE \"myTable\" SET \"bar\"=NOW() WHERE \"name\"='foo'", needsSequelize: true }, { arguments: ['myTable', function (sequelize) { @@ -699,54 +708,54 @@ if (dialect.match(/^postgres/)) { bar: sequelize.col('foo') } }, {name: 'foo'}], - expectation: "UPDATE \"myTable\" SET \"bar\"=\"foo\" WHERE \"name\"='foo' RETURNING *", + expectation: "UPDATE \"myTable\" SET \"bar\"=\"foo\" WHERE \"name\"='foo'", needsSequelize: true }, // Variants when quoteIdentifiers is false { arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {id: 2}], - expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *", + expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, 2], - expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *", + expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {bar: 2}, {name: 'foo'}], - expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *", + expectation: "UPDATE myTable SET bar=2 WHERE name='foo'", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {numbers: new Uint8Array([1,2,3])}, {name: 'foo'}], - expectation: "UPDATE myTable SET numbers=ARRAY[1,2,3] WHERE name='foo' RETURNING *", + expectation: "UPDATE myTable SET numbers=ARRAY[1,2,3] WHERE name='foo'", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}], - expectation: "UPDATE myTable SET name='foo'';DROP TABLE myTable;' WHERE name='foo' RETURNING *", + expectation: "UPDATE myTable SET name='foo'';DROP TABLE myTable;' WHERE name='foo'", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], - expectation: "UPDATE myTable SET bar=2,nullValue=NULL WHERE name='foo' RETURNING *", + expectation: "UPDATE myTable SET bar=2,nullValue=NULL WHERE name='foo'", context: {options: {quoteIdentifiers: false}} }, { arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], - expectation: "UPDATE myTable SET bar=2,nullValue=NULL WHERE name='foo' RETURNING *", + expectation: "UPDATE myTable SET bar=2,nullValue=NULL WHERE name='foo'", context: {options: {omitNull: false, quoteIdentifiers: false}}, }, { arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], - expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *", + expectation: "UPDATE myTable SET bar=2 WHERE name='foo'", context: {options: {omitNull: true, quoteIdentifiers: false}}, }, { arguments: ['myTable', {bar: 2, nullValue: undefined}, {name: 'foo'}], - expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *", + expectation: "UPDATE myTable SET bar=2 WHERE name='foo'", context: {options: {omitNull: true, quoteIdentifiers: false}}, }, { arguments: [{schema: 'mySchema', tableName: 'myTable'}, {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {id: 2}], - expectation: "UPDATE mySchema.myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *", + expectation: "UPDATE mySchema.myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2", context: {options: {quoteIdentifiers: false}} }, { arguments: [{schema: 'mySchema', tableName: 'myTable'}, {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}], - expectation: "UPDATE mySchema.myTable SET name='foo'';DROP TABLE mySchema.myTable;' WHERE name='foo' RETURNING *", + expectation: "UPDATE mySchema.myTable SET name='foo'';DROP TABLE mySchema.myTable;' WHERE name='foo'", context: {options: {quoteIdentifiers: false}} } ],