Merge pull request #157 from strongloop/feature/query-order
Normalize filter.order and enforce more checks
This commit is contained in:
commit
d0e61af26f
|
@ -270,6 +270,12 @@ Memory.prototype.all = function all(model, filter, callback) {
|
|||
}.bind(this));
|
||||
|
||||
if (filter) {
|
||||
if (!filter.order) {
|
||||
var idNames = this.idNames(model);
|
||||
if (idNames && idNames.length) {
|
||||
filter.order = idNames;
|
||||
}
|
||||
}
|
||||
// do we need some sorting?
|
||||
if (filter.order) {
|
||||
var orders = filter.order;
|
||||
|
|
112
lib/dao.js
112
lib/dao.js
|
@ -434,6 +434,48 @@ DataAccessObject._normalize = function (filter) {
|
|||
filter.skip = offset;
|
||||
}
|
||||
|
||||
if (filter.order) {
|
||||
var order = filter.order;
|
||||
if (!Array.isArray(order)) {
|
||||
order = [order];
|
||||
}
|
||||
var fields = [];
|
||||
for (var i = 0, m = order.length; i < m; i++) {
|
||||
if (typeof order[i] === 'string') {
|
||||
// Normalize 'f1 ASC, f2 DESC, f3' to ['f1 ASC', 'f2 DESC', 'f3']
|
||||
var tokens = order[i].split(/(?:\s*,\s*)+/);
|
||||
for (var t = 0, n = tokens.length; t < n; t++) {
|
||||
var token = tokens[t];
|
||||
if (token.length === 0) {
|
||||
// Skip empty token
|
||||
continue;
|
||||
}
|
||||
var parts = token.split(/\s+/);
|
||||
if (parts.length >= 2) {
|
||||
var dir = parts[1].toUpperCase();
|
||||
if (dir === 'ASC' || dir === 'DESC') {
|
||||
token = parts[0] + ' ' + dir;
|
||||
} else {
|
||||
err = new Error(util.format('The order %j has invalid direction', token));
|
||||
err.statusCode = 400;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
fields.push(token);
|
||||
}
|
||||
} else {
|
||||
err = new Error(util.format('The order %j is not valid', order[i]));
|
||||
err.statusCode = 400;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
if (fields.length === 1 && typeof filter.order === 'string') {
|
||||
filter.order = fields[0];
|
||||
} else {
|
||||
filter.order = fields;
|
||||
}
|
||||
}
|
||||
|
||||
// normalize fields as array of included property names
|
||||
if (filter.fields) {
|
||||
filter.fields = fieldsToArray(filter.fields,
|
||||
|
@ -445,6 +487,25 @@ DataAccessObject._normalize = function (filter) {
|
|||
return filter;
|
||||
};
|
||||
|
||||
function DateType(arg) {
|
||||
return new Date(arg);
|
||||
}
|
||||
|
||||
function BooleanType(val) {
|
||||
if (val === 'true') {
|
||||
return true;
|
||||
} else if (val === 'false') {
|
||||
return false;
|
||||
} else {
|
||||
return Boolean(val);
|
||||
}
|
||||
}
|
||||
|
||||
function NumberType(val) {
|
||||
var num = Number(val);
|
||||
return !isNaN(num) ? num : val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Coerce values based the property types
|
||||
* @param {Object} where The where clause
|
||||
|
@ -470,11 +531,11 @@ DataAccessObject._coerce = function (where) {
|
|||
if (p === 'and' || p === 'or' || p === 'nor') {
|
||||
var clauses = where[p];
|
||||
if (Array.isArray(clauses)) {
|
||||
for (var i = 0; i < clauses.length; i++) {
|
||||
self._coerce(clauses[i]);
|
||||
for (var k = 0; k < clauses.length; k++) {
|
||||
self._coerce(clauses[k]);
|
||||
}
|
||||
} else {
|
||||
err = new Error(util.format('The %p operator has invalid clauses %j', p, clauses));
|
||||
err = new Error(util.format('The %s operator has invalid clauses %j', p, clauses));
|
||||
err.statusCode = 400;
|
||||
throw err;
|
||||
}
|
||||
|
@ -488,30 +549,16 @@ DataAccessObject._coerce = function (where) {
|
|||
DataType = DataType[0];
|
||||
}
|
||||
if (DataType === Date) {
|
||||
var OrigDate = Date;
|
||||
DataType = function Date(arg) {
|
||||
return new OrigDate(arg);
|
||||
};
|
||||
DataType = DateType;
|
||||
} else if (DataType === Boolean) {
|
||||
DataType = function (val) {
|
||||
if (val === 'true') {
|
||||
return true;
|
||||
} else if (val === 'false') {
|
||||
return false;
|
||||
} else {
|
||||
return Boolean(val);
|
||||
}
|
||||
};
|
||||
DataType = BooleanType;
|
||||
} else if (DataType === Number) {
|
||||
// This fixes a regression in mongodb connector
|
||||
// For numbers, only convert it produces a valid number
|
||||
// LoopBack by default injects a number id. We should fix it based
|
||||
// on the connector's input, for example, MongoDB should use string
|
||||
// while RDBs typically use number
|
||||
DataType = function (val) {
|
||||
var num = Number(val);
|
||||
return !isNaN(num) ? num : val;
|
||||
};
|
||||
DataType = NumberType;
|
||||
}
|
||||
|
||||
if (!DataType) {
|
||||
|
@ -543,6 +590,31 @@ DataAccessObject._coerce = function (where) {
|
|||
if (op in val) {
|
||||
val = val[op];
|
||||
operator = op;
|
||||
switch(operator) {
|
||||
case 'inq':
|
||||
case 'nin':
|
||||
if (!Array.isArray(val)) {
|
||||
err = new Error(util.format('The %s property has invalid clause %j', p, where[p]));
|
||||
err.statusCode = 400;
|
||||
throw err;
|
||||
}
|
||||
break;
|
||||
case 'between':
|
||||
if (!Array.isArray(val) || val.length !== 2) {
|
||||
err = new Error(util.format('The %s property has invalid clause %j', p, where[p]));
|
||||
err.statusCode = 400;
|
||||
throw err;
|
||||
}
|
||||
break;
|
||||
case 'like':
|
||||
case 'nlike':
|
||||
if (!(typeof val === 'string' || val instanceof RegExp)) {
|
||||
err = new Error(util.format('The %s property has invalid clause %j', p, where[p]));
|
||||
err.statusCode = 400;
|
||||
throw err;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,6 +141,64 @@ describe('Memory connector', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('should throw if the like value is not string or regexp', function (done) {
|
||||
User.find({where: {name: {like: 123}}}, function (err, posts) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if the nlike value is not string or regexp', function (done) {
|
||||
User.find({where: {name: {nlike: 123}}}, function (err, posts) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if the inq value is not an array', function (done) {
|
||||
User.find({where: {name: {inq: '12'}}}, function (err, posts) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if the nin value is not an array', function (done) {
|
||||
User.find({where: {name: {nin: '12'}}}, function (err, posts) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if the between value is not an array', function (done) {
|
||||
User.find({where: {name: {between: '12'}}}, function (err, posts) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if the between value is not an array of length 2', function (done) {
|
||||
User.find({where: {name: {between: ['12']}}}, function (err, posts) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('support order with multiple fields', function (done) {
|
||||
User.find({order: 'vip ASC, seq DESC'}, function (err, posts) {
|
||||
should.not.exist(err);
|
||||
posts[0].seq.should.be.eql(4);
|
||||
posts[1].seq.should.be.eql(3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if order has wrong direction', function (done) {
|
||||
User.find({order: 'seq ABC'}, function (err, posts) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
function seed(done) {
|
||||
var beatles = [
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue