UNPKG

forerunnerdb

Version:

A NoSQL document store database for browsers and Node.js.

448 lines (376 loc) 14.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: Mixin.Matching.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: Mixin.Matching.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>"use strict"; var Matching = { /** * Internal method that checks a document against a test object. * @param {*} source The source object or value to test against. * @param {*} test The test object or value to test with. * @param {Object} queryOptions The options the query was passed with. * @param {String=} opToApply The special operation to apply to the test such * as 'and' or an 'or' operator. * @param {Object=} options An object containing options to apply to the * operation such as limiting the fields returned etc. * @returns {Boolean} True if the test was positive, false on negative. * @private */ _match: function (source, test, queryOptions, opToApply, options) { // TODO: This method is quite long, break into smaller pieces var operation, applyOp = opToApply, recurseVal, tmpIndex, sourceType = typeof source, testType = typeof test, matchedAll = true, opResult, substringCache, i; options = options || {}; queryOptions = queryOptions || {}; // Check if options currently holds a root query object if (!options.$rootQuery) { // Root query not assigned, hold the root query options.$rootQuery = test; } options.$rootData = options.$rootData || {}; // Check if the comparison data are both strings or numbers if ((sourceType === 'string' || sourceType === 'number') &amp;&amp; (testType === 'string' || testType === 'number')) { // The source and test data are flat types that do not require recursive searches, // so just compare them and return the result if (sourceType === 'number') { // Number comparison if (source !== test) { matchedAll = false; } } else { // String comparison // TODO: We can probably use a queryOptions.$locale as a second parameter here // TODO: to satisfy https://github.com/Irrelon/ForerunnerDB/issues/35 if (source.localeCompare(test)) { matchedAll = false; } } } else { for (i in test) { if (test.hasOwnProperty(i)) { // Reset operation flag operation = false; substringCache = i.substr(0, 2); // Check if the property is a comment (ignorable) if (substringCache === '//') { // Skip this property continue; } // Check if the property starts with a dollar (function) if (substringCache.indexOf('$') === 0) { // Ask the _matchOp method to handle the operation opResult = this._matchOp(i, source, test[i], queryOptions, options); // Check the result of the matchOp operation // If the result is -1 then no operation took place, otherwise the result // will be a boolean denoting a match (true) or no match (false) if (opResult > -1) { if (opResult) { if (opToApply === 'or') { return true; } } else { // Set the matchedAll flag to the result of the operation // because the operation did not return true matchedAll = opResult; } // Record that an operation was handled operation = true; } } // Check for regex if (!operation &amp;&amp; test[i] instanceof RegExp) { operation = true; if (sourceType === 'object' &amp;&amp; source[i] !== undefined &amp;&amp; test[i].test(source[i])) { if (opToApply === 'or') { return true; } } else { matchedAll = false; } } if (!operation) { // Check if our query is an object if (typeof(test[i]) === 'object') { // Because test[i] is an object, source must also be an object // Check if our source data we are checking the test query against // is an object or an array if (source[i] !== undefined) { if (source[i] instanceof Array &amp;&amp; !(test[i] instanceof Array)) { // The source data is an array, so check each item until a // match is found recurseVal = false; for (tmpIndex = 0; tmpIndex &lt; source[i].length; tmpIndex++) { recurseVal = this._match(source[i][tmpIndex], test[i], queryOptions, applyOp, options); if (recurseVal) { // One of the array items matched the query so we can // include this item in the results, so break now break; } } if (recurseVal) { if (opToApply === 'or') { return true; } } else { matchedAll = false; } } else if (!(source[i] instanceof Array) &amp;&amp; test[i] instanceof Array) { // The test key data is an array and the source key data is not so check // each item in the test key data to see if the source item matches one // of them. This is effectively an $in search. recurseVal = false; for (tmpIndex = 0; tmpIndex &lt; test[i].length; tmpIndex++) { recurseVal = this._match(source[i], test[i][tmpIndex], queryOptions, applyOp, options); if (recurseVal) { // One of the array items matched the query so we can // include this item in the results, so break now break; } } if (recurseVal) { if (opToApply === 'or') { return true; } } else { matchedAll = false; } } else if (typeof(source) === 'object') { // Recurse down the object tree recurseVal = this._match(source[i], test[i], queryOptions, applyOp, options); if (recurseVal) { if (opToApply === 'or') { return true; } } else { matchedAll = false; } } else { recurseVal = this._match(undefined, test[i], queryOptions, applyOp, options); if (recurseVal) { if (opToApply === 'or') { return true; } } else { matchedAll = false; } } } else { // First check if the test match is an $exists if (test[i] &amp;&amp; test[i].$exists !== undefined) { // Push the item through another match recurse recurseVal = this._match(undefined, test[i], queryOptions, applyOp, options); if (recurseVal) { if (opToApply === 'or') { return true; } } else { matchedAll = false; } } else { matchedAll = false; } } } else { // Check if the prop matches our test value if (source &amp;&amp; source[i] === test[i]) { if (opToApply === 'or') { return true; } } else if (source &amp;&amp; source[i] &amp;&amp; source[i] instanceof Array &amp;&amp; test[i] &amp;&amp; typeof(test[i]) !== "object") { // We are looking for a value inside an array // The source data is an array, so check each item until a // match is found recurseVal = false; for (tmpIndex = 0; tmpIndex &lt; source[i].length; tmpIndex++) { recurseVal = this._match(source[i][tmpIndex], test[i], queryOptions, applyOp, options); if (recurseVal) { // One of the array items matched the query so we can // include this item in the results, so break now break; } } if (recurseVal) { if (opToApply === 'or') { return true; } } else { matchedAll = false; } } else { matchedAll = false; } } } if (opToApply === 'and' &amp;&amp; !matchedAll) { return false; } } } } return matchedAll; }, /** * Internal method, performs a matching process against a query operator such as $gt or $nin. * @param {String} key The property name in the test that matches the operator to perform * matching against. * @param {*} source The source data to match the query against. * @param {*} test The query to match the source against. * @param {Object=} options An options object. * @returns {*} * @private */ _matchOp: function (key, source, test, queryOptions, options) { // Check for commands switch (key) { case '$gt': // Greater than return source > test; case '$gte': // Greater than or equal return source >= test; case '$lt': // Less than return source &lt; test; case '$lte': // Less than or equal return source &lt;= test; case '$exists': // Property exists return (source === undefined) !== test; case '$ne': // Not equals return source != test; // jshint ignore:line case '$nee': // Not equals equals return source !== test; case '$or': // Match true on ANY check to pass for (var orIndex = 0; orIndex &lt; test.length; orIndex++) { if (this._match(source, test[orIndex], queryOptions, 'and', options)) { return true; } } return false; case '$and': // Match true on ALL checks to pass for (var andIndex = 0; andIndex &lt; test.length; andIndex++) { if (!this._match(source, test[andIndex], queryOptions, 'and', options)) { return false; } } return true; case '$in': // In // Check that the in test is an array if (test instanceof Array) { var inArr = test, inArrCount = inArr.length, inArrIndex; for (inArrIndex = 0; inArrIndex &lt; inArrCount; inArrIndex++) { if (inArr[inArrIndex] instanceof RegExp &amp;&amp; inArr[inArrIndex].test(source)) { return true; } else if (inArr[inArrIndex] === source) { return true; } } return false; } else { throw(this.logIdentifier() + ' Cannot use an $in operator on a non-array key: ' + key); } break; case '$nin': // Not in // Check that the not-in test is an array if (test instanceof Array) { var notInArr = test, notInArrCount = notInArr.length, notInArrIndex; for (notInArrIndex = 0; notInArrIndex &lt; notInArrCount; notInArrIndex++) { if (notInArr[notInArrIndex] === source) { return false; } } return true; } else { throw(this.logIdentifier() + ' Cannot use a $nin operator on a non-array key: ' + key); } break; case '$distinct': // Ensure options holds a distinct lookup options.$rootData['//distinctLookup'] = options.$rootData['//distinctLookup'] || {}; for (var distinctProp in test) { if (test.hasOwnProperty(distinctProp)) { options.$rootData['//distinctLookup'][distinctProp] = options.$rootData['//distinctLookup'][distinctProp] || {}; // Check if the options distinct lookup has this field's value if (options.$rootData['//distinctLookup'][distinctProp][source[distinctProp]]) { // Value is already in use return false; } else { // Set the value in the lookup options.$rootData['//distinctLookup'][distinctProp][source[distinctProp]] = true; // Allow the item in the results return true; } } } break; case '$count': var countKey, countArr, countVal; // Iterate the count object's keys for (countKey in test) { if (test.hasOwnProperty(countKey)) { // Check the property exists and is an array. If the property being counted is not // an array (or doesn't exist) then use a value of zero in any further count logic countArr = source[countKey]; if (typeof countArr === 'object' &amp;&amp; countArr instanceof Array) { countVal = countArr.length; } else { countVal = 0; } // Now recurse down the query chain further to satisfy the query for this key (countKey) if (!this._match(countVal, test[countKey], queryOptions, 'and', options)) { return false; } } } // Allow the item in the results return true; } return -1; } }; module.exports = Matching;</code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="ActiveBucket.html">ActiveBucket</a></li><li><a href="Angular.html">Angular</a></li><li><a href="AutoBind.html">AutoBind</a></li><li><a href="BinaryTree.html">BinaryTree</a></li><li><a href="Collection.html">Collection</a></li><li><a href="CollectionGroup.html">CollectionGroup</a></li><li><a href="Core.html">Core</a></li><li><a href="Db.html">Db</a></li><li><a href="Document.html">Document</a></li><li><a href="Grid.html">Grid</a></li><li><a href="Highchart.html">Highchart</a></li><li><a href="IndexBinaryTree.html">IndexBinaryTree</a></li><li><a href="IndexHashMap.html">IndexHashMap</a></li><li><a href="Infinilist.html">Infinilist</a></li><li><a href="KeyValueStore.html">KeyValueStore</a></li><li><a href="Metrics.html">Metrics</a></li><li><a href="OldView.html">OldView</a></li><li><a href="Operation.html">Operation</a></li><li><a href="Overload.html">Overload</a></li><li><a href="Path.html">Path</a></li><li><a href="Persist.html">Persist</a></li><li><a href="ReactorIO.html">ReactorIO</a></li><li><a href="Serialiser.html">Serialiser</a></li><li><a href="Shared.overload.html">overload</a></li><li><a href="View.html">View</a></li></ul><h3>Mixins</h3><ul><li><a href="ChainReactor.html">ChainReactor</a></li><li><a href="crcTable.html">crcTable</a></li><li><a href="Shared.html">Shared</a></li></ul><h3>Global</h3><ul><li><a href="global.html#%2522boolean,function%2522">"boolean, function"</a></li><li><a href="global.html#%2522string,*,function%2522">"string, *, function"</a></li><li><a href="global.html#%2522string,function%2522">"string, function"</a></li><li><a href="global.html#boolean">boolean</a></li><li><a href="global.html#function">function</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.3.3</a> on Thu Nov 19 2015 13:31:32 GMT+0000 (GMT) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>