Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

First commit

  • Loading branch information...
commit 677f3e86b7c9c4f64d43847af9c9bb1716b10261 0 parents
@davidgtonge authored
3  .gitignore
@@ -0,0 +1,3 @@
+# Idea IDE files
+.idea/
+node_modules/
3  .npmignore
@@ -0,0 +1,3 @@
+.idea/
+.git/
+node_modules/
9 .travis.yml
@@ -0,0 +1,9 @@
+language: node_js
+node_js:
+ - 0.6
+
+before_script:
+ - "npm install"
+
+script:
+ - "node_modules/.bin/cake test"
18 Cakefile
@@ -0,0 +1,18 @@
+fs = require 'fs'
+{exec} = require 'child_process'
+
+task 'build', 'Build JS files from Coffee sources', ->
+
+ exec 'coffee -c -o js/ src/', (err, stdout, stderr) ->
+ throw err if err
+ console.log stdout + stderr
+
+ exec 'coffee -c test/node_dirty_query_test.coffee', (err, stdout, stderr) ->
+ throw err if err
+ console.log stdout + stderr
+
+task "test", "Test the code", ->
+ path = require 'path'
+ reporter = require('nodeunit').reporters.default
+
+ reporter.run ["test/node_dirty_query_test.js"]
19 LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Dave Tonge
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
1  README
@@ -0,0 +1 @@
+This file was created by JetBrains PhpStorm 3.0 for binding GitHub repository
345 README.md
@@ -0,0 +1,345 @@
+node-dirty-query
+===================
+
+[![Build Status](https://secure.travis-ci.org/davidgtonge/node_drty_query.png)](http://travis-ci.org/davidgtonge/node_drty_query)
+
+A lightweight utility for querying "node dirty" databases.
+Adds the ability to search for models with a Query API similar to
+[MongoDB](http://www.mongodb.org/display/DOCS/Advanced+Queries)
+Please report any bugs, feature requests in the issue tracker.
+Pull requests are welcome!
+
+Usage
+=====
+
+You can install with NPM: `npm install dirty-query`
+Then simply require in your project: `query = require("dirty-query").query`
+
+Use the query method like this:
+
+```js
+db = require('dirty')('my-db')
+query db, { {featured:true}, {likes: $gt:10} };
+// Returns all results where the featured attribute is true and there are
+// more than 10 likes
+
+
+Query API (NEED TO UPDATE - CURRENTLY DESCRIBES BACKBONE QUERY)
+===
+
+### $equal
+Performs a strict equality test using `===`. If no operator is provided and the query value isn't a regex then `$equal` is assumed.
+
+```javascript
+MyCollection.query({ title:"Test" });
+// Returns all models which have a "title" attribute of "Test"
+
+MyCollection.query({ title: {$equal:"Test"} }); // Same as above
+```
+
+### $contains
+Assumes that the model property is an array and searches for the query value in the array
+
+```js
+MyCollection.query({ colors: {$contains: "red"} });
+// Returns models which contain the value "red" in a "colors" attribute that is an array.
+// e.g. a model with this attribute colors:["red","yellow","blue"] would be returned
+```
+
+### $ne
+"Not equal", the opposite of $equal, returns all models which don't have the query value
+
+```js
+MyCollection.query({ title: {$ne:"Test"} });
+// Returns all models which don't have a "title" attribute of "Test"
+```
+
+### $lt, $lte, $gt, $gte
+These conditional operators can be used for greater than and less than comparisons in queries
+
+```js
+MyCollection.query({ likes: {$lt:10} });
+// Returns all models which have a "likes" attribute of less than 10
+MyCollection.query({ likes: {$lte:10} });
+// Returns all models which have a "likes" attribute of less than or equal to 10
+MyCollection.query({ likes: {$gt:10} });
+// Returns all models which have a "likes" attribute of greater than 10
+MyCollection.query({ likes: {$gte:10} });
+// Returns all models which have a "likes" attribute of greater than or equal to 10
+```
+
+### $between
+To check if a value is in-between 2 query values use the $between operator and supply an array with the min and max value
+
+```js
+MyCollection.query({ likes: {$between:[5,15} });
+// Returns all models which have a "likes" attribute of greater than 5 and less then 15
+```
+
+### $in
+An array of possible values can be supplied using $in, a model will be returned if any of the supplied values is matched
+
+```js
+MyCollection.query({ title: {$in:["About", "Home", "Contact"] } });
+// Returns all models which have a title attribute of either "About", "Home", or "Contact"
+```
+
+### $nin
+"Not in", the opposite of $in. A model will be returned if none of the supplied values is matched
+
+```js
+MyCollection.query({ title: {$nin:["About", "Home", "Contact"] } });
+// Returns all models which don't have a title attribute of either
+// "About", "Home", or "Contact"
+```
+
+### $all
+Assumes the model property is an array and only returns models where all supplied values are matched.
+
+```js
+MyCollection.query({ colors: {$all:["red", "yellow"] } });
+// Returns all models which have "red" and "yellow" in their colors attribute.
+// A model with the attribute colors:["red","yellow","blue"] would be returned
+// But a model with the attribute colors:["red","blue"] would not be returned
+```
+
+### $any
+Assumes the model property is an array and returns models where any of the supplied values are matched.
+
+```js
+MyCollection.query({ colors: {$any:["red", "yellow"] } });
+// Returns models which have either "red" or "yellow" in their colors attribute.
+```
+
+### $size
+Assumes the model property has a length (i.e. is either an array or a string).
+Only returns models the model property's length matches the supplied values
+
+```js
+MyCollection.query({ colors: {$size:2 } });
+// Returns all models which 2 values in the colors attribute
+```
+
+### $exists or $has
+Checks for the existence of an attribute. Can be supplied either true or false.
+
+```js
+MyCollection.query({ title: {$exists: true } });
+// Returns all models which have a "title" attribute
+MyCollection.query({ title: {$has: false } });
+// Returns all models which don't have a "title" attribute
+```
+
+### $like
+Assumes the model attribute is a string and checks if the supplied query value is a substring of the property.
+Uses indexOf rather than regex for performance reasons
+
+```js
+MyCollection.query({ title: {$like: "Test" } });
+//Returns all models which have a "title" attribute that
+//contains the string "Test", e.g. "Testing", "Tests", "Test", etc.
+```
+
+### $likeI
+The same as above but performs a case insensitive search using indexOf and toLowerCase (still faster than Regex)
+
+```js
+MyCollection.query({ title: {$likeI: "Test" } });
+//Returns all models which have a "title" attribute that
+//contains the string "Test", "test", "tEst","tesT", etc.
+```
+
+### $regex
+Checks if the model attribute matches the supplied regular expression. The regex query can be supplied without the `$regex` keyword
+
+```js
+MyCollection.query({ content: {$regex: /coffeescript/gi } });
+// Checks for a regex match in the content attribute
+MyCollection.query({ content: /coffeescript/gi });
+// Same as above
+```
+
+### $cb
+A callback function can be supplied as a test. The callback will receive the attribute and should return either true or false.
+`this` will be set to the current model, this can help with tests against computed properties
+
+```js
+MyCollection.query({ title: {$cb: function(attr){ return attr.charAt(0) === "c";}} });
+// Returns all models that have a title attribute that starts with "c"
+
+MyCollection.query({ computed_test: {$cb: function(){ return this.computed_property() > 10;}} });
+// Returns all models where the computed_property method returns a value greater than 10.
+```
+
+For callbacks that use `this` rather than the model attribute, the key name supplied is arbitrary and has no
+effect on the results. If the only test you were performing was like the above test it would make more sense
+to simply use `MyCollection.filter`. However if you are performing other tests or are using the paging / sorting /
+caching options of backbone query, then this functionality is useful.
+
+Combined Queries
+================
+
+Multiple queries can be combined together. By default all supplied queries must be matched `$and`. However it is possible
+to specify either `$or`, `$nor`, `$not` to implement alternate logic.
+
+### $and
+
+```js
+MyCollection.query({ $and: { title: {$like: "News"}, likes: {$gt: 10}}});
+// Returns all models that contain "News" in the title and have more than 10 likes.
+MyCollection.query({ title: {$like: "News"}, likes: {$gt: 10} });
+// Same as above as $and is assumed if not supplied
+```
+
+### $or
+
+```js
+MyCollection.query({ $or: { title: {$like: "News"}, likes: {$gt: 10}}});
+// Returns all models that contain "News" in the title OR have more than 10 likes.
+```
+
+### $nor
+The opposite of `$or`
+
+```js
+MyCollection.query({ $nor: { title: {$like: "News"}, likes: {$gt: 10}}});
+// Returns all models that don't contain "News" in the title NOR have more than 10 likes.
+```
+
+### $not
+The opposite of `$and`
+
+```js
+MyCollection.query({ $not: { title: {$like: "News"}, likes: {$gt: 10}}});
+// Returns all models that don't contain "News" in the title AND DON'T have more than 10 likes.
+```
+
+Compound Queries
+================
+
+It is possible to use multiple combined queries, for example searching for models that have a specific title attribute,
+and either a category of "abc" or a tag of "xyz"
+
+```js
+MyCollection.query({
+ $and: { title: {$like: "News"}},
+ $or: {likes: {$gt: 10}, color:{$contains:"red"}}
+});
+//Returns models that have "News" in their title and
+//either have more than 10 likes or contain the color red.
+```
+
+Sorting
+=======
+Optional `sortBy` and `order` attributes can be supplied as part of an options object.
+`sortBy` can either be a model key or a callback function which will be called with each model in the array.
+
+```js
+MyCollection.query({title: {$like: "News"}}, {sortBy: "likes"});
+// Returns all models that contain "News" in the title,
+// sorted according to their "likes" attribute (ascending)
+
+MyCollection.query({title: {$like: "News"}}, {sortBy: "likes", order:"desc"});
+// Same as above, but "descending"
+MyCollection.query(
+ {title: {$like: "News"}},
+ {sortBy: function(model){ return model.get("title").charAt(1);}}
+);
+// Results sorted according to 2nd character of the title attribute
+```
+
+
+Paging
+======
+To return only a subset of the results paging properties can be supplied as part of an options object.
+A `limit` property must be supplied and optionally a `offset` or a `page` property can be supplied.
+
+```js
+MyCollection.query({likes:{$gt:10}}, {limit:10});
+// Returns the first 10 models that have more than 10 likes
+
+MyCollection.query({likes:{$gt:10}}, {limit:10, offset:5});
+// Returns 10 models that have more than 10 likes starting
+//at the 6th model in the results
+
+MyCollection.query({likes:{$gt:10}}, {limit:10, page:2});
+// Returns 10 models that have more than 10 likes starting
+//at the 11th model in the results (page 2)
+```
+
+When using the paging functionality, you will normally need to know the number of pages so that you can render
+the correct interface for the user. Backbone Query can send the number of pages of results to a supplied callback.
+The callback should be passed as a `pager` property on the options object. This callback will also receive the sliced
+models as a second variable.
+
+Here is a coffeescript example of a simple paging setup using the pager callback option:
+
+```coffeescript
+class MyView extends Backbone.View
+ initialize: ->
+ @template = -> #templating setup here
+
+ events:
+ "click .page": "change_page"
+
+ query_collection: (page = 1) ->
+ #Collection should be passed in when the view is instantiated
+ @collection.query {category:"javascript"}, {limit:5, page:page, pager:@render_pages}
+
+ change_page: (e) =>
+ page_number = $(e.target).data('page_number')
+ @query_collection page_number
+
+ render_pages: (total_pages, results) =>
+ content = @template results
+ pages = [1..total_pages]
+ nav = """
+ <nav>
+ <span>Total Pages: #{total_pages}</span>
+ """
+ for page in pages
+ nav += "<a href='#' data-page_number='#{page}'>#{page}</a>"
+
+ nav += "</nav>"
+
+ @$el.html content + nav
+
+ render: => @query_collection()
+
+```
+
+
+Caching Results
+================
+To enable caching set the cache flag to true in the options object. This can greatly improve performance when paging
+through results as the unpaged results will be saved. This options is not enabled by default as if models are changed,
+added to, or removed from the collection, then the query cache will be out of date. If you know
+that your data is static and won't change then caching can be enabled without any problems.
+If your data is dynamic (as in most Backbone Apps) then a helper cache reset method is provided:
+`reset_query_cache`. This method should be bound to your collections change, add and remove events
+(depending on how your data can be changed).
+
+Cache will be saved in a `_query_cache` property on each collection where a cache query is performed.
+
+```js
+MyCollection.query({likes:{$gt:10}}, {limit:10, page:1, cache:true});
+//The first query will operate as normal and return the first page of results
+MyCollection.query({likes:{$gt:10}}, {limit:10, page:2, cache:true});
+//The second query has an identical query object to the first query, so therefore the results will be retrieved
+//from the cache, before the paging paramaters are applied.
+
+// Binding the reset_query_cache method
+var MyCollection = Backbone.QueryCollection.extend({
+ initialize: function(){
+ this.bind("change", this.reset_query_cache, this);
+ }
+});
+
+
+```
+
+
+Author
+======
+
+Dave Tonge - [davidgtonge](http://github.com/davidgtonge)
265 js/node_dirty_query.js
@@ -0,0 +1,265 @@
+
+/*
+Backbone Query - A lightweight query API for Backbone Collections
+(c)2012 - Dave Tonge
+May be freely distributed according to MIT license.
+*/
+
+(function() {
+ var cache, get_cache, get_models, get_sorted_models, iterator, page_models, parse_query, perform_query, 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 = [];
+ for (key in raw_query) {
+ query_param = raw_query[key];
+ o = {
+ key: key
+ };
+ if (_.isRegExp(query_param)) {
+ o.type = "$regex";
+ o.value = query_param;
+ } else if (_(query_param).isObject()) {
+ for (type in query_param) {
+ value = query_param[type];
+ if (test_query_value(type, value)) {
+ o.type = type;
+ o.value = value;
+ }
+ }
+ } else {
+ o.type = "$equal";
+ o.value = query_param;
+ }
+ _results.push(o);
+ }
+ return _results;
+ };
+
+ test_query_value = function(type, value) {
+ switch (type) {
+ case "$in":
+ case "$nin":
+ case "$all":
+ case "$any":
+ return _(value).isArray();
+ case "$size":
+ return _(value).isNumber();
+ case "$regex":
+ return _(value).isRegExp();
+ case "$like":
+ case "$likeI":
+ return _(value).isString();
+ case "$between":
+ return _(value).isArray() && (value.length === 2);
+ case "$cb":
+ return _(value).isFunction();
+ default:
+ return true;
+ }
+ };
+
+ test_model_attribute = function(type, value) {
+ switch (type) {
+ case "$like":
+ case "$likeI":
+ case "$regex":
+ return _(value).isString();
+ case "$contains":
+ case "$all":
+ case "$any":
+ return _(value).isArray();
+ case "$size":
+ return _(value).isArray() || _(value).isString();
+ case "$in":
+ case "$nin":
+ return value != null;
+ default:
+ return true;
+ }
+ };
+
+ perform_query = function(type, value, attr, model) {
+ switch (type) {
+ case "$equal":
+ return attr === value;
+ case "$contains":
+ return __indexOf.call(attr, value) >= 0;
+ case "$ne":
+ return attr !== value;
+ case "$lt":
+ return attr < value;
+ case "$gt":
+ return attr > value;
+ case "$lte":
+ return attr <= value;
+ case "$gte":
+ return attr >= value;
+ case "$between":
+ return (value[0] < attr && attr < value[1]);
+ case "$in":
+ return __indexOf.call(value, attr) >= 0;
+ case "$nin":
+ return __indexOf.call(value, attr) < 0;
+ case "$all":
+ return _(attr).all(function(item) {
+ return __indexOf.call(value, item) >= 0;
+ });
+ case "$any":
+ return _(attr).any(function(item) {
+ return __indexOf.call(value, item) >= 0;
+ });
+ case "$size":
+ return attr.length === value;
+ case "$exists":
+ case "$has":
+ return (attr != null) === value;
+ case "$like":
+ return attr.indexOf(value) !== -1;
+ case "$likeI":
+ return attr.toLowerCase().indexOf(value.toLowerCase()) !== -1;
+ case "$regex":
+ return value.test(attr);
+ case "$cb":
+ return value.call(model, attr);
+ default:
+ return false;
+ }
+ };
+
+ iterator = function(collection, query, query_type, find_one) {
+ var key, parsed_query, query_iterator, results, value;
+ parsed_query = parse_query(query);
+ results = {};
+ query_iterator = function(key, value) {
+ var attr, found, model, q, stop, test, _i, _len;
+ stop = false;
+ found = 0;
+ model = {
+ key: value
+ };
+ for (_i = 0, _len = parsed_query.length; _i < _len; _i++) {
+ q = parsed_query[_i];
+ if (!stop) {
+ attr = value[q.key];
+ test = test_model_attribute(q.type, attr);
+ if (test) test = perform_query(q.type, q.value, attr, model);
+ if (test) found++;
+ if (test && query_type === "$or") stop = true;
+ if ((!test) && query_type === "$nor") stop = true;
+ }
+ }
+ switch (query_type) {
+ case "$or":
+ if (found > 0) results[key] = value;
+ break;
+ case "$and":
+ if (found === parsed_query.length) results[key] = value;
+ break;
+ case "$nor":
+ case "$not":
+ if (found === 0) results[key] = value;
+ }
+ if (find_one && results.length) return false;
+ };
+ if (_(collection.forEach).isFunction()) {
+ collection.forEach(query_iterator);
+ } else {
+ for (key in collection) {
+ value = collection[key];
+ query_iterator(key, value);
+ }
+ }
+ return results;
+ };
+
+ cache = {};
+
+ get_cache = function(db, query, options) {
+ var db_cache, models, query_string, _name, _ref;
+ query_string = JSON.stringify(query);
+ db_cache = (_ref = cache[_name = db.path]) != null ? _ref : cache[_name] = {};
+ models = db_cache[query_string];
+ if (!models) {
+ models = get_sorted_models(db, query, options);
+ db_cache[query_string] = models;
+ }
+ return models;
+ };
+
+ get_models = function(db, query) {
+ var compound_query, reduce_iterator;
+ compound_query = _.intersection(["$and", "$not", "$or", "$nor"], _(query).keys());
+ if (compound_query.length === 0) {
+ return iterator(db, query, "$and");
+ } else {
+ reduce_iterator = function(memo, query_type) {
+ return iterator(memo, query[query_type], query_type);
+ };
+ return _.reduce(compound_query, reduce_iterator, db);
+ }
+ };
+
+ get_sorted_models = function(db, query, options) {
+ var key, models, models_array, val;
+ models = get_models(db, query);
+ models_array = (function() {
+ var _results;
+ _results = [];
+ for (key in models) {
+ val = models[key];
+ _results.push(val);
+ }
+ return _results;
+ })();
+ if (options.sortBy) models_array = sort_models(models_array, options);
+ return models_array;
+ };
+
+ sort_models = function(models, options) {
+ if (_(options.sortBy).isString()) {
+ models = _(models).sortBy(function(model) {
+ return model[options.sortBy];
+ });
+ } else if (_(options.sortBy).isFunction()) {
+ models = _(models).sortBy(options.sortBy);
+ }
+ if (options.order === "desc") models = models.reverse();
+ return models;
+ };
+
+ page_models = function(models, options) {
+ var end, sliced_models, start, total_pages;
+ if (options.offset) {
+ start = options.offset;
+ } else if (options.page) {
+ start = (options.page - 1) * options.limit;
+ } else {
+ start = 0;
+ }
+ end = start + options.limit;
+ sliced_models = models.slice(start, end);
+ if (options.pager && _.isFunction(options.pager)) {
+ total_pages = Math.ceil(models.length / options.limit);
+ options.pager(total_pages, sliced_models);
+ }
+ return sliced_models;
+ };
+
+ if (typeof _ === "undefined" || _ === null) _ = require('underscore');
+
+ exports.query = function(db, query, options) {
+ var models;
+ if (options == null) options = {};
+ if (query === "reset_cache") return cache[db.path] = {};
+ if (options.cache) {
+ models = get_cache(db, query, options);
+ } else {
+ models = get_sorted_models(db, query, options);
+ }
+ if (options.limit) models = page_models(models, options);
+ return models;
+ };
+
+}).call(this);
23 package.json
@@ -0,0 +1,23 @@
+{
+ "name": "node-dirty-query"
+ , "description": "Lightweight Query API for Node Dirty Databases"
+ , "version": "0.1.1"
+ , "author": "Dave Tonge <dave@simplecreativity.co.uk> (https://github.com/davidgtonge)"
+ , "tags": ["node-dirty", "underscore", "mongo", "query"]
+ , "main": "./js/node_dirty_query"
+ , "repository": {
+ "type": "git"
+ , "url": "https://davidgtonge@github.com/davidgtonge/node_dirty_query.git"
+ }
+ , "dependencies": {
+ "dirty": ">=0.9.x"
+ , "underscore": ">=1.1.x"
+}
+ , "devDependencies": {
+ "coffee-script" : ">=1.x.x"
+ ,"nodeunit": "0.6.x"
+}
+ , "scripts": {
+ "test": "cake test"
+}
+}
218 src/node_dirty_query.coffee
@@ -0,0 +1,218 @@
+###
+Backbone Query - A lightweight query API for Backbone Collections
+(c)2012 - Dave Tonge
+May be freely distributed according to MIT license.
+###
+
+# This function parses the query and converts it into an array of objects.
+# Each object has a key (model property), type (query type - $gt, $like...) and value (mixed).
+parse_query = (raw_query) ->
+ (for key, query_param of raw_query
+ o = {key}
+ # Test for Regexs as they can be supplied without an operator
+ if _.isRegExp(query_param)
+ o.type = "$regex"
+ o.value = query_param
+ # If the query paramater is an object then extract the key and value
+ else if _(query_param).isObject()
+ for type, value of query_param
+ # Before adding the query, its value is checked to make sure it is the right type
+ if test_query_value type, value
+ o.type = type
+ o.value = value
+ # If the query_param is not an object or a regexp then revert to the default operator: $equal
+ else
+ o.type = "$equal"
+ o.value = query_param
+ o)
+
+# Tests query value, to ensure that it is of the correct type
+test_query_value = (type, value) ->
+ switch type
+ when "$in","$nin","$all", "$any" then _(value).isArray()
+ when "$size" then _(value).isNumber()
+ when "$regex" then _(value).isRegExp()
+ when "$like", "$likeI" then _(value).isString()
+ when "$between" then _(value).isArray() and (value.length is 2)
+ when "$cb" then _(value).isFunction()
+ else true
+
+# Test each attribute that is being tested to ensure that is of the correct type
+test_model_attribute = (type, value) ->
+ switch type
+ when "$like", "$likeI", "$regex" then _(value).isString()
+ when "$contains", "$all", "$any" then _(value).isArray()
+ when "$size" then _(value).isArray() or _(value).isString()
+ when "$in", "$nin" then value?
+ else true
+
+# Perform the actual query logic for each query and each model/attribute
+perform_query = (type, value, attr, model) ->
+ switch type
+ when "$equal" then attr is value
+ when "$contains" then value in attr
+ when "$ne" then attr isnt value
+ when "$lt" then attr < value
+ when "$gt" then attr > value
+ when "$lte" then attr <= value
+ when "$gte" then attr >= value
+ when "$between" then value[0] < attr < value[1]
+ when "$in" then attr in value
+ when "$nin" then attr not in value
+ when "$all" then _(attr).all (item) -> item in value
+ when "$any" then _(attr).any (item) -> item in value
+ when "$size" then attr.length is value
+ when "$exists", "$has" then attr? is value
+ when "$like" then attr.indexOf(value) isnt -1
+ when "$likeI" then attr.toLowerCase().indexOf(value.toLowerCase()) isnt -1
+ when "$regex" then value.test attr
+ when "$cb" then value.call model, attr
+ else false
+
+
+# The main iterator that actually applies the query
+iterator = (collection, query, query_type, single_query, find_one) ->
+ parsed_query = parse_query query
+ # The collections filter or reject method is used to iterate through each model in the collection
+ results = {}
+ count = 0
+ add = (key, value) ->
+ results[key] = value
+ count++
+
+ query_iterator = (key, value) ->
+ stop = false
+ found = 0
+ model = {key:value}
+ for q in parsed_query
+ unless stop
+ # Retrieve the attribute value from the model
+ attr = value[q.key]
+ # Check if the attribute value is the right type (some operators need a string, or an array)
+ test = test_model_attribute(q.type, attr)
+ # If the attribute test is true, perform the query
+ if test then test = perform_query q.type, q.value, attr, model
+ # If the query is an "or" query than as soon as a match is found we return "true"
+ # Whereas if the query is an "and" query then we return "false" as soon as a match isn't found.
+ found++ if test
+ if test and query_type is "$or" then stop = true
+ if (not test) and query_type is "$nor" then stop = true
+
+ switch query_type
+ when "$or" then add key, value if found > 0
+ when "$and" then add key, value if found is parsed_query.length
+ when "$nor", "$not" then add key, value if found is 0
+
+ if find_one and single_query and count
+ return false
+
+ if _(collection.forEach).isFunction()
+ collection.forEach query_iterator
+ else
+ for key, value of collection
+ break if query_iterator(key, value) is false
+
+ results
+
+
+cache = {}
+# This method attempts to retrieve the result from the cache.
+# If no match is found in the cache, then the query is run and
+# the results are saved in the cache
+get_cache = (db, query, options) ->
+ # Convert the query to a string to use as a key in the cache
+ query_string = JSON.stringify query
+
+ db_cache = cache[db.path] ?= {}
+ # Retrieve cached results
+ models = db_cache[query_string]
+ # If no results are retrieved then use the get_models method and cache the result
+ unless models
+ models = get_sorted_models db, query, options
+ db_cache[query_string] = models
+ # Return the results
+ models
+
+# This method get the unsorted results
+get_models = (db, query, findOne) ->
+
+ # Iterate through the query keys to check for any of the compound methods
+ # The resulting array will have "$and" and "$not" first as it is better to use these
+ # operators first when performing a compound query as they are likely to return less results
+ compound_query = _.intersection ["$and", "$not", "$or", "$nor"], _(query).keys()
+
+ if compound_query.length is 0
+ # If no compound methods are found then use the "and" iterator
+ iterator db, query, "$and", true, findOne
+ else
+ # Else iterate through the compound methods using underscore reduce
+ # The reduce iterator takes an array of models, performs the query and returns
+ # the matched models for the next query
+ reduce_iterator = (memo, query_type, index) ->
+ single = (compound_query.length is 1) or (compound_query.length - 1 is index)
+ iterator memo, query[query_type], query_type, single, findOne
+
+ _.reduce compound_query, reduce_iterator, db
+
+# Gets the results and optionally sorts them
+get_sorted_models = (db, query, options) ->
+ models = get_models db, query, options.findOne
+ models_array = (val for key, val of models)
+ if options.sortBy then models_array = sort_models models_array, options
+ models_array
+
+# Sorts models either be a model attribute or with a callback
+sort_models = (models, options) ->
+ # If the sortBy param is a string then we sort according to the model attribute with that string as a key
+ if _(options.sortBy).isString()
+ models = _(models).sortBy (model) -> model[options.sortBy]
+ # If a function is supplied then it is passed directly to the sortBy iterator
+ else if _(options.sortBy).isFunction()
+ models = _(models).sortBy(options.sortBy)
+
+ # If there is an order property of "desc" then the results can be reversed
+ # (sortBy provides result in ascending order by default)
+ if options.order is "desc" then models = models.reverse()
+ # The sorted models are returned
+ models
+
+# Slices the results set according to the supplied options
+page_models = (models, options) ->
+ # Expects object in the form: {limit: num, offset: num, page: num, pager:callback}
+ if options.offset then start = options.offset
+ else if options.page then start = (options.page - 1) * options.limit
+ else start = 0
+
+ end = start + options.limit
+
+ # The results are sliced according to the calculated start and end params
+ sliced_models = models[start...end]
+
+ if options.pager and _.isFunction(options.pager)
+ total_pages = Math.ceil (models.length / options.limit)
+ options.pager total_pages, sliced_models
+
+ sliced_models
+
+# If used on the server, then Backbone and Underscore are loaded as modules
+
+_ ?= require 'underscore'
+
+
+ # The main query method
+exports.query = (db, query, options = {}) ->
+
+ if query is "reset_cache"
+ return cache[db.path] = {}
+
+ # Retrieve matching models using the supplied query
+ if options.cache
+ models = get_cache db, query, options
+ else
+ models = get_sorted_models db, query, options
+
+ # If a limit param is specified than slice the results
+ if options.limit then models = page_models models, options
+
+ # Return the results
+ models
291 test/node_dirty_query_test.coffee
@@ -0,0 +1,291 @@
+_ = require "underscore"
+db = require("dirty")()
+{query} = require "../js/node_dirty_query.js"
+
+
+# Helper functions that turn Qunit tests into nodeunit tests
+equals = []
+
+
+test ?= (name, test_cb) ->
+ exports[name] = (testObj) ->
+ equals = []
+ test_cb()
+ for result in equals
+ testObj.equal result[0], result[1]
+ testObj.done()
+
+equal ?= (real, expected) -> equals.push [real, expected]
+
+test_query = (query_param, options) ->
+ results = query db, query_param, options
+ _(results).map (result) ->
+ get: (key) -> result[key]
+
+create = ->
+ db.set 1, {title:"Home", colors:["red","yellow","blue"], likes:12, featured:true, content: "Dummy content about coffeescript"}
+ db.set 2, {title:"About", colors:["red"], likes:2, featured:true, content: "dummy content about javascript"}
+ db.set 3, {title:"Contact", colors:["red","blue"], likes:20, content: "Dummy content about PHP"}
+ query: test_query
+
+
+test "Simple equals query", ->
+ a = create()
+ result = a.query title:"Home"
+ equal result.length, 1
+ equal result[0].get("title"), "Home"
+
+test "Simple equals query (no results)", ->
+ a = create()
+ result = a.query title:"Homes"
+ equal result.length, 0
+
+test "Simple equals query with explicit $equal", ->
+ a = create()
+ result = a.query title: {$equal: "About"}
+ equal result.length, 1
+ equal result[0].get("title"), "About"
+
+test "$contains operator", ->
+ a = create()
+ result = a.query colors: {$contains: "blue"}
+ equal result.length, 2
+
+test "$ne operator", ->
+ a = create()
+ result = a.query title: {$ne: "Home"}
+ equal result.length, 2
+
+test "$lt operator", ->
+ a = create()
+ result = a.query likes: {$lt: 12}
+ equal result.length, 1
+ equal result[0].get("title"), "About"
+
+test "$lte operator", ->
+ a = create()
+ result = a.query likes: {$lte: 12}
+ equal result.length, 2
+
+test "$gt operator", ->
+ a = create()
+ result = a.query likes: {$gt: 12}
+ equal result.length, 1
+ equal result[0].get("title"), "Contact"
+
+test "$gte operator", ->
+ a = create()
+ result = a.query likes: {$gte: 12}
+ equal result.length, 2
+
+test "$between operator", ->
+ a = create()
+ result = a.query likes: {$between: [1,5]}
+ equal result.length, 1
+ equal result[0].get("title"), "About"
+
+test "$in operator", ->
+ a = create()
+ result = a.query title: {$in: ["Home","About"]}
+ equal result.length, 2
+
+test "$in operator with wrong query value", ->
+ a = create()
+ result = a.query title: {$in: "Home"}
+ equal result.length, 0
+
+test "$nin operator", ->
+ a = create()
+ result = a.query title: {$nin: ["Home","About"]}
+ equal result.length, 1
+ equal result[0].get("title"), "Contact"
+
+test "$all operator", ->
+ a = create()
+ result = a.query colors: {$all: ["red","blue"]}
+ equal result.length, 2
+
+test "$all operator (wrong values)", ->
+ a = create()
+ result = a.query title: {$all: ["red","blue"]}
+ equal result.length, 0
+
+ result = a.query colors: {$all: "red"}
+ equal result.length, 0
+
+test "$any operator", ->
+ a = create()
+ result = a.query colors: {$any: ["red","blue"]}
+ equal result.length, 3
+
+ result = a.query colors: {$any: ["yellow","blue"]}
+ equal result.length, 2
+
+test "$size operator", ->
+ a = create()
+ result = a.query colors: {$size: 3}
+ equal result.length, 1
+ equal result[0].get("title"), "Home"
+
+test "$exists operator", ->
+ a = create()
+ result = a.query featured: {$exists: true}
+ equal result.length, 2
+
+test "$has operator", ->
+ a = create()
+ result = a.query featured: {$exists: false}
+ equal result.length, 1
+ equal result[0].get("title"), "Contact"
+
+test "$like operator", ->
+ a = create()
+ result = a.query content: {$like: "javascript"}
+ equal result.length, 1
+ equal result[0].get("title"), "About"
+
+test "$like operator 2", ->
+ a = create()
+ result = a.query content: {$like: "content"}
+ equal result.length, 3
+
+test "$likeI operator", ->
+ a = create()
+ result = a.query content: {$likeI: "dummy"}
+ equal result.length, 3
+ result = a.query content: {$like: "dummy"}
+ equal result.length, 1
+
+test "$regex", ->
+ a = create()
+ result = a.query content: {$regex: /javascript/gi}
+ equal result.length, 1
+ equal result[0].get("title"), "About"
+
+test "$regex2", ->
+ a = create()
+ result = a.query content: {$regex: /dummy/}
+ equal result.length, 1
+
+test "$regex3", ->
+ a = create()
+ result = a.query content: {$regex: /dummy/i}
+ equal result.length, 3
+
+test "$regex4", ->
+ a = create()
+ result = a.query content: /javascript/i
+ equal result.length, 1
+
+test "$cb - callback", ->
+ a = create()
+ result = a.query title: {$cb: (attr) -> attr.charAt(0).toLowerCase() is "c"}
+ equal result.length, 1
+ equal result[0].get("title"), "Contact"
+
+test "$and operator", ->
+ a = create()
+ result = a.query likes: {$gt: 5}, colors: {$contains: "yellow"}
+ equal result.length, 1
+ equal result[0].get("title"), "Home"
+
+test "$and operator (explicit)", ->
+ a = create()
+ result = a.query $and: {likes: {$gt: 5}, colors: {$contains: "yellow"}}
+ equal result.length, 1
+ equal result[0].get("title"), "Home"
+
+test "$or operator", ->
+ a = create()
+ result = a.query $or: {likes: {$gt: 5}, colors: {$contains: "yellow"}}
+ equal result.length, 2
+
+test "$nor operator", ->
+ a = create()
+ result = a.query $nor: {likes: {$gt: 5}, colors: {$contains: "yellow"}}
+ equal result.length, 1
+ equal result[0].get("title"), "About"
+
+
+test "Compound Queries", ->
+ a = create()
+ result = a.query $and: {likes: {$gt: 5}}, $or: {content: {$like: "PHP"}, colors: {$contains: "yellow"}}
+ equal result.length, 2
+
+ result = a.query
+ $and:
+ likes: $lt: 15
+ $or:
+ content: $like: "Dummy"
+ featured:$exists:true
+ $not:
+ colors: $contains: "yellow"
+ equal result.length, 1
+ equal result[0].get("title"), "About"
+
+
+
+test "Limit", ->
+ a = create()
+ result = a.query {likes: {$gt: 1}}, {limit:2}
+ equal result.length, 2
+
+test "Offset", ->
+ a = create()
+ result = a.query {likes: {$gt: 1}}, {limit:2, offset:2}
+ equal result.length, 1
+
+test "Page", ->
+ a = create()
+ result = a.query {likes: {$gt: 1}}, {limit:3, page:2}
+ equal result.length, 0
+
+test "Sorder by model key", ->
+ a = create()
+ result = a.query {likes: {$gt: 1}}, {sortBy:"likes"}
+ equal result.length, 3
+ equal result[0].get("title"), "About"
+ equal result[1].get("title"), "Home"
+ equal result[2].get("title"), "Contact"
+
+test "Sorder by model key with descending order", ->
+ a = create()
+ result = a.query {likes: {$gt: 1}}, {sortBy:"likes", order:"desc"}
+ equal result.length, 3
+ equal result[2].get("title"), "About"
+ equal result[1].get("title"), "Home"
+ equal result[0].get("title"), "Contact"
+
+test "Sorder by function", ->
+ a = create()
+ result = a.query {likes: {$gt: 1}}, {sortBy: (model) -> model["title"].charAt(2) }
+ equal result.length, 3
+ equal result[2].get("title"), "About"
+ equal result[0].get("title"), "Home"
+ equal result[1].get("title"), "Contact"
+
+
+
+
+test "null attribute with various operators", ->
+ a = create()
+ result = a.query wrong_key: {$like: "test"}
+ equal result.length, 0
+ result = a.query wrong_key: {$regex: /test/}
+ equal result.length, 0
+ result = a.query wrong_key: {$contains: "test"}
+ equal result.length, 0
+ result = a.query wrong_key: {$all: [12,23]}
+ equal result.length, 0
+ result = a.query wrong_key: {$any: [12,23]}
+ equal result.length, 0
+ result = a.query wrong_key: {$size: 10}
+ equal result.length, 0
+ result = a.query wrong_key: {$in: [12,23]}
+ equal result.length, 0
+ result = a.query wrong_key: {$nin: [12,23]}
+ equal result.length, 0
+
+
+
+
646 test/node_dirty_query_test.js
@@ -0,0 +1,646 @@
+(function() {
+ var create, db, equals, query, test_query, _;
+
+ _ = require("underscore");
+
+ db = require("dirty")();
+
+ query = require("../js/node_dirty_query.js").query;
+
+ equals = [];
+
+ if (typeof test === "undefined" || test === null) {
+ test = function(name, test_cb) {
+ return exports[name] = function(testObj) {
+ var result, _i, _len;
+ equals = [];
+ test_cb();
+ for (_i = 0, _len = equals.length; _i < _len; _i++) {
+ result = equals[_i];
+ testObj.equal(result[0], result[1]);
+ }
+ return testObj.done();
+ };
+ };
+ }
+
+ if (typeof equal === "undefined" || equal === null) {
+ equal = function(real, expected) {
+ return equals.push([real, expected]);
+ };
+ }
+
+ test_query = function(query_param, options) {
+ var results;
+ results = query(db, query_param, options);
+ return _(results).map(function(result) {
+ return {
+ get: function(key) {
+ return result[key];
+ }
+ };
+ });
+ };
+
+ create = function() {
+ db.set(1, {
+ title: "Home",
+ colors: ["red", "yellow", "blue"],
+ likes: 12,
+ featured: true,
+ content: "Dummy content about coffeescript"
+ });
+ db.set(2, {
+ title: "About",
+ colors: ["red"],
+ likes: 2,
+ featured: true,
+ content: "dummy content about javascript"
+ });
+ db.set(3, {
+ title: "Contact",
+ colors: ["red", "blue"],
+ likes: 20,
+ content: "Dummy content about PHP"
+ });
+ return {
+ query: test_query
+ };
+ };
+
+ test("Simple equals query", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ title: "Home"
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "Home");
+ });
+
+ test("Simple equals query (no results)", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ title: "Homes"
+ });
+ return equal(result.length, 0);
+ });
+
+ test("Simple equals query with explicit $equal", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ title: {
+ $equal: "About"
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "About");
+ });
+
+ test("$contains operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ colors: {
+ $contains: "blue"
+ }
+ });
+ return equal(result.length, 2);
+ });
+
+ test("$ne operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ title: {
+ $ne: "Home"
+ }
+ });
+ return equal(result.length, 2);
+ });
+
+ test("$lt operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $lt: 12
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "About");
+ });
+
+ test("$lte operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $lte: 12
+ }
+ });
+ return equal(result.length, 2);
+ });
+
+ test("$gt operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $gt: 12
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "Contact");
+ });
+
+ test("$gte operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $gte: 12
+ }
+ });
+ return equal(result.length, 2);
+ });
+
+ test("$between operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $between: [1, 5]
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "About");
+ });
+
+ test("$in operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ title: {
+ $in: ["Home", "About"]
+ }
+ });
+ return equal(result.length, 2);
+ });
+
+ test("$in operator with wrong query value", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ title: {
+ $in: "Home"
+ }
+ });
+ return equal(result.length, 0);
+ });
+
+ test("$nin operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ title: {
+ $nin: ["Home", "About"]
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "Contact");
+ });
+
+ test("$all operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ colors: {
+ $all: ["red", "blue"]
+ }
+ });
+ return equal(result.length, 2);
+ });
+
+ test("$all operator (wrong values)", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ title: {
+ $all: ["red", "blue"]
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ colors: {
+ $all: "red"
+ }
+ });
+ return equal(result.length, 0);
+ });
+
+ test("$any operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ colors: {
+ $any: ["red", "blue"]
+ }
+ });
+ equal(result.length, 3);
+ result = a.query({
+ colors: {
+ $any: ["yellow", "blue"]
+ }
+ });
+ return equal(result.length, 2);
+ });
+
+ test("$size operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ colors: {
+ $size: 3
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "Home");
+ });
+
+ test("$exists operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ featured: {
+ $exists: true
+ }
+ });
+ return equal(result.length, 2);
+ });
+
+ test("$has operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ featured: {
+ $exists: false
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "Contact");
+ });
+
+ test("$like operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ content: {
+ $like: "javascript"
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "About");
+ });
+
+ test("$like operator 2", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ content: {
+ $like: "content"
+ }
+ });
+ return equal(result.length, 3);
+ });
+
+ test("$likeI operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ content: {
+ $likeI: "dummy"
+ }
+ });
+ equal(result.length, 3);
+ result = a.query({
+ content: {
+ $like: "dummy"
+ }
+ });
+ return equal(result.length, 1);
+ });
+
+ test("$regex", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ content: {
+ $regex: /javascript/gi
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "About");
+ });
+
+ test("$regex2", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ content: {
+ $regex: /dummy/
+ }
+ });
+ return equal(result.length, 1);
+ });
+
+ test("$regex3", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ content: {
+ $regex: /dummy/i
+ }
+ });
+ return equal(result.length, 3);
+ });
+
+ test("$regex4", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ content: /javascript/i
+ });
+ return equal(result.length, 1);
+ });
+
+ test("$cb - callback", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ title: {
+ $cb: function(attr) {
+ return attr.charAt(0).toLowerCase() === "c";
+ }
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "Contact");
+ });
+
+ test("$and operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $gt: 5
+ },
+ colors: {
+ $contains: "yellow"
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "Home");
+ });
+
+ test("$and operator (explicit)", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ $and: {
+ likes: {
+ $gt: 5
+ },
+ colors: {
+ $contains: "yellow"
+ }
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "Home");
+ });
+
+ test("$or operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ $or: {
+ likes: {
+ $gt: 5
+ },
+ colors: {
+ $contains: "yellow"
+ }
+ }
+ });
+ return equal(result.length, 2);
+ });
+
+ test("$nor operator", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ $nor: {
+ likes: {
+ $gt: 5
+ },
+ colors: {
+ $contains: "yellow"
+ }
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "About");
+ });
+
+ test("Compound Queries", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ $and: {
+ likes: {
+ $gt: 5
+ }
+ },
+ $or: {
+ content: {
+ $like: "PHP"
+ },
+ colors: {
+ $contains: "yellow"
+ }
+ }
+ });
+ equal(result.length, 2);
+ result = a.query({
+ $and: {
+ likes: {
+ $lt: 15
+ }
+ },
+ $or: {
+ content: {
+ $like: "Dummy"
+ },
+ featured: {
+ $exists: true
+ }
+ },
+ $not: {
+ colors: {
+ $contains: "yellow"
+ }
+ }
+ });
+ equal(result.length, 1);
+ return equal(result[0].get("title"), "About");
+ });
+
+ test("Limit", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $gt: 1
+ }
+ }, {
+ limit: 2
+ });
+ return equal(result.length, 2);
+ });
+
+ test("Offset", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $gt: 1
+ }
+ }, {
+ limit: 2,
+ offset: 2
+ });
+ return equal(result.length, 1);
+ });
+
+ test("Page", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $gt: 1
+ }
+ }, {
+ limit: 3,
+ page: 2
+ });
+ return equal(result.length, 0);
+ });
+
+ test("Sorder by model key", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $gt: 1
+ }
+ }, {
+ sortBy: "likes"
+ });
+ equal(result.length, 3);
+ equal(result[0].get("title"), "About");
+ equal(result[1].get("title"), "Home");
+ return equal(result[2].get("title"), "Contact");
+ });
+
+ test("Sorder by model key with descending order", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $gt: 1
+ }
+ }, {
+ sortBy: "likes",
+ order: "desc"
+ });
+ equal(result.length, 3);
+ equal(result[2].get("title"), "About");
+ equal(result[1].get("title"), "Home");
+ return equal(result[0].get("title"), "Contact");
+ });
+
+ test("Sorder by function", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ likes: {
+ $gt: 1
+ }
+ }, {
+ sortBy: function(model) {
+ return model["title"].charAt(2);
+ }
+ });
+ equal(result.length, 3);
+ equal(result[2].get("title"), "About");
+ equal(result[0].get("title"), "Home");
+ return equal(result[1].get("title"), "Contact");
+ });
+
+ test("null attribute with various operators", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ wrong_key: {
+ $like: "test"
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $regex: /test/
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $contains: "test"
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $all: [12, 23]
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $any: [12, 23]
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $size: 10
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $in: [12, 23]
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $nin: [12, 23]
+ }
+ });
+ return equal(result.length, 0);
+ });
+
+}).call(this);
63 test/test.coffee
@@ -0,0 +1,63 @@
+_ = require "underscore"
+db = require("dirty")('test.db')
+{query} = require "../src/node_dirty_query.coffee"
+request = require "request"
+fs = require "fs"
+
+
+get_sample_data = ->
+ request "http://catalogue.data.gov.uk/dump/data.gov.uk-ckan-meta-data-2012-02-05.json.zip", (req, resp) ->
+ reader = z.Reader(resp)
+ for doc in JSON.parse reader.toObject('utf-8')
+ db.set _.uniqueId('gov_'), doc
+
+ console.log db.length
+
+
+time = -> (new Date).getTime()
+pgm_init = time()
+
+db.on "drain", -> console.log "data written to disk"
+db.on "load", (length) ->
+ loaded = time()
+ console.log "#{length} records read from disk in #{loaded - pgm_init} ms"
+ get_sample_data() if length < 10
+
+ query_param =
+ $not:
+ title: $likeI: "variant"
+ $nor:
+ state: "deleted"
+ title: $likeI: "the"
+ $and:
+ notes: $likeI: "scotland"
+
+ query_param2 = id: "5e831393-bcf2-4c6e-a959-5fb494b653b7"
+
+ options =
+ findOne: true
+
+ a = time()
+ results = query db, query_param2, options
+ b = time()
+ console.log "#{results.length} Matches found in #{b - a} ms (find one)"
+
+
+ a = time()
+ results = query db, query_param2
+ b = time()
+ console.log "#{results.length} Matches found in #{b - a} ms (find many)"
+
+
+
+
+
+ #console.log results
+
+#fs.readFile "../db/gov.json", (err, contents) ->
+# throw err if err
+#
+
+#db.set 1, {title:"Home", colors:["red","yellow","blue"], likes:12, featured:true, content: "Dummy content about coffeescript"}
+#db.set 2, {title:"About", colors:["red"], likes:2, featured:true, content: "dummy content about javascript"}
+#db.set 3, {title:"Contact", colors:["red","blue"], likes:20, content: "Dummy content about PHP"}

0 comments on commit 677f3e8

Please sign in to comment.
Something went wrong with that request. Please try again.