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

Commit bce3d9aa by Gareth Oakley Committed by Sushant

feat(query-generator): Generate INSERTs using bind parameters (#9431)

1 parent b05abdd4
...@@ -33,6 +33,12 @@ ABSTRACT.prototype.stringify = function stringify(value, options) { ...@@ -33,6 +33,12 @@ ABSTRACT.prototype.stringify = function stringify(value, options) {
} }
return value; return value;
}; };
ABSTRACT.prototype.bindParam = function bindParam(value, options) {
if (this._bindParam) {
return this._bindParam(value, options);
}
return options.bindParam(this.stringify(value, options));
};
function STRING(length, binary) { function STRING(length, binary) {
const options = typeof length === 'object' && length || {length, binary}; const options = typeof length === 'object' && length || {length, binary};
...@@ -274,16 +280,22 @@ DECIMAL.prototype.validate = function validate(value) { ...@@ -274,16 +280,22 @@ DECIMAL.prototype.validate = function validate(value) {
for (const floating of [FLOAT, DOUBLE, REAL]) { for (const floating of [FLOAT, DOUBLE, REAL]) {
floating.prototype.escape = false; floating.prototype.escape = false;
floating.prototype._stringify = function _stringify(value) { floating.prototype._value = function _value(value) {
if (isNaN(value)) { if (isNaN(value)) {
return "'NaN'"; return 'NaN';
} else if (!isFinite(value)) { } else if (!isFinite(value)) {
const sign = value < 0 ? '-' : ''; const sign = value < 0 ? '-' : '';
return "'" + sign + "Infinity'"; return sign + 'Infinity';
} }
return value; return value;
}; };
floating.prototype._stringify = function _stringify(value) {
return "'" + this._value(value) + "'";
};
floating.prototype._bindParam = function _bindParam(value, options) {
return options.bindParam(this._value(value));
};
} }
function BOOLEAN() { function BOOLEAN() {
...@@ -529,6 +541,17 @@ BLOB.prototype._hexify = function _hexify(hex) { ...@@ -529,6 +541,17 @@ BLOB.prototype._hexify = function _hexify(hex) {
return "X'" + hex + "'"; return "X'" + hex + "'";
}; };
BLOB.prototype._bindParam = function _bindParam(value, options) {
if (!Buffer.isBuffer(value)) {
if (Array.isArray(value)) {
value = Buffer.from(value);
} else {
value = Buffer.from(value.toString());
}
}
return options.bindParam(value);
};
function RANGE(subtype) { function RANGE(subtype) {
const options = _.isPlainObject(subtype) ? subtype : {subtype}; const options = _.isPlainObject(subtype) ? subtype : {subtype};
...@@ -703,6 +726,9 @@ GEOMETRY.prototype.escape = false; ...@@ -703,6 +726,9 @@ GEOMETRY.prototype.escape = false;
GEOMETRY.prototype._stringify = function _stringify(value, options) { GEOMETRY.prototype._stringify = function _stringify(value, options) {
return 'GeomFromText(' + options.escape(Wkt.convert(value)) + ')'; return 'GeomFromText(' + options.escape(Wkt.convert(value)) + ')';
}; };
GEOMETRY.prototype._bindParam = function _bindParam(value, options) {
return 'GeomFromText(' + options.bindParam(Wkt.convert(value)) + ')';
};
function GEOGRAPHY(type, srid) { function GEOGRAPHY(type, srid) {
const options = _.isPlainObject(type) ? type : {type, srid}; const options = _.isPlainObject(type) ? type : {type, srid};
...@@ -721,6 +747,9 @@ GEOGRAPHY.prototype.escape = false; ...@@ -721,6 +747,9 @@ GEOGRAPHY.prototype.escape = false;
GEOGRAPHY.prototype._stringify = function _stringify(value, options) { GEOGRAPHY.prototype._stringify = function _stringify(value, options) {
return 'GeomFromText(' + options.escape(Wkt.convert(value)) + ')'; return 'GeomFromText(' + options.escape(Wkt.convert(value)) + ')';
}; };
GEOGRAPHY.prototype._bindParam = function _bindParam(value, options) {
return 'GeomFromText(' + options.bindParam(Wkt.convert(value)) + ')';
};
for (const helper of Object.keys(helpers)) { for (const helper of Object.keys(helpers)) {
for (const DataType of helpers[helper]) { for (const DataType of helpers[helper]) {
......
...@@ -106,6 +106,8 @@ class QueryGenerator { ...@@ -106,6 +106,8 @@ class QueryGenerator {
const modelAttributeMap = {}; const modelAttributeMap = {};
const fields = []; const fields = [];
const values = []; const values = [];
const bind = [];
const bindParam = this.bindParam(bind);
let query; let query;
let valueQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>)<%= output %> VALUES (<%= values %>)'; let valueQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>)<%= output %> VALUES (<%= values %>)';
let emptyQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %><%= output %>'; let emptyQuery = '<%= tmpTable %>INSERT<%= ignoreDuplicates %> INTO <%= table %><%= output %>';
...@@ -169,7 +171,14 @@ class QueryGenerator { ...@@ -169,7 +171,14 @@ class QueryGenerator {
} }
} }
if (_.get(this, ['sequelize', 'options', 'dialectOptions', 'prependSearchPath']) || options.searchPath) {
// Not currently supported with search path (requires output of multiple queries)
options.bindParam = false;
}
if (this._dialect.supports.EXCEPTION && options.exception) { if (this._dialect.supports.EXCEPTION && options.exception) {
// Not currently supported with bind parameters (requires output of multiple queries)
options.bindParam = false;
// Mostly for internal use, so we expect the user to know what he's doing! // Mostly for internal use, so we expect the user to know what he's doing!
// pg_temp functions are private per connection, so we never risk this function interfering with another one. // pg_temp functions are private per connection, so we never risk this function interfering with another one.
if (semver.gte(this.sequelize.options.databaseVersion, '9.2.0')) { if (semver.gte(this.sequelize.options.databaseVersion, '9.2.0')) {
...@@ -211,7 +220,11 @@ class QueryGenerator { ...@@ -211,7 +220,11 @@ class QueryGenerator {
identityWrapperRequired = true; identityWrapperRequired = true;
} }
if (value instanceof Utils.SequelizeMethod || options.bindParam === false) {
values.push(this.escape(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'INSERT' })); values.push(this.escape(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'INSERT' }));
} else {
values.push(this.format(value, modelAttributeMap && modelAttributeMap[key] || undefined, { context: 'INSERT' }, bindParam));
}
} }
} }
} }
...@@ -234,7 +247,13 @@ class QueryGenerator { ...@@ -234,7 +247,13 @@ class QueryGenerator {
].join(' '); ].join(' ');
} }
return _.template(query, this._templateSettings)(replacements); query = _.template(query, this._templateSettings)(replacements);
// Used by Postgres upsertQuery and calls to here with options.exception set to true
const result = { query };
if (options.bindParam !== false) {
result.bind = bind;
}
return result;
} }
/** /**
...@@ -967,15 +986,7 @@ class QueryGenerator { ...@@ -967,15 +986,7 @@ class QueryGenerator {
return this.handleSequelizeMethod(value); return this.handleSequelizeMethod(value);
} else { } else {
if (field && field.type) { if (field && field.type) {
if (this.typeValidation && field.type.validate && value) { this.validate(value, field, options);
if (options.isList && Array.isArray(value)) {
for (const item of value) {
field.type.validate(item, options);
}
} else {
field.type.validate(value, options);
}
}
if (field.type.stringify) { if (field.type.stringify) {
// Users shouldn't have to worry about these args - just give them a function that takes a single arg // Users shouldn't have to worry about these args - just give them a function that takes a single arg
...@@ -995,6 +1006,53 @@ class QueryGenerator { ...@@ -995,6 +1006,53 @@ class QueryGenerator {
return SqlString.escape(value, this.options.timezone, this.dialect); return SqlString.escape(value, this.options.timezone, this.dialect);
} }
bindParam(bind) {
return value => {
bind.push(value);
return '$' + bind.length;
};
}
/*
Returns a bind parameter representation of a value (e.g. a string, number or date)
@private
*/
format(value, field, options, bindParam) {
options = options || {};
if (value !== null && value !== undefined) {
if (value instanceof Utils.SequelizeMethod) {
throw new Error('Cannot pass SequelizeMethod as a bind parameter - use escape instead');
} else {
if (field && field.type) {
this.validate(value, field, options);
if (field.type.bindParam) {
return field.type.bindParam(value, { escape: _.identity, field, timezone: this.options.timezone, operation: options.operation, bindParam });
}
}
}
}
return bindParam(value);
}
/*
Validate a value against a field specification
@private
*/
validate(value, field, options) {
if (this.typeValidation && field.type.validate && value) {
if (options.isList && Array.isArray(value)) {
for (const item of value) {
field.type.validate(item, options);
}
} else {
field.type.validate(value, options);
}
}
}
isIdentifierQuoted(identifier) { isIdentifierQuoted(identifier) {
return QuoteHelper.isIdentifierQuoted(identifier); return QuoteHelper.isIdentifierQuoted(identifier);
} }
......
...@@ -41,6 +41,9 @@ class Query extends AbstractQuery { ...@@ -41,6 +41,9 @@ class Query extends AbstractQuery {
paramType.typeOptions = {precision: 30, scale: 15}; paramType.typeOptions = {precision: 30, scale: 15};
} }
} }
if (Buffer.isBuffer(value)) {
paramType.type = TYPES.VarBinary;
}
return paramType; return paramType;
} }
......
...@@ -433,6 +433,9 @@ module.exports = BaseTypes => { ...@@ -433,6 +433,9 @@ module.exports = BaseTypes => {
GEOMETRY.prototype._stringify = function _stringify(value, options) { GEOMETRY.prototype._stringify = function _stringify(value, options) {
return 'ST_GeomFromGeoJSON(' + options.escape(JSON.stringify(value)) + ')'; return 'ST_GeomFromGeoJSON(' + options.escape(JSON.stringify(value)) + ')';
}; };
GEOMETRY.prototype._bindParam = function _bindParam(value, options) {
return 'ST_GeomFromGeoJSON(' + options.bindParam(value) + ')';
};
function GEOGRAPHY(type, srid) { function GEOGRAPHY(type, srid) {
if (!(this instanceof GEOGRAPHY)) return new GEOGRAPHY(type, srid); if (!(this instanceof GEOGRAPHY)) return new GEOGRAPHY(type, srid);
...@@ -469,6 +472,9 @@ module.exports = BaseTypes => { ...@@ -469,6 +472,9 @@ module.exports = BaseTypes => {
GEOGRAPHY.prototype._stringify = function _stringify(value, options) { GEOGRAPHY.prototype._stringify = function _stringify(value, options) {
return 'ST_GeomFromGeoJSON(' + options.escape(JSON.stringify(value)) + ')'; return 'ST_GeomFromGeoJSON(' + options.escape(JSON.stringify(value)) + ')';
}; };
GEOGRAPHY.prototype.bindParam = function bindParam(value, options) {
return 'ST_GeomFromGeoJSON(' + options.bindParam(value) + ')';
};
let hstore; let hstore;
function HSTORE() { function HSTORE() {
...@@ -491,12 +497,18 @@ module.exports = BaseTypes => { ...@@ -491,12 +497,18 @@ module.exports = BaseTypes => {
}; };
HSTORE.prototype.escape = false; HSTORE.prototype.escape = false;
HSTORE.prototype._stringify = function _stringify(value) { HSTORE.prototype._value = function _value(value) {
if (!hstore) { if (!hstore) {
// All datatype files are loaded at import - make sure we don't load the hstore parser before a hstore is instantiated // All datatype files are loaded at import - make sure we don't load the hstore parser before a hstore is instantiated
hstore = require('./hstore'); hstore = require('./hstore');
} }
return "'" + hstore.stringify(value) + "'"; return hstore.stringify(value);
};
HSTORE.prototype._stringify = function _stringify(value) {
return "'" + this._value(value) + "'";
};
HSTORE.prototype._bindParam = function _bindParam(value, options) {
return options.bindParam(this._value(value));
}; };
BaseTypes.HSTORE.types.postgres = { BaseTypes.HSTORE.types.postgres = {
...@@ -533,10 +545,9 @@ module.exports = BaseTypes => { ...@@ -533,10 +545,9 @@ module.exports = BaseTypes => {
}; };
RANGE.prototype.escape = false; RANGE.prototype.escape = false;
RANGE.prototype._stringify = function _stringify(values, options) { RANGE.prototype._value = function _value(values, options) {
if (!Array.isArray(values)) { if (!Array.isArray(values)) {
return "'" + this.options.subtype.stringify(values, options) + "'::" + return this.options.subtype.stringify(values, options);
this.toCastType();
} }
const valueInclusivity = [true, false]; const valueInclusivity = [true, false];
...@@ -560,7 +571,21 @@ module.exports = BaseTypes => { ...@@ -560,7 +571,21 @@ module.exports = BaseTypes => {
// Array.map does not preserve extra array properties // Array.map does not preserve extra array properties
valuesStringified.inclusive = valueInclusivity; valuesStringified.inclusive = valueInclusivity;
return '\'' + range.stringify(valuesStringified) + '\''; return range.stringify(valuesStringified);
};
RANGE.prototype._stringify = function _stringify(values, options) {
const value = this._value(values, options);
if (!Array.isArray(values)) {
return `'${value}'::` + this.toCastType();
}
return `'${value}'`;
};
RANGE.prototype._bindParam = function _bindParam(values, options) {
const value = this._value(values, options);
if (!Array.isArray(values)) {
return options.bindParam(value) + '::' + this.toCastType();
}
return options.bindParam(value);
}; };
BaseTypes.RANGE.types.postgres = { BaseTypes.RANGE.types.postgres = {
...@@ -569,8 +594,11 @@ module.exports = BaseTypes => { ...@@ -569,8 +594,11 @@ module.exports = BaseTypes => {
}; };
BaseTypes.ARRAY.prototype.escape = false; BaseTypes.ARRAY.prototype.escape = false;
BaseTypes.ARRAY.prototype._stringify = function _stringify(values, options) { BaseTypes.ARRAY.prototype._value = function _value(values, options) {
let str = 'ARRAY[' + values.map(value => { return values.map(value => {
if (options && options.bindParam && this.type && this.type._value) {
return this.type._value(value, options);
}
if (this.type && this.type.stringify) { if (this.type && this.type.stringify) {
value = this.type.stringify(value, options); value = this.type.stringify(value, options);
...@@ -579,7 +607,10 @@ module.exports = BaseTypes => { ...@@ -579,7 +607,10 @@ module.exports = BaseTypes => {
} }
} }
return options.escape(value); return options.escape(value);
}, this).join(',') + ']'; }, this);
};
BaseTypes.ARRAY.prototype._stringify = function _stringify(values, options) {
let str = 'ARRAY[' + this._value(values, options).join(',') + ']';
if (this.type) { if (this.type) {
const Utils = require('../../utils'); const Utils = require('../../utils');
...@@ -597,6 +628,9 @@ module.exports = BaseTypes => { ...@@ -597,6 +628,9 @@ module.exports = BaseTypes => {
return str; return str;
}; };
BaseTypes.ARRAY.prototype._bindParam = function _bindParam(values, options) {
return options.bindParam(this._value(values, options));
};
function ENUM(options) { function ENUM(options) {
if (!(this instanceof ENUM)) return new ENUM(options); if (!(this instanceof ENUM)) return new ENUM(options);
......
...@@ -344,17 +344,18 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { ...@@ -344,17 +344,18 @@ class PostgresQueryGenerator extends AbstractQueryGenerator {
upsertQuery(tableName, insertValues, updateValues, where, model, options) { upsertQuery(tableName, insertValues, updateValues, where, model, options) {
const primaryField = this.quoteIdentifier(model.primaryKeyField); const primaryField = this.quoteIdentifier(model.primaryKeyField);
let insert = this.insertQuery(tableName, insertValues, model.rawAttributes, options); const insertOptions = _.defaults({ bindParam: false }, options);
const insert = this.insertQuery(tableName, insertValues, model.rawAttributes, insertOptions);
let update = this.updateQuery(tableName, updateValues, where, options, model.rawAttributes); let update = this.updateQuery(tableName, updateValues, where, options, model.rawAttributes);
insert = insert.replace('RETURNING *', `RETURNING ${primaryField} INTO primary_key`); insert.query = insert.query.replace('RETURNING *', `RETURNING ${primaryField} INTO primary_key`);
update = update.replace('RETURNING *', `RETURNING ${primaryField} INTO primary_key`); update = update.replace('RETURNING *', `RETURNING ${primaryField} INTO primary_key`);
return this.exceptionFn( return this.exceptionFn(
'sequelize_upsert', 'sequelize_upsert',
tableName, tableName,
'OUT created boolean, OUT primary_key text', 'OUT created boolean, OUT primary_key text',
`${insert} created := true;`, `${insert.query} created := true;`,
`${update}; created := false` `${update}; created := false`
); );
} }
......
...@@ -200,9 +200,13 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { ...@@ -200,9 +200,13 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator {
upsertQuery(tableName, insertValues, updateValues, where, model, options) { upsertQuery(tableName, insertValues, updateValues, where, model, options) {
options.ignoreDuplicates = true; options.ignoreDuplicates = true;
const sql = this.insertQuery(tableName, insertValues, model.rawAttributes, options) + ' ' + this.updateQuery(tableName, updateValues, where, options, model.rawAttributes); const insert = this.insertQuery(tableName, insertValues, model.rawAttributes, options);
const update = this.updateQuery(tableName, updateValues, where, options, model.rawAttributes);
return sql; const query = insert.query + ' ' + update;
const bind = insert.bind;
return { query, bind };
} }
updateQuery(tableName, attrValueHash, where, options, attributes) { updateQuery(tableName, attrValueHash, where, options, attributes) {
......
...@@ -57,7 +57,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { ...@@ -57,7 +57,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => {
}); });
}); });
const testSuccess = function(Type, value) { const testSuccess = function(Type, value, options) {
const parse = Type.constructor.parse = sinon.spy(value => { const parse = Type.constructor.parse = sinon.spy(value => {
return value; return value;
}); });
...@@ -65,6 +65,12 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { ...@@ -65,6 +65,12 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => {
const stringify = Type.constructor.prototype.stringify = sinon.spy(function() { const stringify = Type.constructor.prototype.stringify = sinon.spy(function() {
return Sequelize.ABSTRACT.prototype.stringify.apply(this, arguments); return Sequelize.ABSTRACT.prototype.stringify.apply(this, arguments);
}); });
let bindParam;
if (options && options.useBindParam) {
bindParam = Type.constructor.prototype.bindParam = sinon.spy(function() {
return Sequelize.ABSTRACT.prototype.bindParam.apply(this, arguments);
});
}
const User = current.define('user', { const User = current.define('user', {
field: Type field: Type
...@@ -83,10 +89,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { ...@@ -83,10 +89,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => {
return User.findAll().get(0); return User.findAll().get(0);
}).then(() => { }).then(() => {
expect(parse).to.have.been.called; expect(parse).to.have.been.called;
if (options && options.useBindParam) {
expect(bindParam).to.have.been.called;
} else {
expect(stringify).to.have.been.called; expect(stringify).to.have.been.called;
}
delete Type.constructor.parse; delete Type.constructor.parse;
delete Type.constructor.prototype.stringify; delete Type.constructor.prototype.stringify;
if (options && options.useBindParam) {
delete Type.constructor.prototype.bindParam;
}
}); });
}; };
...@@ -117,18 +130,18 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { ...@@ -117,18 +130,18 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => {
} }
if (current.dialect.supports.HSTORE) { if (current.dialect.supports.HSTORE) {
it('calls parse and stringify for HSTORE', () => { it('calls parse and bindParam for HSTORE', () => {
const Type = new Sequelize.HSTORE(); const Type = new Sequelize.HSTORE();
return testSuccess(Type, { test: 42, nested: false }); return testSuccess(Type, { test: 42, nested: false }, { useBindParam: true });
}); });
} }
if (current.dialect.supports.RANGE) { if (current.dialect.supports.RANGE) {
it('calls parse and stringify for RANGE', () => { it('calls parse and bindParam for RANGE', () => {
const Type = new Sequelize.RANGE(new Sequelize.INTEGER()); const Type = new Sequelize.RANGE(new Sequelize.INTEGER());
return testSuccess(Type, [1, 2]); return testSuccess(Type, [1, 2], { useBindParam: true });
}); });
} }
...@@ -147,13 +160,13 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { ...@@ -147,13 +160,13 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => {
it('calls parse and stringify for TIME', () => { it('calls parse and stringify for TIME', () => {
const Type = new Sequelize.TIME(); const Type = new Sequelize.TIME();
return testSuccess(Type, new Date()); return testSuccess(Type, moment(new Date()).format('HH:mm:ss'));
}); });
it('calls parse and stringify for BLOB', () => { it('calls parse and stringify for BLOB', () => {
const Type = new Sequelize.BLOB(); const Type = new Sequelize.BLOB();
return testSuccess(Type, 'foobar'); return testSuccess(Type, 'foobar', { useBindParam: true });
}); });
it('calls parse and stringify for CHAR', () => { it('calls parse and stringify for CHAR', () => {
...@@ -208,27 +221,27 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { ...@@ -208,27 +221,27 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => {
} }
}); });
it('calls parse and stringify for DOUBLE', () => { it('calls parse and bindParam for DOUBLE', () => {
const Type = new Sequelize.DOUBLE(); const Type = new Sequelize.DOUBLE();
return testSuccess(Type, 1.5); return testSuccess(Type, 1.5, { useBindParam: true });
}); });
it('calls parse and stringify for FLOAT', () => { it('calls parse and bindParam for FLOAT', () => {
const Type = new Sequelize.FLOAT(); const Type = new Sequelize.FLOAT();
if (dialect === 'postgres') { if (dialect === 'postgres') {
// Postgres doesn't have float, maps to either decimal or double // Postgres doesn't have float, maps to either decimal or double
testFailure(Type); testFailure(Type);
} else { } else {
return testSuccess(Type, 1.5); return testSuccess(Type, 1.5, { useBindParam: true });
} }
}); });
it('calls parse and stringify for REAL', () => { it('calls parse and bindParam for REAL', () => {
const Type = new Sequelize.REAL(); const Type = new Sequelize.REAL();
return testSuccess(Type, 1.5); return testSuccess(Type, 1.5, { useBindParam: true });
}); });
it('calls parse and stringify for UUID', () => { it('calls parse and stringify for UUID', () => {
...@@ -254,10 +267,10 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { ...@@ -254,10 +267,10 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => {
}); });
if (current.dialect.supports.GEOMETRY) { if (current.dialect.supports.GEOMETRY) {
it('calls parse and stringify for GEOMETRY', () => { it('calls parse and bindParam for GEOMETRY', () => {
const Type = new Sequelize.GEOMETRY(); const Type = new Sequelize.GEOMETRY();
return testSuccess(Type, { type: 'Point', coordinates: [125.6, 10.1] }); return testSuccess(Type, { type: 'Point', coordinates: [125.6, 10.1] }, { useBindParam: true });
}); });
it('should parse an empty GEOMETRY field', () => { it('should parse an empty GEOMETRY field', () => {
......
...@@ -190,15 +190,15 @@ if (dialect.match(/^postgres/)) { ...@@ -190,15 +190,15 @@ if (dialect.match(/^postgres/)) {
}); });
}); });
it('should stringify hstore with insert', function() { it('should NOT stringify hstore with insert', function() {
return this.User.create({ return this.User.create({
username: 'bob', username: 'bob',
email: ['myemail@email.com'], email: ['myemail@email.com'],
settings: { mailing: false, push: 'facebook', frequency: 3 } settings: { mailing: false, push: 'facebook', frequency: 3 }
}, { }, {
logging(sql) { logging(sql) {
const expected = '\'"mailing"=>"false","push"=>"facebook","frequency"=>"3"\',\'"default"=>"\'\'value\'\'"\''; const unexpected = '\'"mailing"=>"false","push"=>"facebook","frequency"=>"3"\',\'"default"=>"\'\'value\'\'"\'';
expect(sql.indexOf(expected)).not.to.equal(-1); expect(sql).not.to.include(unexpected);
} }
}); });
}); });
...@@ -597,12 +597,13 @@ if (dialect.match(/^postgres/)) { ...@@ -597,12 +597,13 @@ if (dialect.match(/^postgres/)) {
return this.User.sync({ force: true }); return this.User.sync({ force: true });
}); });
it('should use postgres "TIMESTAMP WITH TIME ZONE" instead of "DATETIME"', function() { it('should use bind params instead of "TIMESTAMP WITH TIME ZONE"', function() {
return this.User.create({ return this.User.create({
dates: [] dates: []
}, { }, {
logging(sql) { logging(sql) {
expect(sql.indexOf('TIMESTAMP WITH TIME ZONE')).to.be.greaterThan(0); expect(sql).not.to.contain('TIMESTAMP WITH TIME ZONE');
expect(sql).not.to.contain('DATETIME');
} }
}); });
}); });
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
const chai = require('chai'), const chai = require('chai'),
expect = chai.expect, expect = chai.expect,
Support = require('./support'), Support = require('./support'),
dialect = Support.getTestDialect(),
Sequelize = Support.Sequelize, Sequelize = Support.Sequelize,
current = Support.sequelize, current = Support.sequelize,
DataTypes = Sequelize.DataTypes; DataTypes = Sequelize.DataTypes;
...@@ -26,19 +27,17 @@ describe('model', () => { ...@@ -26,19 +27,17 @@ describe('model', () => {
}); });
}); });
it('should stringify json with insert', function() { it('should use a placeholder for json with insert', function() {
return this.User.create({ return this.User.create({
username: 'bob', username: 'bob',
emergency_contact: { name: 'joe', phones: [1337, 42] } emergency_contact: { name: 'joe', phones: [1337, 42] }
}, { }, {
fields: ['id', 'username', 'document', 'emergency_contact'], fields: ['id', 'username', 'document', 'emergency_contact'],
logging: sql => { logging: sql => {
const expected = '\'{"name":"joe","phones":[1337,42]}\''; if (dialect.match(/^mysql/)) {
const expectedEscaped = '\'{\\"name\\":\\"joe\\",\\"phones\\":[1337,42]}\''; expect(sql).to.include('?');
if (sql.indexOf(expected) === -1) {
expect(sql.indexOf(expectedEscaped)).not.to.equal(-1);
} else { } else {
expect(sql.indexOf(expected)).not.to.equal(-1); expect(sql).to.include('$1');
} }
} }
}); });
......
...@@ -940,7 +940,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -940,7 +940,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
} }
}); });
it('casts empty arrays correctly for postgresql insert', function() { it('does not cast arrays for postgresql insert', function() {
if (dialect !== 'postgres') { if (dialect !== 'postgres') {
expect('').to.equal(''); expect('').to.equal('');
return void 0; return void 0;
...@@ -956,8 +956,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -956,8 +956,8 @@ describe(Support.getTestDialectTeaser('Model'), () => {
return User.create({myvals: [], mystr: []}, { return User.create({myvals: [], mystr: []}, {
logging(sql) { logging(sql) {
test = true; test = true;
expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1); expect(sql).not.to.contain('ARRAY[]::INTEGER[]');
expect(sql.indexOf('ARRAY[]::VARCHAR(255)[]')).to.be.above(-1); expect(sql).not.to.contain('ARRAY[]::VARCHAR(255)[]');
} }
}); });
}).then(() => { }).then(() => {
...@@ -984,8 +984,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -984,8 +984,8 @@ describe(Support.getTestDialectTeaser('Model'), () => {
return user.save({ return user.save({
logging(sql) { logging(sql) {
test = true; test = true;
expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1); expect(sql).to.contain('ARRAY[]::INTEGER[]');
expect(sql.indexOf('ARRAY[]::VARCHAR(255)[]')).to.be.above(-1); expect(sql).to.contain('ARRAY[]::VARCHAR(255)[]');
} }
}); });
}); });
...@@ -1139,7 +1139,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -1139,7 +1139,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
logging(sql) { logging(sql) {
expect(sql).to.exist; expect(sql).to.exist;
test = true; test = true;
expect(sql.toUpperCase().indexOf('INSERT')).to.be.above(-1); expect(sql.toUpperCase()).to.contain('INSERT');
} }
}); });
}).then(() => { }).then(() => {
......
...@@ -213,7 +213,8 @@ const Support = { ...@@ -213,7 +213,8 @@ const Support = {
return url; return url;
}, },
expectsql(query, expectations) { expectsql(query, assertions) {
const expectations = assertions.query || assertions;
let expectation = expectations[Support.sequelize.dialect.name]; let expectation = expectations[Support.sequelize.dialect.name];
if (!expectation) { if (!expectation) {
...@@ -229,7 +230,12 @@ const Support = { ...@@ -229,7 +230,12 @@ const Support = {
if (_.isError(query)) { if (_.isError(query)) {
expect(query.message).to.equal(expectation.message); expect(query.message).to.equal(expectation.message);
} else { } else {
expect(query).to.equal(expectation); expect(query.query || query).to.equal(expectation);
}
if (assertions.bind) {
const bind = assertions.bind[Support.sequelize.dialect.name] || assertions.bind['default'] || assertions.bind;
expect(query.bind).to.deep.equal(bind);
} }
} }
}; };
......
...@@ -93,5 +93,13 @@ describe('QueryGenerator', () => { ...@@ -93,5 +93,13 @@ describe('QueryGenerator', () => {
.should.be.equal('foo LIKE bar'); .should.be.equal('foo LIKE bar');
}); });
}); });
describe('format', () => {
it('should throw an error if passed SequelizeMethod', function() {
const QG = getAbstractQueryGenerator(this.sequelize);
const value = this.sequelize.fn('UPPER', 'test');
expect(() => QG.format(value)).to.throw(Error);
});
});
}); });
...@@ -445,47 +445,83 @@ if (dialect === 'mysql') { ...@@ -445,47 +445,83 @@ if (dialect === 'mysql') {
insertQuery: [ insertQuery: [
{ {
arguments: ['myTable', {name: 'foo'}], arguments: ['myTable', {name: 'foo'}],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo');" expectation: {
query: 'INSERT INTO `myTable` (`name`) VALUES ($1);',
bind: ['foo']
}
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;');" expectation: {
query: 'INSERT INTO `myTable` (`name`) VALUES ($1);',
bind: ["foo';DROP TABLE myTable;"]
}
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}],
expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55');" expectation: {
query: 'INSERT INTO `myTable` (`name`,`birthday`) VALUES ($1,$2);',
bind: ['foo', new Date(Date.UTC(2011, 2, 27, 10, 1, 55))]
}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);" expectation: {
query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);',
bind: ['foo', 1]
}
}, { }, {
arguments: ['myTable', {data: new Buffer('Sequelize') }], arguments: ['myTable', {data: new Buffer('Sequelize') }],
expectation: "INSERT INTO `myTable` (`data`) VALUES (X'53657175656c697a65');" expectation: {
query: 'INSERT INTO `myTable` (`data`) VALUES ($1);',
bind: [new Buffer('Sequelize')]
}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);" expectation: {
query: 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($1,$2,$3);',
bind: ['foo', 1, null]
}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);", expectation: {
query: 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($1,$2,$3);',
bind: ['foo', 1, null]
},
context: {options: {omitNull: false}} context: {options: {omitNull: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);", expectation: {
query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);',
bind: ['foo', 1]
},
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: undefined}], arguments: ['myTable', {name: 'foo', foo: 1, nullValue: undefined}],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);", expectation: {
query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);',
bind: ['foo', 1]
},
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['myTable', {foo: false}], arguments: ['myTable', {foo: false}],
expectation: 'INSERT INTO `myTable` (`foo`) VALUES (false);' expectation: {
query: 'INSERT INTO `myTable` (`foo`) VALUES ($1);',
bind: [false]
}
}, { }, {
arguments: ['myTable', {foo: true}], arguments: ['myTable', {foo: true}],
expectation: 'INSERT INTO `myTable` (`foo`) VALUES (true);' expectation: {
query: 'INSERT INTO `myTable` (`foo`) VALUES ($1);',
bind: [true]
}
}, { }, {
arguments: ['myTable', function(sequelize) { arguments: ['myTable', function(sequelize) {
return { return {
foo: sequelize.fn('NOW') foo: sequelize.fn('NOW')
}; };
}], }],
expectation: 'INSERT INTO `myTable` (`foo`) VALUES (NOW());', expectation: {
query: 'INSERT INTO `myTable` (`foo`) VALUES (NOW());',
bind: []
},
needsSequelize: true needsSequelize: true
} }
], ],
...@@ -616,7 +652,8 @@ if (dialect === 'mysql') { ...@@ -616,7 +652,8 @@ if (dialect === 'mysql') {
}); });
tests.forEach(test => { tests.forEach(test => {
const title = test.title || 'MySQL correctly returns ' + test.expectation + ' for ' + JSON.stringify(test.arguments); const query = test.expectation.query || test.expectation;
const title = test.title || 'MySQL correctly returns ' + query + ' for ' + JSON.stringify(test.arguments);
it(title, function() { it(title, function() {
if (test.needsSequelize) { if (test.needsSequelize) {
if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize); if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize);
......
'use strict';
const chai = require('chai'),
expect = chai.expect,
Support = require(__dirname + '/../../support'),
dialect = Support.getTestDialect(),
BaseTypes = require(__dirname + '/../../../../lib/data-types'),
DataTypes = require(__dirname + '/../../../../lib/dialects/postgres/data-types')(BaseTypes),
QueryGenerator = require('../../../../lib/dialects/postgres/query-generator');
if (dialect.match(/^postgres/)) {
describe('[POSTGRES Specific] DataTypes', () => {
beforeEach(function() {
this.queryGenerator = new QueryGenerator({
sequelize: this.sequelize,
_dialect: this.sequelize.dialect
});
});
describe('GEOMETRY', () => {
it('should use bindParam fn', function() {
const value = { type: 'Point' };
const bind = [];
const bindParam = this.queryGenerator.bindParam(bind);
const result = DataTypes.GEOMETRY.prototype.bindParam(value, { bindParam });
expect(result).to.equal('ST_GeomFromGeoJSON($1)');
expect(bind).to.eql([value]);
});
});
describe('GEOGRAPHY', () => {
it('should use bindParam fn', function() {
const value = { type: 'Point' };
const bind = [];
const bindParam = this.queryGenerator.bindParam(bind);
const result = DataTypes.GEOGRAPHY.prototype.bindParam(value, { bindParam });
expect(result).to.equal('ST_GeomFromGeoJSON($1)');
expect(bind).to.eql([value]);
});
});
});
}
...@@ -364,42 +364,78 @@ if (dialect === 'sqlite') { ...@@ -364,42 +364,78 @@ if (dialect === 'sqlite') {
insertQuery: [ insertQuery: [
{ {
arguments: ['myTable', { name: 'foo' }], arguments: ['myTable', { name: 'foo' }],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo');" expectation: {
query: 'INSERT INTO `myTable` (`name`) VALUES ($1);',
bind: ['foo']
}
}, { }, {
arguments: ['myTable', { name: "'bar'" }], arguments: ['myTable', { name: "'bar'" }],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('''bar''');" expectation: {
query: 'INSERT INTO `myTable` (`name`) VALUES ($1);',
bind: ["'bar'"]
}
}, { }, {
arguments: ['myTable', {data: new Buffer('Sequelize') }], arguments: ['myTable', {data: new Buffer('Sequelize') }],
expectation: "INSERT INTO `myTable` (`data`) VALUES (X'53657175656c697a65');" expectation: {
query: 'INSERT INTO `myTable` (`data`) VALUES ($1);',
bind: [new Buffer('Sequelize')]
}
}, { }, {
arguments: ['myTable', { name: 'bar', value: null }], arguments: ['myTable', { name: 'bar', value: null }],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL);" expectation: {
query: 'INSERT INTO `myTable` (`name`,`value`) VALUES ($1,$2);',
bind: ['bar', null]
}
}, { }, {
arguments: ['myTable', { name: 'bar', value: undefined }], arguments: ['myTable', { name: 'bar', value: undefined }],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL);" expectation: {
query: 'INSERT INTO `myTable` (`name`,`value`) VALUES ($1,$2);',
bind: ['bar', undefined]
}
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate()}], 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');" expectation: {
query: 'INSERT INTO `myTable` (`name`,`birthday`) VALUES ($1,$2);',
bind: ['foo', moment('2011-03-27 10:01:55 +0000', 'YYYY-MM-DD HH:mm:ss Z').toDate()]
}
}, { }, {
arguments: ['myTable', { name: 'foo', value: true }], arguments: ['myTable', { name: 'foo', value: true }],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',1);" expectation: {
query: 'INSERT INTO `myTable` (`name`,`value`) VALUES ($1,$2);',
bind: ['foo', true]
}
}, { }, {
arguments: ['myTable', { name: 'foo', value: false }], arguments: ['myTable', { name: 'foo', value: false }],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',0);" expectation: {
query: 'INSERT INTO `myTable` (`name`,`value`) VALUES ($1,$2);',
bind: ['foo', false]
}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);" expectation: {
query: 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($1,$2,$3);',
bind: ['foo', 1, null]
}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);", expectation: {
query: 'INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ($1,$2,$3);',
bind: ['foo', 1, null]
},
context: {options: {omitNull: false}} context: {options: {omitNull: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);", expectation: {
query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);',
bind: ['foo', 1]
},
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: undefined}], arguments: ['myTable', {name: 'foo', foo: 1, nullValue: undefined}],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);", expectation: {
query: 'INSERT INTO `myTable` (`name`,`foo`) VALUES ($1,$2);',
bind: ['foo', 1]
},
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['myTable', function(sequelize) { arguments: ['myTable', function(sequelize) {
...@@ -407,7 +443,10 @@ if (dialect === 'sqlite') { ...@@ -407,7 +443,10 @@ if (dialect === 'sqlite') {
foo: sequelize.fn('NOW') foo: sequelize.fn('NOW')
}; };
}], }],
expectation: 'INSERT INTO `myTable` (`foo`) VALUES (NOW());', expectation: {
query: 'INSERT INTO `myTable` (`foo`) VALUES (NOW());',
bind: []
},
needsSequelize: true needsSequelize: true
} }
], ],
...@@ -547,7 +586,8 @@ if (dialect === 'sqlite') { ...@@ -547,7 +586,8 @@ if (dialect === 'sqlite') {
}); });
tests.forEach(test => { tests.forEach(test => {
const title = test.title || 'SQLite correctly returns ' + test.expectation + ' for ' + JSON.stringify(test.arguments); const query = test.expectation.query || test.expectation;
const title = test.title || 'SQLite correctly returns ' + query + ' for ' + JSON.stringify(test.arguments);
it(title, function() { it(title, function() {
if (test.needsSequelize) { if (test.needsSequelize) {
if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize); if (_.isFunction(test.arguments[1])) test.arguments[1] = test.arguments[1](this.sequelize);
......
...@@ -27,13 +27,16 @@ describe(Support.getTestDialectTeaser('SQL'), () => { ...@@ -27,13 +27,16 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
}; };
expectsql(sql.insertQuery(User.tableName, {user_name: 'triggertest'}, User.rawAttributes, options), expectsql(sql.insertQuery(User.tableName, {user_name: 'triggertest'}, User.rawAttributes, options),
{ {
mssql: 'declare @tmp table ([id] INTEGER,[user_name] NVARCHAR(255));INSERT INTO [users] ([user_name]) OUTPUT INSERTED.[id],INSERTED.[user_name] into @tmp VALUES (N\'triggertest\');select * from @tmp;', query: {
postgres: 'INSERT INTO "users" ("user_name") VALUES (\'triggertest\') RETURNING *;', mssql: 'declare @tmp table ([id] INTEGER,[user_name] NVARCHAR(255));INSERT INTO [users] ([user_name]) OUTPUT INSERTED.[id],INSERTED.[user_name] into @tmp VALUES ($1);select * from @tmp;',
default: "INSERT INTO `users` (`user_name`) VALUES ('triggertest');" postgres: 'INSERT INTO "users" ("user_name") VALUES ($1) RETURNING *;',
}); default: 'INSERT INTO `users` (`user_name`) VALUES ($1);'
},
bind: ['triggertest']
}); });
}); });
});
describe('dates', () => { describe('dates', () => {
it('formats the date correctly when inserting', () => { it('formats the date correctly when inserting', () => {
...@@ -51,10 +54,16 @@ describe(Support.getTestDialectTeaser('SQL'), () => { ...@@ -51,10 +54,16 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(timezoneSequelize.dialect.QueryGenerator.insertQuery(User.tableName, {date: new Date(Date.UTC(2015, 0, 20))}, User.rawAttributes, {}), expectsql(timezoneSequelize.dialect.QueryGenerator.insertQuery(User.tableName, {date: new Date(Date.UTC(2015, 0, 20))}, User.rawAttributes, {}),
{ {
postgres: 'INSERT INTO "users" ("date") VALUES (\'2015-01-20 01:00:00.000 +01:00\');', query: {
sqlite: 'INSERT INTO `users` (`date`) VALUES (\'2015-01-20 00:00:00.000 +00:00\');', postgres: 'INSERT INTO "users" ("date") VALUES ($1);',
mssql: 'INSERT INTO [users] ([date]) VALUES (N\'2015-01-20 01:00:00.000 +01:00\');', mssql: 'INSERT INTO [users] ([date]) VALUES ($1);',
mysql: "INSERT INTO `users` (`date`) VALUES ('2015-01-20 01:00:00');" default: 'INSERT INTO `users` (`date`) VALUES ($1);'
},
bind: {
sqlite: ['2015-01-20 00:00:00.000 +00:00'],
mysql: ['2015-01-20 01:00:00'],
default: ['2015-01-20 01:00:00.000 +01:00']
}
}); });
}); });
...@@ -73,10 +82,16 @@ describe(Support.getTestDialectTeaser('SQL'), () => { ...@@ -73,10 +82,16 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(timezoneSequelize.dialect.QueryGenerator.insertQuery(User.tableName, {date: new Date(Date.UTC(2015, 0, 20, 1, 2, 3, 89))}, User.rawAttributes, {}), expectsql(timezoneSequelize.dialect.QueryGenerator.insertQuery(User.tableName, {date: new Date(Date.UTC(2015, 0, 20, 1, 2, 3, 89))}, User.rawAttributes, {}),
{ {
postgres: 'INSERT INTO "users" ("date") VALUES (\'2015-01-20 02:02:03.089 +01:00\');', query: {
sqlite: 'INSERT INTO `users` (`date`) VALUES (\'2015-01-20 01:02:03.089 +00:00\');', postgres: 'INSERT INTO "users" ("date") VALUES ($1);',
mssql: 'INSERT INTO [users] ([date]) VALUES (N\'2015-01-20 02:02:03.089 +01:00\');', mssql: 'INSERT INTO [users] ([date]) VALUES ($1);',
mysql: "INSERT INTO `users` (`date`) VALUES ('2015-01-20 02:02:03.089');" default: 'INSERT INTO `users` (`date`) VALUES ($1);'
},
bind: {
sqlite: ['2015-01-20 01:02:03.089 +00:00'],
mysql: ['2015-01-20 02:02:03.089'],
default: ['2015-01-20 02:02:03.089 +01:00']
}
}); });
}); });
}); });
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!