Add support for regex operator
This commit is contained in:
parent
01af886c7d
commit
b8f1598723
|
@ -350,9 +350,8 @@ Memory.prototype.all = function all(model, filter, options, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// do we need some filtration?
|
// do we need some filtration?
|
||||||
if (filter.where) {
|
if (filter.where && nodes)
|
||||||
nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes;
|
nodes = nodes.filter(applyFilter(filter));
|
||||||
}
|
|
||||||
|
|
||||||
// field selection
|
// field selection
|
||||||
if (filter.fields) {
|
if (filter.fields) {
|
||||||
|
@ -401,7 +400,7 @@ function applyFilter(filter) {
|
||||||
var keys = Object.keys(where);
|
var keys = Object.keys(where);
|
||||||
return function (obj) {
|
return function (obj) {
|
||||||
var pass = true;
|
var pass = true;
|
||||||
keys.forEach(function (key) {
|
keys.forEach(function(key) {
|
||||||
if(key === 'and' || key === 'or') {
|
if(key === 'and' || key === 'or') {
|
||||||
if(Array.isArray(where[key])) {
|
if(Array.isArray(where[key])) {
|
||||||
if(key === 'and') {
|
if(key === 'and') {
|
||||||
|
@ -461,6 +460,10 @@ function applyFilter(filter) {
|
||||||
if (typeof value === 'string' && (example instanceof RegExp)) {
|
if (typeof value === 'string' && (example instanceof RegExp)) {
|
||||||
return value.match(example);
|
return value.match(example);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (example.regexp)
|
||||||
|
return value.match(example.regexp);
|
||||||
|
|
||||||
if (example === undefined) {
|
if (example === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
16
lib/dao.js
16
lib/dao.js
|
@ -939,7 +939,8 @@ var operators = {
|
||||||
nin: 'NOT IN',
|
nin: 'NOT IN',
|
||||||
neq: '!=',
|
neq: '!=',
|
||||||
like: 'LIKE',
|
like: 'LIKE',
|
||||||
nlike: 'NOT LIKE'
|
nlike: 'NOT LIKE',
|
||||||
|
regexp: 'REGEXP'
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1169,6 +1170,13 @@ DataAccessObject._coerce = function (where) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'regexp':
|
||||||
|
val = utils.toRegExp(val);
|
||||||
|
if (val instanceof Error) {
|
||||||
|
result.statusCode = 400;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1183,8 +1191,9 @@ DataAccessObject._coerce = function (where) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
if (!((operator === 'like' || operator === 'nlike') &&
|
if (operator === 'regexp' && val instanceof RegExp) {
|
||||||
val instanceof RegExp)) {
|
// do not coerce regex literals/objects
|
||||||
|
} else if (!((operator === 'like' || operator === 'nlike') && val instanceof RegExp)) {
|
||||||
val = DataType(val);
|
val = DataType(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1237,6 +1246,7 @@ DataAccessObject._coerce = function (where) {
|
||||||
* - neq: !=
|
* - neq: !=
|
||||||
* - like: LIKE
|
* - like: LIKE
|
||||||
* - nlike: NOT LIKE
|
* - nlike: NOT LIKE
|
||||||
|
* - regexp: REGEXP
|
||||||
*
|
*
|
||||||
* You can also use `and` and `or` operations. See [Querying models](http://docs.strongloop.com/display/DOC/Querying+models) for more information.
|
* You can also use `and` and `or` operations. See [Querying models](http://docs.strongloop.com/display/DOC/Querying+models) for more information.
|
||||||
* @property {String|Object|Array} include Allows you to load relations of several objects and optimize numbers of requests.
|
* @property {String|Object|Array} include Allows you to load relations of several objects and optimize numbers of requests.
|
||||||
|
|
52
lib/utils.js
52
lib/utils.js
|
@ -12,6 +12,9 @@ exports.mergeQuery = mergeQuery;
|
||||||
exports.mergeIncludes = mergeIncludes;
|
exports.mergeIncludes = mergeIncludes;
|
||||||
exports.createPromiseCallback = createPromiseCallback;
|
exports.createPromiseCallback = createPromiseCallback;
|
||||||
exports.uniq = uniq;
|
exports.uniq = uniq;
|
||||||
|
exports.toRegExp = toRegExp;
|
||||||
|
exports.hasRegExpFlags = hasRegExpFlags;
|
||||||
|
exports.getRegExpExpression = getRegExpExpression;
|
||||||
|
|
||||||
var traverse = require('traverse');
|
var traverse = require('traverse');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
@ -506,3 +509,52 @@ function uniq(a) {
|
||||||
}
|
}
|
||||||
return uniqArray;
|
return uniqArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string, regex literal, or a RegExp object to a RegExp object.
|
||||||
|
* @param {String|Object} The string, regex literal, or RegExp object to convert
|
||||||
|
* @returns {Object} A RegExp object
|
||||||
|
*/
|
||||||
|
function toRegExp(regex) {
|
||||||
|
var isString = typeof regex === 'string';
|
||||||
|
var isRegExp = regex instanceof RegExp;
|
||||||
|
|
||||||
|
if (!(isString || isRegExp))
|
||||||
|
return new Error('Invalid argument, must be a string, regex literal, or ' +
|
||||||
|
'RegExp object');
|
||||||
|
|
||||||
|
if (isRegExp)
|
||||||
|
return regex;
|
||||||
|
|
||||||
|
if (!hasRegExpFlags(regex))
|
||||||
|
return new RegExp(regex);
|
||||||
|
|
||||||
|
// only accept i, g, or m as valid regex flags
|
||||||
|
var flags = regex.split('/').pop().split('');
|
||||||
|
var validFlags = ['i', 'g', 'm'];
|
||||||
|
var invalidFlags = [];
|
||||||
|
flags.forEach(function(flag) {
|
||||||
|
if (validFlags.indexOf(flag) === -1)
|
||||||
|
invalidFlags.push(flag);
|
||||||
|
});
|
||||||
|
|
||||||
|
var hasInvalidFlags = invalidFlags.length > 0;
|
||||||
|
if (hasInvalidFlags)
|
||||||
|
return new Error('Invalid regex flags: ' + invalidFlags);
|
||||||
|
|
||||||
|
// strip regex delimiter forward slashes
|
||||||
|
var expression = regex.substr(1, regex.lastIndexOf('/') - 1);
|
||||||
|
return new RegExp(expression, flags.join(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasRegExpFlags(regex) {
|
||||||
|
return regex instanceof RegExp ?
|
||||||
|
regex.toString().split('/').pop() :
|
||||||
|
!!regex.match(/.*\/.+$/);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRegExpExpression(regex) {
|
||||||
|
return regex instanceof RegExp ?
|
||||||
|
regex.source :
|
||||||
|
regex.split('/').shift();
|
||||||
|
}
|
||||||
|
|
|
@ -600,6 +600,24 @@ describe('basic-querying', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context('regexp operator', function() {
|
||||||
|
var invalidDataTypes = [0, true, {}, [], Function, null];
|
||||||
|
|
||||||
|
before(seed);
|
||||||
|
|
||||||
|
it('should return an error for invalid data types', function(done) {
|
||||||
|
// `undefined` is not tested because the `removeUndefined` function
|
||||||
|
// in `lib/dao.js` removes it before coercion
|
||||||
|
invalidDataTypes.forEach(function(invalidDataType) {
|
||||||
|
User.find({where: {name: {regexp: invalidDataType}}}, function(err,
|
||||||
|
users) {
|
||||||
|
should.exist(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function seed(done) {
|
function seed(done) {
|
||||||
|
|
|
@ -356,6 +356,34 @@ describe('Memory connector', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support the regexp operator with regex strings', function(done) {
|
||||||
|
User.find({where: {name: {regexp: '^J'}}}, function(err, users) {
|
||||||
|
should.not.exist(err);
|
||||||
|
users.length.should.equal(1);
|
||||||
|
users[0].name.should.equal('John Lennon');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support the regexp operator with regex literals', function(done) {
|
||||||
|
User.find({where: {name: {regexp: /^J/}}}, function(err, users) {
|
||||||
|
should.not.exist(err);
|
||||||
|
users.length.should.equal(1);
|
||||||
|
users[0].name.should.equal('John Lennon');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support the regexp operator with regex objects', function(done) {
|
||||||
|
User.find({where: {name: {regexp: new RegExp(/^J/)}}}, function(err,
|
||||||
|
users) {
|
||||||
|
should.not.exist(err);
|
||||||
|
users.length.should.equal(1);
|
||||||
|
users[0].name.should.equal('John Lennon');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should support nested property in query', function(done) {
|
it('should support nested property in query', function(done) {
|
||||||
User.find({where: {'address.city': 'San Jose'}}, function(err, users) {
|
User.find({where: {'address.city': 'San Jose'}}, function(err, users) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
|
|
@ -420,3 +420,111 @@ describe('util.uniq', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('util.toRegExp', function() {
|
||||||
|
var invalidDataTypes;
|
||||||
|
var validDataTypes;
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
invalidDataTypes = [0, true, {}, [], Function, null];
|
||||||
|
validDataTypes = ['string', /^regex/, new RegExp(/^regex/)];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not accept invalid data types', function() {
|
||||||
|
invalidDataTypes.forEach(function(invalid) {
|
||||||
|
utils.toRegExp(invalid).should.be.an.Error;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept valid data types', function() {
|
||||||
|
validDataTypes.forEach(function(valid) {
|
||||||
|
utils.toRegExp(valid).should.not.be.an.Error;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with a regex string', function() {
|
||||||
|
it('should return a RegExp object when no regex flags are provided',
|
||||||
|
function() {
|
||||||
|
utils.toRegExp('^regex$').should.be.an.instanceOf(RegExp);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when invalid regex flags are provided',
|
||||||
|
function() {
|
||||||
|
utils.toRegExp('^regex$/abc').should.be.an.Error;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RegExp object when valid flags are provided',
|
||||||
|
function() {
|
||||||
|
utils.toRegExp('regex/igm').should.be.an.instanceOf(RegExp);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with a regex literal', function() {
|
||||||
|
it('should return a RegExp object', function() {
|
||||||
|
utils.toRegExp(/^regex$/igm).should.be.an.instanceOf(RegExp);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with a regex object', function() {
|
||||||
|
it('should return a RegExp object', function() {
|
||||||
|
utils.toRegExp(new RegExp('^regex$', 'igm')).should.be.an.instanceOf(RegExp);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('util.hasRegExpFlags', function() {
|
||||||
|
context('with a regex string', function() {
|
||||||
|
it('should be true when the regex has invalid flags', function() {
|
||||||
|
utils.hasRegExpFlags('^regex$/abc').should.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be true when the regex has valid flags', function() {
|
||||||
|
utils.hasRegExpFlags('^regex$/igm').should.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be false when the regex has no flags', function() {
|
||||||
|
utils.hasRegExpFlags('^regex$').should.not.be.ok;
|
||||||
|
utils.hasRegExpFlags('^regex$/').should.not.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with a regex literal', function() {
|
||||||
|
it('should be true when the regex has valid flags', function() {
|
||||||
|
utils.hasRegExpFlags(/^regex$/igm).should.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be false when the regex has no flags', function() {
|
||||||
|
utils.hasRegExpFlags(/^regex$/).should.not.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with a regex object', function() {
|
||||||
|
it('should be true when the regex has valid flags', function() {
|
||||||
|
utils.hasRegExpFlags(new RegExp(/^regex$/igm)).should.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be false when the regex has no flags', function() {
|
||||||
|
utils.hasRegExpFlags(new RegExp(/^regex$/)).should.not.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('util.getRegExpExpression', function() {
|
||||||
|
context('with a regex string', function() {
|
||||||
|
it('should return the expression without flags', function() {
|
||||||
|
utils.getRegExpExpression('^regex$/abc').should.equal('^regex$');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with a regex literal', function() {
|
||||||
|
it('should return the expression without flags', function() {
|
||||||
|
utils.hasRegExpFlags(/^regex$/igm).should.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with a regex object', function() {
|
||||||
|
it('should return the expression without flags', function() {
|
||||||
|
utils.hasRegExpFlags(new RegExp(/^regex$/igm)).should.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue