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

Commit 6541fb4f by Jan Aagaard Meier

Changes to how sequelize.literal and cousins is escaped

1 parent 34fddc2a
......@@ -638,33 +638,29 @@ module.exports = (function() {
mainAttributes = mainAttributes && mainAttributes.map(function(attr) {
var addTable = true;
if (attr instanceof Utils.literal) {
return attr.toString(this);
}
if (attr instanceof Utils.fn || attr instanceof Utils.col) {
if (attr._isSequelizeMethod) {
return attr.toString(self);
}
if (Array.isArray(attr) && attr.length === 2) {
if (attr[0] instanceof Utils.fn || attr[0] instanceof Utils.col) {
if (attr[0]._isSequelizeMethod) {
attr[0] = attr[0].toString(self);
addTable = false;
} else {
if (attr[0].indexOf('(') === -1 && attr[0].indexOf(')') === -1) {
attr[0] = this.quoteIdentifier(attr[0]);
attr[0] = self.quoteIdentifier(attr[0]);
}
}
attr = [attr[0], this.quoteIdentifier(attr[1])].join(' as ');
attr = [attr[0], self.quoteIdentifier(attr[1])].join(' AS ');
} else {
attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? this.quoteIdentifiers(attr) : attr;
attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? self.quoteIdentifiers(attr) : attr;
}
if (options.include && attr.indexOf('.') === -1 && addTable) {
attr = mainTableAs + '.' + attr;
}
return attr;
}.bind(this));
});
// If no attributes specified, use *
mainAttributes = mainAttributes || (options.include ? [mainTableAs + '.*'] : ['*']);
......@@ -700,18 +696,43 @@ module.exports = (function() {
// includeIgnoreAttributes is used by aggregate functions
if (options.includeIgnoreAttributes !== false) {
attributes = include.attributes.map(function(attr) {
var attrAs = attr;
var attrAs = attr,
verbatim = false;
if (Array.isArray(attr) && attr.length === 2) {
if (attr[0]._isSequelizeMethod) {
if (attr[0] instanceof Utils.literal ||
attr[0] instanceof Utils.cast ||
attr[0] instanceof Utils.fn
) {
verbatim = true;
}
}
attr = attr.map(function($attr) {
return $attr._isSequelizeMethod ? $attr.toString(self) : $attr;
});
attrAs = attr[1];
attr = attr[0];
} else if (attr instanceof Utils.literal) {
return attr.toString(self); // We trust the user to rename the field correctly
} else if (attr instanceof Utils.cast ||
attr instanceof Utils.fn
) {
throw new Error("Tried to select attributes using Sequelize.cast or Sequelize.fn without specifying an alias for the result, during eager loading. " +
"This means the attribute will not be added to the returned instance");
}
var prefix;
if (verbatim === true) {
prefix = attr;
} else {
prefix = self.quoteIdentifier(as) + '.' + self.quoteIdentifier(attr);
}
return self.quoteIdentifier(as) + '.' + self.quoteIdentifier(attr) + ' AS ' + self.quoteIdentifier(as + '.' + attrAs);
return prefix + ' AS ' + self.quoteIdentifier(as + '.' + attrAs);
});
if (include.subQuery && subQuery) {
......@@ -1094,7 +1115,7 @@ module.exports = (function() {
result = (value === 'NULL') ? key + ' IS NULL' : [key, value].join('=');
}
} else if (Utils.isHash(smth)) {
} else if (Utils._.isPlainObject(smth)) {
if (prepend) {
if (tableName) options.keysEscaped = true;
smth = this.prependTableNameToHash(tableName, smth);
......@@ -1125,7 +1146,7 @@ module.exports = (function() {
if (treatAsAnd) {
return treatAsAnd;
} else {
return !(arg instanceof Date) && ((arg instanceof Utils.and) || (arg instanceof Utils.or) || Utils.isHash(arg));
return !(arg instanceof Date) && ((arg instanceof Utils.and) || (arg instanceof Utils.or) || Utils._.isPlainObject(arg));
}
}, false);
......@@ -1246,7 +1267,7 @@ module.exports = (function() {
, self = this
, joinedTables = {};
if (Utils.isHash(options.where)) {
if (Utils._.isPlainObject(options.where)) {
Object.keys(options.where).forEach(function(filterStr) {
var associationParts = filterStr.split('.')
, attributePart = associationParts.pop()
......
......@@ -282,7 +282,7 @@ module.exports = (function() {
for (var name in attributes) {
var dataType = attributes[name];
if (Utils.isHash(dataType)) {
if (Utils._.isPlainObject(dataType)) {
var template;
if (dataType.type.toString() === DataTypes.ENUM.toString()) {
......
......@@ -414,7 +414,7 @@ module.exports = (function() {
for (var name in attributes) {
var dataType = attributes[name];
if (Utils.isHash(dataType)) {
if (Utils._.isObject(dataType)) {
var template = '<%= type %>'
, replacements = { type: dataType.type };
......
......@@ -221,7 +221,7 @@ module.exports = (function() {
for (var name in attributes) {
var dataType = attributes[name];
if (Utils.isHash(dataType)) {
if (Utils._.isObject(dataType)) {
var template = "<%= type %>"
, replacements = { type: dataType.type };
......
......@@ -69,7 +69,7 @@ module.exports = (function() {
// If you don't specify a valid data type lets help you debug it
Utils._.each(attributes, function(attribute, name) {
var dataType;
if (Utils.isHash(attribute)) {
if (Utils._.isPlainObject(attribute)) {
// We have special cases where the type is an object containing
// the values (e.g. Sequelize.ENUM(value, value2) returns an object
// instead of a function)
......@@ -81,7 +81,7 @@ module.exports = (function() {
} else {
dataType = attribute;
}
if (dataType === undefined) {
throw new Error('Unrecognized data type for field ' + name);
}
......@@ -639,7 +639,7 @@ module.exports = (function() {
*
* @param {Object} [options] A hash of options to describe the scope of the search
* @param {Object} [options.where] A hash of attributes to describe your search. See above for examples.
* @param {Array<String>} [options.attributes] A list of the attributes that you want to select
* @param {Array<String>} [options.attributes] A list of the attributes that you want to select. To rename an attribute, you can pass an array, with two elements - the first is the name of the attribute in the DB (or some kind of expression such as `Sequelize.literal`, `Sequelize.fn` and so on), and the second is the name you want the attribute to have in the returned instance
* @param {Array<Object|Model>} [options.include] A list of associations to eagerly load using a left join. Supported is either `{ include: [ Model1, Model2, ...]}` or `{ include: [{ model: Model1, as: 'Alias' }]}`. If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in the as attribute when eager loading Y).
* @param {Model} [optinos.include[].model] The model you want to eagerly load
* @param {String} [options.include[].as] The alias of the relation, in case the model you want to eagerly load is aliassed.
......
......@@ -179,7 +179,7 @@ var Utils = module.exports = {
_where[i].in = _where[i]. in || [];
_where[i]. in .concat(where[i]);
}
else if (Utils.isHash(where[i])) {
else if (Utils._.isPlainObject(where[i])) {
Object.keys(where[i]).forEach(function(ii) {
logic = self.getWhereLogic(ii, where[i][ii]);
......@@ -319,9 +319,6 @@ var Utils = module.exports = {
return '';
}
},
isHash: function(obj) {
return Utils._.isObject(obj) && !Array.isArray(obj) && !Buffer.isBuffer(obj);
},
hasChanged: function(attrValue, value) {
//If attribute value is Date, check value as a date
if (Utils._.isDate(attrValue) && !Utils._.isDate(value)) {
......@@ -412,7 +409,7 @@ var Utils = module.exports = {
setAttributes: function(hash, identifier, instance, prefix) {
prefix = prefix || '';
if (this.isHash(identifier)) {
if (this._.isPlainObject(identifier)) {
this._.each(identifier, function(elem, key) {
hash[prefix + key] = Utils._.isString(instance) ? instance : Utils._.isObject(instance) ? instance[elem.key || elem] : null;
});
......
......@@ -577,6 +577,41 @@ describe(Support.getTestDialectTeaser("Include"), function () {
})
})
it('should support Sequelize.literal and renaming of attributes in included model attributes', function () {
var Post = this.sequelize.define('Post',{});
var PostComment = this.sequelize.define('PostComment', {
someProperty: Sequelize.VIRTUAL, // Since we specify the AS part as a part of the literal string, not with sequelize syntax, we have to tell sequelize about the field
comment_title: Sequelize.STRING
});
Post.hasMany(PostComment);
return this.sequelize.sync({ force: true }).then(function () {
return Post.create({});
}).then(function (post) {
return post.createPostComment({
comment_title: 'WAT'
});
}).then(function () {
return Post.findAll({
include: [
{
model: PostComment,
attributes: [
Sequelize.literal('EXISTS(SELECT 1) AS "PostComments.someProperty"'),
[Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2'],
['comment_title', 'commentTitle']
]
}
]
})
}).then(function (posts) {
expect(posts[0].postComments[0].get('someProperty')).to.be.ok;
expect(posts[0].postComments[0].get('someProperty2')).to.be.ok;
expect(posts[0].postComments[0].get('commentTitle')).to.equal('WAT');
});
});
it('should support self associated hasMany (with through) include', function (done) {
var Group = this.sequelize.define('Group', {
name: DataTypes.STRING
......
......@@ -256,6 +256,26 @@ describe(Support.getTestDialectTeaser("Model"), function () {
});
});
});
it('should support renaming of sequelize method fields', function () {
var User = this.sequelize.define('user', {
someProperty: Sequelize.VIRTUAL // Since we specify the AS part as a part of the literal string, not with sequelize syntax, we have to tell sequelize about the field
});
return this.sequelize.sync({ force: true }).then(function () {
return User.create({});
}).then(function () {
return User.findAll({
attributes: [
Sequelize.literal('EXISTS(SELECT 1) AS "someProperty"'),
[Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2']
]
});
}).then(function (users) {
expect(users[0].get('someProperty')).to.be.ok;
expect(users[0].get('someProperty2')).to.be.ok;
});
});
it('field names that are the same as property names should create, update, and read correctly', function () {
var self = this;
......
......@@ -154,7 +154,7 @@ if (Support.dialectIsMySQL()) {
context: QueryGenerator
}, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as `count` FROM `foo`;',
expectation: 'SELECT count(*) AS `count` FROM `foo`;',
context: QueryGenerator
}, {
arguments: ['myTable', {where: "foo='bar'"}],
......@@ -246,7 +246,7 @@ if (Support.dialectIsMySQL()) {
having: ['creationYear > ?', 2002]
}
}],
expectation: "SELECT *, YEAR(`createdAt`) as `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING creationYear > 2002;",
expectation: "SELECT *, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING creationYear > 2002;",
context: QueryGenerator,
needsSequelize: true
}, {
......@@ -258,7 +258,7 @@ if (Support.dialectIsMySQL()) {
having: { creationYear: { gt: 2002 } }
}
}],
expectation: "SELECT *, YEAR(`createdAt`) as `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002;",
expectation: "SELECT *, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002;",
context: QueryGenerator,
needsSequelize: true
}, {
......
......@@ -245,7 +245,7 @@ if (dialect.match(/^postgres/)) {
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"id\"=2;"
}, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as \"count\" FROM \"foo\";'
expectation: 'SELECT count(*) AS \"count\" FROM \"foo\";'
}, {
arguments: ['myTable', {where: "foo='bar'"}],
expectation: "SELECT * FROM \"myTable\" WHERE foo='bar';"
......@@ -330,7 +330,7 @@ if (dialect.match(/^postgres/)) {
having: ['creationYear > ?', 2002]
}
}],
expectation: "SELECT *, YEAR(\"createdAt\") as \"creationYear\" FROM \"myTable\" GROUP BY \"creationYear\", \"title\" HAVING creationYear > 2002;",
expectation: "SELECT *, YEAR(\"createdAt\") AS \"creationYear\" FROM \"myTable\" GROUP BY \"creationYear\", \"title\" HAVING creationYear > 2002;",
context: QueryGenerator,
needsSequelize: true
}, {
......@@ -342,7 +342,7 @@ if (dialect.match(/^postgres/)) {
having: { creationYear: { gt: 2002 } }
}
}],
expectation: "SELECT *, YEAR(\"createdAt\") as \"creationYear\" FROM \"myTable\" GROUP BY \"creationYear\", \"title\" HAVING \"creationYear\" > 2002;",
expectation: "SELECT *, YEAR(\"createdAt\") AS \"creationYear\" FROM \"myTable\" GROUP BY \"creationYear\", \"title\" HAVING \"creationYear\" > 2002;",
context: QueryGenerator,
needsSequelize: true
}, {
......@@ -395,7 +395,7 @@ if (dialect.match(/^postgres/)) {
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as count FROM foo;',
expectation: 'SELECT count(*) AS count FROM foo;',
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['myTable', {where: "foo='bar'"}],
......
......@@ -135,7 +135,7 @@ if (dialect === 'sqlite') {
context: QueryGenerator
}, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as `count` FROM `foo`;',
expectation: 'SELECT count(*) AS `count` FROM `foo`;',
context: QueryGenerator
}, {
arguments: ['myTable', {where: "foo='bar'"}],
......@@ -231,7 +231,7 @@ if (dialect === 'sqlite') {
having: ['creationYear > ?', 2002]
}
}],
expectation: "SELECT *, YEAR(`createdAt`) as `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING creationYear > 2002;",
expectation: "SELECT *, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING creationYear > 2002;",
context: QueryGenerator,
needsSequelize: true
}, {
......@@ -243,7 +243,7 @@ if (dialect === 'sqlite') {
having: { creationYear: { gt: 2002 } }
}
}],
expectation: "SELECT *, YEAR(`createdAt`) as `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002;",
expectation: "SELECT *, YEAR(`createdAt`) AS `creationYear` FROM `myTable` GROUP BY `creationYear`, `title` HAVING `creationYear` > 2002;",
context: QueryGenerator,
needsSequelize: true
}, {
......
"use strict";
var fs = require('fs')
, path = require('path')
, _ = require('lodash')
, Sequelize = require(__dirname + "/../index")
, DataTypes = require(__dirname + "/../lib/data-types")
, Config = require(__dirname + "/config/config")
, Config = require(__dirname + "/config/config");
// Make sure errors get thrown when testing
Sequelize.Promise.onPossiblyUnhandledRejection(function(e, promise) {
......@@ -15,41 +17,41 @@ var Support = {
Sequelize: Sequelize,
initTests: function(options) {
var sequelize = this.createSequelizeInstance(options)
var sequelize = this.createSequelizeInstance(options);
this.clearDatabase(sequelize, function() {
if (options.context) {
options.context.sequelize = sequelize
options.context.sequelize = sequelize;
}
if (options.beforeComplete) {
options.beforeComplete(sequelize, DataTypes)
options.beforeComplete(sequelize, DataTypes);
}
if (options.onComplete) {
options.onComplete(sequelize, DataTypes)
options.onComplete(sequelize, DataTypes);
}
})
});
},
prepareTransactionTest: function(sequelize, callback) {
var dialect = Support.getTestDialect()
var dialect = Support.getTestDialect();
if (dialect === 'sqlite') {
var options = Sequelize.Utils._.extend({}, sequelize.options, { storage: path.join(__dirname, 'tmp', 'db.sqlite') })
, _sequelize = new Sequelize(sequelize.config.database, null, null, options)
, _sequelize = new Sequelize(sequelize.config.database, null, null, options);
_sequelize.sync({ force: true }).success(function() { callback(_sequelize) })
_sequelize.sync({ force: true }).success(function() { callback(_sequelize); });
} else {
callback(sequelize)
callback(sequelize);
}
},
createSequelizeInstance: function(options) {
options = options || {}
options.dialect = options.dialect || 'mysql'
options = options || {};
options.dialect = options.dialect || 'mysql';
var config = Config[options.dialect]
var config = Config[options.dialect];
var sequelizeOptions = _.defaults(options, {
host: options.host || config.host,
......@@ -58,136 +60,130 @@ var Support = {
port: options.port || process.env.SEQ_PORT || config.port,
pool: config.pool,
dialectOptions: options.dialectOptions || {}
})
});
if (process.env.DIALECT === 'postgres-native') {
sequelizeOptions.native = true
sequelizeOptions.native = true;
}
if (!!config.storage) {
sequelizeOptions.storage = config.storage
sequelizeOptions.storage = config.storage;
}
return this.getSequelizeInstance(config.database, config.username, config.password, sequelizeOptions)
return this.getSequelizeInstance(config.database, config.username, config.password, sequelizeOptions);
},
getSequelizeInstance: function(db, user, pass, options) {
options = options || {}
options.dialect = options.dialect || this.getTestDialect()
return new Sequelize(db, user, pass, options)
options = options || {};
options.dialect = options.dialect || this.getTestDialect();
return new Sequelize(db, user, pass, options);
},
clearDatabase: function(sequelize, callback) {
sequelize
clearDatabase: function(sequelize) {
return sequelize
.getQueryInterface()
.dropAllTables()
.success(function() {
sequelize.daoFactoryManager.daos = []
sequelize
.then(function() {
sequelize.daoFactoryManager.daos = [];
return sequelize
.getQueryInterface()
.dropAllEnums()
.success(callback)
.error(function (err) {
console.log('Error in support.clearDatabase() dropAllEnums() :: ', err)
})
})
.error(function(err) {
console.log('Error in support.clearDatabase() dropAllTables() :: ', err)
.catch(function (err) {
console.log('Error in support.clearDatabase() dropAllEnums() :: ', err);
});
})
.catch(function(err) {
console.log('Error in support.clearDatabase() dropAllTables() :: ', err);
});
},
getSupportedDialects: function() {
return fs.readdirSync(__dirname + '/../lib/dialects').filter(function(file) {
return ((file.indexOf('.js') === -1) && (file.indexOf('abstract') === -1))
})
return ((file.indexOf('.js') === -1) && (file.indexOf('abstract') === -1));
});
},
checkMatchForDialects: function(dialect, value, expectations) {
if (!!expectations[dialect]) {
expect(value).to.match(expectations[dialect])
expect(value).to.match(expectations[dialect]);
} else {
throw new Error('Undefined expectation for "' + dialect + '"!')
throw new Error('Undefined expectation for "' + dialect + '"!');
}
},
getTestDialect: function() {
var envDialect = process.env.DIALECT || 'mysql'
var envDialect = process.env.DIALECT || 'mysql';
if (envDialect === 'postgres-native') {
envDialect = 'postgres'
envDialect = 'postgres';
}
if (this.getSupportedDialects().indexOf(envDialect) === -1) {
throw new Error('The dialect you have passed is unknown. Did you really mean: ' + envDialect)
throw new Error('The dialect you have passed is unknown. Did you really mean: ' + envDialect);
}
return envDialect
return envDialect;
},
dialectIsMySQL: function(strict) {
var envDialect = process.env.DIALECT || 'mysql'
var envDialect = process.env.DIALECT || 'mysql';
if (strict === undefined) {
strict = false
strict = false;
}
if (strict) {
return envDialect === 'mysql'
return envDialect === 'mysql';
} else {
return ['mysql', 'mariadb'].indexOf(envDialect) !== -1
return ['mysql', 'mariadb'].indexOf(envDialect) !== -1;
}
},
getTestDialectTeaser: function(moduleName) {
var dialect = this.getTestDialect()
var dialect = this.getTestDialect();
if (process.env.DIALECT === 'postgres-native') {
dialect = 'postgres-native'
dialect = 'postgres-native';
}
return "[" + dialect.toUpperCase() + "] " + moduleName
return "[" + dialect.toUpperCase() + "] " + moduleName;
},
getTestUrl: function(config) {
var url,
dbConfig = config[config.dialect]
dbConfig = config[config.dialect];
if (config.dialect === 'sqlite') {
url = 'sqlite://' + dbConfig.storage
url = 'sqlite://' + dbConfig.storage;
} else {
var credentials = dbConfig.username
var credentials = dbConfig.username;
if(dbConfig.password) {
credentials += ":" + dbConfig.password
credentials += ":" + dbConfig.password;
}
url = config.dialect + "://" + credentials
+ "@" + dbConfig.host + ":" + dbConfig.port + "/" + dbConfig.database
+ "@" + dbConfig.host + ":" + dbConfig.port + "/" + dbConfig.database;
}
return url
return url;
}
}
};
var sequelize = Support.createSequelizeInstance({ dialect: Support.getTestDialect() })
var sequelize = Support.createSequelizeInstance({ dialect: Support.getTestDialect() });
// For Postgres' HSTORE functionality and to properly execute it's commands we'll need this...
before(function(done) {
var dialect = Support.getTestDialect()
before(function() {
var dialect = Support.getTestDialect();
if (dialect !== "postgres" && dialect !== "postgres-native") {
return done()
return;
}
sequelize.query('CREATE EXTENSION IF NOT EXISTS hstore', null, {raw: true}).success(function() {
done()
})
})
return sequelize.query('CREATE EXTENSION IF NOT EXISTS hstore', null, {raw: true});
});
beforeEach(function(done) {
this.sequelize = sequelize
beforeEach(function() {
this.sequelize = sequelize;
Support.clearDatabase(this.sequelize, function() {
done()
})
})
return Support.clearDatabase(this.sequelize);
});
module.exports = Support
module.exports = Support;
......@@ -110,43 +110,6 @@ describe(Support.getTestDialectTeaser("Utils"), function() {
})
})
describe('isHash', function() {
it('doesn\'t match arrays', function(done) {
expect(Utils.isHash([])).to.be.false
done()
})
it('doesn\'t match null', function(done) {
expect(Utils.isHash(null)).to.be.false
done()
})
it('matches plain objects', function(done) {
var values = {
'name': {
'first': 'Foo',
'last': 'Bar'
}
}
expect(Utils.isHash(values)).to.be.true
done()
})
it('matches plain objects with length property/key', function(done) {
var values = {
'name': {
'first': 'Foo',
'last': 'Bar'
},
'length': 1
}
expect(Utils.isHash(values)).to.be.true
done()
})
})
describe('format', function() {
it('should format where clause correctly when the value is truthy', function(done) {
var where = ['foo = ?', 1]
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!