UNPKG

mongo-portable

Version:

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

774 lines 30.6 kB
"use strict"; var __values = (this && this.__values) || function (o) { var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; if (m) return m.call(o); return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; }; Object.defineProperty(exports, "__esModule", { value: true }); var jsw_logger_1 = require("jsw-logger"); var _ = require("lodash"); var SelectorMatcher = /** @class */ (function () { function SelectorMatcher(selector) { this.clauses = selector.clauses; this.logger = jsw_logger_1.JSWLogger.instance; } SelectorMatcher.prototype.test = function (document) { var e_1, _a; this.logger.debug("Called SelectorMatcher->test"); var _match = false; if (_.isNil(document)) { this.logger.debug("document -> null"); this.logger.throw("Parameter \"document\" required"); } this.logger.debug("document -> not null"); try { for (var _b = __values(this.clauses), _c = _b.next(); !_c.done; _c = _b.next()) { var clause = _c.value; if (clause.kind === "function") { this.logger.debug("clause -> function"); _match = clause.value.call(null, document); } else if (clause.kind === "plain") { this.logger.debug("clause -> plain on field \"" + clause.key + "\" and value = " + JSON.stringify(clause.value)); _match = testClause(clause, document[clause.key]); this.logger.debug("clause result -> " + _match); } else if (clause.kind === "object") { this.logger.debug("clause -> object on field \"" + clause.key.join(".") + "\" and value = " + JSON.stringify(clause.value)); _match = testObjectClause(clause, document, _.clone(clause.key).reverse()); this.logger.debug("clause result -> " + _match); } else if (clause.kind === "operator") { this.logger.debug("clause -> operator \"" + clause.key + "\""); _match = testLogicalClause(clause, document, clause.key); this.logger.debug("clause result -> " + _match); } // If any test case fails, the document will not match if (_match === false /* || <string>_match === "false"*/) { this.logger.debug("the document do not matches"); return false; } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } // Everything matches this.logger.debug("the document matches"); return true; }; SelectorMatcher.all = function (arr, value) { var e_2, _a; // $all is only meaningful on arrays if (!(arr 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++; } }); try { for (var arr_1 = __values(arr), arr_1_1 = arr_1.next(); !arr_1_1.done; arr_1_1 = arr_1.next()) { var item = arr_1_1.value; var hash = JSON.stringify(item); if (parts[hash]) { delete parts[hash]; remaining--; if (0 === remaining) { return true; } } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (arr_1_1 && !arr_1_1.done && (_a = arr_1.return)) _a.call(arr_1); } finally { if (e_2) throw e_2.error; } } return false; }; SelectorMatcher.in = function (arr, value) { var e_3, _a, e_4, _b; if (!_.isObject(arr)) { try { // optimization: use scalar equality (fast) for (var value_1 = __values(value), value_1_1 = value_1.next(); !value_1_1.done; value_1_1 = value_1.next()) { var item = value_1_1.value; if (arr === item) { return true; } } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (value_1_1 && !value_1_1.done && (_a = value_1.return)) _a.call(value_1); } finally { if (e_3) throw e_3.error; } } return false; } else { try { // nope, have to use deep equality for (var value_2 = __values(value), value_2_1 = value_2.next(); !value_2_1.done; value_2_1 = value_2.next()) { var item = value_2_1.value; if (SelectorMatcher.equal(arr, item)) { return true; } } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (value_2_1 && !value_2_1.done && (_b = value_2.return)) _b.call(value_2); } finally { if (e_4) throw e_4.error; } } return false; } }; // deep equality test: use for literal document and array matches SelectorMatcher.equal = function (arr, qval) { var match = function (valA, valB) { var e_5, _a, e_6, _b; // scalars if (_.isNumber(valA) || _.isString(valA) || _.isBoolean(valA) || _.isNil(valA)) { return valA === valB; } if (_.isFunction(valA)) { return false; } // Not allowed yet // OK, typeof valA === "object" if (!_.isObject(valB)) { return false; } // arrays if (_.isArray(valA)) { if (!_.isArray(valB)) { return false; } if (valA.length !== valB.length) { return false; } for (var i = 0; i < valA.length; i++) { if (!match(valA[i], valB[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 bKeys = []; try { for (var _c = __values(Object.keys(valB)), _d = _c.next(); !_d.done; _d = _c.next()) { var item = _d.value; bKeys.push(valB[item]); } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_5) throw e_5.error; } } var index = 0; try { for (var _e = __values(Object.keys(valA)), _f = _e.next(); !_f.done; _f = _e.next()) { var item = _f.value; if (index >= bKeys.length) { return false; } if (!match(valA[item], bKeys[index])) { return false; } index++; } } catch (e_6_1) { e_6 = { error: e_6_1 }; } finally { try { if (_f && !_f.done && (_b = _e.return)) _b.call(_e); } finally { if (e_6) throw e_6.error; } } if (index !== bKeys.length) { return false; } return true; }; return match(arr, qval); }; // 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. SelectorMatcher.matches = function (value, func) { var e_7, _a; if (_.isArray(value)) { try { for (var value_3 = __values(value), value_3_1 = value_3.next(); !value_3_1.done; value_3_1 = value_3.next()) { var item = value_3_1.value; if (func(item)) { return true; } } } catch (e_7_1) { e_7 = { error: e_7_1 }; } finally { try { if (value_3_1 && !value_3_1.done && (_a = value_3.return)) _a.call(value_3); } finally { if (e_7) throw e_7.error; } } return false; } return func(value); }; // 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]}}. SelectorMatcher.matches_plus = function (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); }; // 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 v is less, positive if valueB is less, or 0 if equal SelectorMatcher.cmp = function (valueA, valueB) { if (_.isUndefined(valueA)) { return valueB === undefined ? 0 : -1; } if (_.isUndefined(valueB)) { return 1; } var aType = BSON_TYPES.getByValue(valueA); var bType = BSON_TYPES.getByValue(valueB); 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(valueA)) { return 1; } if (_.isSymbol(valueB)) { return -1; } // TODO Integer, Date and Timestamp } if (_.isNumber(valueA)) { return valueA - valueB; } if (_.isString(valueA)) { return valueA < valueB ? -1 : (valueA === valueA ? 0 : 1); } if (_.isBoolean(valueA)) { if (valueA) { return valueB ? 0 : 1; } return valueB ? -1 : 0; } if (_.isArray(valueA)) { for (var i = 0;; i++) { if (i === valueA.length) { return (i === valueB.length) ? 0 : -1; } if (i === valueB.length) { return 1; } if (valueA.length !== valueB.length) { return valueA.length - valueB.length; } var result = SelectorMatcher.cmp(valueA[i], valueB[i]); if (result !== 0) { return result; } } } if (_.isNull(valueA)) { return 0; } if (_.isRegExp(valueA)) { throw Error("Sorting not supported on regular expression"); } // TODO // if (_.isFunction(valueA)) return {type: 13, order: 100, fnc: _.isFunction}; if (_.isPlainObject(valueA)) { var toArray = function (obj) { var e_8, _a; var ret = []; try { for (var _b = __values(Object.keys(obj)), _c = _b.next(); !_c.done; _c = _b.next()) { var key = _c.value; ret.push(key); ret.push(obj[key]); } } catch (e_8_1) { e_8 = { error: e_8_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_8) throw e_8.error; } } return ret; }; return SelectorMatcher.cmp(toArray(valueA), toArray(valueB)); } // 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(valueA)) { // 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 // } }; return SelectorMatcher; }()); exports.SelectorMatcher = SelectorMatcher; var testClause = function (clause, val) { jsw_logger_1.JSWLogger.instance.debug("Called testClause"); // var _val = clause.value; // if RegExp || $ -> Operator return SelectorMatcher.matches_plus(val, function (value) { var e_9, _a; // TODO object ids, dates, timestamps? switch (clause.type) { case "null": jsw_logger_1.JSWLogger.instance.debug("test Null equality"); // http://www.mongodb.org/display/DOCS/Querying+and+nulls if (_.isNil(value)) { return true; } else { return false; } case "regexp": jsw_logger_1.JSWLogger.instance.debug("test RegExp equality"); return testOperatorClause(clause, value); case "literal_object": jsw_logger_1.JSWLogger.instance.debug("test Literal Object equality"); return SelectorMatcher.equal(value, clause.value); case "operator_object": jsw_logger_1.JSWLogger.instance.debug("test Operator Object equality"); return testOperatorClause(clause, value); case "string": jsw_logger_1.JSWLogger.instance.debug("test String equality"); return _.toString(value) === _.toString(clause.value); case "number": jsw_logger_1.JSWLogger.instance.debug("test Number equality"); return _.toNumber(value) === _.toNumber(clause.value); case "boolean": jsw_logger_1.JSWLogger.instance.debug("test Boolean equality"); return (_.isBoolean(value) && _.isBoolean(clause.value) && (value === clause.value)); case "array": jsw_logger_1.JSWLogger.instance.debug("test Boolean equality"); // Check type if (_.isArray(value) && _.isArray(clause.value)) { // Check length if (value.length === clause.value.length) { try { // Check items for (var value_4 = __values(value), value_4_1 = value_4.next(); !value_4_1.done; value_4_1 = value_4.next()) { var item = value_4_1.value; if (clause.value.indexOf(item) === -1) { return false; } } } catch (e_9_1) { e_9 = { error: e_9_1 }; } finally { try { if (value_4_1 && !value_4_1.done && (_a = value_4.return)) _a.call(value_4); } finally { if (e_9) throw e_9.error; } } return true; } else { return false; } } else { return false; } case "function": jsw_logger_1.JSWLogger.instance.debug("test Function equality"); jsw_logger_1.JSWLogger.instance.throw("Bad value type in query"); break; default: jsw_logger_1.JSWLogger.instance.throw("Bad value type in query"); } }); }; var testObjectClause = function (clause, doc, key) { jsw_logger_1.JSWLogger.instance.debug("Called testObjectClause"); var val = null; var path = null; if (key.length > 0) { path = key.pop(); val = doc[path]; jsw_logger_1.JSWLogger.instance.debug("check on field " + path); // TODO add _.isNumber(val) and treat it as an array if (val) { jsw_logger_1.JSWLogger.instance.log(val); jsw_logger_1.JSWLogger.instance.debug("going deeper"); return testObjectClause(clause, val, key); } } else { jsw_logger_1.JSWLogger.instance.debug("lowest path: " + path); return testClause(clause, doc); } }; var testLogicalClause = function (clause, doc, key) { var e_10, _a; var matches = null; try { for (var _b = __values(clause.value), _c = _b.next(); !_c.done; _c = _b.next()) { var clauseValue = _c.value; var matcher = new SelectorMatcher({ clauses: [clauseValue] }); 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; } } } catch (e_10_1) { e_10 = { error: e_10_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_10) throw e_10.error; } } return matches || false; }; var testOperatorClause = function (clause, value) { var e_11, _a; jsw_logger_1.JSWLogger.instance.debug("Called testOperatorClause"); try { for (var _b = __values(Object.keys(clause.value)), _c = _b.next(); !_c.done; _c = _b.next()) { var key = _c.value; if (!testOperatorConstraint(key, clause.value[key], clause.value, value, clause)) { return false; } } } catch (e_11_1) { e_11 = { error: e_11_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_11) throw e_11.error; } } return true; }; var testOperatorConstraint = function (key, operatorValue, clauseValue, docVal, clause) { jsw_logger_1.JSWLogger.instance.debug("Called testOperatorConstraint"); switch (key) { // Comparison Query Operators case "$gt": jsw_logger_1.JSWLogger.instance.debug("testing operator $gt"); return SelectorMatcher.cmp(docVal, operatorValue) > 0; case "$lt": jsw_logger_1.JSWLogger.instance.debug("testing operator $lt"); return SelectorMatcher.cmp(docVal, operatorValue) < 0; case "$gte": jsw_logger_1.JSWLogger.instance.debug("testing operator $gte"); return SelectorMatcher.cmp(docVal, operatorValue) >= 0; case "$lte": jsw_logger_1.JSWLogger.instance.debug("testing operator $lte"); return SelectorMatcher.cmp(docVal, operatorValue) <= 0; case "$eq": jsw_logger_1.JSWLogger.instance.debug("testing operator $eq"); return SelectorMatcher.equal(docVal, operatorValue); case "$ne": jsw_logger_1.JSWLogger.instance.debug("testing operator $ne"); return !SelectorMatcher.equal(docVal, operatorValue); case "$in": jsw_logger_1.JSWLogger.instance.debug("testing operator $in"); return SelectorMatcher.in(docVal, operatorValue); case "$nin": jsw_logger_1.JSWLogger.instance.debug("testing operator $nin"); return !SelectorMatcher.in(docVal, operatorValue); // Logical Query Operators case "$not": jsw_logger_1.JSWLogger.instance.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 jsw_logger_1.JSWLogger.instance.throw("$not unimplemented"); break; // Element Query Operators case "$exists": jsw_logger_1.JSWLogger.instance.debug("testing operator $exists"); return operatorValue ? !_.isUndefined(docVal) : _.isUndefined(docVal); case "$type": jsw_logger_1.JSWLogger.instance.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; jsw_logger_1.JSWLogger.instance.throw("$type unimplemented"); break; // Evaluation Query Operators case "$mod": jsw_logger_1.JSWLogger.instance.debug("testing operator $mod"); return docVal % operatorValue[0] === operatorValue[1]; case "$options": jsw_logger_1.JSWLogger.instance.debug("testing operator $options (ignored)"); // Ignore, as it is to the RegExp return true; case "$regex": jsw_logger_1.JSWLogger.instance.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.. jsw_logger_1.JSWLogger.instance.throw("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": jsw_logger_1.JSWLogger.instance.debug("testing operator $text"); // TODO implement throw Error("$text unimplemented"); case "$where": jsw_logger_1.JSWLogger.instance.debug("testing operator $where"); // TODO implement throw Error("$where unimplemented"); // Geospatial Query Operators // TODO -> in operator kind // Query Operator Array case "$all": jsw_logger_1.JSWLogger.instance.debug("testing operator $all"); // return SelectorMatcher.all(operatorValue, docVal) > 0; return SelectorMatcher.all(operatorValue, docVal); case "$elemMatch": jsw_logger_1.JSWLogger.instance.debug("testing operator $elemMatch"); // TODO implement throw Error("$elemMatch unimplemented"); case "$size": jsw_logger_1.JSWLogger.instance.debug("testing operator $size"); return _.isArray(docVal) && docVal.length === operatorValue; // Bitwise Query Operators // TODO default: jsw_logger_1.JSWLogger.instance.debug("testing operator " + key); jsw_logger_1.JSWLogger.instance.throw("Unrecognized key in selector: " + key); } }; var BSON_TYPES = { types: [ { alias: "minKey", number: -1, order: 1, isType: null }, { alias: "null", number: 10, order: 2, isType: null }, { alias: "int", number: 16, order: 3, isType: _.isInteger }, { alias: "long", number: 18, order: 3, isType: _.isNumber }, { alias: "double", number: 1, order: 3, isType: _.isNumber }, { alias: "number", number: null, order: 3, isType: _.isNumber }, { alias: "string", number: 2, order: 4, isType: _.isString }, { alias: "symbol", number: 14, order: 4, isType: _.isSymbol }, { alias: "object", number: 3, order: 5, isType: _.isPlainObject }, { alias: "array", number: 4, order: 6, isType: _.isArray }, { alias: "binData", number: 5, order: 7, isType: null }, { alias: "objectId", number: 7, order: 8, isTypefnc: null }, { alias: "bool", number: 8, order: 9, isType: _.isBoolean }, { alias: "date", number: 9, order: 10, isTypefnc: _.isDate }, { alias: "timestamp", number: 17, order: 11, isType: _.isDate }, { alias: "regex", number: 11, order: 12, isType: _.isRegExp }, { alias: "maxKey", number: 127, order: 13, isType: null } // undefined 6 // dbPointer // javascript // javascriptWithScope // function ], getByAlias: function (alias) { var e_12, _a; try { for (var _b = __values(this.types), _c = _b.next(); !_c.done; _c = _b.next()) { var type = _c.value; if (type.alias === alias) { return type; } } } catch (e_12_1) { e_12 = { error: e_12_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_12) throw e_12.error; } } }, getByValue: function (val) { 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"); } jsw_logger_1.JSWLogger.instance.throw("Unaccepted BSON type"); } }; //# sourceMappingURL=SelectorMatcher.js.map