UNPKG

mongo-portable

Version:

Portable Pure JS MongoDB - Based on Monglodb (https://github.com/euforic/monglodb.git) by Christian Sullivan (http://RogueSynaptics.com)

1 lines 48.7 kB
{"filter":false,"title":"SelectorMatcher.js","tooltip":"/browser/SelectorMatcher.js","undoManager":{"mark":0,"position":0,"stack":[[{"start":{"row":5,"column":0},"end":{"row":708,"column":33},"action":"remove","lines":["var logger = null;","","class SelectorMatcher {","\tconstructor(selector) {"," this.clauses = selector.clauses;",""," logger = Logger.instance;","\t}","\t","\ttest(document) {","\t\tlogger.debug('Called SelectorMatcher->test');","\t\t","\t\tvar _match = false;","","\t\tif (_.isNil(document)) {","\t\t\tlogger.debug('document -> null');","\t\t\t","\t\t\tlogger.throw(\"Parameter 'document' required\");","\t\t}","\t\t","\t\tlogger.debug('document -> not null');","\t\t","\t\tfor (var i = 0; i < this.clauses.length; i++) {","\t\t\tvar clause = this.clauses[i];","\t\t\t","\t\t\tif (clause.kind === 'function') {","\t\t\t\tlogger.debug('clause -> function');","\t\t\t\t","\t\t\t\t_match = clause.value.call(null, document);","\t\t\t} else if (clause.kind === 'plain') {","\t\t\t\tlogger.debug(`clause -> plain on field \"${clause.key}\" and value = ${JSON.stringify(clause.value)}`);","\t\t\t\t","\t\t\t\t_match = _testClause(clause, document[clause.key]);","\t\t\t\t","\t\t\t\tlogger.debug('clause result -> ' + _match);","\t\t\t} else if (clause.kind === 'object') {","\t\t\t\tlogger.debug(`clause -> object on field \"${clause.key.join('.')}\" and value = ${JSON.stringify(clause.value)}`);","\t\t\t\t","\t\t\t\t_match = _testObjectClause(clause, document, _.clone(clause.key).reverse());","\t\t\t\t","\t\t\t\tlogger.debug('clause result -> ' + _match);","\t\t\t} else if (clause.kind === 'operator') {","\t\t\t logger.debug(`clause -> operator '${clause.key}'`);","\t\t\t ","\t\t\t _match = _testLogicalClause(clause, document, clause.key);","\t\t ","\t\t\t\tlogger.debug('clause result -> ' + _match);","\t\t\t}","\t\t\t","\t\t\t// If any test case fails, the document will not match","\t\t\tif (_match === false || _match === 'false') {","\t\t\t\tlogger.debug('the document do not matches');","\t\t\t\t","\t\t\t\treturn false;","\t\t\t}","\t\t}","\t\t","\t\t// Everything matches","\t\tlogger.debug('the document matches');","\t\t","\t\treturn true;","\t}","\t","\tstatic all(array, value) {"," // $all is only meaningful on arrays"," if (!(array instanceof Array)) {"," return false;"," }",""," // TODO should use a canonicalizing representation, so that we"," // don't get screwed by key order"," var parts = {};"," var remaining = 0;",""," _.forEach(value, function (val) {"," var hash = JSON.stringify(val);",""," if (!(hash in parts)) {"," parts[hash] = true;"," remaining++;"," }"," });",""," for (var i = 0; i < array.length; i++) {"," var hash = JSON.stringify(array[i]);"," if (parts[hash]) {"," delete parts[hash];"," remaining--;",""," if (0 === remaining) return true;"," }"," }",""," return false;"," }","\t","\tstatic in(array, value) {"," if (!_.isObject(array)) {"," // optimization: use scalar equality (fast)"," for (var i = 0; i < value.length; i++) {"," if (array === value[i]) {"," return true;"," }"," }",""," return false;"," } else {"," // nope, have to use deep equality"," for (var i = 0; i < value.length; i++) {"," if (SelectorMatcher.equal(array, value[i])) {"," return true;"," }"," }",""," return false;"," }"," }","\t","\t// deep equality test: use for literal document and array matches","\tstatic equal(array, qval) {"," var match = function (a, b) {"," // scalars"," if (_.isNumber(a) || _.isString(a) || _.isBoolean(a) || _.isNil(a)) return a === b;",""," if (_.isFunction(a)) return false; // Not allowed yet",""," // OK, typeof a === 'object'"," if (!_.isObject(b)) return false;",""," // arrays"," if (_.isArray(a)) {"," if (!_.isArray(b)) return false;",""," if (a.length !== b.length) return false;",""," for (let i = 0; i < a.length; i++) {"," if (!match(a[i],b[i])) return false;"," }",""," return true;"," }",""," // objects"," /*"," var unmatched_b_keys = 0;"," for (var x in b)"," unmatched_b_keys++;"," for (var x in a) {"," if (!(x in b) || !match(a[x], b[x]))"," return false;"," unmatched_b_keys--;"," }"," return unmatched_b_keys === 0;"," */"," // Follow Mongo in considering key order to be part of"," // equality. Key enumeration order is actually not defined in"," // the ecmascript spec but in practice most implementations"," // preserve it. (The exception is Chrome, which preserves it"," // usually, but not for keys that parse as ints.)"," var b_keys = [];",""," for (var array in b) {"," b_keys.push(b[array]);"," }",""," let i = 0;"," for (let array in a) {"," if (i >= b_keys.length) return false;",""," if (!match(a[array], b_keys[i])) return false;",""," i++;"," }"," if (i !== b_keys.length) return false;",""," return true;"," };",""," return match(array, qval);"," }","\t","\t// if x is not an array, true iff f(x) is true. if x is an array,"," // true iff f(y) is true for any y in x."," //"," // this is the way most mongo operators (like $gt, $mod, $type..)"," // treat their arguments."," static matches(value, func) {"," if (_.isArray(value)) {"," for (var i = 0; i < value.length; i++) {"," if (func(value[i])) return true;"," }",""," return false;"," }",""," return func(value);"," }","\t","\t// like _matches, but if x is an array, it's true not only if f(y)"," // is true for some y in x, but also if f(x) is true."," //"," // this is the way mongo value comparisons usually work, like {x:"," // 4}, {x: [4]}, or {x: {$in: [1,2,3]}}."," static matches_plus(value, func) {"," // if (_.isArray(value)) {"," // for (var i = 0; i < value.length; i++) {"," // if (func(value[i])) return true;"," // }",""," // // fall through!"," // }",""," // return func(value);"," return SelectorMatcher.matches(value, func) || func(value);"," }","\t","\t// compare two values of unknown type according to BSON ordering"," // semantics. (as an extension, consider 'undefined' to be less than"," // any other value.)"," // return negative if a is less, positive if b is less, or 0 if equal"," static cmp(a, b) {"," if (_.isUndefined(a)) return b === undefined ? 0 : -1;",""," if (_.isUndefined(b)) return 1;","\t\t"," var aType = BsonTypes.getByValue(a);"," var bType = BsonTypes.getByValue(b);",""," if (aType.order !== bType.order) return aType.order < bType.order ? -1 : 1;",""," // Same sort order, but distinct value type"," if (aType.number !== bType.number) {"," // Currently, Symbols can not be sortered in JS, so we are setting the Symbol as greater"," if (_.isSymbol(a)) return 1;"," if (_.isSymbol(b)) return -1;"," "," // TODO Integer, Date and Timestamp"," }"," "," if (_.isNumber(a)) return a - b;"," "," if (_.isString(a)) return a < b ? -1 : (a === b ? 0 : 1);"," "," if (_.isBoolean(a)) {"," if (a) return b ? 0 : 1;",""," return b ? -1 : 0;"," }"," "," if (_.isArray(a)) {"," for (var i = 0; ; i++) {"," if (i === a.length) return (i === b.length) ? 0 : -1;",""," if (i === b.length) return 1;"," "," if (a.length !== b.length) return a.length - b.length;",""," var s = SelectorMatcher.cmp(a[i], b[i]);",""," if (s !== 0) return s;"," }"," }"," "," if (_.isNull(a)) return 0;"," "," if (_.isRegExp(a)) throw Error(\"Sorting not supported on regular expression\"); // TODO"," "," // if (_.isFunction(a)) return {type: 13, order: 100, fnc: _.isFunction};"," "," if (_.isPlainObject(a)) {"," var to_array = function (obj) {"," var ret = [];",""," for (var key in obj) {"," ret.push(key);"," ret.push(obj[key]);"," }",""," return ret;"," };",""," return SelectorMatcher.cmp(to_array(a), to_array(b));"," }",""," // double"," // if (ta === 1) return a - b;",""," // string"," // if (tb === 2) return a < b ? -1 : (a === b ? 0 : 1);",""," // Object"," // if (ta === 3) {"," // // this could be much more efficient in the expected case ..."," // var to_array = function (obj) {"," // var ret = [];",""," // for (var key in obj) {"," // ret.push(key);"," // ret.push(obj[key]);"," // }",""," // return ret;"," // };",""," // return Selector._f._cmp(to_array(a), to_array(b));"," // }",""," // Array"," // if (ta === 4) {"," // for (var i = 0; ; i++) {"," // if (i === a.length) return (i === b.length) ? 0 : -1;",""," // if (i === b.length) return 1;"," "," // if (a.length !== b.length) return a.length - b.length;",""," // var s = Selector._f._cmp(a[i], b[i]);",""," // if (s !== 0) return s;"," // }"," // }",""," // 5: binary data"," // 7: object id",""," // boolean"," // if (ta === 8) {"," // if (a) return b ? 0 : 1;",""," // return b ? -1 : 0;"," // }",""," // 9: date",""," // null"," // if (ta === 10) return 0;",""," // regexp"," // if (ta === 11) {"," // throw Error(\"Sorting not supported on regular expression\"); // TODO"," // }",""," // 13: javascript code"," // 14: symbol"," if (_.isSymbol(a)) {"," // Currently, Symbols can not be sortered in JS, so we are returning an equality"," return 0;"," }"," // 15: javascript code with scope"," // 16: 32-bit integer"," // 17: timestamp"," // 18: 64-bit integer"," // 255: minkey"," // 127: maxkey",""," // javascript code"," // if (ta === 13) {"," // throw Error(\"Sorting not supported on Javascript code\"); // TODO"," // }"," }","}","","var _testClause = function(clause, val) {"," logger.debug('Called _testClause');"," "," // var _val = clause.value;"," "," // if RegExp || $ -> Operator"," "," return SelectorMatcher.matches_plus(val, function(_value) {"," // TODO object ids, dates, timestamps?"," switch (clause.type) {"," case 'null':"," logger.debug('test Null equality');"," "," // http://www.mongodb.org/display/DOCS/Querying+and+nulls"," if (_.isNil(_value)) {"," return true;"," } else {"," return false;"," }"," case 'regexp':"," logger.debug('test RegExp equality');"," "," return _testOperatorClause(clause, _value);"," case 'literal_object':"," logger.debug('test Literal Object equality');"," "," return SelectorMatcher.equal(_value, clause.value);"," case 'operator_object':"," logger.debug('test Operator Object equality');"," "," return _testOperatorClause(clause, _value);"," case 'string':"," logger.debug('test String equality');"," "," return _.toString(_value) === _.toString(clause.value);"," case 'number':"," logger.debug('test Number equality');"," "," return _.toNumber(_value) === _.toNumber(clause.value);"," case 'boolean':"," logger.debug('test Boolean equality');"," "," return (_.isBoolean(_value) && _.isBoolean(clause.value) && (_value === clause.value));"," case 'array':"," logger.debug('test Boolean equality');"," "," // Check type"," if (_.isArray(_value) && _.isArray(clause.value)) {"," // Check length"," if (_value.length === clause.value.length) {"," // Check items"," for (let i = 0; i < _value.length; i++) {"," if (clause.value.indexOf(_value[i]) === -1) {"," return false;"," }"," }"," "," return true;"," } else {"," return false;"," }"," } else {"," return false;"," }"," case 'function':"," logger.debug('test Function equality');"," "," throw Error(\"Bad value type in query\");"," default:"," throw Error(\"Bad value type in query\");"," }"," });","};","","var _testObjectClause = function(clause, doc, key) {"," logger.debug('Called _testObjectClause');"," "," var val = null;"," "," if (key.length > 0) {"," var path = key.pop();"," val = doc[path];"," "," logger.debug('check on field ' + path);"," "," // TODO add _.isNumber(val) and treat it as an array"," if (val) {"," logger.log(val);"," logger.debug('going deeper');"," "," return _testObjectClause(clause, val, key);"," }"," } else {"," logger.debug('lowest path: ' + path);"," "," return _testClause(clause, doc);"," }","};","","var _testLogicalClause = function(clause, doc, key) {"," var matches = null;"," "," for (let i = 0; i < clause.value.length; i++) {"," let _matcher = new SelectorMatcher({ clauses: [clause.value[i]] });"," "," switch (key) {"," case 'and':"," // True unless it has one that do not match"," if (_.isNil(matches)) matches = true;"," "," if (!_matcher.test(doc)) {"," return false;"," }"," "," break;"," case 'or':"," // False unless it has one match at least"," if (_.isNil(matches)) matches = false;"," "," if (_matcher.test(doc)) {"," return true;"," }"," "," break;"," }"," }"," "," return matches || false;","};","","var _testOperatorClause = function(clause, value) {"," logger.debug('Called _testOperatorClause');"," "," for (var key in clause.value) {"," if (!_testOperatorConstraint(key, clause.value[key], clause.value, value, clause)) {"," return false;"," }"," }"," "," return true;","};","","var _testOperatorConstraint = function (key, operatorValue, clauseValue, docVal, clause) {"," logger.debug('Called _testOperatorConstraint');"," "," switch (key) {"," // Comparison Query Operators"," case '$gt':"," logger.debug('testing operator $gt');",""," return SelectorMatcher.cmp(docVal, operatorValue) > 0;"," case '$lt':"," logger.debug('testing operator $lt');"," "," return SelectorMatcher.cmp(docVal, operatorValue) < 0;"," case '$gte':"," logger.debug('testing operator $gte');"," "," return SelectorMatcher.cmp(docVal, operatorValue) >= 0;"," case '$lte':"," logger.debug('testing operator $lte');"," "," return SelectorMatcher.cmp(docVal, operatorValue) <= 0;"," case '$eq':"," logger.debug('testing operator $eq');"," "," return SelectorMatcher.equal(docVal, operatorValue);"," case '$ne':"," logger.debug('testing operator $ne');"," "," return !SelectorMatcher.equal(docVal, operatorValue);"," case '$in':"," logger.debug('testing operator $in');"," "," return SelectorMatcher.in(docVal, operatorValue);"," case '$nin':"," logger.debug('testing operator $nin');"," "," return !SelectorMatcher.in(docVal, operatorValue);"," // Logical Query Operators"," case '$not':"," logger.debug('testing operator $not');"," "," // $or, $and, $nor are in the 'operator' kind treatment"," /*"," var _clause = {"," kind: 'plain',"," key: clause.key,"," value: operatorValue,"," type: "," };"," var _parent = clause.value;"," var _key = "," return !(_testClause(_clause, docVal));"," */"," // TODO implement"," throw Error(\"$not unimplemented\");"," // Element Query Operators"," case '$exists':"," logger.debug('testing operator $exists');"," "," return operatorValue ? !_.isUndefined(docVal) : _.isUndefined(docVal);"," case '$type':"," logger.debug('testing operator $type');"," "," // $type: 1 is true for an array if any element in the array is of"," // type 1. but an array doesn't have type array unless it contains"," // an array.."," // var Selector._f._type(docVal);"," // return Selector._f._type(docVal).type === operatorValue;"," throw Error(\"$type unimplemented\");"," // Evaluation Query Operators"," case '$mod':"," logger.debug('testing operator $mod');"," "," return docVal % operatorValue[0] === operatorValue[1];"," case '$options':"," logger.debug('testing operator $options (ignored)');"," "," // Ignore, as it is to the RegExp"," return true;"," case '$regex':"," logger.debug('testing operator $regex');"," "," var _opt = null;"," if (_.hasIn(clauseValue, '$options')) {"," _opt = clauseValue['$options'];"," "," if (/[xs]/.test(_opt)) {"," //g, i, m, x, s"," // TODO mongo uses PCRE and supports some additional flags: 'x' and"," // 's'. javascript doesn't support them. so this is a divergence"," // between our behavior and mongo's behavior. ideally we would"," // implement x and s by transforming the regexp, but not today.."," "," throw Error(\"Only the i, m, and g regexp options are supported\");"," }"," }"," "," // Review flags -> g & m"," var regexp = operatorValue;"," "," if (_.isRegExp(regexp) && _.isNil(_opt)) {"," return regexp.test(docVal);"," } else if (_.isNil(_opt)) {"," regexp = new RegExp(regexp);"," } else if (_.isRegExp(regexp)) {"," regexp = new RegExp(regexp.source, _opt);"," } else {"," regexp = new RegExp(regexp, _opt);"," }"," "," return regexp.test(docVal);"," case '$text':"," logger.debug('testing operator $text');"," "," // TODO implement"," throw Error(\"$text unimplemented\");"," case '$where':"," logger.debug('testing operator $where');"," "," // TODO implement"," throw Error(\"$where unimplemented\");"," // Geospatial Query Operators"," // TODO -> in operator kind"," // Query Operator Array"," case '$all':"," logger.debug('testing operator $all');"," "," return SelectorMatcher.all(operatorValue, docVal) > 0;"," case '$elemMatch':"," logger.debug('testing operator $elemMatch');"," "," // TODO implement"," throw Error(\"$elemMatch unimplemented\");"," case '$size':"," logger.debug('testing operator $size');"," "," return _.isArray(docVal) && docVal.length === operatorValue;"," // Bitwise Query Operators"," // TODO"," default:"," logger.debug('testing operator ' + key);"," "," throw Error(\"Unrecognized key in selector: \" + key);"," }","};","","var BsonTypes = {","\t_types: [","\t\t{ alias: 'minKey', number: -1, order: 1, isType: null },","\t\t{ alias: 'null', number: 10, order: 2, isType: null },","\t\t{ alias: 'int', number: 16, order: 3, isType: _.isInteger },","\t\t{ alias: 'long', number: 18, order: 3, isType: _.isNumber },","\t\t{ alias: 'double', number: 1, order: 3, isType: _.isNumber },","\t\t{ alias: 'number', number: null, order: 3, isType: _.isNumber },","\t\t{ alias: 'string', number: 2, order: 4, isType: _.isString },","\t\t{ alias: 'symbol', number: 14, order: 4, isType: _.isSymbol },","\t\t{ alias: 'object', number: 3, order: 5, isType: _.isPlainObject },","\t\t{ alias: 'array', number: 4, order: 6, isType: _.isArray },","\t\t{ alias: 'binData', number: 5, order: 7, isType: null },","\t\t{ alias: 'objectId', number: 7, order: 8, isTypefnc: null },","\t\t{ alias: 'bool', number: 8, order: 9, isType: _.isBoolean },","\t\t{ alias: 'date', number: 9, order: 10, isTypefnc: _.isDate }, // format","\t\t{ alias: 'timestamp', number: 17, order: 11, isType: _.isDate }, // format","\t\t{ alias: 'regex', number: 11, order: 12, isType: _.isRegExp },","\t\t{ alias: 'maxKey', number: 127, order: 13, isType: null }","\t\t","// \t\tundefined 6","// \t\tdbPointer","// \t\tjavascript","// \t\tjavascriptWithScope","// \t\tfunction","\t],","\t","\tgetByAlias: function(alias) {","\t\tfor (var i = 0; i < this._types.length; i++) {","\t\t\tif (this._types[i].alias === alias) return this._types[i];","\t\t}","\t},","\tgetByValue: function(val) {","\t if (_.isNumber(val)) return this.getByAlias(\"double\");"," "," if (_.isString(val)) return this.getByAlias(\"string\");"," "," if (_.isBoolean(val)) return this.getByAlias(\"bool\");"," "," if (_.isArray(val)) return this.getByAlias(\"array\");"," "," if (_.isNull(val)) return this.getByAlias(\"null\");"," "," if (_.isRegExp(val)) return this.getByAlias(\"regex\");"," "," if (_.isPlainObject(val)) return this.getByAlias(\"object\");"," "," if (_.isSymbol(val)) return this.getByAlias(\"symbol\");"," "," throw Error(\"Unaccepted BSON type\");","\t}","};","","module.exports = SelectorMatcher;"],"id":2},{"start":{"row":5,"column":0},"end":{"row":708,"column":33},"action":"insert","lines":["var logger = null;","","class SelectorMatcher {","\tconstructor(selector) {"," this.clauses = selector.clauses;",""," logger = Logger.instance;","\t}","\t","\ttest(document) {","\t\tlogger.debug('Called SelectorMatcher->test');","\t\t","\t\tvar _match = false;","","\t\tif (_.isNil(document)) {","\t\t\tlogger.debug('document -> null');","\t\t\t","\t\t\tlogger.throw(\"Parameter 'document' required\");","\t\t}","\t\t","\t\tlogger.debug('document -> not null');","\t\t","\t\tfor (var i = 0; i < this.clauses.length; i++) {","\t\t\tvar clause = this.clauses[i];","\t\t\t","\t\t\tif (clause.kind === 'function') {","\t\t\t\tlogger.debug('clause -> function');","\t\t\t\t","\t\t\t\t_match = clause.value.call(null, document);","\t\t\t} else if (clause.kind === 'plain') {","\t\t\t\tlogger.debug(`clause -> plain on field \"${clause.key}\" and value = ${JSON.stringify(clause.value)}`);","\t\t\t\t","\t\t\t\t_match = _testClause(clause, document[clause.key]);","\t\t\t\t","\t\t\t\tlogger.debug('clause result -> ' + _match);","\t\t\t} else if (clause.kind === 'object') {","\t\t\t\tlogger.debug(`clause -> object on field \"${clause.key.join('.')}\" and value = ${JSON.stringify(clause.value)}`);","\t\t\t\t","\t\t\t\t_match = _testObjectClause(clause, document, _.clone(clause.key).reverse());","\t\t\t\t","\t\t\t\tlogger.debug('clause result -> ' + _match);","\t\t\t} else if (clause.kind === 'operator') {","\t\t\t logger.debug(`clause -> operator '${clause.key}'`);","\t\t\t ","\t\t\t _match = _testLogicalClause(clause, document, clause.key);","\t\t ","\t\t\t\tlogger.debug('clause result -> ' + _match);","\t\t\t}","\t\t\t","\t\t\t// If any test case fails, the document will not match","\t\t\tif (_match === false || _match === 'false') {","\t\t\t\tlogger.debug('the document do not matches');","\t\t\t\t","\t\t\t\treturn false;","\t\t\t}","\t\t}","\t\t","\t\t// Everything matches","\t\tlogger.debug('the document matches');","\t\t","\t\treturn true;","\t}","\t","\tstatic all(array, value) {"," // $all is only meaningful on arrays"," if (!(array instanceof Array)) {"," return false;"," }",""," // TODO should use a canonicalizing representation, so that we"," // don't get screwed by key order"," var parts = {};"," var remaining = 0;",""," _.forEach(value, function (val) {"," var hash = JSON.stringify(val);",""," if (!(hash in parts)) {"," parts[hash] = true;"," remaining++;"," }"," });",""," for (var i = 0; i < array.length; i++) {"," var hash = JSON.stringify(array[i]);"," if (parts[hash]) {"," delete parts[hash];"," remaining--;",""," if (0 === remaining) return true;"," }"," }",""," return false;"," }","\t","\tstatic in(array, value) {"," if (!_.isObject(array)) {"," // optimization: use scalar equality (fast)"," for (var i = 0; i < value.length; i++) {"," if (array === value[i]) {"," return true;"," }"," }",""," return false;"," } else {"," // nope, have to use deep equality"," for (var i = 0; i < value.length; i++) {"," if (SelectorMatcher.equal(array, value[i])) {"," return true;"," }"," }",""," return false;"," }"," }","\t","\t// deep equality test: use for literal document and array matches","\tstatic equal(array, qval) {"," var match = function (a, b) {"," // scalars"," if (_.isNumber(a) || _.isString(a) || _.isBoolean(a) || _.isNil(a)) return a === b;",""," if (_.isFunction(a)) return false; // Not allowed yet",""," // OK, typeof a === 'object'"," if (!_.isObject(b)) return false;",""," // arrays"," if (_.isArray(a)) {"," if (!_.isArray(b)) return false;",""," if (a.length !== b.length) return false;",""," for (let i = 0; i < a.length; i++) {"," if (!match(a[i],b[i])) return false;"," }",""," return true;"," }",""," // objects"," /*"," var unmatched_b_keys = 0;"," for (var x in b)"," unmatched_b_keys++;"," for (var x in a) {"," if (!(x in b) || !match(a[x], b[x]))"," return false;"," unmatched_b_keys--;"," }"," return unmatched_b_keys === 0;"," */"," // Follow Mongo in considering key order to be part of"," // equality. Key enumeration order is actually not defined in"," // the ecmascript spec but in practice most implementations"," // preserve it. (The exception is Chrome, which preserves it"," // usually, but not for keys that parse as ints.)"," var b_keys = [];",""," for (var array in b) {"," b_keys.push(b[array]);"," }",""," let i = 0;"," for (let array in a) {"," if (i >= b_keys.length) return false;",""," if (!match(a[array], b_keys[i])) return false;",""," i++;"," }"," if (i !== b_keys.length) return false;",""," return true;"," };",""," return match(array, qval);"," }","\t","\t// if x is not an array, true iff f(x) is true. if x is an array,"," // true iff f(y) is true for any y in x."," //"," // this is the way most mongo operators (like $gt, $mod, $type..)"," // treat their arguments."," static matches(value, func) {"," if (_.isArray(value)) {"," for (var i = 0; i < value.length; i++) {"," if (func(value[i])) return true;"," }",""," return false;"," }",""," return func(value);"," }","\t","\t// like _matches, but if x is an array, it's true not only if f(y)"," // is true for some y in x, but also if f(x) is true."," //"," // this is the way mongo value comparisons usually work, like {x:"," // 4}, {x: [4]}, or {x: {$in: [1,2,3]}}."," static matches_plus(value, func) {"," // if (_.isArray(value)) {"," // for (var i = 0; i < value.length; i++) {"," // if (func(value[i])) return true;"," // }",""," // // fall through!"," // }",""," // return func(value);"," return SelectorMatcher.matches(value, func) || func(value);"," }","\t","\t// compare two values of unknown type according to BSON ordering"," // semantics. (as an extension, consider 'undefined' to be less than"," // any other value.)"," // return negative if a is less, positive if b is less, or 0 if equal"," static cmp(a, b) {"," if (_.isUndefined(a)) return b === undefined ? 0 : -1;",""," if (_.isUndefined(b)) return 1;","\t\t"," var aType = BsonTypes.getByValue(a);"," var bType = BsonTypes.getByValue(b);",""," if (aType.order !== bType.order) return aType.order < bType.order ? -1 : 1;",""," // Same sort order, but distinct value type"," if (aType.number !== bType.number) {"," // Currently, Symbols can not be sortered in JS, so we are setting the Symbol as greater"," if (_.isSymbol(a)) return 1;"," if (_.isSymbol(b)) return -1;"," "," // TODO Integer, Date and Timestamp"," }"," "," if (_.isNumber(a)) return a - b;"," "," if (_.isString(a)) return a < b ? -1 : (a === b ? 0 : 1);"," "," if (_.isBoolean(a)) {"," if (a) return b ? 0 : 1;",""," return b ? -1 : 0;"," }"," "," if (_.isArray(a)) {"," for (var i = 0; ; i++) {"," if (i === a.length) return (i === b.length) ? 0 : -1;",""," if (i === b.length) return 1;"," "," if (a.length !== b.length) return a.length - b.length;",""," var s = SelectorMatcher.cmp(a[i], b[i]);",""," if (s !== 0) return s;"," }"," }"," "," if (_.isNull(a)) return 0;"," "," if (_.isRegExp(a)) throw Error(\"Sorting not supported on regular expression\"); // TODO"," "," // if (_.isFunction(a)) return {type: 13, order: 100, fnc: _.isFunction};"," "," if (_.isPlainObject(a)) {"," var to_array = function (obj) {"," var ret = [];",""," for (var key in obj) {"," ret.push(key);"," ret.push(obj[key]);"," }",""," return ret;"," };",""," return SelectorMatcher.cmp(to_array(a), to_array(b));"," }",""," // double"," // if (ta === 1) return a - b;",""," // string"," // if (tb === 2) return a < b ? -1 : (a === b ? 0 : 1);",""," // Object"," // if (ta === 3) {"," // // this could be much more efficient in the expected case ..."," // var to_array = function (obj) {"," // var ret = [];",""," // for (var key in obj) {"," // ret.push(key);"," // ret.push(obj[key]);"," // }",""," // return ret;"," // };",""," // return Selector._f._cmp(to_array(a), to_array(b));"," // }",""," // Array"," // if (ta === 4) {"," // for (var i = 0; ; i++) {"," // if (i === a.length) return (i === b.length) ? 0 : -1;",""," // if (i === b.length) return 1;"," "," // if (a.length !== b.length) return a.length - b.length;",""," // var s = Selector._f._cmp(a[i], b[i]);",""," // if (s !== 0) return s;"," // }"," // }",""," // 5: binary data"," // 7: object id",""," // boolean"," // if (ta === 8) {"," // if (a) return b ? 0 : 1;",""," // return b ? -1 : 0;"," // }",""," // 9: date",""," // null"," // if (ta === 10) return 0;",""," // regexp"," // if (ta === 11) {"," // throw Error(\"Sorting not supported on regular expression\"); // TODO"," // }",""," // 13: javascript code"," // 14: symbol"," if (_.isSymbol(a)) {"," // Currently, Symbols can not be sortered in JS, so we are returning an equality"," return 0;"," }"," // 15: javascript code with scope"," // 16: 32-bit integer"," // 17: timestamp"," // 18: 64-bit integer"," // 255: minkey"," // 127: maxkey",""," // javascript code"," // if (ta === 13) {"," // throw Error(\"Sorting not supported on Javascript code\"); // TODO"," // }"," }","}","","var _testClause = function(clause, val) {"," logger.debug('Called _testClause');"," "," // var _val = clause.value;"," "," // if RegExp || $ -> Operator"," "," return SelectorMatcher.matches_plus(val, function(_value) {"," // TODO object ids, dates, timestamps?"," switch (clause.type) {"," case 'null':"," logger.debug('test Null equality');"," "," // http://www.mongodb.org/display/DOCS/Querying+and+nulls"," if (_.isNil(_value)) {"," return true;"," } else {"," return false;"," }"," case 'regexp':"," logger.debug('test RegExp equality');"," "," return _testOperatorClause(clause, _value);"," case 'literal_object':"," logger.debug('test Literal Object equality');"," "," return SelectorMatcher.equal(_value, clause.value);"," case 'operator_object':"," logger.debug('test Operator Object equality');"," "," return _testOperatorClause(clause, _value);"," case 'string':"," logger.debug('test String equality');"," "," return _.toString(_value) === _.toString(clause.value);"," case 'number':"," logger.debug('test Number equality');"," "," return _.toNumber(_value) === _.toNumber(clause.value);"," case 'boolean':"," logger.debug('test Boolean equality');"," "," return (_.isBoolean(_value) && _.isBoolean(clause.value) && (_value === clause.value));"," case 'array':"," logger.debug('test Boolean equality');"," "," // Check type"," if (_.isArray(_value) && _.isArray(clause.value)) {"," // Check length"," if (_value.length === clause.value.length) {"," // Check items"," for (let i = 0; i < _value.length; i++) {"," if (clause.value.indexOf(_value[i]) === -1) {"," return false;"," }"," }"," "," return true;"," } else {"," return false;"," }"," } else {"," return false;"," }"," case 'function':"," logger.debug('test Function equality');"," "," throw Error(\"Bad value type in query\");"," default:"," throw Error(\"Bad value type in query\");"," }"," });","};","","var _testObjectClause = function(clause, doc, key) {"," logger.debug('Called _testObjectClause');"," "," var val = null;"," "," if (key.length > 0) {"," var path = key.pop();"," val = doc[path];"," "," logger.debug('check on field ' + path);"," "," // TODO add _.isNumber(val) and treat it as an array"," if (val) {"," logger.log(val);"," logger.debug('going deeper');"," "," return _testObjectClause(clause, val, key);"," }"," } else {"," logger.debug('lowest path: ' + path);"," "," return _testClause(clause, doc);"," }","};","","var _testLogicalClause = function(clause, doc, key) {"," var matches = null;"," "," for (let i = 0; i < clause.value.length; i++) {"," let _matcher = new SelectorMatcher({ clauses: [clause.value[i]] });"," "," switch (key) {"," case 'and':"," // True unless it has one that do not match"," if (_.isNil(matches)) matches = true;"," "," if (!_matcher.test(doc)) {"," return false;"," }"," "," break;"," case 'or':"," // False unless it has one match at least"," if (_.isNil(matches)) matches = false;"," "," if (_matcher.test(doc)) {"," return true;"," }"," "," break;"," }"," }"," "," return matches || false;","};","","var _testOperatorClause = function(clause, value) {"," logger.debug('Called _testOperatorClause');"," "," for (var key in clause.value) {"," if (!_testOperatorConstraint(key, clause.value[key], clause.value, value, clause)) {"," return false;"," }"," }"," "," return true;","};","","var _testOperatorConstraint = function (key, operatorValue, clauseValue, docVal, clause) {"," logger.debug('Called _testOperatorConstraint');"," "," switch (key) {"," // Comparison Query Operators"," case '$gt':"," logger.debug('testing operator $gt');",""," return SelectorMatcher.cmp(docVal, operatorValue) > 0;"," case '$lt':"," logger.debug('testing operator $lt');"," "," return SelectorMatcher.cmp(docVal, operatorValue) < 0;"," case '$gte':"," logger.debug('testing operator $gte');"," "," return SelectorMatcher.cmp(docVal, operatorValue) >= 0;"," case '$lte':"," logger.debug('testing operator $lte');"," "," return SelectorMatcher.cmp(docVal, operatorValue) <= 0;"," case '$eq':"," logger.debug('testing operator $eq');"," "," return SelectorMatcher.equal(docVal, operatorValue);"," case '$ne':"," logger.debug('testing operator $ne');"," "," return !SelectorMatcher.equal(docVal, operatorValue);"," case '$in':"," logger.debug('testing operator $in');"," "," return SelectorMatcher.in(docVal, operatorValue);"," case '$nin':"," logger.debug('testing operator $nin');"," "," return !SelectorMatcher.in(docVal, operatorValue);"," // Logical Query Operators"," case '$not':"," logger.debug('testing operator $not');"," "," // $or, $and, $nor are in the 'operator' kind treatment"," /*"," var _clause = {"," kind: 'plain',"," key: clause.key,"," value: operatorValue,"," type: "," };"," var _parent = clause.value;"," var _key = "," return !(_testClause(_clause, docVal));"," */"," // TODO implement"," throw Error(\"$not unimplemented\");"," // Element Query Operators"," case '$exists':"," logger.debug('testing operator $exists');"," "," return operatorValue ? !_.isUndefined(docVal) : _.isUndefined(docVal);"," case '$type':"," logger.debug('testing operator $type');"," "," // $type: 1 is true for an array if any element in the array is of"," // type 1. but an array doesn't have type array unless it contains"," // an array.."," // var Selector._f._type(docVal);"," // return Selector._f._type(docVal).type === operatorValue;"," throw Error(\"$type unimplemented\");"," // Evaluation Query Operators"," case '$mod':"," logger.debug('testing operator $mod');"," "," return docVal % operatorValue[0] === operatorValue[1];"," case '$options':"," logger.debug('testing operator $options (ignored)');"," "," // Ignore, as it is to the RegExp"," return true;"," case '$regex':"," logger.debug('testing operator $regex');"," "," var _opt = null;"," if (_.hasIn(clauseValue, '$options')) {"," _opt = clauseValue['$options'];"," "," if (/[xs]/.test(_opt)) {"," //g, i, m, x, s"," // TODO mongo uses PCRE and supports some additional flags: 'x' and"," // 's'. javascript doesn't support them. so this is a divergence"," // between our behavior and mongo's behavior. ideally we would"," // implement x and s by transforming the regexp, but not today.."," "," throw Error(\"Only the i, m, and g regexp options are supported\");"," }"," }"," "," // Review flags -> g & m"," var regexp = operatorValue;"," "," if (_.isRegExp(regexp) && _.isNil(_opt)) {"," return regexp.test(docVal);"," } else if (_.isNil(_opt)) {"," regexp = new RegExp(regexp);"," } else if (_.isRegExp(regexp)) {"," regexp = new RegExp(regexp.source, _opt);"," } else {"," regexp = new RegExp(regexp, _opt);"," }"," "," return regexp.test(docVal);"," case '$text':"," logger.debug('testing operator $text');"," "," // TODO implement"," throw Error(\"$text unimplemented\");"," case '$where':"," logger.debug('testing operator $where');"," "," // TODO implement"," throw Error(\"$where unimplemented\");"," // Geospatial Query Operators"," // TODO -> in operator kind"," // Query Operator Array"," case '$all':"," logger.debug('testing operator $all');"," "," return SelectorMatcher.all(operatorValue, docVal) > 0;"," case '$elemMatch':"," logger.debug('testing operator $elemMatch');"," "," // TODO implement"," throw Error(\"$elemMatch unimplemented\");"," case '$size':"," logger.debug('testing operator $size');"," "," return _.isArray(docVal) && docVal.length === operatorValue;"," // Bitwise Query Operators"," // TODO"," default:"," logger.debug('testing operator ' + key);"," "," throw Error(\"Unrecognized key in selector: \" + key);"," }","};","","var BsonTypes = {","\t_types: [","\t\t{ alias: 'minKey', number: -1, order: 1, isType: null },","\t\t{ alias: 'null', number: 10, order: 2, isType: null },","\t\t{ alias: 'int', number: 16, order: 3, isType: _.isInteger },","\t\t{ alias: 'long', number: 18, order: 3, isType: _.isNumber },","\t\t{ alias: 'double', number: 1, order: 3, isType: _.isNumber },","\t\t{ alias: 'number', number: null, order: 3, isType: _.isNumber },","\t\t{ alias: 'string', number: 2, order: 4, isType: _.isString },","\t\t{ alias: 'symbol', number: 14, order: 4, isType: _.isSymbol },","\t\t{ alias: 'object', number: 3, order: 5, isType: _.isPlainObject },","\t\t{ alias: 'array', number: 4, order: 6, isType: _.isArray },","\t\t{ alias: 'binData', number: 5, order: 7, isType: null },","\t\t{ alias: 'objectId', number: 7, order: 8, isTypefnc: null },","\t\t{ alias: 'bool', number: 8, order: 9, isType: _.isBoolean },","\t\t{ alias: 'date', number: 9, order: 10, isTypefnc: _.isDate }, // format","\t\t{ alias: 'timestamp', number: 17, order: 11, isType: _.isDate }, // format","\t\t{ alias: 'regex', number: 11, order: 12, isType: _.isRegExp },","\t\t{ alias: 'maxKey', number: 127, order: 13, isType: null }","\t\t","// \t\tundefined 6","// \t\tdbPointer","// \t\tjavascript","// \t\tjavascriptWithScope","// \t\tfunction","\t],","\t","\tgetByAlias: function(alias) {","\t\tfor (var i = 0; i < this._types.length; i++) {","\t\t\tif (this._types[i].alias === alias) return this._types[i];","\t\t}","\t},","\tgetByValue: function(val) {","\t if (_.isNumber(val)) return this.getByAlias(\"double\");"," "," if (_.isString(val)) return this.getByAlias(\"string\");"," "," if (_.isBoolean(val)) return this.getByAlias(\"bool\");"," "," if (_.isArray(val)) return this.getByAlias(\"array\");"," "," if (_.isNull(val)) return this.getByAlias(\"null\");"," "," if (_.isRegExp(val)) return this.getByAlias(\"regex\");"," "," if (_.isPlainObject(val)) return this.getByAlias(\"object\");"," "," if (_.isSymbol(val)) return this.getByAlias(\"symbol\");"," "," throw Error(\"Unaccepted BSON type\");","\t}","};","","module.exports = SelectorMatcher;"]}]]},"ace":{"folds":[],"scrolltop":10248,"scrollleft":0,"selection":{"start":{"row":708,"column":33},"end":{"row":708,"column":33},"isBackwards":false},"options":{"guessTabSize":true,"useWrapMode":false,"wrapToView":true},"firstLineState":{"row":682,"state":"start","mode":"ace/mode/javascript"}},"timestamp":1470298251004,"hash":"b5820aa7dabd74764cc7785495e93834742ca6fa"}