Permalink
Browse files

Speed optimisations to the query method using for loops

  • Loading branch information...
1 parent 5c1e02f commit 5b1196a92d23115af56f71238c68053de22cb8a2 @davidgtonge committed Feb 21, 2012
Showing with 116 additions and 27 deletions.
  1. +55 −20 js/backbone-query.js
  2. +1 −1 js/backbone-query.min.js
  3. +1 −1 package.json
  4. +10 −5 src/backbone-query.coffee
  5. +49 −0 test/backbone-query-test.js
View
@@ -1,17 +1,14 @@
+
+/*
+Backbone Query - A lightweight query API for Backbone Collections
+(c)2012 - Dave Tonge
+May be freely distributed according to MIT license.
+*/
+
(function() {
- /*
- Backbone Query - A lightweight query API for Backbone Collections
- (c)2012 - Dave Tonge
- May be freely distributed according to MIT license.
- */
-
- var get_cache, get_models, get_sorted_models, iterator, page_models, parse_query, perform_query, process_query, sort_models, test_model_attribute, test_query_value;
- var __indexOf = Array.prototype.indexOf || function(item) {
- for (var i = 0, l = this.length; i < l; i++) {
- if (this[i] === item) return i;
- }
- return -1;
- };
+ var filter, get_cache, get_models, get_sorted_models, iterator, page_models, parse_query, perform_query, process_query, reject, sort_models, test_model_attribute, test_query_value,
+ __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
parse_query = function(raw_query) {
var key, o, query_param, type, value, _results;
_results = [];
@@ -39,6 +36,7 @@
}
return _results;
};
+
test_query_value = function(type, value) {
switch (type) {
case "$in":
@@ -61,6 +59,7 @@
return true;
}
};
+
test_model_attribute = function(type, value) {
switch (type) {
case "$like":
@@ -80,6 +79,7 @@
return true;
}
};
+
perform_query = function(type, value, attr, model) {
switch (type) {
case "$equal":
@@ -127,10 +127,11 @@
return false;
}
};
+
iterator = function(models, query, andOr, filterReject) {
var parsed_query;
parsed_query = parse_query(query);
- return _[filterReject](models, function(model) {
+ return filterReject(models, function(model) {
var attr, q, test, _i, _len;
for (_i = 0, _len = parsed_query.length; _i < _len; _i++) {
q = parsed_query[_i];
@@ -142,20 +143,42 @@
return !andOr;
});
};
+
+ filter = function(array, test) {
+ var index, val, _i, _len, _results;
+ _results = [];
+ for (index = _i = 0, _len = array.length; _i < _len; index = ++_i) {
+ val = array[index];
+ if (test(val)) _results.push(val);
+ }
+ return _results;
+ };
+
+ reject = function(array, test) {
+ var index, val, _i, _len, _results;
+ _results = [];
+ for (index = _i = 0, _len = array.length; _i < _len; index = ++_i) {
+ val = array[index];
+ if (!test(val)) _results.push(val);
+ }
+ return _results;
+ };
+
process_query = {
$and: function(models, query) {
- return iterator(models, query, false, "filter");
+ return iterator(models, query, false, filter);
},
$or: function(models, query) {
- return iterator(models, query, true, "filter");
+ return iterator(models, query, true, filter);
},
$nor: function(models, query) {
- return iterator(models, query, true, "reject");
+ return iterator(models, query, true, reject);
},
$not: function(models, query) {
- return iterator(models, query, false, "reject");
+ return iterator(models, query, false, reject);
}
};
+
get_cache = function(collection, query, options) {
var cache, models, query_string, _ref;
query_string = JSON.stringify(query);
@@ -167,6 +190,7 @@
}
return models;
};
+
get_models = function(collection, query) {
var compound_query, models, reduce_iterator;
compound_query = _.intersection(["$and", "$not", "$or", "$nor"], _(query).keys());
@@ -180,12 +204,14 @@
return _.reduce(compound_query, reduce_iterator, models);
}
};
+
get_sorted_models = function(collection, query, options) {
var models;
models = get_models(collection, query);
if (options.sortBy) models = sort_models(models, options);
return models;
};
+
sort_models = function(models, options) {
if (_(options.sortBy).isString()) {
models = _(models).sortBy(function(model) {
@@ -197,6 +223,7 @@
if (options.order === "desc") models = models.reverse();
return models;
};
+
page_models = function(models, options) {
var end, sliced_models, start, total_pages;
if (options.offset) {
@@ -214,10 +241,14 @@
}
return sliced_models;
};
+
if (typeof require !== 'undefined') {
if (typeof _ === "undefined" || _ === null) _ = require('underscore');
- if (typeof Backbone === "undefined" || Backbone === null) Backbone = require('backbone');
+ if (typeof Backbone === "undefined" || Backbone === null) {
+ Backbone = require('backbone');
+ }
}
+
Backbone.QueryCollection = Backbone.Collection.extend({
query: function(query, options) {
var models;
@@ -238,5 +269,9 @@
return this._query_cache = {};
}
});
- if (typeof exports !== "undefined") exports.QueryCollection = Backbone.QueryCollection;
+
+ if (typeof exports !== "undefined") {
+ exports.QueryCollection = Backbone.QueryCollection;
+ }
+
}).call(this);
@@ -1 +1 @@
-((function(){var a,b,c,d,e,f,g,h,i,j,k,l=Array.prototype.indexOf||function(a){for(var b=0,c=this.length;b<c;b++)if(this[b]===a)return b;return-1};f=function(a){var b,c,d,e,f,g;g=[];for(b in a){d=a[b],c={key:b};if(_.isRegExp(d))c.type="$regex",c.value=d;else if(_(d).isObject())for(e in d)f=d[e],k(e,f)&&(c.type=e,c.value=f);else c.type="$equal",c.value=d;g.push(c)}return g},k=function(a,b){switch(a){case"$in":case"$nin":case"$all":case"$any":return _(b).isArray();case"$size":return _(b).isNumber();case"$regex":return _(b).isRegExp();case"$like":case"$likeI":return _(b).isString();case"$between":return _(b).isArray()&&b.length===2;case"$cb":return _(b).isFunction();default:return!0}},j=function(a,b){switch(a){case"$like":case"$likeI":case"$regex":return _(b).isString();case"$contains":case"$all":case"$any":return _(b).isArray();case"$size":return _(b).isArray()||_(b).isString();case"$in":case"$nin":return b!=null;default:return!0}},g=function(a,b,c,d){switch(a){case"$equal":return c===b;case"$contains":return l.call(c,b)>=0;case"$ne":return c!==b;case"$lt":return c<b;case"$gt":return c>b;case"$lte":return c<=b;case"$gte":return c>=b;case"$between":return b[0]<c&&c<b[1];case"$in":return l.call(b,c)>=0;case"$nin":return l.call(b,c)<0;case"$all":return _(c).all(function(a){return l.call(b,a)>=0});case"$any":return _(c).any(function(a){return l.call(b,a)>=0});case"$size":return c.length===b;case"$exists":case"$has":return c!=null===b;case"$like":return c.indexOf(b)!==-1;case"$likeI":return c.toLowerCase().indexOf(b.toLowerCase())!==-1;case"$regex":return b.test(c);case"$cb":return b.call(d,c);default:return!1}},d=function(a,b,c,d){var e;return e=f(b),_[d](a,function(a){var b,d,f,h,i;for(h=0,i=e.length;h<i;h++){d=e[h],b=a.get(d.key),f=j(d.type,b),f&&(f=g(d.type,d.value,b,a));if(c===f)return c}return!c})},h={$and:function(a,b){return d(a,b,!1,"filter")},$or:function(a,b){return d(a,b,!0,"filter")},$nor:function(a,b){return d(a,b,!0,"reject")},$not:function(a,b){return d(a,b,!1,"reject")}},a=function(a,b,d){var e,f,g,h;return g=JSON.stringify(b),e=(h=a._query_cache)!=null?h:a._query_cache={},f=e[g],f||(f=c(a,b,d),e[g]=f),f},b=function(a,b){var c,d,e;return c=_.intersection(["$and","$not","$or","$nor"],_(b).keys()),d=a.models,c.length===0?h.$and(d,b):(e=function(a,c){return h[c](a,b[c])},_.reduce(c,e,d))},c=function(a,c,d){var e;return e=b(a,c),d.sortBy&&(e=i(e,d)),e},i=function(a,b){return _(b.sortBy).isString()?a=_(a).sortBy(function(a){return a.get(b.sortBy)}):_(b.sortBy).isFunction()&&(a=_(a).sortBy(b.sortBy)),b.order==="desc"&&(a=a.reverse()),a},e=function(a,b){var c,d,e,f;return b.offset?e=b.offset:b.page?e=(b.page-1)*b.limit:e=0,c=e+b.limit,d=a.slice(e,c),b.pager&&_.isFunction(b.pager)&&(f=Math.ceil(a.length/b.limit),b.pager(f,d)),d};if(typeof require!="undefined"){if(typeof _=="undefined"||_===null)_=require("underscore");if(typeof Backbone=="undefined"||Backbone===null)Backbone=require("backbone")}Backbone.QueryCollection=Backbone.Collection.extend({query:function(b,d){var f;return d==null&&(d={}),d.cache?f=a(this,b,d):f=c(this,b,d),d.limit&&(f=e(f,d)),f},where:function(a,b){return b==null&&(b={}),new this.constructor(this.query(a,b))},reset_query_cache:function(){return this._query_cache={}}}),typeof exports!="undefined"&&(exports.QueryCollection=Backbone.QueryCollection)})).call(this)
+((function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n=Array.prototype.indexOf||function(a){for(var b=0,c=this.length;b<c;b++)if(b in this&&this[b]===a)return b;return-1};g=function(a){var b,c,d,e,f,g;g=[];for(b in a){d=a[b],c={key:b};if(_.isRegExp(d))c.type="$regex",c.value=d;else if(_(d).isObject())for(e in d)f=d[e],m(e,f)&&(c.type=e,c.value=f);else c.type="$equal",c.value=d;g.push(c)}return g},m=function(a,b){switch(a){case"$in":case"$nin":case"$all":case"$any":return _(b).isArray();case"$size":return _(b).isNumber();case"$regex":return _(b).isRegExp();case"$like":case"$likeI":return _(b).isString();case"$between":return _(b).isArray()&&b.length===2;case"$cb":return _(b).isFunction();default:return!0}},l=function(a,b){switch(a){case"$like":case"$likeI":case"$regex":return _(b).isString();case"$contains":case"$all":case"$any":return _(b).isArray();case"$size":return _(b).isArray()||_(b).isString();case"$in":case"$nin":return b!=null;default:return!0}},h=function(a,b,c,d){switch(a){case"$equal":return c===b;case"$contains":return n.call(c,b)>=0;case"$ne":return c!==b;case"$lt":return c<b;case"$gt":return c>b;case"$lte":return c<=b;case"$gte":return c>=b;case"$between":return b[0]<c&&c<b[1];case"$in":return n.call(b,c)>=0;case"$nin":return n.call(b,c)<0;case"$all":return _(c).all(function(a){return n.call(b,a)>=0});case"$any":return _(c).any(function(a){return n.call(b,a)>=0});case"$size":return c.length===b;case"$exists":case"$has":return c!=null===b;case"$like":return c.indexOf(b)!==-1;case"$likeI":return c.toLowerCase().indexOf(b.toLowerCase())!==-1;case"$regex":return b.test(c);case"$cb":return b.call(d,c);default:return!1}},e=function(a,b,c,d){var e;return e=g(b),d(a,function(a){var b,d,f,g,i;for(g=0,i=e.length;g<i;g++){d=e[g],b=a.get(d.key),f=l(d.type,b),f&&(f=h(d.type,d.value,b,a));if(c===f)return c}return!c})},a=function(a,b){var c,d,e,f,g;g=[];for(c=e=0,f=a.length;e<f;c=++e)d=a[c],b(d)&&g.push(d);return g},j=function(a,b){var c,d,e,f,g;g=[];for(c=e=0,f=a.length;e<f;c=++e)d=a[c],b(d)||g.push(d);return g},i={$and:function(b,c){return e(b,c,!1,a)},$or:function(b,c){return e(b,c,!0,a)},$nor:function(a,b){return e(a,b,!0,j)},$not:function(a,b){return e(a,b,!1,j)}},b=function(a,b,c){var e,f,g,h;return g=JSON.stringify(b),e=(h=a._query_cache)!=null?h:a._query_cache={},f=e[g],f||(f=d(a,b,c),e[g]=f),f},c=function(a,b){var c,d,e;return c=_.intersection(["$and","$not","$or","$nor"],_(b).keys()),d=a.models,c.length===0?i.$and(d,b):(e=function(a,c){return i[c](a,b[c])},_.reduce(c,e,d))},d=function(a,b,d){var e;return e=c(a,b),d.sortBy&&(e=k(e,d)),e},k=function(a,b){return _(b.sortBy).isString()?a=_(a).sortBy(function(a){return a.get(b.sortBy)}):_(b.sortBy).isFunction()&&(a=_(a).sortBy(b.sortBy)),b.order==="desc"&&(a=a.reverse()),a},f=function(a,b){var c,d,e,f;return b.offset?e=b.offset:b.page?e=(b.page-1)*b.limit:e=0,c=e+b.limit,d=a.slice(e,c),b.pager&&_.isFunction(b.pager)&&(f=Math.ceil(a.length/b.limit),b.pager(f,d)),d};if(typeof require!="undefined"){if(typeof _=="undefined"||_===null)_=require("underscore");if(typeof Backbone=="undefined"||Backbone===null)Backbone=require("backbone")}Backbone.QueryCollection=Backbone.Collection.extend({query:function(a,c){var e;return c==null&&(c={}),c.cache?e=b(this,a,c):e=d(this,a,c),c.limit&&(e=f(e,c)),e},where:function(a,b){return b==null&&(b={}),new this.constructor(this.query(a,b))},reset_query_cache:function(){return this._query_cache={}}}),typeof exports!="undefined"&&(exports.QueryCollection=Backbone.QueryCollection)})).call(this)
View
@@ -1,7 +1,7 @@
{
"name": "backbone-query"
, "description": "Lightweight Query API for Backbone Collections"
- , "version": "0.1.1"
+ , "version": "0.1.2"
, "author": "Dave Tonge <dave@simplecreativity.co.uk> (https://github.com/davidgtonge)"
, "tags": ["backbone", "underscore", "mongo", "query"]
, "main": "./js/backbone-query"
@@ -74,7 +74,7 @@ perform_query = (type, value, attr, model) ->
iterator = (models, query, andOr, filterReject) ->
parsed_query = parse_query query
# The collections filter or reject method is used to iterate through each model in the collection
- _[filterReject] models, (model) ->
+ filterReject models, (model) ->
# For each model in the collection, iterate through the supplied queries
for q in parsed_query
# Retrieve the attribute value from the model
@@ -91,12 +91,17 @@ iterator = (models, query, andOr, filterReject) ->
# For an "and" query, if all the queries are true, then we return true
not andOr
+# Custom Filter / Reject methods faster than underscore methods as use for loops
+# http://jsperf.com/filter-vs-for-loop2
+filter = (array, test) -> (val for val, index in array when test val)
+reject = (array, test) -> (val for val, index in array when not test val)
+
# An object with or, and, nor and not methods
process_query =
- $and: (models, query) -> iterator models, query, false, "filter"
- $or: (models, query) -> iterator models, query, true, "filter"
- $nor: (models, query) -> iterator models, query, true, "reject"
- $not: (models, query) -> iterator models, query, false, "reject"
+ $and: (models, query) -> iterator models, query, false, filter
+ $or: (models, query) -> iterator models, query, true, filter
+ $nor: (models, query) -> iterator models, query, true, reject
+ $not: (models, query) -> iterator models, query, false, reject
# This method attempts to retrieve the result from the cache.
Oops, something went wrong.

0 comments on commit 5b1196a

Please sign in to comment.