From 3473b02422ae622b7657bb2af377d46649e8c4f4 Mon Sep 17 00:00:00 2001 From: Artem Artemyev Date: Sat, 2 Jan 2016 03:09:34 +0500 Subject: [PATCH] experemental indexes moved to a separate brunch --- lib/experemental/BPlusTree.js | 605 ------------------------- lib/experemental/IndexMatcher.js | 348 -------------- test/experemental/BPlusTree.test.js | 344 -------------- test/experemental/Index.test.js | 525 --------------------- test/experemental/IndexManager.test.js | 282 ------------ test/experemental/IndexMatcher.test.js | 135 ------ test/experemental/lib/bpvalidator.js | 196 -------- 7 files changed, 2435 deletions(-) delete mode 100644 lib/experemental/BPlusTree.js delete mode 100644 lib/experemental/IndexMatcher.js delete mode 100644 test/experemental/BPlusTree.test.js delete mode 100644 test/experemental/Index.test.js delete mode 100644 test/experemental/IndexManager.test.js delete mode 100644 test/experemental/IndexMatcher.test.js delete mode 100644 test/experemental/lib/bpvalidator.js diff --git a/lib/experemental/BPlusTree.js b/lib/experemental/BPlusTree.js deleted file mode 100644 index d7486fe..0000000 --- a/lib/experemental/BPlusTree.js +++ /dev/null @@ -1,605 +0,0 @@ -/** - * Originally based on: https://github.com/internalfx/bplus-index - * Thanks, @internalfx - */ - -// NOT WORKING YET -const Utils = {}; -const uniqArray = {}; - -// Internal utils -function _makeRequestNullSearcheble(val) { - const result = []; - for (const v of val) { - if (v === undefined || v === null) { - result.push(undefined); - result.push(null); - } else { - result.push(v); - } - } - return result; -} - -function _getUniqueKeysArray(keyOrKeys, comparator) { - let result; - if (Utils.isArray(keyOrKeys)) { - const flatArray = Utils.flattenArray(keyOrKeys, 1); - result = uniqArray(flatArray, comparator, false); - if (!result.length) { - result.push([]); - } - } else { - result = [keyOrKeys]; - } - return result; -} - -function _stepForward(index, leaf) { - if (index + 1 < leaf.keys.length) { - return {index: (index + 1), leaf: leaf}; - } else if (leaf.next) { - return {index: 0, leaf: leaf.next}; - } else { - return null; - } -} - - - -export class Leaf { - constructor(config = {}) { - this.id = Utils.uniqueId(); - this.parent = null; - this.prev = null; - this.next = null; - this.children = []; - this.keys = []; - this.values = []; - this.comparator = config.comparator; - this.unique = config.unique; - } - - insertData(key, val) { - var location = Utils.binarySearch(this.keys, key, this.comparator); - if (location.found) { - if (this.unique) { - throw new Error(`insertData(...): key ${key} already exists in unique index`); - } - var dataLocation = Utils.binarySearch(this.values[location.index], val, this.comparator); - Utils.insertAt(this.values[location.index], val, dataLocation.index); - } else { - Utils.insertAt(this.keys, key, location.index); - Utils.insertAt(this.values, [val], location.index); - } - } - - deleteData(key, val) { - var keyLocation = Utils.binarySearch(this.keys, key, this.comparator); - if (keyLocation.found) { - var dataLocation = Utils.binarySearch( - this.values[keyLocation.index], val, this.comparator - ); - if (dataLocation.found) { - Utils.removeAt(this.values[keyLocation.index], dataLocation.index); - if (this.values[keyLocation.index].length === 0) { // if this was the last value at this key, delete the key too. - Utils.removeAt(this.keys, keyLocation.index); - Utils.removeAt(this.values, keyLocation.index); - } - } - } - - return keyLocation; - } - - get(key) { - var location = Utils.binarySearch(this.keys, key, this.comparator); - if (location.found) { - return this.values[location.index]; - } else { - return []; - } - } - - size() { - return this.keys.length; - } - - hasChildren() { - return this.children.length > 0; - } - - setParentOnChildren() { - for (const child of this.children) { - child.parent = this; - } - } - - replaceKey(key, newKey) { - var loc = Utils.binarySearch(this.keys, key, this.comparator); - - if (loc.found) { - if (this.debug) { - console.log(`replace ${key} with ${newKey} in leaf ${this.id}`); - } - Utils.replaceAt(this.keys, newKey, loc.index); - } - - if (this.parent) { - this.parent.replaceKey(key, newKey); - } - } - - updateKeys() { - if (this.hasChildren()) { - var keys = []; - for (let i = 1; i < this.children.length; i++) { - const child = this.children[i]; - keys.push(Utils.detectKey(child)); - } - if (keys.length > 0) { - this.keys = keys; - } - } - } -} - -/** - * BPlusTree implementation, aimed to be an index - * of MarsDB. - * May have custom comparator index and trow error - * on duplicates if unique key presented. - */ -export class BPlusTree { - constructor(config = {}) { - this.bf = config.branchingFactor || 50; - this.debug = config.debug || false; - this.comparator = config.comparator; - this.unique = config.unique || false; - this.root = new Leaf({comparator: this.comparator, unique: this.unique}); - } - - dumpTree(leaf) { - leaf = leaf || this.root; - var struct = { - id: leaf.id, - keys: leaf.keys, - values: leaf.values, - prev: leaf.prev ? leaf.prev.id : null, - next: leaf.next ? leaf.next.id : null, - children: [], - }; - - for (const child of leaf.children) { - struct.children.push(this.dumpTree(child)); - } - - return struct; - } - - search(keyOrKeys) { - const uniqKeys = _getUniqueKeysArray(keyOrKeys, this.comparator); - const nullSearcheble = _makeRequestNullSearcheble(uniqKeys); - - const r = Utils.flattenArray(nullSearcheble.map(k => { - const res = this._findLeaf(k).get(k); - return res; - }), 1); - return r; - } - - getAll(options = {}) { - const startLeaf = this._findLeaf(Utils.detectKey(this.root)); - var currLoc = {index: 0, leaf: startLeaf}; - var result = []; - - while (currLoc !== null) { - if (currLoc.leaf.keys.length > 0) { - const key = currLoc.leaf.keys[currLoc.index]; - const values = currLoc.leaf.values[currLoc.index]; - - values.forEach(x => { - result.push({ - key: key, - value: x, - }); - }); - } - currLoc = _stepForward(currLoc.index, currLoc.leaf); - } - - return options.matcher - ? result.filter(options.matcher) - : result; - } - - getNumberOfKeys() { - return new Set(this.getAll().map(x => x.key)).size; - } - - getBetweenBounds(query = {}) { - var result = []; - var options = { - lowerInclusive: false, - upperInclusive: false, - }; - var lowerBound = -Infinity; - var upperBound = +Infinity; - - if (query.hasOwnProperty('$gt') || query.hasOwnProperty('$gte')) { - lowerBound = (this.comparator(query.$gte, query.$gt)) === -1 ? query.$gt : query.$gte; - lowerBound = Utils.isNumber(lowerBound) ? lowerBound : -Infinity; - options.lowerInclusive = query.$gte === lowerBound; - } - - if (query.hasOwnProperty('$lt') || query.hasOwnProperty('$lte')) { - upperBound = (this.comparator(query.$lte, query.$lt)) === -1 ? query.$lt : query.$lte; - upperBound = Utils.isNumber(upperBound) ? upperBound : +Infinity; - options.upperInclusive = query.$lte === upperBound; - } - - var startLeaf = this._findLeaf(lowerBound); - var loc = Utils.binarySearch(startLeaf.keys, lowerBound, this.comparator); - var currLoc = {index: loc.index, leaf: startLeaf}; - - if (loc.index >= startLeaf.keys.length) { - currLoc = _stepForward(currLoc.index, currLoc.leaf); - } - - if (loc.found && options.lowerInclusive === false) { - currLoc = _stepForward(currLoc.index, currLoc.leaf); - } - - while (currLoc && currLoc.leaf.keys[currLoc.index] < upperBound) { - result = result.concat(currLoc.leaf.values[currLoc.index]); - currLoc = _stepForward(currLoc.index, currLoc.leaf); - } - - if ( - currLoc && - currLoc.leaf.keys[currLoc.index] <= upperBound && - options.upperInclusive === true - ) { - result = result.concat(currLoc.leaf.values[currLoc.index]); - } - - return result; - } - - insert(keyOrKeys, val) { - if (this.debug) { - console.log(`INJECT ${keyOrKeys} = ${val}`); - } - - const uniqKeys = _getUniqueKeysArray(keyOrKeys, this.comparator); - let failingIndex = null; - try { - uniqKeys.forEach((key, i) => { - failingIndex = i; - var leaf = this._findLeaf(key); - leaf.insertData(key, val); - this._splitLeaf(leaf); - }); - } catch (e) { - this.delete(uniqKeys.slice(0, failingIndex), val); - throw e; - } - } - - delete(keyOrKeys, val) { - if (this.debug) { - console.log(`EJECT ${keyOrKeys} = ${val}`); - } - - const uniqKeys = _getUniqueKeysArray(keyOrKeys, this.comparator); - let failingIndex = null; - try { - uniqKeys.forEach((key, i) => { - failingIndex = i; - var leaf = this._findLeaf(key); - var loc = leaf.deleteData(key, val); - if (loc.found && loc.index === 0 && leaf.parent) { - if (leaf.keys.length > 0 && key !== leaf.keys[0]) { - if (this.debug) { - console.log(`REPLACE LEAF KEYS ${key} -> ${leaf.keys[0]}`); - } - leaf.parent.replaceKey(key, leaf.keys[0]); - } - } - this._mergeLeaf(leaf); - }); - } catch (e) { - this.insert(uniqKeys.slice(0, failingIndex), val); - throw e; - } - } - - _minKeys() { - return Math.floor(this.bf / 2); - } - - _maxKeys() { - return this.bf - 1; - } - - // Private Methods - _findLeaf(key, leaf) { - leaf = leaf || this.root; - if (leaf.children.length === 0) { - return leaf; - } else { - var loc = Utils.binarySearch(leaf.keys, key, this.comparator); - var index = loc.found ? loc.index + 1 : loc.index; - return this._findLeaf(key, leaf.children[index]); - } - } - - _splitLeaf(leaf) { - if (leaf.size() > this._maxKeys()) { - if (this.debug) { - console.log(`BEFORE SPLIT LEAF ${leaf.id}`); - console.log(JSON.stringify(this.dumpTree(), null, 2)); - } - var splitPoint = Math.floor(leaf.size() / 2); - var {parent, prev, next, children, keys, values} = leaf; - - // TODO: Optimize: we could re-use one of the leaves - var leftLeaf = new Leaf({comparator: this.comparator, unique: this.unique}); - var rightLeaf = new Leaf({comparator: this.comparator, unique: this.unique}); - - if (prev != null) { - prev.next = leftLeaf; - } - if (next != null) { - next.prev = rightLeaf; - } - - leftLeaf.parent = parent; - leftLeaf.children = children.slice(0, splitPoint); - leftLeaf.keys = keys.slice(0, splitPoint); - leftLeaf.values = values.slice(0, splitPoint); - - rightLeaf.parent = parent; - rightLeaf.children = children.slice(splitPoint); - rightLeaf.keys = keys.slice(splitPoint); - rightLeaf.values = values.slice(splitPoint); - - // In a B+tree only leaves contain data, everything else is a node - - if (leaf === this.root) { // If we are splitting the root - if (leaf.values.length > 0) { // If the root is also a leaf (has data) - parent = this.root = new Leaf({comparator: this.comparator, unique: this.unique}); - parent.children = [leftLeaf, rightLeaf]; - parent.keys = [keys[splitPoint]]; - leftLeaf.parent = parent; - rightLeaf.parent = parent; - leftLeaf.next = rightLeaf; - rightLeaf.prev = leftLeaf; - if (this.debug) { - console.log('SPLIT ROOT LEAF'); - console.log(JSON.stringify(this.dumpTree(), null, 2)); - } - } else { // If the root is a node) - parent = this.root = new Leaf({comparator: this.comparator, unique: this.unique}); - parent.children = [leftLeaf, rightLeaf]; - parent.keys = [keys[splitPoint]]; - leftLeaf.parent = parent; - leftLeaf.children = children.slice(0, splitPoint + 1); - leftLeaf.setParentOnChildren(); - - rightLeaf.parent = parent; - rightLeaf.keys = keys.slice(splitPoint + 1); - rightLeaf.children = children.slice(splitPoint + 1); - rightLeaf.setParentOnChildren(); - if (this.debug) { - console.log('SPLIT ROOT NODE'); - console.log(JSON.stringify(this.dumpTree(), null, 2)); - } - } - - } else { // If we are not splitting root - - var childPos = parent.children.indexOf(leaf); - - if (leaf.values.length > 0) { // If we are splitting a leaf - - if (childPos !== 0) { - Utils.replaceAt(parent.keys, leftLeaf.keys[0], childPos - 1); - } - Utils.replaceAt(parent.children, leftLeaf, childPos); - Utils.insertAt(parent.keys, rightLeaf.keys[0], childPos); - Utils.insertAt(parent.children, rightLeaf, childPos + 1); - - leftLeaf.prev = leaf.prev; - leftLeaf.next = rightLeaf; - rightLeaf.prev = leftLeaf; - rightLeaf.next = leaf.next; - - if (this.debug) { - console.log('SPLIT BRANCH LEAF'); - console.log(JSON.stringify(this.dumpTree(), null, 2)); - } - this._splitLeaf(parent); - - } else { // If we are splitting a node - - rightLeaf.keys = keys.slice(splitPoint + 1); - leftLeaf.children = children.slice(0, splitPoint + 1); - leftLeaf.setParentOnChildren(); - rightLeaf.children = children.slice(splitPoint + 1); - rightLeaf.setParentOnChildren(); - Utils.replaceAt(parent.children, leftLeaf, childPos); - Utils.insertAt(parent.keys, keys[splitPoint], childPos); - Utils.insertAt(parent.children, rightLeaf, childPos + 1); - if (this.debug) { - console.log('SPLIT BRANCH NODE'); - console.log(JSON.stringify(this.dumpTree(), null, 2)); - } - this._splitLeaf(parent); - } - } - } - } - - _mergeLeaf(leaf) { - if (leaf.hasChildren()) { - if (leaf.children.length > this._minKeys()) { - if (leaf.children.length > leaf.keys.length) { - return; // Doesn't need to merge - } - } - } else { - if (leaf.size() >= this._minKeys()) { - return; // Doesn't need to merge - } - } - - if (this.debug) { - console.log(`BEFORE MERGE LEAF ${leaf.id}`); - console.log(JSON.stringify(this.dumpTree(), null, 2)); - } - - if (leaf === this.root) { // If we are merging the root - if (leaf.children.length === 1) { - leaf.children[0].parent = null; - this.root = leaf.children[0]; - this.root.updateKeys(); - - leaf.children = null; - } else { - leaf.updateKeys(); - leaf.setParentOnChildren(); - } - } else { - // Check Siblings - var childPos = leaf.parent.children.indexOf(leaf); - var leftSibling = null; - var rightSibling = null; - - if (childPos > 0) { - leftSibling = leaf.parent.children[childPos - 1]; - } - - if (childPos < (leaf.parent.children.length - 1)) { - rightSibling = leaf.parent.children[childPos + 1]; - } - - if (leaf.children.length > 0) { // If we are merging a branch - - // Try to get a key from a sibling if they are big enough - if (leftSibling && leftSibling.size() > this._minKeys()) { // Check the left sibling - - leaf.keys.unshift(leftSibling.keys.pop()); - leaf.children.unshift(leftSibling.children.pop()); - Utils.replaceAt(leaf.parent.keys, leaf.keys[0], (childPos - 1)); - leaf.updateKeys(); - leaf.setParentOnChildren(); - leftSibling.updateKeys(); - leftSibling.setParentOnChildren(); - - leaf.parent.updateKeys(); - - } else if (rightSibling && rightSibling.size() > this._minKeys()) { // Check the right sibling - - leaf.keys.push(rightSibling.keys.shift()); - leaf.children.push(rightSibling.children.shift()); - Utils.replaceAt( - leaf.parent.keys, rightSibling.keys[0], - (leaf.parent.children.indexOf(rightSibling) - 1) - ); - leaf.updateKeys(); - leaf.setParentOnChildren(); - rightSibling.updateKeys(); - rightSibling.setParentOnChildren(); - - leaf.parent.updateKeys(); - } else { - if (leftSibling) { // Copy remaining keys and children to a sibling - leftSibling.keys = leftSibling.keys.concat(leaf.keys); - leftSibling.children = leftSibling.children.concat(leaf.children); - leftSibling.updateKeys(); - leftSibling.setParentOnChildren(); - } else { - rightSibling.keys = leaf.keys.concat(rightSibling.keys); - rightSibling.children = leaf.children.concat(rightSibling.children); - rightSibling.updateKeys(); - rightSibling.setParentOnChildren(); - } - - // Empty Leaf - leaf.keys = []; - leaf.children = []; - - // Remove leaf from parent - Utils.removeAt(leaf.parent.children, childPos); - - // Update keys on parent branch - leaf.parent.updateKeys(); - } - - if (this.debug) { - console.log('MERGE BRANCH NODE'); - console.log(JSON.stringify(this.dumpTree(), null, 2)); - } - - this._mergeLeaf(leaf.parent); - - } else { // If we are merging a leaf - - // Try to get a key from a sibling if they are big enough - if (leftSibling && leftSibling.size() > this._minKeys()) { // Check the left sibling - - leaf.keys.unshift(leftSibling.keys.pop()); - leaf.values.unshift(leftSibling.values.pop()); - Utils.replaceAt(leaf.parent.keys, leaf.keys[0], (childPos - 1)); - - } else if (rightSibling && rightSibling.size() > this._minKeys()) { // Check the right sibling - - leaf.keys.push(rightSibling.keys.shift()); - leaf.values.push(rightSibling.values.shift()); - Utils.replaceAt( - leaf.parent.keys, rightSibling.keys[0], - (leaf.parent.children.indexOf(rightSibling) - 1) - ); - - } else { // There is no sibling to get a value from, remove the leaf - - if (leftSibling) { // Copy remaining keys and values to a sibling - leftSibling.keys = leftSibling.keys.concat(leaf.keys); - leftSibling.values = leftSibling.values.concat(leaf.values); - } else { - rightSibling.keys = leaf.keys.concat(rightSibling.keys); - rightSibling.values = leaf.values.concat(rightSibling.values); - } - - if (leaf.prev) { - leaf.prev.next = leaf.next; - } - if (leaf.next) { - leaf.next.prev = leaf.prev; - } - - // Empty Leaf - leaf.keys = []; - leaf.values = []; - - // Remove leaf from parent - Utils.removeAt(leaf.parent.children, childPos); - - // // Update keys on parent branch - leaf.parent.updateKeys(); - - } - - if (this.debug) { - console.log('MERGE BRANCH LEAF'); - console.log(JSON.stringify(this.dumpTree(), null, 2)); - } - - this._mergeLeaf(leaf.parent); - } - } - } -} - -export default BPlusTree; diff --git a/lib/experemental/IndexMatcher.js b/lib/experemental/IndexMatcher.js deleted file mode 100644 index 752ecc2..0000000 --- a/lib/experemental/IndexMatcher.js +++ /dev/null @@ -1,348 +0,0 @@ -import _keys from 'lodash/object/keys'; -import _each from 'lodash/collection/each'; -import invariant from 'invariant'; -import {QUERY_OPS} from './Document'; -import flattenArray from 'flattenArray'; -import isEmpty from 'isEmpty'; -import Utils from './Utils'; -import EJSON from './EJSON'; - - -// Internal utils -// Exported for testing -export function _getIntersection(include, arrs) { - const sets = arrs.filter(x => x).map(x => new Set(x)); - const result = new Set(); - - if (!sets.length) { - return []; - } - - _each(sets[0], (v) => { - let isResValue = true; - for (let i = 1; i < sets.length; i++) { - isResValue = sets[i].has(v); - isResValue = include ? isResValue : !isResValue; - if (!isResValue) { - return false; - } - } - if (isResValue) { - result.add(v); - } - }); - - return Array.from(result); -} - -export function _getIncludeIntersection(arrs) { - return _getIntersection(true, arrs); -} - -export function _getExcludeIntersection(arrs) { - return _getIntersection(false, arrs); -} - -export function _getUnion(...arrs) { - return Array.from(new Set(flattenArray(arrs))); -} - -export function _makeMatchResult(options = {}, basic = {}) { - if (options.include) { - basic.include = basic.include || []; - Array.prototype.push.apply(basic.include, options.include); - } - if (options.exclude) { - basic.exclude = basic.exclude || []; - Array.prototype.push.apply(basic.exclude, options.exclude); - } - return basic; -} - -export function _normilizeQuery(query) { - if (query === undefined || query === null) { - return {_id: {$exists: false}}; - } else if (!Utils.isObject(query)) { - return {_id: query}; - } else if (isEmpty(query)) { - return {_id: {$exists: true}}; - } - return query; -} - - -// Query parts implementation -const QUERY_LOGIC_OPS_IMPL = { - [QUERY_OPS.$and]: function(retriver, key, value) { - invariant( - Utils.isArray(value), - '$and(...): argument must be an array' - ); - - const matchPromises = value.map(q => retriver.execQuery(q)); - return Promise.all(matchPromises).then((matches) => { - const result = _getIncludeIntersection(matches); - return _makeMatchResult({include: result}); - }); - }, - - [QUERY_OPS.$or]: function(retriver, key, value) { - invariant( - Utils.isArray(value), - '$or(...): argument must be an array' - ); - - const matchPromises = value.map(q => retriver.execQuery(q)); - return Promise.all(matchPromises).then((matches) => { - const result = _getUnion(matches); - return _makeMatchResult({include: result}); - }); - }, -}; - -const QUERY_COMP_OPS_IMPL = { - [QUERY_OPS.$in]: function(index, value, basic, query) { - invariant( - Utils.isArray(value), - '$in(...): argument must be an array' - ); - _makeMatchResult({include: index.getMatching(value)}, basic); - }, - - [QUERY_OPS.$options]: function() {}, - [QUERY_OPS.$regex]: function(index, value, basic, query) { - invariant( - value instanceof RegExp || typeof value === 'string', - '$regex(...): argument must be a RegExp or string' - ); - - const regex = Utils.ensureRegExp(value, query.$options); - const matcher = (v) => v.key && regex.test(v.key); - _makeMatchResult({include: index.getAll({matcher: matcher})}, basic); - }, - - [QUERY_OPS.$exists]: function(index, value, basic, query) { - const withoutField = new Set(); - const withField = new Set(); - index.getAll({matcher: (v) => { - if (v.key === undefined) { - withoutField.add(v.value); - } else { - withField.add(v.value); - } - }}); - - value = !!(value || value === ''); - const result = value - ? {include: Array.from(withField)} - : {include: _getExcludeIntersection([withoutField, withField])}; - - _makeMatchResult(result, basic); - }, - - [QUERY_OPS.$lt]: function(index, value, basic, query) { - _makeMatchResult({include: index.getBetweenBounds(query)}, basic); - delete query.$lt; - delete query.$lte; - delete query.$gt; - delete query.$get; - }, - - [QUERY_OPS.$lte]: function(...args) { - QUERY_COMP_OPS_IMPL[QUERY_OPS.$lt](...args); - }, - - [QUERY_OPS.$gt]: function(...args) { - QUERY_COMP_OPS_IMPL[QUERY_OPS.$lt](...args); - }, - - [QUERY_OPS.$gte]: function(...args) { - QUERY_COMP_OPS_IMPL[QUERY_OPS.$lt](...args); - }, -}; - - -/** - * Class for getting list of IDs by - * given query object. For now it uses only - * indexes for making requests and build indexes - * on the fly. With this condition it have - * some restrictions in supported operators - * of MongoDB-like queries. - */ -export class IndexMatcher { - constructor(db, queryObj, sortObj = {}) { - this.db = db; - this.queryObj = queryObj; - this.sortObj = sortObj; - this._usedKeys = new Set(); - } - - /** - * Matchs the query and resolve a list of Object IDs - * @return {Promise} - */ - match() { - return this.execSortQuery().then((sortedIds) => { - sortedIds = (isEmpty(sortedIds)) ? this.db.getIndexIds(): sortedIds; - return this.execQuery(this.queryObj, sortedIds); - }).then((queryRes) => { - return queryRes; - }); - } - - /** - * Try to get sorted ids by sort object. - * If no sort object provided returns empty array. - * if more then one sort key provided rises an exception. - * First of all it try to execute a key-value query to - * get result for sorting. If query not exists it gets - * all objects in a collection. - * - * @return {Promise} - */ - execSortQuery() { - const sortKeys = _keys(this.sortObj); - - if (sortKeys.length > 1) { - throw new Error('Multiple sort keys are not supported yet'); - } else if (sortKeys.length === 0) { - return Promise.resolve(); - } else { - const sortKey = sortKeys[0]; - const keyQuery = this.queryObj[sortKey] || {}; - const sortDir = this.sortObj[sortKeys]; - const docIdsPromise = this.execLogicalQuery(sortKey, keyQuery); - - return docIdsPromise.then((matchedRes) => { - const sortedRes = (sortDir === -1) - ? matchedRes.include.reverse() - : matchedRes.include; - - this._usedKeys.add(sortKey); - return sortedRes; - }); - } - } - - /** - * Execute given query and return a list of - * ids. If result superset provided then result - * can't be larger then this superset. - * - * @param {Object} query - * @param {Object} resultSuperset - * @return {Promise} - */ - execQuery(query, resultSuperset) { - query = _normilizeQuery(query); - const queryKeys = _keys(query); - const unusedKeys = queryKeys.filter(x => !this._usedKeys.has(x)); - const matchPromises = unusedKeys.map( - k => this.execLogicalQuery(k, query[k]) - ); - - return Promise.all(matchPromises).then((matches) => { - // Get all included data - const allIncludes = matches.filter(x => x.include).map(x => x.include); - if (Array.isArray(resultSuperset)) { - allIncludes.unshift(resultSuperset); - } - var result = _getIncludeIntersection(allIncludes); - - // Exclude from result data - const allExcludes = matches.filter(x => x.exclude).map(x => x.exclude); - allExcludes.unshift(result); - return _getExcludeIntersection(allExcludes); - }); - } - - /** - * By given key execute a query. It process - * special keys for logical operators ($and, $or, $not). - * For executing regular key it uses indexes. - * If index is not exists it invokes ensure - * and then execute a query. - * - * @param {String} key - * @param {Object} value - * @return {Promise} - */ - execLogicalQuery(key, query) { - if (key[0] === '$') { - invariant( - QUERY_LOGIC_OPS_IMPL[key], - 'execLogicalQuery(...): logical operator %s is not supported', - key - ); - return QUERY_LOGIC_OPS_IMPL[key](this, key, query); - } - - return this.db.ensureIndex({fieldName: key}).then(() => { - return this.execCompareQuery(this.db.indexes[key], query); - }); - } - - /** - * Retrives ids from given index by given query. - * Supported operations are: - * $in, $lt, $lte, $gt, $gte, - * $exists, $regex - * Returns a list of ids. - * - * @param {Object} index - * @param {Object} query - * @return {Array} - */ - execCompareQuery(index, query) { - // Uncombinable - if (query && query.$exists !== undefined && index.fieldName === '_id') { - if (query.$exists) { - return _makeMatchResult({include: index.getAll()}); - } else { - return _makeMatchResult({exclude: index.getAll()}); - } - } - if (Utils.isPrimitiveType(query)) { - return _makeMatchResult({include: index.getMatching(query)}); - } - if (query instanceof RegExp) { - query = {$regex: query}; - } - - // Clone for modify in processors - query = EJSON.clone(query); - - // Combinable - const basic = _makeMatchResult(); - const keys = _keys(query); - const dollarFirstKeys = keys.filter(k => k && k[0] === '$'); - const pathKeys = keys.filter(k => k && k[0] !== '$'); - const isOnlyDollar = !pathKeys.length && dollarFirstKeys.length; - const isMixed = pathKeys.length && dollarFirstKeys.length; - - invariant( - !isMixed, - 'execCompareQuery(...): operators $... can\'t be mixed with normal fields' - ); - - if (isOnlyDollar && dollarFirstKeys.length) { - _each(dollarFirstKeys, (key) => { - invariant( - QUERY_COMP_OPS_IMPL[key], - 'execCompareQuery(...): operation %s not supported', - key - ); - if (query[key] !== undefined) { - QUERY_COMP_OPS_IMPL[key](index, query[key], basic, query); - } - }); - } else { - _makeMatchResult({include: index.getMatching(query)}, basic); - } - - return basic; - } -} - -export default IndexMatcher; diff --git a/test/experemental/BPlusTree.test.js b/test/experemental/BPlusTree.test.js deleted file mode 100644 index daf326f..0000000 --- a/test/experemental/BPlusTree.test.js +++ /dev/null @@ -1,344 +0,0 @@ -import {compareThings} from '../lib/DocumentMatcher'; -import BPlusTree from '../lib/BPlusTree'; -import validate from './lib/bpvalidator'; -import chai, {except, assert} from 'chai'; -chai.use(require('chai-as-promised')); -chai.should(); - - -var db = [ - { age: 46, - name: 'Lamar Goodwin', - phone: '817-529-2557', - title: 'Global Usability Coordinator' }, - { age: 13, - name: 'Elmo Hansen', - phone: '1-632-301-4062 x8351', - title: 'Customer Paradigm Assistant' }, - { age: 43, - name: 'Ashton Oberbrunner', - phone: '1-292-015-9298 x19171', - title: 'Legacy Security Planner' }, - { age: 32, - name: 'Ms. Kiera Hodkiewicz', - phone: '1-742-905-8677', - title: 'Legacy Security Planner' }, - { age: 10, - name: 'Hilda O\'Kon', - phone: '118.357.9132 x76245', - title: 'Dynamic Communications Agent' }, - { age: 38, - name: 'Leland Bahringer', - phone: '114.754.5482 x7853', - title: 'Internal Program Officer' }, - { age: 21, - name: 'Axel Block', - phone: '816-557-8326 x083', - title: 'Forward Interactions Liason' }, - { age: 14, - name: 'Wendy Dare', - phone: '966.968.5997 x42838', - title: 'Infrastructure Associate' }, - { age: 23, - name: 'Miss Fermin Bartell', - phone: '(003) 694-6712', - title: 'Product Applications Designer' }, - { age: 68, - name: 'Marquise Weimann', - phone: '400.157.6206', - title: 'Corporate Response Orchestrator' }, - { age: 24, - name: 'Kaley Jones', - phone: '1-426-266-8041', - title: 'District Brand Producer' }, - { age: 51, - name: 'Dr. Jess Stokes', - phone: '1-754-630-5989 x8753', - title: 'Chief Tactics Supervisor' }, - { age: 24, - name: 'Durward Runolfsson', - phone: '726.255.5565', - title: 'Regional Configuration Planner' }, - { age: 64, - name: 'Clemens Howell Dr.', - phone: '1-926-168-6208', - title: 'Global Communications Orchestrator' }, - { age: 22, - name: 'Catherine Predovic', - phone: '206-479-6915 x835', - title: 'Dynamic Accountability Architect' }, - { age: 36, - name: 'Odie Reichel', - phone: '(695) 562-6049 x68079', - title: 'Forward Configuration Representative' }, - { age: 32, - name: 'Wilfredo Strosin', - phone: '071.478.7926', - title: 'Dynamic Web Consultant' }, - { age: 8, - name: 'Makayla McLaughlin', - phone: '1-667-221-6294 x87922', - title: 'Implementation Facilitator' }, - { age: 41, - name: 'Ardella O\'Conner', - phone: '1-927-933-8004', - title: 'Product Operations Supervisor' }, - { age: 8, - name: 'Magdalen Zulauf Mr.', - phone: '992-726-6046 x72367', - title: 'Central Accountability Manager' }, - { age: 62, - name: 'Santino Kuvalis', - phone: '(297) 534-9135', - title: 'Direct Accounts Analyst' }, - { age: 48, - name: 'Elva Graham', - phone: '229.798.4078 x4705', - title: 'International Mobility Facilitator' }, - { age: 16, - name: 'Lesley Howe', - phone: '(829) 112-7415 x2891', - title: 'Internal Response Agent' }, - { age: 49, - name: 'Antonio Monahan Mr.', - phone: '(682) 162-2301', - title: 'Integration Technician' }, - { age: 69, - name: 'Shana Lubowitz', - phone: '849-809-2691 x787', - title: 'Internal Division Liason' }, - - { age: 1, - name: 'Serena Bruen', - phone: '1-070-021-2968', - title: 'Senior Detector Agent' }, - { age: 1, - name: 'Clemmie Powlowski', - phone: '1-796-310-8197 x253', - title: 'Senior Detector Agent' }, - { age: 2, - name: 'Albertha Simonis Ms.', - phone: '1-421-993-2782 x073', - title: 'Senior Fax Administrator' }, - { age: 4, - name: 'Kavon Hammes', - phone: '(913) 113-1961 x68847', - title: 'Senior Identity Engineer' }, - { age: 5, - name: 'Kyle MacGyver', - phone: '333-464-6778 x7218', - title: 'Senior Klingon Consultant' } -] - - -describe('BPlusTree', () => { - describe('Numeric key indexes', () => { - var bpindex = new BPlusTree({debug: false, branchingFactor: 5, comparator: compareThings}) - - it(`should have a valid structure after every insert`, () => { - var errors = [] - - for (let rec of db) { - bpindex.insert(rec.age, rec.name) - errors = validate(bpindex) - if (errors.length > 0) { break } - } - - if (errors.length > 0) { - console.log(errors) - console.log(JSON.stringify(bpindex.dumpTree(), null, 4)) - } - - assert.lengthOf(errors, 0, 'Errors array is not empty') - }) - - it(`should correctly lookup keys`, () => { - assert.deepEqual(bpindex.search(13), ['Elmo Hansen']) - assert.deepEqual(bpindex.search(64), ['Clemens Howell Dr.']) - assert.sameMembers(bpindex.search(8), ['Makayla McLaughlin', 'Magdalen Zulauf Mr.']) - assert.deepEqual(bpindex.search(99), []) - }) - - it(`should correctly lookup ranges sorted asc`, () => { - assert.deepEqual( - bpindex.getBetweenBounds({$gte: 1, $lt: 5}), - ['Clemmie Powlowski', 'Serena Bruen', 'Albertha Simonis Ms.', 'Kavon Hammes'] - ) - assert.deepEqual( - bpindex.getBetweenBounds({$gte: 1, $lte: 5}), - ['Clemmie Powlowski', 'Serena Bruen', 'Albertha Simonis Ms.', 'Kavon Hammes', 'Kyle MacGyver'] - ) - assert.deepEqual( - bpindex.getBetweenBounds({$gt: 1, $lte: 5}), - ['Albertha Simonis Ms.', 'Kavon Hammes', 'Kyle MacGyver'] - ) - assert.deepEqual( - bpindex.getBetweenBounds({$gt: 1, $lt: 5}), - ['Albertha Simonis Ms.', 'Kavon Hammes'] - ) - }) - - it(`should correctly return the entire index sorted asc`, () => { - assert.deepEqual( - bpindex.getAll().map(x => x.value), - [ 'Clemmie Powlowski', - 'Serena Bruen', - 'Albertha Simonis Ms.', - 'Kavon Hammes', - 'Kyle MacGyver', - 'Magdalen Zulauf Mr.', - 'Makayla McLaughlin', - 'Hilda O\'Kon', - 'Elmo Hansen', - 'Wendy Dare', - 'Lesley Howe', - 'Axel Block', - 'Catherine Predovic', - 'Miss Fermin Bartell', - 'Durward Runolfsson', - 'Kaley Jones', - 'Ms. Kiera Hodkiewicz', - 'Wilfredo Strosin', - 'Odie Reichel', - 'Leland Bahringer', - 'Ardella O\'Conner', - 'Ashton Oberbrunner', - 'Lamar Goodwin', - 'Elva Graham', - 'Antonio Monahan Mr.', - 'Dr. Jess Stokes', - 'Santino Kuvalis', - 'Clemens Howell Dr.', - 'Marquise Weimann', - 'Shana Lubowitz' ] - ) - }) - - it(`should have a valid structure after every delete`, () => { - var errors = [] - - for (let rec of db) { - bpindex.delete(rec.age, rec.name) - errors = validate(bpindex) - if (errors.length > 0) { break } - } - - if (errors.length > 0) { - console.log(errors) - console.log(JSON.stringify(bpindex.dumpTree(), null, 4)) - } - - assert.lengthOf(errors, 0, 'Errors array is not empty') - }) - - it(`should be empty after deleteing all previously inserted records`, () => { - assert.lengthOf(bpindex.root.children, 0, 'Errors array is not empty') - assert.lengthOf(bpindex.root.values, 0, 'Errors array is not empty') - }) - }) - - describe('String key indexes', () => { - var bpindex = new BPlusTree({debug: false, branchingFactor: 5, comparator: compareThings}) - - it(`should have a valid structure after every insert`, () => { - var errors = [] - - for (let rec of db) { - bpindex.insert(rec.title, rec.name) - errors = validate(bpindex) - if (errors.length > 0) { break } - } - - if (errors.length > 0) { - console.log(errors) - console.log(JSON.stringify(bpindex.dumpTree(), null, 4)) - } - - assert.lengthOf(errors, 0, 'Errors array is not empty') - }) - - it(`should correctly lookup keys`, () => { - assert.deepEqual(bpindex.search('Legacy Security Planner'), ['Ashton Oberbrunner', 'Ms. Kiera Hodkiewicz']) - assert.deepEqual(bpindex.search('Dynamic Communications Agent'), ['Hilda O\'Kon']) - assert.deepEqual(bpindex.search('A job nobody has'), []) - }) - - it(`should correctly lookup ranges sorted asc`, () => { - assert.deepEqual( - bpindex.getBetweenBounds({$gte: 'Senior Detector Agent', $lt: 'Senior Klingon Consultant'}), - ['Clemmie Powlowski', 'Serena Bruen', 'Albertha Simonis Ms.', 'Kavon Hammes'] - ) - assert.deepEqual( - bpindex.getBetweenBounds({$gte: 'Senior Detector Agent', $lte: 'Senior Klingon Consultant'}), - ['Clemmie Powlowski', 'Serena Bruen', 'Albertha Simonis Ms.', 'Kavon Hammes', 'Kyle MacGyver'] - ) - assert.deepEqual( - bpindex.getBetweenBounds({$gt: 'Senior Detector Agent', $lte: 'Senior Klingon Consultant'}), - ['Albertha Simonis Ms.', 'Kavon Hammes', 'Kyle MacGyver'] - ) - assert.deepEqual( - bpindex.getBetweenBounds({$gt: 'Senior Detector Agent', $lt: 'Senior Klingon Consultant'}), - ['Albertha Simonis Ms.', 'Kavon Hammes'] - ) - }) - - it(`should correctly return the entire index sorted asc`, () => { - assert.deepEqual( - bpindex.getAll().map(x => x.value), - [ 'Magdalen Zulauf Mr.', - 'Dr. Jess Stokes', - 'Marquise Weimann', - 'Elmo Hansen', - 'Santino Kuvalis', - 'Kaley Jones', - 'Catherine Predovic', - 'Hilda O\'Kon', - 'Wilfredo Strosin', - 'Odie Reichel', - 'Axel Block', - 'Clemens Howell Dr.', - 'Lamar Goodwin', - 'Makayla McLaughlin', - 'Wendy Dare', - 'Antonio Monahan Mr.', - 'Shana Lubowitz', - 'Leland Bahringer', - 'Lesley Howe', - 'Elva Graham', - 'Ashton Oberbrunner', - 'Ms. Kiera Hodkiewicz', - 'Miss Fermin Bartell', - 'Ardella O\'Conner', - 'Durward Runolfsson', - 'Clemmie Powlowski', - 'Serena Bruen', - 'Albertha Simonis Ms.', - 'Kavon Hammes', - 'Kyle MacGyver' ] - ) - }) - - it(`should have a valid structure after every delete`, () => { - var errors = [] - - for (let rec of db) { - bpindex.delete(rec.title, rec.name) - errors = validate(bpindex) - if (errors.length > 0) { break } - } - - if (errors.length > 0) { - console.log(errors) - console.log(JSON.stringify(bpindex.dumpTree(), null, 4)) - } - - assert.lengthOf(errors, 0, 'Errors array is not empty') - }) - - it(`should be empty after deleteing all previously inserted records`, () => { - assert.lengthOf(bpindex.root.children, 0, 'Errors array is not empty') - assert.lengthOf(bpindex.root.values, 0, 'Errors array is not empty') - }) - - }) -}); \ No newline at end of file diff --git a/test/experemental/Index.test.js b/test/experemental/Index.test.js deleted file mode 100644 index 50870a9..0000000 --- a/test/experemental/Index.test.js +++ /dev/null @@ -1,525 +0,0 @@ -import model from '../lib/Document'; -import CollectionIndex from '../lib/CollectionIndex'; -import chai, {except, assert} from 'chai'; -chai.use(require('chai-as-promised')); -chai.should(); - - -describe('CollectionIndexe', function () { - - describe('Insertion', function () { - - it('Can insert pointers to documents in the index correctly when they have the field', function () { - var idx = new CollectionIndex({ fieldName: '_id' }) - , doc1 = { a: 5, _id: 'hello' } - , doc2 = { a: 8, _id: 'world' } - , doc3 = { a: 2, _id: 'bloup' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - - // The underlying BST now has 3 nodes which contain the docs where it's expected - idx.tree.getNumberOfKeys().should.equal(3); - assert.deepEqual(idx.tree.search('hello'), ['hello']); - assert.deepEqual(idx.tree.search('world'), ['world']); - assert.deepEqual(idx.tree.search('bloup'), ['bloup']); - - // The nodes contain _id of the actual documents - idx.tree.search('world')[0].should.equal(doc2._id); - }); - - it('Inserting twice for the same fieldName in a unique index will result in an error thrown', function () { - var idx = new CollectionIndex({ fieldName: 'tf', unique: true }) - , doc1 = { a: 5, tf: 'hello' } - ; - - idx.insert(doc1); - idx.tree.getNumberOfKeys().should.equal(1); - (function () { idx.insert(doc1); }).should.throw(); - }); - - it('Inserting twice for a fieldName the docs dont have with a unique index results in an error thrown', function () { - var idx = new CollectionIndex({ fieldName: 'nope', unique: true }) - , doc1 = { a: 5, tf: 'hello' } - , doc2 = { a: 5, tf: 'world' } - ; - - idx.insert(doc1); - idx.tree.getNumberOfKeys().should.equal(1); - (function () { idx.insert(doc2); }).should.throw(); - }); - - it('Inserting twice for a fieldName the docs dont have with a unique and sparse index will not throw, since the docs will be non indexed', function () { - var idx = new CollectionIndex({ fieldName: 'nope', unique: true, sparse: true }) - , doc1 = { a: 5, tf: 'hello' } - , doc2 = { a: 5, tf: 'world' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.tree.getNumberOfKeys().should.equal(0); // Docs are not indexed - }); - - it('Works with dot notation', function () { - var idx = new CollectionIndex({ fieldName: 'tf.nested' }) - , doc1 = { _id: 5, tf: { nested: 'hello' } } - , doc2 = { _id: 8, tf: { nested: 'world', additional: true } } - , doc3 = { _id: 2, tf: { nested: 'bloup', age: 42 } } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - - // The underlying BST now has 3 nodes which contain the docs where it's expected - idx.tree.getNumberOfKeys().should.equal(3); - assert.deepEqual(idx.tree.search('hello'), [doc1._id]); - assert.deepEqual(idx.tree.search('world'), [doc2._id]); - assert.deepEqual(idx.tree.search('bloup'), [doc3._id]); - }); - - describe('Array fields', function () { - - it('Inserts one entry per array element in the index', function () { - var obj = { tf: ['aa', 'bb'], really: 'yeah', _id: 1 } - , obj2 = { tf: 'normal', yes: 'indeed', _id: 2 } - , idx = new CollectionIndex({ fieldName: 'tf' }) - ; - - idx.insert(obj); - idx.getAll().length.should.equal(2); - idx.getAll()[0].should.equal(obj._id); - idx.getAll()[1].should.equal(obj._id); - - idx.insert(obj2); - idx.getAll().length.should.equal(3); - }); - - it('Inserts one entry per array element in the index, type-checked', function () { - var obj = { tf: ['42', 42, new Date(42), 42], really: 'yeah', _id: 1 } - , idx = new CollectionIndex({ fieldName: 'tf' }) - ; - - idx.insert(obj); - idx.getAll().length.should.equal(3); - idx.getAll()[0].should.equal(obj._id); - idx.getAll()[1].should.equal(obj._id); - idx.getAll()[2].should.equal(obj._id); - }); - - it('Inserts one entry per unique array element in the index, the unique constraint only holds across documents', function () { - var obj = { tf: ['aa', 'aa'], really: 'yeah', _id: 1 } - , obj2 = { tf: ['cc', 'yy', 'cc'], yes: 'indeed', _id: 2 } - , idx = new CollectionIndex({ fieldName: 'tf', unique: true }) - ; - - idx.insert(obj); - idx.getAll().length.should.equal(1); - idx.getAll()[0].should.equal(obj._id); - - idx.insert(obj2); - idx.getAll().length.should.equal(3); - }); - - it('The unique constraint holds across documents', function () { - var obj = { tf: ['aa', 'aa'], really: 'yeah', _id: 1 } - , obj2 = { tf: ['cc', 'aa', 'cc'], yes: 'indeed', _id: 2 } - , idx = new CollectionIndex({ fieldName: 'tf', unique: true }) - ; - - idx.insert(obj); - idx.getAll().length.should.equal(1); - idx.getAll()[0].should.equal(obj._id); - - (function () { idx.insert(obj2); }).should.throw(); - }); - - it('When removing a document, remove it from the index at all unique array elements', function () { - var obj = { tf: ['aa', 'aa'], really: 'yeah', _id: 1 } - , obj2 = { tf: ['cc', 'aa', 'cc'], yes: 'indeed', _id: 2 } - , idx = new CollectionIndex({ fieldName: 'tf' }) - ; - - idx.insert(obj); - idx.insert(obj2); - idx.getMatching('aa').length.should.equal(2); - idx.getMatching('aa').indexOf(obj._id).should.not.equal(-1); - idx.getMatching('aa').indexOf(obj2._id).should.not.equal(-1); - idx.getMatching('cc').length.should.equal(1); - - idx.remove(obj2); - idx.getMatching('aa').length.should.equal(1); - idx.getMatching('aa').indexOf(obj._id).should.not.equal(-1); - idx.getMatching('aa').indexOf(obj2._id).should.equal(-1); - idx.getMatching('cc').length.should.equal(0); - }); - - it('If a unique constraint is violated when inserting an array key, roll back all inserts before the key', function () { - var obj = { tf: ['aa', 'bb'], really: 'yeah' } - , obj2 = { tf: ['cc', 'dd', 'aa', 'ee'], yes: 'indeed' } - , idx = new CollectionIndex({ fieldName: 'tf', unique: true }) - ; - - idx.insert(obj); - idx.getAll().length.should.equal(2); - idx.getMatching('aa').length.should.equal(1); - idx.getMatching('bb').length.should.equal(1); - idx.getMatching('cc').length.should.equal(0); - idx.getMatching('dd').length.should.equal(0); - idx.getMatching('ee').length.should.equal(0); - - (function () { idx.insert(obj2); }).should.throw(); - idx.getAll().length.should.equal(2); - idx.getMatching('aa').length.should.equal(1); - idx.getMatching('bb').length.should.equal(1); - idx.getMatching('cc').length.should.equal(0); - idx.getMatching('dd').length.should.equal(0); - idx.getMatching('ee').length.should.equal(0); - }); - - }); // ==== End of 'Array fields' ==== // - - }); // ==== End of 'Insertion' ==== // - - - describe('Removal', function () { - - it('Can remove pointers from the index, even when multiple documents have the same key', function () { - var idx = new CollectionIndex({ fieldName: 'tf' }) - , doc1 = { a: 5, tf: 'hello', _id: 1 } - , doc2 = { a: 8, tf: 'world', _id: 2 } - , doc3 = { a: 2, tf: 'bloup', _id: 3 } - , doc4 = { a: 23, tf: 'world', _id: 4 } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - idx.insert(doc4); - idx.tree.getNumberOfKeys().should.equal(3); - - idx.remove(doc1); - idx.tree.getNumberOfKeys().should.equal(2); - idx.tree.search('hello').length.should.equal(0); - - idx.remove(doc2); - idx.tree.getNumberOfKeys().should.equal(2); - idx.tree.search('world').length.should.equal(1); - idx.tree.search('world')[0].should.equal(doc4._id); - }); - - it('If we have a sparse index, removing a non indexed doc has no effect', function () { - var idx = new CollectionIndex({ fieldName: 'nope', sparse: true }) - , doc1 = { a: 5, tf: 'hello' } - , doc2 = { a: 5, tf: 'world' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.tree.getNumberOfKeys().should.equal(0); - - idx.remove(doc1); - idx.tree.getNumberOfKeys().should.equal(0); - }); - - it('Works with dot notation', function () { - var idx = new CollectionIndex({ fieldName: 'tf.nested' }) - , doc1 = { _id: 5, tf: { nested: 'hello' } } - , doc2 = { _id: 8, tf: { nested: 'world', additional: true } } - , doc3 = { _id: 2, tf: { nested: 'bloup', age: 42 } } - , doc4 = { _id: 2, tf: { nested: 'world', fruits: ['apple', 'carrot'] } } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - idx.insert(doc4); - idx.tree.getNumberOfKeys().should.equal(3); - - idx.remove(doc1); - idx.tree.getNumberOfKeys().should.equal(2); - idx.tree.search('hello').length.should.equal(0); - - idx.remove(doc2); - idx.tree.getNumberOfKeys().should.equal(2); - idx.tree.search('world').length.should.equal(1); - idx.tree.search('world')[0].should.equal(doc4._id); - }); - - }); // ==== End of 'Removal' ==== // - - - describe('Update', function () { - - it('Can update a document whose key did or didnt change', function () { - var idx = new CollectionIndex({ fieldName: 'tf' }) - , doc1 = { _id: 5, tf: 'hello' } - , doc2 = { _id: 8, tf: 'world' } - , doc3 = { _id: 2, tf: 'bloup' } - , doc4 = { _id: 23, tf: 'world' } - , doc5 = { _id: 1, tf: 'changed' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - idx.tree.getNumberOfKeys().should.equal(3); - assert.deepEqual(idx.tree.search('world'), [doc2._id]); - - idx.update(doc2, doc4); - idx.tree.getNumberOfKeys().should.equal(3); - assert.deepEqual(idx.tree.search('world'), [doc4._id]); - - idx.update(doc1, doc5); - idx.tree.getNumberOfKeys().should.equal(3); - assert.deepEqual(idx.tree.search('hello'), []); - assert.deepEqual(idx.tree.search('changed'), [doc5._id]); - }); - - it('If a simple update violates a unique constraint, changes are rolled back and an error thrown', function () { - var idx = new CollectionIndex({ fieldName: 'tf', unique: true }) - , doc1 = { _id: 5, tf: 'hello' } - , doc2 = { _id: 8, tf: 'world' } - , doc3 = { _id: 2, tf: 'bloup' } - , bad = { _id: 23, tf: 'world' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - - idx.tree.getNumberOfKeys().should.equal(3); - assert.deepEqual(idx.tree.search('hello'), [doc1._id]); - assert.deepEqual(idx.tree.search('world'), [doc2._id]); - assert.deepEqual(idx.tree.search('bloup'), [doc3._id]); - - (() => idx.update(doc3, bad)).should.throw(Error); - - // No change - idx.tree.getNumberOfKeys().should.equal(3); - assert.deepEqual(idx.tree.search('hello'), [doc1._id]); - assert.deepEqual(idx.tree.search('world'), [doc2._id]); - assert.deepEqual(idx.tree.search('bloup'), [doc3._id]); - }); - - it('If an update doesnt change a document, the unique constraint is not violated', function () { - var idx = new CollectionIndex({ fieldName: 'tf', unique: true }) - , doc1 = { _id: 5, tf: 'hello' } - , doc2 = { _id: 8, tf: 'world' } - , doc3 = { _id: 2, tf: 'bloup' } - , noChange = { _id: 8, tf: 'world' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - idx.tree.getNumberOfKeys().should.equal(3); - assert.deepEqual(idx.tree.search('world'), [doc2._id]); - - idx.update(doc2, noChange); // No error thrown - idx.tree.getNumberOfKeys().should.equal(3); - assert.deepEqual(idx.tree.search('world'), [noChange._id]); - }); - - }); // ==== End of 'Update' ==== // - - - describe('Get matching documents', function () { - - it('Get all documents where fieldName is equal to the given value, or an empty array if no match', function () { - var idx = new CollectionIndex({ fieldName: 'tf' }) - , doc1 = { _id: 5, tf: 'hello' } - , doc2 = { _id: 8, tf: 'world' } - , doc3 = { _id: 2, tf: 'bloup' } - , doc4 = { _id: 23, tf: 'world' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - idx.insert(doc4); - - assert.deepEqual(idx.getMatching('bloup'), [doc3._id]); - assert.deepEqual(idx.getMatching('world'), [doc2._id, doc4._id]); - assert.deepEqual(idx.getMatching('nope'), []); - }); - - it('Can get all documents for a given key in a unique index', function () { - var idx = new CollectionIndex({ fieldName: 'tf', unique: true }) - , doc1 = { _id: 5, tf: 'hello' } - , doc2 = { _id: 8, tf: 'world' } - , doc3 = { _id: 2, tf: 'bloup' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - - assert.deepEqual(idx.getMatching('bloup'), [doc3._id]); - assert.deepEqual(idx.getMatching('world'), [doc2._id]); - assert.deepEqual(idx.getMatching('nope'), []); - }); - - it('Can get all documents for which a field is undefined', function () { - var idx = new CollectionIndex({ fieldName: 'tf' }) - , doc1 = { _id: 5, tf: 'hello' } - , doc2 = { _id: 2, nottf: 'bloup' } - , doc3 = { _id: 8, tf: 'world' } - , doc4 = { _id: 7, nottf: 'yes' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - - assert.deepEqual(idx.getMatching('bloup'), []); - assert.deepEqual(idx.getMatching('hello'), [doc1._id]); - assert.deepEqual(idx.getMatching('world'), [doc3._id]); - assert.deepEqual(idx.getMatching('yes'), []); - assert.deepEqual(idx.getMatching(undefined), [doc2._id]); - - idx.insert(doc4); - - assert.deepEqual(idx.getMatching('bloup'), []); - assert.deepEqual(idx.getMatching('hello'), [doc1._id]); - assert.deepEqual(idx.getMatching('world'), [doc3._id]); - assert.deepEqual(idx.getMatching('yes'), []); - assert.deepEqual(idx.getMatching(undefined), [doc2._id, doc4._id]); - }); - - it('Can get all documents for which a field is null', function () { - var idx = new CollectionIndex({ fieldName: 'tf' }) - , doc1 = { _id: 5, tf: 'hello' } - , doc2 = { _id: 2, tf: null } - , doc3 = { _id: 8, tf: 'world' } - , doc4 = { _id: 7, tf: null } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - - assert.deepEqual(idx.getMatching('bloup'), []); - assert.deepEqual(idx.getMatching('hello'), [doc1._id]); - assert.deepEqual(idx.getMatching('world'), [doc3._id]); - assert.deepEqual(idx.getMatching('yes'), []); - assert.deepEqual(idx.getMatching(null), [doc2._id]); - - idx.insert(doc4); - - assert.deepEqual(idx.getMatching('bloup'), []); - assert.deepEqual(idx.getMatching('hello'), [doc1._id]); - assert.deepEqual(idx.getMatching('world'), [doc3._id]); - assert.deepEqual(idx.getMatching('yes'), []); - assert.deepEqual(idx.getMatching(null), [doc2._id, doc4._id]); - }); - - it('Can get all documents for a given key in a sparse index, but not unindexed docs (= field undefined)', function () { - var idx = new CollectionIndex({ fieldName: 'tf', sparse: true }) - , doc1 = { _id: 5, tf: 'hello' } - , doc2 = { _id: 2, nottf: 'bloup' } - , doc3 = { _id: 8, tf: 'world' } - , doc4 = { _id: 7, nottf: 'yes' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - idx.insert(doc4); - - assert.deepEqual(idx.getMatching('bloup'), []); - assert.deepEqual(idx.getMatching('hello'), [doc1._id]); - assert.deepEqual(idx.getMatching('world'), [doc3._id]); - assert.deepEqual(idx.getMatching('yes'), []); - assert.deepEqual(idx.getMatching(undefined), []); - }); - - it('Can get all documents whose key is in an array of keys', function () { - var idx = new CollectionIndex({ fieldName: 'tf' }) - , doc1 = { _id: 5, tf: 'hello' } - , doc2 = { _id: 2, tf: 'bloup' } - , doc3 = { _id: 8, tf: 'world' } - , doc4 = { _id: 7, tf: 'yes' } - , doc5 = { _id: 7, tf: 'yes' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - idx.insert(doc4); - idx.insert(doc5); - - assert.deepEqual(idx.getMatching([]), []); - assert.deepEqual(idx.getMatching(['bloup']), [doc2._id]); - assert.deepEqual(idx.getMatching(['bloup', 'yes']), [doc2._id, doc4._id, doc5._id]); - assert.deepEqual(idx.getMatching(['hello', 'no']), [doc1._id]); - assert.deepEqual(idx.getMatching(['nope', 'no']), []); - }); - - it('Can get all documents whose key is between certain bounds', function () { - var idx = new CollectionIndex({ fieldName: '_id' }) - , doc1 = { _id: 5, tf: 'hello' } - , doc2 = { _id: 2, tf: 'bloup' } - , doc3 = { _id: 8, tf: 'world' } - , doc4 = { _id: 7, tf: 'yes' } - , doc5 = { _id: 10, tf: 'yes' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - idx.insert(doc4); - idx.insert(doc5); - - assert.deepEqual(idx.getBetweenBounds({ $lt: 10, $gte: 5 }), [ doc1._id, doc4._id, doc3._id ]); - assert.deepEqual(idx.getBetweenBounds({ $lte: 8 }), [ doc2._id, doc1._id, doc4._id, doc3._id ]); - assert.deepEqual(idx.getBetweenBounds({ $gt: 7 }), [ doc3._id, doc5._id ]); - }); - - }); // ==== End of 'Get matching documents' ==== // - - - describe('Resetting', function () { - - it('Can reset an index without any new data, the index will be empty afterwards', function () { - var idx = new CollectionIndex({ fieldName: 'tf' }) - , doc1 = { a: 5, tf: 'hello' } - , doc2 = { a: 8, tf: 'world' } - , doc3 = { a: 2, tf: 'bloup' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - - idx.tree.getNumberOfKeys().should.equal(3); - idx.getMatching('hello').length.should.equal(1); - idx.getMatching('world').length.should.equal(1); - idx.getMatching('bloup').length.should.equal(1); - - idx.reset(); - idx.tree.getNumberOfKeys().should.equal(0); - idx.getMatching('hello').length.should.equal(0); - idx.getMatching('world').length.should.equal(0); - idx.getMatching('bloup').length.should.equal(0); - }); - }); // ==== End of 'Resetting' ==== // - - it('Get all elements in the index', function () { - var idx = new CollectionIndex({ fieldName: 'a' }) - , doc1 = { a: 5, _id: 'hello' } - , doc2 = { a: 8, _id: 'world' } - , doc3 = { a: 2, _id: 'bloup' } - ; - - idx.insert(doc1); - idx.insert(doc2); - idx.insert(doc3); - - assert.deepEqual(idx.getAll(), ['bloup', 'hello', 'world']); - }); - - -}); diff --git a/test/experemental/IndexManager.test.js b/test/experemental/IndexManager.test.js deleted file mode 100644 index c3f0b1c..0000000 --- a/test/experemental/IndexManager.test.js +++ /dev/null @@ -1,282 +0,0 @@ -import Collection from '../lib/Collection'; -import IndexManager from '../lib/IndexManager'; -import Index from '../lib/Index'; -import chai from 'chai'; -chai.use(require('chai-as-promised')); -chai.should(); - - -describe('IndexManager', () => { - let db, idxMan; - beforeEach(function () { - db = new Collection('test') - idxMan = db.indexManager; - }); - - it('should be created with _id index', () => { - idxMan.indexes['_id'].should.be.instanceof(Index); - }); - - - - describe('#buildIndex', function () { - it('should build an existing index', function () { - return Promise.all([ - db.insert({_id: 1, a: '1', b: 2}), - db.insert({_id: 2, a: '2', b: 3}), - db.insert({_id: 3, a: '3', b: 4}), - db.insert({_id: 4, a: '4', b: 5}), - db.insert({_id: 5, a: '5', b: 6}) - ]).then(() => { - idxMan.indexes._id.getBetweenBounds({$lte: 3, $gt: 1}) - .should.be.deep.equal([2, 3]); - idxMan.indexes._id.reset(); - idxMan.indexes._id.getBetweenBounds({$lte: 3, $gt: 1}) - .should.be.deep.equal([]); - return idxMan.buildIndex('_id'); - }).then(() => { - idxMan.indexes._id.getBetweenBounds({$lte: 3, $gt: 1}) - .should.be.deep.equal([2, 3]); - }); - }); - - it('should throw an error when no index found to build', function () { - // TODO - }); - - it('should reject with error when building some index failed', function () { - // TODO - }); - }); - - - - describe('#buildAllIndexes', function () { - it('should build all existing indexes', function () { - return Promise.all([ - db.insert({_id: 1, a: '1', b: 2}), - db.insert({_id: 2, a: '2', b: 3}), - db.insert({_id: 3, a: '3', b: 4}), - db.insert({_id: 4, a: '4', b: 5}), - db.insert({_id: 5, a: '5', b: 6}), - idxMan.ensureIndex({fieldName: 'b'}) - ]).then(() => { - idxMan.indexes.b.getBetweenBounds({$lt: 6, $gt: 2}) - .should.be.deep.equal([2, 3, 4]); - idxMan.indexes._id.getBetweenBounds({$lte: 3, $gt: 1}) - .should.be.deep.equal([2, 3]); - - idxMan.indexes.b.reset(); - idxMan.indexes._id.reset(); - - idxMan.indexes.b.getBetweenBounds({$lt: 6, $gt: 2}) - .should.be.deep.equal([]); - idxMan.indexes._id.getBetweenBounds({$lte: 3, $gt: 1}) - .should.be.deep.equal([]); - - return idxMan.buildAllIndexes(); - }).then(() => { - idxMan.indexes.b.getBetweenBounds({$lt: 6, $gt: 2}) - .should.be.deep.equal([2, 3, 4]); - idxMan.indexes._id.getBetweenBounds({$lte: 3, $gt: 1}) - .should.be.deep.equal([2, 3]); - }); - }); - }); - - - - describe('#ensureIndex', function () { - it('should create and build index when index does not exists', () => { - return Promise.all([ - db.insert({_id: 1, a: '1', b: 2}), - db.insert({_id: 2, a: '2', b: 3}), - db.insert({_id: 3, a: '3', b: 4}), - db.insert({_id: 4, a: '4', b: 5}), - db.insert({_id: 5, a: '5', b: 6}) - ]).then(() => { - return idxMan.ensureIndex({fieldName: 'b'}); - }).then(() => { - idxMan.indexes['b'].should.be.instanceof(Index); - idxMan.indexes['b'].getBetweenBounds({$lt: 6, $gt: 2}) - .should.be.deep.equal([2, 3, 4]); - }); - }); - - it('should return resolved promise when index exists and built', () => { - const prevIndex = idxMan.indexes._id; - return Promise.all([ - db.insert({_id: 1, a: '1', b: 2}), - db.insert({_id: 2, a: '2', b: 3}), - db.insert({_id: 3, a: '3', b: 4}), - db.insert({_id: 4, a: '4', b: 5}), - db.insert({_id: 5, a: '5', b: 6}) - ]).then(() => { - return idxMan.ensureIndex({fieldName: '_id'}); - }).then(() => { - prevIndex.should.be.equal(idxMan.indexes._id); - idxMan.indexes._id.getBetweenBounds({$lte: 3, $gt: 1}) - .should.be.deep.equal([2, 3]); - }); - }); - - it('should return promise for already running index building task', () => { - return Promise.all([ - db.insert({_id: 1, a: '1', b: 2}), - db.insert({_id: 2, a: '2', b: 3}), - db.insert({_id: 3, a: '3', b: 4}), - db.insert({_id: 4, a: '4', b: 5}), - db.insert({_id: 5, a: '5', b: 6}) - ]).then(() => { - const runProm = idxMan.ensureIndex({fieldName: '_id', forceRebuild: true}); - const anotherRunProm = idxMan.ensureIndex({fieldName: '_id', forceRebuild: true}); - runProm.should.be.equal(anotherRunProm); - }); - }); - - it('should be able to force rebuild indexes', () => { - return Promise.all([ - db.insert({_id: 1, a: '1', b: 2}), - db.insert({_id: 2, a: '2', b: 3}), - db.insert({_id: 3, a: '3', b: 4}), - db.insert({_id: 4, a: '4', b: 5}), - db.insert({_id: 5, a: '5', b: 6}) - ]).then(() => { - idxMan.indexes._id.getBetweenBounds({$lte: 3, $gt: 1}) - .should.be.deep.equal([2, 3]); - idxMan.indexes._id.reset(); - idxMan.indexes._id.getBetweenBounds({$lte: 3, $gt: 1}) - .should.be.deep.equal([]); - return idxMan.ensureIndex({fieldName: '_id', forceRebuild: true}); - }).then(() => { - idxMan.indexes._id.getBetweenBounds({$lte: 3, $gt: 1}) - .should.be.deep.equal([2, 3]); - }); - }); - }); - - - - describe('#removeIndex', function () { - it('should remove index', function () { - return idxMan.removeIndex().then(() => { - idxMan.indexes.should.not.have.equal('_id'); - }); - }); - }); - - - - describe('#indexDocument', function () { - it('should index document in all existing indexes', function () { - return Promise.all([ - idxMan.ensureIndex({fieldName: 'b', unique: true}), - idxMan.ensureIndex({fieldName: 'a'}) - ]).then(() => { - return idxMan.indexDocument({_id: 5, a: '5', b: 6}); - }).then(() => { - idxMan.indexes._id.getMatching(5) - .should.be.deep.equal([5]); - idxMan.indexes.b.getMatching(6) - .should.be.deep.equal([5]); - idxMan.indexes.a.getMatching('5') - .should.be.deep.equal([5]); - }); - }); - - it('should reject and rollback index when indexing errored', function () { - return Promise.all([ - idxMan.ensureIndex({fieldName: 'b', unique: true}), - idxMan.ensureIndex({fieldName: 'a'}), - idxMan.indexDocument({_id: 5, a: '5', b: 6}) - ]).then(() => { - return idxMan.indexDocument({_id: 4, a: '7', b: 6}); - }).then(null, (err) => { - err.should.not.be.equal(undefined); - idxMan.indexes._id.getMatching(4) - .should.be.deep.equal([]); - idxMan.indexes.a.getMatching('7') - .should.be.deep.equal([]); - idxMan.indexes.b.getMatching(6) - .should.be.deep.equal([5]); - }); - }); - }); - - - - describe('#reindexDocument', function () { - it('should reindex document in all existing indexes', function () { - return Promise.all([ - idxMan.ensureIndex({fieldName: 'b', unique: true}), - idxMan.ensureIndex({fieldName: 'a'}), - idxMan.indexDocument({_id: 5, a: '5', b: 6}) - ]).then(() => { - return idxMan.reindexDocument( - {_id: 5, a: '5', b: 6}, - {_id: 5, a: '6', b: 7} - ); - }).then(() => { - idxMan.indexes._id.getMatching(5) - .should.be.deep.equal([5]); - idxMan.indexes.b.getMatching(7) - .should.be.deep.equal([5]); - idxMan.indexes.a.getMatching('6') - .should.be.deep.equal([5]); - idxMan.indexes.b.getMatching(6) - .should.be.deep.equal([]); - idxMan.indexes.a.getMatching('5') - .should.be.deep.equal([]); - }); - }); - - it('should reject and rollback index when reindexing errored', function () { - return Promise.all([ - idxMan.ensureIndex({fieldName: 'b', unique: true}), - idxMan.ensureIndex({fieldName: 'a'}), - idxMan.indexDocument({_id: 5, a: '5', b: 6}), - idxMan.indexDocument({_id: 10, a: '10', b: 5}) - ]).then(() => { - return idxMan.reindexDocument( - {_id: 10, a: '10', b: 5}, - {_id: 10, a: '7', b: 6} - ); - }).then(null, (err) => { - err.should.not.be.equal(undefined); - idxMan.indexes._id.getMatching(10) - .should.be.deep.equal([10]); - idxMan.indexes.a.getMatching('10') - .should.be.deep.equal([10]); - idxMan.indexes.a.getMatching('7') - .should.be.deep.equal([]); - idxMan.indexes.b.getMatching(5) - .should.be.deep.equal([10]); - idxMan.indexes.b.getMatching(6) - .should.be.deep.equal([5]); - }); - }); - }); - - - - describe('#deindexDocument', function () { - it('should deindex document in all existing indexes', function () { - return Promise.all([ - idxMan.ensureIndex({fieldName: 'b', unique: true}), - idxMan.ensureIndex({fieldName: 'a'}), - idxMan.indexDocument({_id: 5, a: '5', b: 6}) - ]).then(() => { - return idxMan.deindexDocument({_id: 5, a: '5', b: 6}); - }).then(() => { - idxMan.indexes._id.getMatching(5) - .should.be.deep.equal([]); - idxMan.indexes.b.getMatching(6) - .should.be.deep.equal([]); - idxMan.indexes.a.getMatching('5') - .should.be.deep.equal([]); - }); - }); - }); - - -}); diff --git a/test/experemental/IndexMatcher.test.js b/test/experemental/IndexMatcher.test.js deleted file mode 100644 index 032d70f..0000000 --- a/test/experemental/IndexMatcher.test.js +++ /dev/null @@ -1,135 +0,0 @@ -import documentMatchingTest from './MatchingTests'; -import IndexMatcher, * as IndexMatcherInternals from '../lib/IndexMatcher'; -import EJSON from '../lib/EJSON'; -import Collection from '../lib/Collection'; -import Index from '../lib/Index'; -import chai, {except, assert} from 'chai'; -chai.use(require('chai-as-promised')); -chai.should(); - - -describe('IndexMatcher', () => { - - describe('Internalts', () => { - it('should intersecting including', () => { - const a = ['a', 'b', 'c']; - const b = ['b', 'e', 'k']; - const c = ['c', 'b', 'l']; - const res = IndexMatcherInternals._getIncludeIntersection([a, b, c]); - res.should.be.deep.equal(['b']); - }); - - it('should intersecting excluding', () => { - const a = ['a', 'b', 'c']; - const b = ['b', 'e', 'k']; - const c = ['c', 'b', 'l']; - const res = IndexMatcherInternals._getExcludeIntersection([a, b, c]); - res.should.be.deep.equal(['a']); - }); - - it('should intersecting including with undefs', () => { - const a = ['a', 'b', 'c']; - const b = ['b', 'e', 'k']; - const c = ['c', 'b', 'l']; - const res = IndexMatcherInternals._getExcludeIntersection( - [undefined, undefined, a, undefined, b, c, undefined] - ); - res.should.be.deep.equal(['a']); - }); - - it('should unios', () => { - const a = ['a', 'b', 'c']; - const b = ['b', 'e', 'k']; - const c = ['c', 'b', 'l']; - const res = IndexMatcherInternals._getUnion([a, b, c]); - res.should.be.deep.equal(['a', 'b', 'c', 'e', 'k', 'l']); - }); - - it('should make match result from scratch', () => { - const res = IndexMatcherInternals._makeMatchResult({ - include: ['a', 'b', 'c'], - exclude: ['c', 'd', 'e'], - }); - res.should.be.deep.equal({ - include: ['a', 'b', 'c'], - exclude: ['c', 'd', 'e'], - }); - }); - - it('should make match result from base result', () => { - const base = {include: ['a'], exclude: ['b']}; - IndexMatcherInternals._makeMatchResult({ - include: ['a', 'b', 'c'], - exclude: ['c', 'd', 'e'], - }, base); - base.should.be.deep.equal({ - include: ['a', 'a', 'b', 'c'], - exclude: ['b', 'c', 'd', 'e'], - }); - }); - }); - - describe('Match stream', () => { - let db; - beforeEach(function () { - db = new Collection('test'); - - return Promise.all([ - db.insert({text: 'little text 1', numbr: 10, _id: 'id1', nested: {'_id': 'id1'}}), - db.insert({text: 'little text 2', numbr: 11, _id: 'id2', nested: {'_id': 'id2'}}), - db.insert({text: 'little text 3', numbr: 12, _id: 'id3', nested: {'_id': 'id3'}}), - db.insert({text: 'little text 4', numbr: 13, _id: 'id4', nested: {'_id': 'id4'}}), - db.insert({text: 'little text 5', numbr: 14, _id: 'id5', nested: {'_id': 'id5'}}), - db.insert({text: 'little text 6', numbr: 15, _id: 'id6', nested: {'_id': 'id6'}}), - db.insert({text: 'little text 7', numbr: 16, _id: 'id7', nested: {'_id': 'id7'}}), - ]); - }); - - it('should get ids with _id request', () => { - return new IndexMatcher(db, - {'_id': 'id2'} - ); - }); - - it('should get ids with logical request', () => { - return new IndexMatcher(db, { - $and: [ - {_id: {$in: ['id1', 'id2', 'id3']}}, - {$or: [ - {'nested._id': 'id1'}, - {'nested._id': 'id3'} - ]}, - ], - }); - }); - - it('should get ids with _id request and sorting', () => { - return new IndexMatcher(db, - {_id: {$in: ['id3', 'id4', 'id2']}, numbr: {$gt: 11}}, - {numbr: -1} - ); - }); - }); - - describe('Minimongo tests', function () { - var matches = function (shouldMatch, selector, doc) { - const db = new Collection('test'); - return db.insert(doc).then((result) => { - return db.find(selector); - }).then((docs) => { - const result = docs.length > 0; - if (result !== shouldMatch) { - console.log(shouldMatch); - console.log('doc: ', JSON.stringify(doc)); - console.log('selector: ', JSON.stringify(selector)); - const mess = `minimongo match failure: document ${shouldMatch ? 'should match, but doesn\'t' : 'shouldn\'t match, but does'}, selector ${EJSON.stringify(selector)}, document ${EJSON.stringify(doc)}`; - assert.fail(result, shouldMatch, mess) - } - }) - }; - - var match = matches.bind(null, true); - var nomatch = matches.bind(null, false); - documentMatchingTest(match, nomatch); - }); -}); diff --git a/test/experemental/lib/bpvalidator.js b/test/experemental/lib/bpvalidator.js deleted file mode 100644 index 024beea..0000000 --- a/test/experemental/lib/bpvalidator.js +++ /dev/null @@ -1,196 +0,0 @@ - -var validator = { - - getChildRanges: (node, parentRange) => { - var ranges = [] - - for (let i = 0; i <= node.keys.length; i++) { - let range = [] - - if (i === 0) { - range = [null, node.keys[i]] - } else if (i === (node.keys.length)) { - range = [node.keys[i - 1], null] - } else { - range = [node.keys[i - 1], node.keys[i]] - } - - if (parentRange) { - if (parentRange[0] > range[0]) { - range[0] = parentRange[0] - } - if (parentRange[1] < range[1]) { - range[1] = parentRange[1] - } - } - - ranges.push(range) - } - return ranges - }, - - levelView: (level) => { - var text = '' - for (let i = 0; i < level; i++) { - text += '--' - } - return text - }, - - validateNode: (tree, node=null, checks={}) => { - var level = checks.level || 1 - node = node || tree.root - var errors = [] - var childRanges = validator.getChildRanges(node, checks.range) - var minNodeKeys = Math.floor(tree.bf / 2) - 1 - var minChildren = Math.floor(tree.bf / 2) - var maxNodeKeys = tree.bf - 1 - var maxChildren = tree.bf - var nodeType = node.hasChildren() ? 'node' : 'leaf' - var isRoot = node === tree.root - - // B-tree docs from http://www.cburch.com/cs/340/reading/btree/index.html - - // A B+-tree maintains the following invariants: - - // Every node has one more references than it has keys. - // All leaves are at the same distance from the root. - // For every non-leaf node N with k being the number of keys in N: all keys in the first child's subtree are less than N's first key; and all keys in the ith child's subtree (2 ≤ i ≤ k) are between the (i − 1)th key of n and the ith key of n. - // The root has at least two children. - // Every non-leaf, non-root node has at least floor(d / 2) children. - // Each leaf contains at least floor(d / 2) keys. - // Every key from the table appears in a leaf, in left-to-right sorted order. - - // Every node has one more references than it has keys. - if (nodeType === 'node') { - if ((node.keys.length + 1) !== node.children.length) { - errors.push(`${validator.levelView(level)} ${node.id} wrong number of references! - keys=${node.keys.length}, children=${node.children.length}`) - } - } - - if (isRoot) { // The root has at least two children. - if (node.values.length === 0) { - if (node.children.length === 1) { - errors.push(`${validator.levelView(level)} ${node.id} root must have at least 2 children!`) - } - } - if (node.keys.length > maxNodeKeys) { - errors.push(`${validator.levelView(level)} ${node.id} has too many keys! - should have no more than ${maxNodeKeys} (has ${node.keys.length})`) - } - } else { // Check for correct number of keys - if (node.keys.length > maxNodeKeys) { - errors.push(`${validator.levelView(level)} ${node.id} has too many keys! - should have no more than ${maxNodeKeys} (has ${node.keys.length})`) - } - if (node.keys.length < minNodeKeys) { - errors.push(`${validator.levelView(level)} ${node.id} has too few keys! - should have no less than ${minNodeKeys} (has ${node.keys.length})`) - } - } - - // Every non-leaf, non-root node has at least floor(branchingFactor / 2) children. - if (isRoot === false && nodeType === 'node') { - if (node.next !== null) { // nodes dont have references to siblings - errors.push(`${validator.levelView(level)} ${node.id} should not have a next node`) - } - if (node.prev !== null) { - errors.push(`${validator.levelView(level)} ${node.id} should not have a prev node`) - } - if (node.children.length < minChildren) { - errors.push(`${validator.levelView(level)} ${node.id} has too few children! - (has ${node.keys.length} keys, and ${node.children.length} children)`) - } - if (node.children.length > maxChildren) { // and at most (branchingFactor) children - errors.push(`${validator.levelView(level)} ${node.id} has too many children! - (has ${node.keys.length} keys, and ${node.children.length} children)`) - } - } - - // Each leaf contains at least floor(branchingFactor / 2) keys, and values. - if (isRoot === false && nodeType === 'leaf') { - if (node.keys.length < minChildren) { - errors.push(`${validator.levelView(level)} ${node.id} has too few keys! - (has ${node.keys.length} keys, and ${node.values.length} values)`) - } - if (node.values.length < minChildren) { - errors.push(`${validator.levelView(level)} ${node.id} has too few values! - (has ${node.keys.length} keys, and ${node.values.length} values)`) - } - if (node.keys.length > maxChildren) { - errors.push(`${validator.levelView(level)} ${node.id} has too many keys! - (has ${node.keys.length} keys, and ${node.values.length} values)`) - } - if (node.values.length > maxChildren) { - errors.push(`${validator.levelView(level)} ${node.id} has too many values! - (has ${node.keys.length} keys, and ${node.values.length} values)`) - } - } - - // Validate parent child relationship - if (checks.parent) { - if (node.parent !== checks.parent) { - errors.push(`${validator.levelView(level)} ${node.id} has invalid parent! - should be child of ${checks.parent.id}`) - } - } - - // For every non-leaf node N with k being the number of keys in N: all keys in the first child's subtree are less than N's first key; and all keys in the ith child's subtree (2 ≤ i ≤ k) are between the (i − 1)th key of n and the ith key of n. - if (checks.range) { - for (let key of node.keys) { - if (checks.range[0] !== null) { - if ((key >= checks.range[0]) === false) { - errors.push(`${validator.levelView(level)} ${node.id} has invalid key! - ${key} should be >= ${checks.range[0]}`) - } - } - if (checks.range[1] !== null) { - if ((key < checks.range[1]) === false) { - errors.push(`${validator.levelView(level)} ${node.id} has invalid key! - ${key} should be < ${checks.range[1]}`) - } - } - } - } - - // Every key from the table appears in a leaf, in left-to-right sorted order. - if (isRoot) { - var currNode = node - var maxLoop = 10000 - var loop = 1 - var currKey = null - - // Descend to first leaf - while (currNode.children.length > 0 && loop < maxLoop) { - currNode = currNode.children[0] - loop++ - } - - if (currNode.prev !== null) { - errors.push(`${validator.levelView(level)} ${node.id} leftmost node should have no previous node!`) - } - - // Traverse entire array of keys - loop = 1 - while (currNode.next && loop < maxLoop) { - - for (let i = 0; i < currNode.keys.length; i++) { - if (currKey !== null) { - if (currKey >= currNode.keys[i]) { - errors.push(`${validator.levelView(level)} ${node.id} Keys are not in sorted order!`) - } - } - currKey = currNode.keys[i] - } - - currNode = currNode.next - loop++ - } - - if (currNode.next !== null) { - errors.push(`${validator.levelView(level)} ${node.id} rightmost node should have no next node!`) - } - - } - - // Validate all children - for (let i = 0; i < node.children.length; i++) { - errors = errors.concat(validator.validateNode(tree, node.children[i], {parent: node, range: childRanges[i], level: level + 1})) - } - - return errors - } - -} - -module.exports = (tree) => { - return validator.validateNode(tree) -}