Able to mix logical operators and comparison operators

pull/2/head
Louis Chatriot 12 years ago
parent 4256632f72
commit a42842470b
  1. 85
      lib/model.js
  2. 2
      test/model.test.js

@ -9,7 +9,8 @@ var dateToJSON = function () { return { $$date: this.getTime() }; }
, util = require('util') , util = require('util')
, _ = require('underscore') , _ = require('underscore')
, modifierFunctions = {} , modifierFunctions = {}
, matcherFunctions = {} , comparisonFunctions = {}
, logicalOperators = {}
; ;
@ -230,6 +231,24 @@ function modify (obj, updateQuery) {
// Finding documents // Finding documents
// ============================================================== // ==============================================================
/**
* Get a value from object with dot notation
* @param {Object} obj
* @param {String} field
*/
function getDotValue (obj, field) {
var fieldParts = typeof field === 'string' ? field.split('.') : field;
if (!obj) { return undefined; } // field cannot be empty so that means we should return undefined so that nothing can match
if (fieldParts.length === 1) {
return obj[fieldParts[0]];
} else {
return getDotValue(obj[fieldParts[0]], fieldParts.slice(1));
}
}
/** /**
* Check whether 'things' are equal * Check whether 'things' are equal
* Things are defined as any native types (string, number, boolean, null, date) and objects * Things are defined as any native types (string, number, boolean, null, date) and objects
@ -268,28 +287,22 @@ function areThingsEqual (a, b) {
} }
/** comparisonFunctions.$lt = function (a, b) {
* Get a value from object with dot notation if (typeof a !== 'string' && typeof a !== 'number' && !util.isDate(a) &&
* @param {Object} obj typeof b !== 'string' && typeof b !== 'number' && !util.isDate(b)) {
* @param {String} field return false;
*/ }
function getDotValue (obj, field) {
var fieldParts = typeof field === 'string' ? field.split('.') : field;
if (!obj) { return undefined; } // field cannot be empty so that means we should return undefined so that nothing can match if (typeof a !== typeof b) { return false; }
if (fieldParts.length === 1) { return a < b;
return obj[fieldParts[0]]; };
} else {
return getDotValue(obj[fieldParts[0]], fieldParts.slice(1));
}
}
/** /**
* Match any of the subqueries * Match any of the subqueries
*/ */
matcherFunctions.$or = function (obj, query) { logicalOperators.$or = function (obj, query) {
var i; var i;
if (!util.isArray(query)) { throw "$or operator used without an array"; } if (!util.isArray(query)) { throw "$or operator used without an array"; }
@ -305,7 +318,7 @@ matcherFunctions.$or = function (obj, query) {
/** /**
* Match all of the subqueries * Match all of the subqueries
*/ */
matcherFunctions.$and = function (obj, query) { logicalOperators.$and = function (obj, query) {
var i; var i;
if (!util.isArray(query)) { throw "$and operator used without an array"; } if (!util.isArray(query)) { throw "$and operator used without an array"; }
@ -342,16 +355,18 @@ function match (obj, query) {
function matchQueryPart (obj, queryKey, queryValue) { function matchQueryPart (obj, queryKey, queryValue) {
var objValue = getDotValue(obj, queryKey) var objValue = getDotValue(obj, queryKey)
, i , i
, keys, firstChars, dollarFirstChars
; ;
// Query part begins with a logical operator: apply it // Query part begins with a logical operator: apply it
if (queryKey[0] === '$') { if (queryKey[0] === '$') {
if (!matcherFunctions[queryKey]) { throw "Unknown query operator " + queryKey; } if (!logicalOperators[queryKey]) { throw "Unknown logical operator " + queryKey; }
return matcherFunctions[queryKey](obj, queryValue); return logicalOperators[queryKey](obj, queryValue);
} }
// Check if the object value is an array and we need to rewrite the query // Check if the object value is an array treat it as an array of { obj, query }
// Where there needs to be at least one match
if (util.isArray(objValue)) { if (util.isArray(objValue)) {
for (i = 0; i < objValue.length; i += 1) { for (i = 0; i < objValue.length; i += 1) {
if (match({ queryKey: objValue[i] }, { queryKey: queryValue })) { return true; } if (match({ queryKey: objValue[i] }, { queryKey: queryValue })) { return true; }
@ -359,7 +374,35 @@ function matchQueryPart (obj, queryKey, queryValue) {
return false; return false;
} }
// Normal field matching // queryValue is an actual object. Determine whether it contains comparison operators
// or only normal fields. Mixed objects are not allowed
if (queryValue !== null && typeof queryValue === 'object') {
keys = Object.keys(queryValue);
firstChars = _.map(keys, function (item) { return item[0]; })
dollarFirstChars = _.filter(firstChars, function (c) { return c === '$'; })
if (dollarFirstChars.length !== 0 && dollarFirstChars.length !== firstChars.length) {
throw "You cannot mix operators and normal fields";
}
// queryValue is an object of this form: { $comparisonOperator1: value1, ... }
if (dollarFirstChars.length > 0) {
for (i = 0; i < keys.length; i += 1) {
if (!comparisonFunctions[keys[i]]) { throw "Unknown comparison function " + keys[i]; }
if (!comparisonFunctions[keys[i]](objValue, queryValue[keys[i]])) { return false; }
}
return true;
}
}
// Query part if of the form { field: $logicaloperator }
//if (logicalOperators[queryValue]) { throw "Can't use logical operator " + queryValue + " here"; }
// queryValue is either a native value or a normal object
// Simple matching is possible
if (!areThingsEqual(objValue, queryValue)) { return false; } if (!areThingsEqual(objValue, queryValue)) { return false; }
return true; return true;

@ -431,7 +431,7 @@ describe('Model', function () {
model.match({ a: { b: 5, c: 3 } }, { a: { b: 5 } }).should.equal(false); model.match({ a: { b: 5, c: 3 } }, { a: { b: 5 } }).should.equal(false);
model.match({ a: { b: 5 } }, { a: { b: { $lt: 10 } } }).should.equal(false); model.match({ a: { b: 5 } }, { a: { b: { $lt: 10 } } }).should.equal(false);
model.match({ a: { b: 5 } }, { a: { $or: { b: 10, b: 5 } } }).should.equal(false); (function () { model.match({ a: { b: 5 } }, { a: { $or: [ { b: 10 }, { b: 5 } ] } }) }).should.throw();
}); });
}); });

Loading…
Cancel
Save