Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 135 additions & 40 deletions lib/retrieve.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ exports.findAndLoad = function findAndLoad(searches, callback) {
* Finds ids of objects by search arguments
*/
exports.find = function find(searches, callback) {
var self = this, sets = [], zsetKeys = [], s, prop,
var self = this, sets = [], zsetKeys = [],
regexp_uniques=[], regexp_sets=[], reKeys={sets:[], uniques:[]}, s, prop,
returnFunction = function (err, values) {
var found = [];
Nohm.logError(err);
Expand All @@ -112,10 +113,42 @@ exports.find = function find(searches, callback) {
} else if (values === null) {
found = [];
}
convertIdsToInt(found, function (ids) {
convertIdsToInt.call(self, found, function (ids) {
callback.call(self, err, ids);
});
},
regExpSearch = function(callback){
var regExp = function(arrayKeys, fnGetKey, callback){
if (arrayKeys.length === 0) callback(null, [])
else
{
// let's apply keys on every arrayKeys, then for each keys result we apply
// the specific data-type command to retrieve the ids
async.map(arrayKeys, self.getClient()['keys'].bind(self.getClient()), function(err, listKeys){
if (err || listKeys.length === 0) callback(err, [])
else async.map(listKeys[0], self.getClient()[fnGetKey].bind(self.getClient()), callback)
})
}
}
regExp(reKeys.sets, 'smembers', function(err, setResult){
if (err) callback(err)
else
{
// smembers return an array of array, so we concat *all* of them
if (setResult.length) setResult = [].concat.apply([], setResult)
regexp_sets = regexp_sets.concat(setResult)
// get return value, so we just concat the result
regExp(reKeys.uniques, 'get', function(err, uniqueResult){
if (err) callback(err)
else
{
regexp_uniques = regexp_uniques.concat(uniqueResult)
callback(null)
}
})
}
})
},
getSets = function (callback) {
self.getClient().sinter(sets, callback);
},
Expand All @@ -140,7 +173,10 @@ exports.find = function find(searches, callback) {
return converted;
}

options = zSet.options;
if (Object.prototype.toString.call(zSet.options) != '[object Object]')
options = {}
else
options = zSet.options;

options.min = getRedisZSetArg(options.min, parseFloat, "-inf");
options.max = getRedisZSetArg(options.max, parseFloat, "+inf");
Expand All @@ -162,6 +198,7 @@ exports.find = function find(searches, callback) {
} else if ( options.endpoints.length > 2) {
return returnFunction('Invalid search parameters: endpoints expression is invalid.');
}

endpoints = [
(options.endpoints[0] === '(' ? '(' : ''),
(options.endpoints[1] === ')' ? '(' : '')
Expand All @@ -187,56 +224,114 @@ exports.find = function find(searches, callback) {
});
};


if (typeof searches === 'function') {
callback = searches;
searches = {};
}
else if (searches.regexp)
{
searches = {}
}

for (s in searches) {
if (searches.hasOwnProperty(s) && this.properties.hasOwnProperty(s)) {
prop = this.properties[s];
if (prop.unique) {
if (prop.type === 'string') {
if ( ! searches[s].toLowerCase) {
return returnFunction('Invalid search parameters: Searching for a unique (type "string") with a non-string value is not supported.');
}
searches[s] = searches[s].toLowerCase()
if (typeof searches[s] == 'object' && searches[s].regexp)
{
if (typeof searches[s].regexp == 'string' && searches[s].regexp.length)
{
if (prop.unique)
reKeys.uniques.push(Nohm.prefix.unique + self.modelName + ':' + s + ':' + searches[s].regexp);
else if (prop.index)
reKeys.sets.push(Nohm.prefix.index + self.modelName + ':' + s + ':' + searches[s].regexp);
}
var key = Nohm.prefix.unique+self.modelName+':'+s+':'+searches[s];
return this.getClient().get([key], returnFunction);
else
return returnFunction("Invalid regexp search parameters: Regular expression searching must be object {pattern:\'regexp\'}, with 'pattern' the key, and 'regexp' the regular expression.");
}
var isNum = ! isNaN(parseInt(searches[s], 10));
if (prop.index && ( ! prop.__numericIndex || isNum) ) {
sets.push(Nohm.prefix.index + self.modelName + ':' + s + ':' + searches[s]);
} else if (prop.__numericIndex) {
zsetKeys.push({
key: Nohm.prefix.scoredindex + self.modelName + ':' + s,
options: searches[s]
});
else
{
if (prop.unique) {
if (prop.type === 'string') {
if ( ! searches[s].toLowerCase) {
return returnFunction('Invalid search parameters: Searching for a unique (type "string") with a non-string value is not supported.');
}
else searches[s] = searches[s].toLowerCase()
}
var key = Nohm.prefix.unique+self.modelName+':'+s+':'+searches[s];
return this.getClient().get([key], returnFunction);
}
var isNum = ! isNaN(parseInt(searches[s], 10));
if (prop.index && ( ! prop.__numericIndex || isNum) ) {
sets.push(Nohm.prefix.index + self.modelName + ':' + s + ':' + searches[s]);
} else if (prop.__numericIndex) {
zsetKeys.push({
key: Nohm.prefix.scoredindex + self.modelName + ':' + s,
options: searches[s]
});
}
}
}
}
if (sets.length === 0 && zsetKeys.length === 0) {
if (JSON.stringify(searches) != '{}') {
Nohm.logError("Invalid search: Index not found.");
return returnFunction(null, []);
}
// no specific searches, retrieve all ids
this.getClient().smembers(Nohm.prefix.idsets + this.modelName, returnFunction);
} else if (zsetKeys.length === 0) {
getSets(returnFunction);
} else if (sets.length === 0) {
getZSets(returnFunction);
} else {
getSets(function (err, setids) {
getZSets(function (err2, zsetids) {
if (err2) {
err = [err, err2];
// we shouldnt need 'this' here, so no need to bind it
return regExpSearch(function(err){
if (err) returnFunction(err)
else
{
if (sets.length === 0 && zsetKeys.length === 0 && regexp_uniques.length === 0
&& regexp_sets.length === 0) {
if (JSON.stringify(searches) != '{}') {
Nohm.logError("Invalid search: Index not found.");
return returnFunction(null, []);
}
returnFunction(err, h.idIntersection(setids, zsetids).sort());
});
});
}
// no specific searches, retrieve all ids
self.getClient().smembers(Nohm.prefix.idsets + self.modelName, returnFunction);
}
else {
var searchScope = {
regexp_uniques:{
fn: function(cb){cb(null, regexp_uniques)},
value: regexp_uniques,
},
regexp_sets:{
fn: function(cb){cb(null, regexp_sets)},
value: regexp_sets,
},
standard_index:{
fn:getSets,
value: sets,
},
standard_scoredIndex:{
fn:getZSets,
value: zsetKeys,
}
}
/*
To make an OR operator, you just need to set null to [] and change
"if (!memo)" to "if (!memo.length)"
*/
async.reduce(Object.keys(searchScope), null, function(memo, item, callback){
if (searchScope[item].value.length)
{
searchScope[item].fn(function(err, ids){
if (!memo) memo = ids
else memo = h.idIntersection(memo, ids)
callback(null, memo)
})
}
else callback(null, memo)
}, function(err, result){
/*
In the initial find function, there is a sort when sets/zsetKeys are not empty
I don't know why, but we do it to avoid some tests fails because
they expect specific order
*/
if ((sets.length && zsetKeys.length)||regexp_sets.length||regexp_uniques.length)
result = result.sort()
returnFunction(err, result);
})
}
}
})
};

exports.sort = function (options, ids) {
Expand Down
158 changes: 156 additions & 2 deletions test/findTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1155,7 +1155,7 @@ loadArray: function (t) {
endpoints: '('
}
}, function(err, ids) {
t.ok(!err, 'Unexpected redis error in custom query');
t.ok(!err, 'Unexpected redis error in custom query :' + err);
t.same([7, 6, 5, 4], ids, 'Defining an endpoint failed.');
UserFindMockup.find({
number: {
Expand Down Expand Up @@ -1236,7 +1236,161 @@ loadArray: function (t) {
t.same(ids, [2, 3, 8], 'The found ids were incorrect.');
t.done();
});
}
},

"star '*pattern*'": function (t) {
var findUser = new UserFindMockup();
t.expect(2);

findUser.find({
name:'*numeric*'
}, function(err, ids) {
t.ok(!err, 'Unexpected redis error in custom query');
t.same(ids, [], 'This shouldnt find any ids.');
t.done();
});
},

"regexp - field with star '*pattern*'": function (t) {
var findUser = new UserFindMockup();
t.expect(2);

findUser.find({
name:{
regexp:'*numeric*'
}
}, function(err, ids) {
t.ok(!err, 'Unexpected redis error in custom query');
t.same(ids, [1,2,3], 'This should find three ids.');
t.done();
});
},

"regexp - field with star 'pattern*'": function (t) {
var findUser = new UserFindMockup();
t.expect(2);

findUser.find({
name:{
regexp:'unique*'
}
}, function(err, ids) {
t.ok(!err, 'Unexpected redis error in custom query');
t.same(ids, [4], 'This should return one id.');
t.done();
});
},

"regexp - field with star '[p][a][t][t][e][r][n]*'": function (t) {
var findUser = new UserFindMockup();
t.expect(2);

findUser.find({
name:{
regexp:'[u][n][i][q][u][e]*'
}
}, function(err, ids) {
t.ok(!err, 'Unexpected redis error in custom query');
t.same(ids, [4], 'This should return one id.');
t.done();
});
},

"regexp - field with star '*[_]*'": function (t) {
var findUser = new UserFindMockup();
t.expect(2);

findUser.find({
name:{
regexp:'*[_]*'
}
}, function(err, ids) {
t.ok(!err, 'Unexpected redis error in custom query');
t.same(ids, [7,8], 'This should return two ids.');
t.done();
});
},

"regexp - field with star '*'": function (t) {
var findUser = new UserFindMockup();
t.expect(2);

findUser.find({
name:{
regexp:'*'
}
}, function(err, ids) {
t.ok(!err, 'Unexpected redis error in custom query');
t.same(ids, [1,2,3,4,5,6,7,8], 'This should find all ids.');
t.done();
});
},

"regexp - unique field with star '*'": function (t) {
var findUser = new UserFindMockup();
t.expect(2);

findUser.find({
email:{
regexp:'*'
}
}, function(err, ids) {
t.ok(!err, 'Unexpected redis error in custom query');
t.same(ids, [1,2,3,4,5,6,7,8], 'This should find all ids.');
t.done();
});
},

"regexp - unique field with regexp field star '*'": function (t) {
var findUser = new UserFindMockup();
t.expect(2);

findUser.find({
name:{
regexp:'*'
},
email:'numericindextest@hurgel.de',
}, function(err, ids) {
t.ok(!err, 'Unexpected redis error in custom query');
t.same(ids, [1], 'This should find one id.');
t.done();
});
},

"regexp - field with star '*@hu*el.de' + numeric with min without limit": function (t) {
var findUser = new UserFindMockup();
t.expect(2);

findUser.find({
email:{
regexp:'*@hu*el.de'
},
number: {
min: 2,
}
}, function(err, ids) {
t.ok(!err, 'Unexpected redis error in custom query');
t.same(ids, [1, 2, 3, 8], 'The found ids were incorrect.');
t.done();
});
},

"regexp - field with star '*' + numeric with min without limit": function (t) {
var findUser = new UserFindMockup();
t.expect(2);

findUser.find({
email:{
regexp:'*'
},
number: {
min: 0,
}
}, function(err, ids) {
t.ok(!err, 'Unexpected redis error in custom query');
t.same(ids, [1, 2, 3, 4, 5, 6, 7, 8], 'The found ids were incorrect.');
t.done();
});
}

};