Merge branch 'release/1.2.3' into production
This commit is contained in:
commit
39445698e9
68
lib/mysql.js
68
lib/mysql.js
|
@ -203,7 +203,7 @@ MySQL.prototype.create = function (model, data, callback) {
|
|||
* @param {Object} data The model instance data
|
||||
* @param {Function} [callback] The callback function
|
||||
*/
|
||||
MySQL.prototype.updateOrCreate = function (model, data, callback) {
|
||||
MySQL.prototype.updateOrCreate = MySQL.prototype.save = function (model, data, callback) {
|
||||
var mysql = this;
|
||||
var fieldsNames = [];
|
||||
var fieldValues = [];
|
||||
|
@ -244,7 +244,9 @@ MySQL.prototype.toFields = function (model, data) {
|
|||
Object.keys(data).forEach(function (key) {
|
||||
if (props[key]) {
|
||||
var value = this.toDatabase(props[key], data[key]);
|
||||
if ('undefined' === typeof value) return;
|
||||
if (undefined === value) {
|
||||
return;
|
||||
}
|
||||
fields.push(self.columnEscaped(model, key) + ' = ' + value);
|
||||
}
|
||||
}.bind(this));
|
||||
|
@ -271,8 +273,9 @@ function dateToMysql(val) {
|
|||
* @returns {*}
|
||||
*/
|
||||
MySQL.prototype.toDatabase = function (prop, val) {
|
||||
if (val === null) return 'NULL';
|
||||
if (val === undefined) return 'NULL';
|
||||
if (val === null || val === undefined) {
|
||||
return 'NULL';
|
||||
}
|
||||
if (val.constructor.name === 'Object') {
|
||||
var operator = Object.keys(val)[0]
|
||||
val = val[operator];
|
||||
|
@ -280,27 +283,36 @@ MySQL.prototype.toDatabase = function (prop, val) {
|
|||
return this.toDatabase(prop, val[0]) +
|
||||
' AND ' +
|
||||
this.toDatabase(prop, val[1]);
|
||||
} else if (operator == 'inq' || operator == 'nin') {
|
||||
if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array
|
||||
} else if (operator === 'inq' || operator === 'nin') {
|
||||
if (Array.isArray(val)) { //if value is array
|
||||
for (var i = 0; i < val.length; i++) {
|
||||
val[i] = this.client.escape(val[i]);
|
||||
val[i] = this.toDatabase(prop, val[i]);
|
||||
}
|
||||
return val.join(',');
|
||||
} else {
|
||||
return val;
|
||||
return this.toDatabase(prop, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!prop) return val;
|
||||
if (prop.type.name === 'Number') return Number(val);
|
||||
if (prop.type.name === 'Date') {
|
||||
if (!val) return 'NULL';
|
||||
if (!prop) {
|
||||
return this.client.escape(val);
|
||||
}
|
||||
if (prop.type === Number) {
|
||||
val = Number(val);
|
||||
return isNaN(val) ? 'NULL' : val;
|
||||
}
|
||||
if (prop.type === Date) {
|
||||
if (!val) {
|
||||
return 'NULL';
|
||||
}
|
||||
if (!val.toUTCString) {
|
||||
val = new Date(val);
|
||||
}
|
||||
return '"' + dateToMysql(val) + '"';
|
||||
}
|
||||
if (prop.type.name == "Boolean") return val ? 1 : 0;
|
||||
if (prop.type === Boolean) {
|
||||
return val ? 1 : 0;
|
||||
}
|
||||
if (prop.type.name === 'GeoPoint') {
|
||||
return val ? 'Point(' + val.lat + ',' + val.lng + ')' : 'NULL';
|
||||
}
|
||||
|
@ -399,6 +411,9 @@ MySQL.prototype.buildWhere = function (model, conds) {
|
|||
};
|
||||
|
||||
MySQL.prototype._buildWhere = function (model, conds) {
|
||||
if (conds === null || conds === undefined || (typeof conds !== 'object')) {
|
||||
return '';
|
||||
}
|
||||
var self = this;
|
||||
var props = self._models[model].properties;
|
||||
|
||||
|
@ -483,7 +498,13 @@ function buildOrderBy(self, model, order) {
|
|||
}
|
||||
|
||||
function buildLimit(limit, offset) {
|
||||
return 'LIMIT ' + (offset ? (offset + ', ' + limit) : limit);
|
||||
if (isNaN(limit)) {
|
||||
limit = 0;
|
||||
}
|
||||
if (isNaN(offset)) {
|
||||
offset = 0;
|
||||
}
|
||||
return 'LIMIT ' + (offset ? (offset + ',' + limit) : limit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -517,7 +538,7 @@ MySQL.prototype.all = function all(model, filter, callback) {
|
|||
}
|
||||
|
||||
if (filter.limit) {
|
||||
sql += ' ' + buildLimit(filter.limit, filter.skip || 0);
|
||||
sql += ' ' + buildLimit(filter.limit, filter.skip || filter.offset || 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -541,6 +562,21 @@ MySQL.prototype.all = function all(model, filter, callback) {
|
|||
|
||||
};
|
||||
|
||||
MySQL.prototype.count = function count(model, callback, where) {
|
||||
|
||||
this.query('SELECT count(*) as cnt FROM ' +
|
||||
this.tableEscaped(model) + ' ' + this.buildWhere(model, where),
|
||||
function (err, res) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var c = (res && res[0] && res[0].cnt) || 0;
|
||||
callback(err, c);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delete instances for the given model
|
||||
*
|
||||
|
@ -554,7 +590,7 @@ MySQL.prototype.destroyAll = function destroyAll(model, where, callback) {
|
|||
callback = where;
|
||||
where = undefined;
|
||||
}
|
||||
this.query('DELETE FROM '
|
||||
this.query('DELETE FROM '
|
||||
+ this.tableEscaped(model) + ' ' + this.buildWhere(model, where || {}),
|
||||
function (err, data) {
|
||||
callback && callback(err, data);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loopback-connector-mysql",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.3",
|
||||
"description": "MySQL connector for loopback-datasource-juggler",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -15,7 +15,7 @@
|
|||
"loopback-datasource-juggler": "1.x.x",
|
||||
"should": "~1.3.0",
|
||||
"mocha": "~1.18.0",
|
||||
"rc": "~0.3.1"
|
||||
"rc": "~0.4.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -0,0 +1,434 @@
|
|||
var should = require('./init.js');
|
||||
|
||||
var Post, PostWithStringId, db;
|
||||
|
||||
describe('mysql', function () {
|
||||
|
||||
before(function (done) {
|
||||
db = getDataSource();
|
||||
|
||||
Post = db.define('PostWithDefaultId', {
|
||||
title: { type: String, length: 255, index: true },
|
||||
content: { type: String },
|
||||
stars: Number
|
||||
});
|
||||
|
||||
PostWithStringId = db.define('PostWithStringId', {
|
||||
id: {type: String, id: true},
|
||||
title: { type: String, length: 255, index: true },
|
||||
content: { type: String }
|
||||
});
|
||||
|
||||
db.automigrate(['PostWithDefaultId', 'PostWithStringId'], function (err) {
|
||||
should.not.exist(err);
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(function (done) {
|
||||
Post.destroyAll(function () {
|
||||
PostWithStringId.destroyAll(function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('updateOrCreate should update the instance', function (done) {
|
||||
Post.create({title: 'a', content: 'AAA'}, function (err, post) {
|
||||
post.title = 'b';
|
||||
Post.updateOrCreate(post, function (err, p) {
|
||||
should.not.exist(err);
|
||||
p.id.should.be.equal(post.id);
|
||||
p.content.should.be.equal(post.content);
|
||||
|
||||
Post.findById(post.id, function (err, p) {
|
||||
p.id.should.be.equal(post.id);
|
||||
|
||||
p.content.should.be.equal(post.content);
|
||||
p.title.should.be.equal('b');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('updateOrCreate should update the instance without removing existing properties', function (done) {
|
||||
Post.create({title: 'a', content: 'AAA'}, function (err, post) {
|
||||
post = post.toObject();
|
||||
delete post.title;
|
||||
Post.updateOrCreate(post, function (err, p) {
|
||||
should.not.exist(err);
|
||||
p.id.should.be.equal(post.id);
|
||||
p.content.should.be.equal(post.content);
|
||||
Post.findById(post.id, function (err, p) {
|
||||
p.id.should.be.equal(post.id);
|
||||
|
||||
p.content.should.be.equal(post.content);
|
||||
p.title.should.be.equal('a');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('updateOrCreate should create a new instance if it does not exist', function (done) {
|
||||
var post = {id: 123, title: 'a', content: 'AAA'};
|
||||
Post.updateOrCreate(post, function (err, p) {
|
||||
should.not.exist(err);
|
||||
p.title.should.be.equal(post.title);
|
||||
p.content.should.be.equal(post.content);
|
||||
p.id.should.be.equal(post.id);
|
||||
|
||||
Post.findById(p.id, function (err, p) {
|
||||
p.id.should.be.equal(post.id);
|
||||
|
||||
p.content.should.be.equal(post.content);
|
||||
p.title.should.be.equal(post.title);
|
||||
p.id.should.be.equal(post.id);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('save should update the instance with the same id', function (done) {
|
||||
Post.create({title: 'a', content: 'AAA'}, function (err, post) {
|
||||
post.title = 'b';
|
||||
post.save(function (err, p) {
|
||||
should.not.exist(err);
|
||||
p.id.should.be.equal(post.id);
|
||||
p.content.should.be.equal(post.content);
|
||||
|
||||
Post.findById(post.id, function (err, p) {
|
||||
p.id.should.be.equal(post.id);
|
||||
|
||||
p.content.should.be.equal(post.content);
|
||||
p.title.should.be.equal('b');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('save should update the instance without removing existing properties', function (done) {
|
||||
Post.create({title: 'a', content: 'AAA'}, function (err, post) {
|
||||
delete post.title;
|
||||
post.save(function (err, p) {
|
||||
should.not.exist(err);
|
||||
p.id.should.be.equal(post.id);
|
||||
p.content.should.be.equal(post.content);
|
||||
|
||||
Post.findById(post.id, function (err, p) {
|
||||
p.id.should.be.equal(post.id);
|
||||
|
||||
p.content.should.be.equal(post.content);
|
||||
p.title.should.be.equal('a');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('save should create a new instance if it does not exist', function (done) {
|
||||
var post = new Post({id: 123, title: 'a', content: 'AAA'});
|
||||
post.save(post, function (err, p) {
|
||||
should.not.exist(err);
|
||||
p.title.should.be.equal(post.title);
|
||||
p.content.should.be.equal(post.content);
|
||||
p.id.should.be.equal(post.id);
|
||||
|
||||
Post.findById(p.id, function (err, p) {
|
||||
should.not.exist(err);
|
||||
p.id.should.be.equal(post.id);
|
||||
|
||||
p.content.should.be.equal(post.content);
|
||||
p.title.should.be.equal(post.title);
|
||||
p.id.should.be.equal(post.id);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('all return should honor filter.fields', function (done) {
|
||||
var post = new Post({title: 'b', content: 'BBB'})
|
||||
post.save(function (err, post) {
|
||||
Post.all({fields: ['title'], where: {title: 'b'}}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.lengthOf(1);
|
||||
post = posts[0];
|
||||
post.should.have.property('title', 'b');
|
||||
post.should.not.have.property('content');
|
||||
should.not.exist(post.id);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('find should order by id if the order is not set for the query filter',
|
||||
function (done) {
|
||||
PostWithStringId.create({id: '2', title: 'c', content: 'CCC'}, function (err, post) {
|
||||
PostWithStringId.create({id: '1', title: 'd', content: 'DDD'}, function (err, post) {
|
||||
PostWithStringId.find(function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.length.should.be.equal(2);
|
||||
posts[0].id.should.be.equal('1');
|
||||
|
||||
PostWithStringId.find({limit: 1, offset: 0}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.length.should.be.equal(1);
|
||||
posts[0].id.should.be.equal('1');
|
||||
|
||||
PostWithStringId.find({limit: 1, offset: 1}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.length.should.be.equal(1);
|
||||
posts[0].id.should.be.equal('2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow to find using like', function (done) {
|
||||
Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
|
||||
Post.find({where: {title: {like: 'M%st'}}}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should support like for no match', function (done) {
|
||||
Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
|
||||
Post.find({where: {title: {like: 'M%XY'}}}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow to find using nlike', function (done) {
|
||||
Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
|
||||
Post.find({where: {title: {nlike: 'M%st'}}}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should support nlike for no match', function (done) {
|
||||
Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
|
||||
Post.find({where: {title: {nlike: 'M%XY'}}}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should support "and" operator that is satisfied', function (done) {
|
||||
Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
|
||||
Post.find({where: {and: [
|
||||
{title: 'My Post'},
|
||||
{content: 'Hello'}
|
||||
]}}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should support "and" operator that is not satisfied', function (done) {
|
||||
Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
|
||||
Post.find({where: {and: [
|
||||
{title: 'My Post'},
|
||||
{content: 'Hello1'}
|
||||
]}}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should support "or" that is satisfied', function (done) {
|
||||
Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
|
||||
Post.find({where: {or: [
|
||||
{title: 'My Post'},
|
||||
{content: 'Hello1'}
|
||||
]}}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should support "or" operator that is not satisfied', function (done) {
|
||||
Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
|
||||
Post.find({where: {or: [
|
||||
{title: 'My Post1'},
|
||||
{content: 'Hello1'}
|
||||
]}}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// The where object should be parsed by the connector
|
||||
it('should support where for count', function (done) {
|
||||
Post.create({title: 'My Post', content: 'Hello'}, function (err, post) {
|
||||
Post.count({and: [
|
||||
{title: 'My Post'},
|
||||
{content: 'Hello'}
|
||||
]}, function (err, count) {
|
||||
should.not.exist(err);
|
||||
count.should.be.equal(1);
|
||||
Post.count({and: [
|
||||
{title: 'My Post1'},
|
||||
{content: 'Hello'}
|
||||
]}, function (err, count) {
|
||||
should.not.exist(err);
|
||||
count.should.be.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// The where object should be parsed by the connector
|
||||
it('should support where for destroyAll', function (done) {
|
||||
Post.create({title: 'My Post1', content: 'Hello'}, function (err, post) {
|
||||
Post.create({title: 'My Post2', content: 'Hello'}, function (err, post) {
|
||||
Post.destroyAll({and: [
|
||||
{title: 'My Post1'},
|
||||
{content: 'Hello'}
|
||||
]}, function (err) {
|
||||
should.not.exist(err);
|
||||
Post.count(function (err, count) {
|
||||
should.not.exist(err);
|
||||
count.should.be.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow SQL injection for inq operator', function (done) {
|
||||
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
|
||||
function (err, post) {
|
||||
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
|
||||
function (err, post) {
|
||||
Post.find({where: {title: {inq: 'SELECT title from PostWithDefaultId'}}},
|
||||
function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow SQL injection for lt operator', function (done) {
|
||||
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
|
||||
function (err, post) {
|
||||
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
|
||||
function (err, post) {
|
||||
Post.find({where: {stars: {lt: 'SELECT title from PostWithDefaultId'}}},
|
||||
function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow SQL injection for nin operator', function (done) {
|
||||
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
|
||||
function (err, post) {
|
||||
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
|
||||
function (err, post) {
|
||||
Post.find({where: {title: {nin: 'SELECT title from PostWithDefaultId'}}},
|
||||
function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not allow SQL injection for inq operator with number column', function (done) {
|
||||
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
|
||||
function (err, post) {
|
||||
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
|
||||
function (err, post) {
|
||||
Post.find({where: {stars: {inq: 'SELECT title from PostWithDefaultId'}}},
|
||||
function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow SQL injection for inq operator with array value', function (done) {
|
||||
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
|
||||
function (err, post) {
|
||||
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
|
||||
function (err, post) {
|
||||
Post.find({where: {stars: {inq: [5, 'SELECT title from PostWithDefaultId']}}},
|
||||
function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow SQL injection for between operator', function (done) {
|
||||
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
|
||||
function (err, post) {
|
||||
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
|
||||
function (err, post) {
|
||||
Post.find({where: {stars: {between: [5, 'SELECT title from PostWithDefaultId']}}},
|
||||
function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts.should.have.property('length', 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
Post.destroyAll(function () {
|
||||
PostWithStringId.destroyAll(done);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue