diff --git a/README.md b/README.md index 1968248..fc774d0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ [![Build Status](https://travis-ci.org/kuzzleio/koncorde.svg?branch=master)](https://travis-ci.org/kuzzleio/koncorde) [![Codecov](http://codecov.io/github/kuzzleio/koncorde/coverage.svg?branch=master)](http://codecov.io/github/kuzzleio/koncorde?branch=master) +[![Code Quality: Javascript](https://img.shields.io/lgtm/grade/javascript/g/kuzzleio/koncorde.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/kuzzleio/koncorde/context:javascript) +[![Total Alerts](https://img.shields.io/lgtm/alerts/g/kuzzleio/koncorde.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/kuzzleio/koncorde/alerts) # Koncorde @@ -1379,36 +1381,44 @@ The following results are obtained running `node benchmark.js` at the root of th Filter count per tested keyword: 10000 > Benchmarking keyword: equals - Registration: time = 0.453s, mem = +40MB - Matching x 3,444,291 ops/sec ±0.83% (94 runs sampled) + Indexation: time = 0.435s, mem = +41MB + Matching x 4,006,895 ops/sec ±0.35% (97 runs sampled) + Filters removal: time = 0.02s > Benchmarking keyword: exists - Registration: time = 0.518s, mem = +12MB - Matching x 1,953,425 ops/sec ±0.68% (94 runs sampled) + Indexation: time = 0.487s, mem = +-2MB + Matching x 2,449,897 ops/sec ±0.95% (97 runs sampled) + Filters removal: time = 0.023s > Benchmarking keyword: geoBoundingBox - Registration: time = 0.936s, mem = +17MB - Matching x 1,234,466 ops/sec ±0.50% (94 runs sampled) + Indexation: time = 0.751s, mem = +14MB + Matching x 1,339,779 ops/sec ±0.21% (95 runs sampled) + Filters removal: time = 0.096s > Benchmarking keyword: geoDistance - Registration: time = 1.25s, mem = +16MB - Matching x 1,255,571 ops/sec ±0.84% (97 runs sampled) + Indexation: time = 1.254s, mem = +6MB + Matching x 1,226,643 ops/sec ±0.73% (92 runs sampled) + Filters removal: time = 0.093s > Benchmarking keyword: geoDistanceRange - Registration: time = 1.857s, mem = +12MB - Matching x 1,338,788 ops/sec ±0.77% (93 runs sampled) + Indexation: time = 1.762s, mem = +-10MB + Matching x 1,199,081 ops/sec ±0.26% (96 runs sampled) + Filters removal: time = 0.088s > Benchmarking keyword: geoPolygon (10 vertices) - Registration: time = 1.148s, mem = +21MB - Matching x 52,636 ops/sec ±0.16% (97 runs sampled) + Indexation: time = 1.184s, mem = +1MB + Matching x 53,395 ops/sec ±0.95% (96 runs sampled) + Filters removal: time = 0.103s > Benchmarking keyword: in (5 random values) - Registration: time = 1.554s, mem = +61MB - Matching x 1,782,624 ops/sec ±0.25% (96 runs sampled) + Indexation: time = 1.417s, mem = +40MB + Matching x 2,086,572 ops/sec ±2.02% (92 runs sampled) + Filters removal: time = 0.058s > Benchmarking keyword: range (random bounds) - Registration: time = 0.41s, mem = +17MB - Matching x 31,933 ops/sec ±13.76% (92 runs sampled) + Indexation: time = 0.407s, mem = +-140MB + Matching x 38,611 ops/sec ±0.32% (95 runs sampled) + Filters removal: time = 0.064s ``` _(results obtained with node v10.2.1)_ diff --git a/benchmark.js b/benchmark.js index 5bad271..192733b 100644 --- a/benchmark.js +++ b/benchmark.js @@ -18,6 +18,7 @@ const int: Random.integer(-10000, 10000) }; +let filters = []; const koncorde = new Koncorde(); const matching = (name, document) => { @@ -29,10 +30,22 @@ const matching = (name, document) => { }) .on('cycle', event => { console.log(String(event.target)); + removeFilters(); }) .run({async: false}); }; +function removeFilters() { + const removalStart = Date.now(); + + for (const filter of filters) { + koncorde.remove(filter); + } + + filters = []; + console.log(`\tFilters removal: time = ${(Date.now() - removalStart)/1000}s`); +} + const test = Bluebird.coroutine(function *_register(name, generator, document) { let i, filterStartTime, @@ -46,11 +59,11 @@ const test = Bluebird.coroutine(function *_register(name, generator, document) { for (i = 0;i < max; i++) { // Using the filter name as a collection to isolate // benchmark calculation per keyword - yield koncorde.register('i', name, generator()); + filters.push((yield koncorde.register('i', name, generator())).id); } filterEndTime = (Date.now() - filterStartTime) / 1000; - console.log(`\tRegistration: time = ${filterEndTime}s, mem = +${Math.round((v8.getHeapStatistics().total_heap_size - baseHeap) / 1024 / 1024)}MB`); + console.log(`\tIndexation: time = ${filterEndTime}s, mem = +${Math.round((v8.getHeapStatistics().total_heap_size - baseHeap) / 1024 / 1024)}MB`); matching(name, document); }); diff --git a/lib/README.md b/lib/README.md index d0f3529..c2ac1fb 100644 --- a/lib/README.md +++ b/lib/README.md @@ -24,10 +24,8 @@ The canonicalized filter is split and its parts are stored in different structur - `storage.subfilters` provides a bidirectional link between a subfilter, its associated filters, and its associated conditions - `storage.conditions` provides a link between a condition and its associated subfilters. It also contains the condition's value -Once stored, filters are indexed: - -- `storage.foPairs` regroups all conditions associated to a field-operand pair. It means that, for instance, all "equals" condition on a field "field" are regrouped and stored together. The way these values are stored closely depends on the corresponding operand (for instance, "range" operands use a specific augmented AVL tree, while geospatial operands use a R\* tree) -- `storage.testTables` is the index allowing to efficiently track how many conditions a given subfilter has validated. This structure is the most important part of the matching mechanism (performance-wise) as it allows to very quickly check if a subfilter is completely matched and what filters should be returned for a given document. +Once stored, filters are indexed in the `storage.foPairs` structure, regrouping all conditions associated to a field-operand pair. +It means that, for instance, all "equals" condition on a field "field" are regrouped and stored together. The way these values are stored closely depends on the corresponding operand (for instance, "range" operands use a specific augmented AVL tree, while geospatial operands use a R\* tree) ## Matching @@ -41,4 +39,3 @@ The way each field-operand pair performs its match depends closely on the keywor ## Deleting a filter When a filter gets deleted, the filters, subfilters, conditions and field-operand structures are cleaned up. -The indexes are left alone, unless more than 10% of the referenced subfilters have been deleted. If so, an index rebuild is triggered. This allow mutualizing the cost of rebuilding the indexes. diff --git a/lib/index.js b/lib/index.js index 3d2257a..517bea1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -81,10 +81,10 @@ class Koncorde { /** * Returns an optimized version of the provided filter, with - * its associated filter unique ID. + * its associated filter unique ID. * Does not store anything in the DSL structures * The returned object can either be used with store(), or discarded. - * + * * @param {string} index index * @param {[type]} collection collection * @param {[type]} filter filter @@ -95,7 +95,7 @@ class Koncorde { .then(normalized => ({ index, collection, - normalized, + normalized, id: this.storage.getFilterId(index, collection, normalized) })); } @@ -103,7 +103,7 @@ class Koncorde { /** * Stores a normalized filter into this DSL structures. * A normalized filter is obtained using a call to normalize() - * + * * @param {Object} normalized Obtained with a call to normalize() * @return {{diff: Object, id: String}} */ @@ -130,7 +130,7 @@ class Koncorde { * @returns {Array} Array of matching filter IDs */ getFilterIds(index, collection) { - return this.exists(index, collection) ? this.storage.filtersIndex[index][collection] : []; + return this.exists(index, collection) ? Array.from(this.storage.filtersIndex[index][collection]) : []; } /** @@ -155,7 +155,6 @@ class Koncorde { * Removes all references to a given filter from the real-time engine * * @param {string} filterId - ID of the filter to remove - * @returns {Promise} */ remove(filterId) { return this.storage.remove(filterId); diff --git a/lib/match/index.js b/lib/match/index.js index a34e4cc..9de322b 100644 --- a/lib/match/index.js +++ b/lib/match/index.js @@ -57,11 +57,12 @@ class Matcher { * @return {Array} */ match(index, collection, data) { - const testTables = new TestTables(this.store.testTables, index, collection); + const testTables = new TestTables(); for (const matcher of this.matchers) { - if (this.store.foPairs[index][collection][matcher[0]]) { - matcher[1](this.store.foPairs[index][collection][matcher[0]], testTables, data); + const matcherStorage = this.store.foPairs[index][collection].get(matcher[0]); + if (matcherStorage) { + matcher[1](matcherStorage, testTables, data); } } diff --git a/lib/match/matchGeospatial.js b/lib/match/matchGeospatial.js index fbb0e5a..dfc4c8b 100644 --- a/lib/match/matchGeospatial.js +++ b/lib/match/matchGeospatial.js @@ -50,7 +50,7 @@ function MatchGeospatial (storage, testTables, document) { const result = storage.custom.index.queryPoint(point.lat, point.lon); for(j = 0; j < result.length; j++) { - testTables.addMatch(storage.fields[key][result[j]]); + testTables.addMatch(storage.fields[key].get(result[j])); } } } diff --git a/lib/match/matchRange.js b/lib/match/matchRange.js index 297307b..20f40f6 100644 --- a/lib/match/matchRange.js +++ b/lib/match/matchRange.js @@ -33,10 +33,17 @@ */ function MatchRange (storage, testTables, document, not = false) { for (const key of storage.keys) { + let rangeConditions; if (typeof document[key] === 'number') { - testTables.addMatch(storage.fields[key].tree.search(document[key], document[key])); + rangeConditions = storage.fields[key].tree.search(document[key], document[key]); } else if (not) { - testTables.addMatch(storage.fields[key].tree.search(-Infinity, Infinity)); + rangeConditions = storage.fields[key].conditions.values(); + } + + if (rangeConditions !== undefined) { + for (const cond of rangeConditions) { + testTables.addMatch(cond.subfilters); + } } } } diff --git a/lib/match/testTables.js b/lib/match/testTables.js index 67d0f29..58bc498 100644 --- a/lib/match/testTables.js +++ b/lib/match/testTables.js @@ -42,8 +42,7 @@ * @param collection */ class TestTables { - constructor(testTablesRef, index, collection) { - this.conditions = testTablesRef[index][collection].conditions; + constructor() { this.matchedConditions = {}; this.matched = {}; } @@ -51,33 +50,20 @@ class TestTables { /** * Registers a matching subfilters in the test tables * - * @param {Array} subfilters - array of matching subfilters + * @param {Set} subfilters - matching subfilters */ addMatch(subfilters) { - // Declaring "i" inside the "for" statement downgrades - // performances by a factor of 3 to 4 - // Should be fixed in later V8 versions - // (tested on Node 6.9.x) - let i; // NOSONAR - - for (i = 0; i < subfilters.length; i++) { - const sf = subfilters[i]; - const matched = this.matchedConditions[sf.cidx] || this.conditions[sf.cidx]; + subfilters.forEach(sf => { + const matched = this.matchedConditions[sf.id] || sf.conditions.size; if (matched > 1) { - this.matchedConditions[sf.cidx] = matched - 1; + this.matchedConditions[sf.id] = matched - 1; } else { - // Declaring "j" inside the "for" statement downgrades - // performances by a factor of 3 to 4 - // Should be fixed in later V8 versions - // (tested on Node 6.9.x) - let j; // NOSONAR - - for (j = 0; j < sf.filters.length; j++) { - this.matched[sf.filters[j].id] = 1; - } + sf.filters.forEach(filter => { + this.matched[filter.id] = 1; + }); } - } + }); } } diff --git a/lib/storage/index.js b/lib/storage/index.js index 49cae23..0597259 100644 --- a/lib/storage/index.js +++ b/lib/storage/index.js @@ -30,7 +30,6 @@ const Filter = require('./objects/filter'), Subfilter = require('./objects/subfilter'), Condition = require('./objects/condition'), - TestTable = require('./objects/testTable'), FieldOperand = require('./objects/fieldOperand'), containsOne = require('../util/containsOne'); @@ -54,7 +53,7 @@ class Storage { * @example * { * index: { - * collection: [filterId1, filterId2, ...] + * collection: Set() * } * } */ @@ -66,16 +65,9 @@ class Storage { * against OR operands, meaning if at least 1 subfilter matches, the * whole filter matches. * - * @type {object} - * - * @example - * { - * filterId: , - * filterId: , - * ... - * } + * @type {Map.} */ - this.filters = {}; + this.filters = new Map(); /** * Subfilters link table @@ -89,11 +81,7 @@ class Storage { * @example * { * index: { - * collection: { - * subfilterId: , - * subfilterId: , - * ... - * } + * collection: Map. * } * } */ @@ -109,11 +97,7 @@ class Storage { * @example * { * index: { - * collection: { - * conditionId: , - * conditionId: , - * ... - * } + * collection: Map. * } * } */ @@ -128,37 +112,11 @@ class Storage { * @example * { * index: { - * collection: { - * [operandName]: { - * - * } - * } + * collection: Map> * } * } */ this.foPairs = {}; - - /** - * Contains reference tables. - * Each time a document is to be matched against registered - * filters, the corresponding reference tables is duplicated - * and is used to keep track of validated conditions and - * filters - * - * The filterIds table is used to link the validated filters - * table with their corresponding ids, to minimize object - * lookups when building large sets of matching filter IDs - * - * @type {object} - * - * @example - * { - * index: { - * collection: - * } - * } - */ - this.testTables = {}; } /** @@ -193,42 +151,34 @@ class Storage { * @param {string} filterId */ remove (filterId) { - if (!this.filters[filterId]) { + const filter = this.filters.get(filterId); + + if (!filter) { return; } - const {index, collection} = this.filters[filterId]; - - this._removeFromTestTables(index, collection, this.filters[filterId]); - - let i; // NOSONAR - - for (i = 0; i < this.filters[filterId].subfilters.length; i++) { - const subfilter = this.filters[filterId].subfilters[i]; - - if (subfilter.filters.length === 1) { - let j; // NOSONAR - for (j = 0; j < subfilter.conditions.length; j++) { - const condition = subfilter.conditions[j]; + const {index, collection} = filter; + for (const subfilter of filter.subfilters) { + if (subfilter.filters.size === 1) { + for (const condition of subfilter.conditions) { this.removeOperand[condition.keyword](this.foPairs, index, collection, subfilter, condition); - if (condition.subfilters.length === 1) { + if (condition.subfilters.size === 1) { Storage.destroy(this.conditions, index, collection, condition.id); - } - else { - condition.subfilters.splice(condition.subfilters.indexOf(subfilter), 1); + } else { + condition.subfilters.delete(subfilter); } } Storage.destroy(this.subfilters, index, collection, subfilter.id); } else { - subfilter.filters.splice(subfilter.filters.indexOf(this.filters[filterId]), 1); + subfilter.filters.delete(filter); } } - if (this.filtersIndex[index][collection].length === 1) { + if (this.filtersIndex[index][collection].size === 1) { if (containsOne(this.filtersIndex[index])) { delete this.filtersIndex[index]; } @@ -237,19 +187,10 @@ class Storage { } } else { - this.filtersIndex[index][collection].splice(this.filtersIndex[index][collection].indexOf(filterId), 1); + this.filtersIndex[index][collection].delete(filterId); } - delete this.filters[filterId]; - } - - reset () { - this.filtersIndex = {}; - this.filters = {}; - this.subfilters = {}; - this.conditions = {}; - this.foPairs = {}; - this.testTables = {}; + this.filters.delete(filterId); } /** @@ -284,39 +225,28 @@ class Storage { for(i = 0; i < filters.length; i++) { const sf = filters[i], - sfResult = this._addSubfilter(this.filters[result.id], sf); + sfResult = this._addSubfilter(this.filters.get(result.id), sf); if (sfResult.created) { const - subfilter = this.subfilters[index][collection][sfResult.id], + subfilter = this.subfilters[index][collection].get(sfResult.id), addedConditions = this._addConditions(subfilter, index, collection, sf); - this._addTestTables(subfilter, index, collection); Storage.addIndexCollectionToObject(this.foPairs, index, collection); let j; // NOSONAR for(j = 0; j < addedConditions.length; j++) { const cond = addedConditions[j]; - if (!this.foPairs[index][collection][cond.keyword]) { - this.foPairs[index][collection][cond.keyword] = new FieldOperand(); + if (!this.foPairs[index][collection].has(cond.keyword)) { + this.foPairs[index][collection].set(cond.keyword, new FieldOperand()); } - this.storeOperand[cond.keyword](this.foPairs[index][collection][cond.keyword], subfilter, cond); + this.storeOperand[cond.keyword](this.foPairs[index][collection].get(cond.keyword), subfilter, cond); } } } - // ref https://github.com/kuzzleio/kuzzle/issues/740 - const filter = this.filters[result.id]; - if (filter.fidx === -1 - && this.testTables[index] - && this.testTables[index][collection] - ) { - filter.fidx = this.testTables[index][collection].filtersCount; - this.testTables[index][collection].filtersCount++; - } - return response; } @@ -329,13 +259,11 @@ class Storage { */ _addFiltersIndex (index, collection, id) { if (!this.filtersIndex[index]) { - this.filtersIndex[index] = {[collection]: [id]}; - } - else if (!this.filtersIndex[index][collection]) { - this.filtersIndex[index][collection] = [id]; - } - else { - this.filtersIndex[index][collection].push(id); + this.filtersIndex[index] = {[collection]: new Set([id])}; + } else if (!this.filtersIndex[index][collection]) { + this.filtersIndex[index][collection] = new Set([id]); + } else { + this.filtersIndex[index][collection].add(id); } } @@ -353,10 +281,10 @@ class Storage { _addFilter (index, collection, filters, filterId = null) { const id = filterId || this.getFilterId(index, collection, filters), - created = !this.filters[id]; + created = !this.filters.has(id); if (created) { - this.filters[id] = new Filter(id, index, collection, filters); + this.filters.set(id, new Filter(id, index, collection, filters)); } return {created, id}; @@ -379,16 +307,16 @@ class Storage { Storage.addIndexCollectionToObject(this.subfilters, filter.index, filter.collection); - if (this.subfilters[filter.index][filter.collection][sfId]) { - const sfRef = this.subfilters[filter.index][filter.collection][sfId]; - + const sfRef = this.subfilters[filter.index][filter.collection].get(sfId); + if (sfRef) { created = false; - sfRef.filters.push(filter); - filter.subfilters.push(sfRef); + sfRef.filters.add(filter); + filter.subfilters.add(sfRef); } else { - this.subfilters[filter.index][filter.collection][sfId] = new Subfilter(sfId, filter); - filter.subfilters.push(this.subfilters[filter.index][filter.collection][sfId]); + const sfObj = new Subfilter(sfId, filter); + this.subfilters[filter.index][filter.collection].set(sfId, sfObj); + filter.subfilters.add(sfObj); } return {created, id: sfId}; @@ -422,160 +350,27 @@ class Storage { const cond = conditions[i], cId = this.hash(cond), - condLink = this.conditions[index][collection][cId]; + condLink = this.conditions[index][collection].get(cId); if (condLink) { - if (condLink.subfilters.indexOf(subfilter) === -1) { - condLink.subfilters.push(subfilter); - subfilter.conditions.push(condLink); + if (!condLink.subfilters.has(subfilter)) { + condLink.subfilters.add(subfilter); + subfilter.conditions.add(condLink); diff.push(condLink); } } else { - const keyword = Object.keys(cond).filter(k => k !== 'not')[0]; - - this.conditions[index][collection][cId] = new Condition(cId, subfilter, cond.not ? 'not' + keyword : keyword, cond[keyword]); - subfilter.conditions.push(this.conditions[index][collection][cId]); - diff.push(this.conditions[index][collection][cId]); - } - } - - return diff; - } - - /** - * Updates the test tables with a new subfilter - * - * @param {object} subfilter to be added - * @param {string} index - * @param {string} collection - */ - _addTestTables (subfilter, index, collection) { - if (!this.testTables[index]) { - this.testTables[index] = {}; - } - - if (!this.testTables[index][collection]) { - this.testTables[index][collection] = new TestTable(subfilter); - } - else { - this.testTables[index][collection].addSubfilter(subfilter); - } - } - - /** - * Removes a filter from test tables and rebuilds the structure if necessary - * - * @param {string} index - * @param {string} collection - * @param {object} filter - filter to be removed - */ - _removeFromTestTables (index, collection, filter) { - const tt = this.testTables[index][collection]; - - tt.removedFilters.add(filter.id); - - if (tt.removedFilters.size === tt.filtersCount) { - if (containsOne(this.testTables[index])) { - delete this.testTables[index]; - } - else { - delete this.testTables[index][collection]; - } - - return; - } - - // Declaring "i" inside the "for" statement downgrades - // performances by a factor of 3 to 4 - // Should be fixed in later V8 versions - // (tested on Node 6.9.x) - let i; // NOSONAR - - for(i = 0; i < filter.subfilters.length; i++) { - if (filter.subfilters[i].filters.length === 1) { - tt.removedConditions.add(filter.subfilters[i].cidx); - } - } - - /* - Perform a reindex only if the number of deleted conditions is greater - than 10% of the total number of registered conditions - - In that case, we flag the reindex operation and launch it a few seconds - after that. This allows large unsubscriptions activity to finish. - Best case scenario: the test table is removed altogether, thus avoiding reindexation. - Worst case scenario: we still have a reindexation to perform, but luckily - including a lot more unsubscriptions to remove, greatly reducing the overall reindex cost - */ - if (!tt.reindexing && tt.removedConditions.size > tt.clength / 10) { - tt.reindexing = true; - setTimeout(() => this._reindexTestTable(index, collection), 5000); - } - } - - /** - * Performs a reindexation of a test table - * - * @param {string} index - * @param {string} collection - */ - _reindexTestTable (index, collection) { - /* - * Since the reindexation has been triggered, the test table &/|| filters might have been - * destroyed - */ - if (!this.testTables[index] || !this.testTables[index][collection] || !this.testTables[index][collection].reindexing) { - return; - } - if (!this.filtersIndex[index]) { - delete this.testTables[index]; - return; - } - if (!this.filtersIndex[index][collection]) { - delete this.testTables[index][collection]; - return; - } - - const tt = this.testTables[index][collection]; - - // rebuild conditions index - { - const - conditionKeys = Object.keys(this.subfilters[index][collection]), - conditions = new Uint8Array(tt.conditions.length); - - let i; // perf cf https://jsperf.com/bvidis-for-oddities - NOSONAR - for (i = 0; i < conditionKeys.length; i++) { const - key = conditionKeys[i], - sf = this.subfilters[index][collection][key]; + keyword = Object.keys(cond).filter(k => k !== 'not')[0], + condObj = new Condition(cId, subfilter, cond.not ? 'not' + keyword : keyword, cond[keyword]); - conditions[i] = sf.conditions.length; - sf.cidx = i; + this.conditions[index][collection].set(cId, condObj); + subfilter.conditions.add(condObj); + diff.push(condObj); } - - tt.conditions = conditions; - tt.clength = conditionKeys.length; } - // rebuild filter index pointers - if (tt.removedFilters.size > 0) { - let i; // perf cf https://jsperf.com/bvidis-for-oddities - NOSONAR - for (i = 0; i < this.filtersIndex[index][collection].length; i++) { - const - filterId = this.filtersIndex[index][collection][i], - filter = this.filters[filterId]; - - filter.fidx = i; - } - } - - // updates the test table indicators - tt.filtersCount = this.filtersIndex[index][collection].length; - tt.removedFilters.clear(); - tt.removedConditions.clear(); - tt.reindexing = false; + return diff; } /** @@ -589,10 +384,10 @@ class Storage { */ static addIndexCollectionToObject (obj, index, collection) { if (!obj[index]) { - obj[index] = { [collection]: {} }; + obj[index] = { [collection]: new Map() }; } else if (!obj[index][collection]) { - obj[index][collection] = {}; + obj[index][collection] = new Map(); } } @@ -607,7 +402,7 @@ class Storage { * @param {string} field */ static destroy (obj, index, collection, field) { - if (containsOne(obj[index][collection])) { + if (obj[index][collection].size === 1) { if (containsOne(obj[index])) { delete obj[index]; } @@ -616,10 +411,9 @@ class Storage { } } else { - delete obj[index][collection][field]; + obj[index][collection].delete(field); } } - } module.exports = Storage; diff --git a/lib/storage/objects/condition.js b/lib/storage/objects/condition.js index 44473c7..1be38ad 100644 --- a/lib/storage/objects/condition.js +++ b/lib/storage/objects/condition.js @@ -44,7 +44,7 @@ class Condition { constructor(id, subfilter, keyword, value) { this.id = id; - this.subfilters = [subfilter]; + this.subfilters = new Set([subfilter]); this.keyword = keyword; this.value = value; } diff --git a/lib/storage/objects/filter.js b/lib/storage/objects/filter.js index c9f3b75..75e54f0 100644 --- a/lib/storage/objects/filter.js +++ b/lib/storage/objects/filter.js @@ -35,7 +35,6 @@ * @property {string} collection * @property {Array>} filters in their canonical form * @property {Array} subfilters - * @property {number} fidx - maps to the filters test table * * @param {string} id - filter unique id * @param {string} index @@ -48,8 +47,7 @@ class Filter { this.index = index; this.collection = collection; this.filters = filters; - this.subfilters = []; - this.fidx = -1; + this.subfilters = new Set(); } } diff --git a/lib/storage/objects/rangeCondition.js b/lib/storage/objects/rangeCondition.js new file mode 100644 index 0000000..54e92f1 --- /dev/null +++ b/lib/storage/objects/rangeCondition.js @@ -0,0 +1,73 @@ +/* + * Kuzzle, a backend software, self-hostable and ready to use + * to power modern apps + * + * Copyright 2015-2017 Kuzzle + * mailto: support AT kuzzle.io + * website: http://kuzzle.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +/** + * Describe either a range or a not-range condition + * + * @class RangeCondition + * @param subfilter + * @param condition + */ +class RangeCondition { + constructor(subfilter, condition) { + this.subfilters = new Set([subfilter]); + this.not = condition.keyword === 'notrange'; + + /* + Initializes lower and upper bounds depending on condition arguments + As the interval tree library used only considers inclusive boundaries, + we need to add or substract an epsilon value to provided arguments + for lt and gt options. + */ + this.low = -Infinity; + this.high = Infinity; + + const + field = Object.keys(condition.value)[0], + args = condition.value[field]; + + for (const key of Object.keys(args)) { + if (key === 'gt' || key === 'gte') { + this.low = args[key]; + + if (this.not && key === 'gte') { + this.low -= 1e-10; + } else if (!this.not && key === 'gt') { + this.low += 1e-10; + } + } + + if (key === 'lt' || key === 'lte') { + this.high = args[key]; + + if (this.not && key === 'lte') { + this.high += 1e-10; + } else if (!this.not && key === 'lt') { + this.high -= 1e-10; + } + } + } + } +} + +module.exports = RangeCondition; diff --git a/lib/storage/objects/regexpCondition.js b/lib/storage/objects/regexpCondition.js index 2f20ff7..088b261 100644 --- a/lib/storage/objects/regexpCondition.js +++ b/lib/storage/objects/regexpCondition.js @@ -39,7 +39,7 @@ class RegexpCondition { constructor(pattern, subfilter, flags) { this.regexp = new RegExp(pattern, flags); this.stringValue = this.regexp.toString(); - this.subfilters = [subfilter]; + this.subfilters = new Set([subfilter]); } } diff --git a/lib/storage/objects/subfilter.js b/lib/storage/objects/subfilter.js index bdff2db..c8b83e3 100644 --- a/lib/storage/objects/subfilter.js +++ b/lib/storage/objects/subfilter.js @@ -33,7 +33,6 @@ * @property {string} id * @property {Array} filters * @property {Array} conditions - * @property {number} cidx - maps to the condition counts test table * * @param {string} id - subfilter unique id * @param {Filter} filter - filter referring to this subfilter @@ -41,9 +40,8 @@ class Subfilter { constructor(id, filter) { this.id = id; - this.filters = [filter]; - this.conditions = []; - this.cidx = -1; + this.filters = new Set([filter]); + this.conditions = new Set(); } } diff --git a/lib/storage/objects/testTable.js b/lib/storage/objects/testTable.js deleted file mode 100644 index fd2e032..0000000 --- a/lib/storage/objects/testTable.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Kuzzle, a backend software, self-hostable and ready to use - * to power modern apps - * - * Copyright 2015-2017 Kuzzle - * mailto: support AT kuzzle.io - * website: http://kuzzle.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -'use strict'; - -/** - * Creates a test table entry. Mutates the provided subfilter object - * and its associated filters, in order to update their index - * references. - * - * This allows V8 to convert this object to a pure - * C++ class, with direct access to its members, - * instead of a dictionary with b-search access time - * - * @class TestTable - * @param {object} subfilter - * @constructor - */ -class TestTable { - constructor(subfilter) { - this.clength = 0; - this.conditions = new Uint8Array(10); - this.removedConditions = new Set(); - this.removedFilters = new Set(); - this.reindexing = false; - - this.conditions[this.clength] = subfilter.conditions.length; - this.clength++; - this.filtersCount = subfilter.filters.length; - - subfilter.cidx = 0; - - let i; // NOSONAR - for (i = 0; i < subfilter.filters.length; i++) { - subfilter.filters[i].fidx = i; - } - } - - /** - * Adds a subfilter to this test table. Mutates the provided subfilter object. - * @param subfilter - */ - addSubfilter(subfilter) { - if (subfilter.cidx === -1) { - subfilter.cidx = this.clength; - - if (this.clength >= this.conditions.length) { - const tmp = new Uint8Array(this.clength + Math.floor(this.clength / 2)); - tmp.set(this.conditions, 0); - this.conditions = tmp; - } - - this.conditions[this.clength] = subfilter.conditions.length; - this.clength++; - - let i; // NOSONAR - for(i = 0; i < subfilter.filters.length; i++) { - if (subfilter.filters[i].fidx === -1) { - subfilter.filters[i].fidx = this.filtersCount; - this.filtersCount++; - } - } - } - } -} - -/** - * @type {TestTable} - */ -module.exports = TestTable; diff --git a/lib/storage/removeOperands.js b/lib/storage/removeOperands.js index d770a57..94433de 100644 --- a/lib/storage/removeOperands.js +++ b/lib/storage/removeOperands.js @@ -58,11 +58,11 @@ class OperandsRemoval { const fieldName = Object.keys(condition.value)[0], value = condition.value[fieldName], - operand = foPairs[index][collection].equals, + operand = foPairs[index][collection].get('equals'), entries = operand.fields[fieldName].get(value); - if (entries && entries.length > 1) { - entries.splice(entries.indexOf(subfilter), 1); + if (entries && entries.size > 1) { + entries.delete(subfilter); } else if (operand.fields[fieldName].size > 1) { operand.fields[fieldName].delete(value); } else if (operand.keys.size > 1) { @@ -87,11 +87,11 @@ class OperandsRemoval { const fieldName = Object.keys(condition.value)[0], value = condition.value[fieldName], - operand = foPairs[index][collection].notequals, + operand = foPairs[index][collection].get('notequals'), entries = operand.fields[fieldName].get(value); - if (entries && entries.length > 1) { - entries.splice(entries.indexOf(subfilter), 1); + if (entries && entries.size > 1) { + entries.delete(subfilter); } else if (operand.fields[fieldName].size > 1) { operand.fields[fieldName].delete(value); } else if (operand.keys.size > 1) { @@ -116,21 +116,21 @@ class OperandsRemoval { exists(foPairs, index, collection, subfilter, condition, keyword = 'exists') { const {path, value, array} = condition.value, - operand = foPairs[index][collection][keyword]; + operand = foPairs[index][collection].get(keyword); if (!array) { - operand.fields[path].subfilters.splice(operand.fields[path].subfilters.indexOf(subfilter), 1); + operand.fields[path].subfilters.delete(subfilter); } else { const entries = operand.fields[path].values.get(value); - if (entries.length > 1) { - entries.splice(entries.indexOf(subfilter), 1); + if (entries.size > 1) { + entries.delete(subfilter); } else { operand.fields[path].values.delete(value); } } - if (operand.fields[path].subfilters.length === 0 && operand.fields[path].values.size === 0) { + if (operand.fields[path].subfilters.size === 0 && operand.fields[path].values.size === 0) { if (operand.keys.size > 1) { operand.keys.delete(path); delete operand.fields[path]; @@ -177,19 +177,16 @@ class OperandsRemoval { */ range(foPairs, index, collection, subfilter, condition) { const - operand = foPairs[index][collection].range, - field = Object.keys(condition.value)[0]; + operand = foPairs[index][collection].get('range'), + field = Object.keys(condition.value)[0], + rangeCondition = operand.fields[field].conditions.get(condition.id); - if (operand.fields[field].count > 1) { - const info = operand.fields[field].subfilters[subfilter.id][condition.id]; - - operand.fields[field].tree.remove(info.low, info.high, info.subfilter); - operand.fields[field].count--; - - if (Object.keys(operand.fields[field].subfilters[subfilter.id]).length === 1) { - delete operand.fields[field].subfilters[subfilter.id]; + if (operand.fields[field].conditions.size > 1 || rangeCondition.subfilters.size > 1) { + if (rangeCondition.subfilters.size > 1) { + rangeCondition.subfilters.delete(subfilter); } else { - delete operand.fields[field].subfilters[subfilter.id][condition.id]; + operand.fields[field].tree.remove(rangeCondition.low, rangeCondition.high, rangeCondition); + operand.fields[field].conditions.delete(condition.id); } } else if (operand.keys.size > 1) { operand.keys.delete(field); @@ -211,30 +208,27 @@ class OperandsRemoval { */ notrange(foPairs, index, collection, subfilter, condition) { const - operand = foPairs[index][collection].notrange, - field = Object.keys(condition.value)[0]; - - if (operand.fields[field].count > 1) { - const info = operand.fields[field].subfilters[subfilter.id][condition.id]; + operand = foPairs[index][collection].get('notrange'), + field = Object.keys(condition.value)[0], + rangeCondition = operand.fields[field].conditions.get(condition.id); - if (info.low !== -Infinity) { - operand.fields[field].tree.remove(-Infinity, info.low, info.subfilter); - } - - if (info.high !== Infinity) { - operand.fields[field].tree.remove(info.high, Infinity, info.subfilter); - } + if (operand.fields[field].conditions.size > 1 || rangeCondition.subfilters.size > 1) { + if (rangeCondition.subfilters.size > 1) { + rangeCondition.subfilters.delete(subfilter); + } else { + if (rangeCondition.low !== -Infinity) { + operand.fields[field].tree.remove(-Infinity, rangeCondition.low, rangeCondition); + } - operand.fields[field].count--; + if (rangeCondition.high !== Infinity) { + operand.fields[field].tree.remove(rangeCondition.high, Infinity, rangeCondition); + } - if (Object.keys(operand.fields[field].subfilters[subfilter.id]).length === 1) { - delete operand.fields[field].subfilters[subfilter.id]; - } else { - delete operand.fields[field].subfilters[subfilter.id][condition.id]; + operand.fields[field].conditions.delete(condition.id); } } else if (operand.keys.size > 1) { - delete operand.fields[field]; operand.keys.delete(field); + delete operand.fields[field]; } else { destroy(foPairs, index, collection, 'notrange'); } @@ -255,11 +249,11 @@ class OperandsRemoval { const fieldName = Object.keys(condition.value)[0], stringValue = (new RegExp(condition.value[fieldName].value, condition.value[fieldName].flags)).toString(), - operand = foPairs[index][collection][keyword], + operand = foPairs[index][collection].get(keyword), regexpCondition = operand.fields[fieldName].get(stringValue); - if (regexpCondition.subfilters.length > 1) { - regexpCondition.subfilters.splice(regexpCondition.subfilters.indexOf(subfilter), 1); + if (regexpCondition.subfilters.size > 1) { + regexpCondition.subfilters.delete(subfilter); } else if (operand.fields[fieldName].size > 1) { operand.fields[fieldName].delete(stringValue); } else if (operand.keys.size > 1) { @@ -294,23 +288,25 @@ class OperandsRemoval { * @param {object} subfilter * @param {object} condition */ - geospatial(foPairs, index, collection, subfilter, condition) { + geospatial(foPairs, index, collection, subfilter, condition, keyword = 'geospatial') { const - operand = foPairs[index][collection].geospatial, + operand = foPairs[index][collection].get(keyword), geotype = Object.keys(condition.value)[0], fieldName = Object.keys(condition.value[geotype])[0]; - if (operand.fields[fieldName][condition.id].length > 1) { - operand.fields[fieldName][condition.id].splice(operand.fields[fieldName][condition.id].indexOf(subfilter), 1); - } else if (Object.keys(operand.fields[fieldName]).length > 1) { - delete operand.fields[fieldName][condition.id]; + const subfilters = operand.fields[fieldName].get(condition.id); + + if (subfilters.size > 1) { + subfilters.delete(subfilter); + } else if (operand.fields[fieldName].size > 1) { + operand.fields[fieldName].delete(condition.id); operand.custom.index.remove(condition.id); } else if (operand.keys.size > 1) { delete operand.fields[fieldName]; operand.keys.delete(fieldName); operand.custom.index.remove(condition.id); } else { - destroy(foPairs, index, collection, 'geospatial'); + destroy(foPairs, index, collection, keyword); } } @@ -325,22 +321,7 @@ class OperandsRemoval { * @param {object} condition */ notgeospatial(foPairs, index, collection, subfilter, condition) { - const - operand = foPairs[index][collection].notgeospatial, - geotype = Object.keys(condition.value)[0], - fieldName = Object.keys(condition.value[geotype])[0], - entries = operand.fields[fieldName].get(condition.id); - - if (entries && entries.length > 1) { - entries.splice(entries.indexOf(subfilter), 1); - } else if (operand.fields[fieldName].size > 1) { - operand.fields[fieldName].delete(condition.id); - } else if (operand.keys.size > 1) { - operand.keys.delete(fieldName); - delete operand.fields[fieldName]; - } else { - destroy(foPairs, index, collection, 'notgeospatial'); - } + this.geospatial(foPairs, index, collection, subfilter, condition, 'notgeospatial'); } } @@ -353,14 +334,14 @@ class OperandsRemoval { * @param operand */ function destroy(foPairs, index, collection, operand) { - if (containsOne(foPairs[index][collection])) { + if (foPairs[index][collection].size === 1) { if (containsOne(foPairs[index])) { delete foPairs[index]; } else { delete foPairs[index][collection]; } }else { - delete foPairs[index][collection][operand]; + foPairs[index][collection].delete(operand); } } diff --git a/lib/storage/storeOperands.js b/lib/storage/storeOperands.js index ab04f70..80415e1 100644 --- a/lib/storage/storeOperands.js +++ b/lib/storage/storeOperands.js @@ -25,6 +25,7 @@ import IntervalTree from 'node-interval-tree'; const RegexpCondition = require('./objects/regexpCondition'), + RangeCondition = require('./objects/rangeCondition'), BoostSpatialIndex = require('boost-geospatial-index'); /** @@ -64,11 +65,11 @@ class OperandsStorage { if (!foPairs.fields[fieldName]) { foPairs.keys.add(fieldName); - foPairs.fields[fieldName] = new Map([[value, [subfilter]]]); + foPairs.fields[fieldName] = new Map([[value, new Set([subfilter])]]); } else if ((entries = foPairs.fields[fieldName].get(value)) === undefined) { - foPairs.fields[fieldName].set(value, [subfilter]); + foPairs.fields[fieldName].set(value, new Set([subfilter])); } else { - entries.push(subfilter); + entries.add(subfilter); } } @@ -96,7 +97,7 @@ class OperandsStorage { if (!foPairs.fields[path]) { foPairs.keys.add(path); foPairs.fields[path] = { - subfilters: [], + subfilters: new Set(), values: new Map() }; } @@ -104,11 +105,11 @@ class OperandsStorage { let entries; if (!condition.value.array) { - foPairs.fields[path].subfilters.push(subfilter); + foPairs.fields[path].subfilters.add(subfilter); } else if ((entries = foPairs.fields[path].values.get(value)) !== undefined) { - entries.push(subfilter); + entries.add(subfilter); } else { - foPairs.fields[path].values.set(value, [subfilter]); + foPairs.fields[path].values.set(value, new Set([subfilter])); } } @@ -139,53 +140,25 @@ class OperandsStorage { range(foPairs, subfilter, condition) { const field = Object.keys(condition.value)[0], - args = condition.value[field]; - let - low = -Infinity, - high = Infinity; - - /* - Initializes low and high values depending on condition arguments - As the interval tree library used only considers inclusive boundaries, - we need to add or substract an epsilon value to provided arguments - for lt and gt options. - */ - const keys = Object.keys(args); - let i; // NOSONAR - - for(i = 0; i < keys.length; i++) { - const a = keys[i]; - - if (['gt', 'gte'].indexOf(a) !== -1) { - low = a === 'gt' ? args[a] + 1e-10 : args[a]; - } + rangeCondition = new RangeCondition(subfilter, condition); - if (['lt', 'lte'].indexOf(a) !== -1) { - high = a === 'lt' ? args[a] - 1e-10 : args[a]; - } - } + let entry; if (!foPairs.fields[field]) { foPairs.keys.add(field); foPairs.fields[field] = { tree: new IntervalTree(), - count: 1, - subfilters: { - [subfilter.id]: { - [condition.id]: {subfilter, low, high} - } - } + conditions: new Map([[condition.id, rangeCondition]]) }; - } - else { - if (!foPairs.fields[field].subfilters[subfilter.id]) { - foPairs.fields[field].subfilters[subfilter.id] = {}; - } - foPairs.fields[field].subfilters[subfilter.id][condition.id] = {subfilter, low, high}; - foPairs.fields[field].count++; + } else if ((entry = foPairs.fields[field].conditions.get(condition.id)) !== undefined) { + entry.subfilters.add(subfilter); + } else { + foPairs.fields[field].conditions.set(condition.id, rangeCondition); } - foPairs.fields[field].tree.insert(low, high, subfilter); + if (!entry) { + foPairs.fields[field].tree.insert(rangeCondition.low, rangeCondition.high, rangeCondition); + } } /** @@ -201,12 +174,6 @@ class OperandsStorage { * (boundaries are also reversed: inclusive boundaries become * exclusive, and vice-versa) * - * Matching is then executed exactly like for "range" conditions. - * This does not hurt performances as searches in the interval tree - * are in O(log n) - * - * (kudos to @asendra for this neat trick) - * * @param {object} foPairs * @param {object} subfilter * @param {object} condition @@ -214,60 +181,30 @@ class OperandsStorage { notrange(foPairs, subfilter, condition) { const field = Object.keys(condition.value)[0], - args = condition.value[field]; - let - low = -Infinity, - high = Infinity; - - /* - Initializes low and high values depending on condition arguments - As the interval tree library used only considers inclusive boundaries, - we need to add or substract an epsilon value to provided arguments - for lte and gte options - This is the reverse operation than the one done for the "range" - keyword, as we then invert the searched range. - */ - const keys = Object.keys(args); - let i; // NOSONAR - - for(i = 0; i < keys.length; i++) { - const a = keys[i]; - - if (['gt', 'gte'].indexOf(a) !== -1) { - low = a === 'gte' ? args[a] - 1e-10 : args[a]; - } + rangeCondition = new RangeCondition(subfilter, condition); - if (['lt', 'lte'].indexOf(a) !== -1) { - high = a === 'lte' ? args[a] + 1e-10 : args[a]; - } - } + let entry; if (!foPairs.fields[field]) { foPairs.keys.add(field); foPairs.fields[field] = { tree: new IntervalTree(), - count: 1, - subfilters: { - [subfilter.id]: { - [condition.id]: {subfilter, low, high} - } - } + conditions: new Map([[condition.id, rangeCondition]]) }; - } - else { - if (!foPairs.fields[field].subfilters[subfilter.id]) { - foPairs.fields[field].subfilters[subfilter.id] = {}; - } - foPairs.fields[field].subfilters[subfilter.id][condition.id] = {subfilter, low, high}; - foPairs.fields[field].count++; + } else if ((entry = foPairs.fields[field].conditions.get(condition.id)) !== undefined) { + entry.subfilters.add(subfilter); + } else { + foPairs.fields[field].conditions.set(condition.id, rangeCondition); } - if (low !== -Infinity) { - foPairs.fields[field].tree.insert(-Infinity, low, subfilter); - } + if (!entry) { + if (rangeCondition.low !== -Infinity) { + foPairs.fields[field].tree.insert(-Infinity, rangeCondition.low, rangeCondition); + } - if (high !== Infinity) { - foPairs.fields[field].tree.insert(high, Infinity, subfilter); + if (rangeCondition.high !== Infinity) { + foPairs.fields[field].tree.insert(rangeCondition.high, Infinity, rangeCondition); + } } } @@ -288,7 +225,7 @@ class OperandsStorage { foPairs.keys.add(fieldName); foPairs.fields[fieldName] = new Map([[value.stringValue, value]]); } else if ((entry = foPairs.fields[fieldName].get(value.stringValue)) !== undefined) { - entry.subfilters.push(subfilter); + entry.subfilters.add(subfilter); } else { foPairs.fields[fieldName].set(value.stringValue, value); } @@ -322,20 +259,18 @@ class OperandsStorage { foPairs.custom.index = new BoostSpatialIndex(); } + let subfilters; + if (!foPairs.fields[fieldName]) { foPairs.keys.add(fieldName); - foPairs.fields[fieldName] = { - [condition.id]: [subfilter] - }; - } - else if (foPairs.fields[fieldName][condition.id]) { - foPairs.fields[fieldName][condition.id].push(subfilter); + foPairs.fields[fieldName] = new Map([[condition.id, new Set([subfilter])]]); + } else if ((subfilters = foPairs.fields[fieldName].get(condition.id)) !== undefined) { + subfilters.add(subfilter); // skip the shape insertion in the geospatial index return; - } - else { - foPairs.fields[fieldName][condition.id] = [subfilter]; + } else { + foPairs.fields[fieldName].set(condition.id, new Set([subfilter])); } storeGeoshape(foPairs.custom.index, geotype, condition.id, value); @@ -349,29 +284,7 @@ class OperandsStorage { * @param {object} condition */ notgeospatial(foPairs, subfilter, condition) { - const - geotype = Object.keys(condition.value)[0], - fieldName = Object.keys(condition.value[geotype])[0], - value = condition.value[geotype][fieldName]; - let entries; - - if (!foPairs.custom.index) { - foPairs.custom.index = new BoostSpatialIndex(); - } - - if (!foPairs.fields[fieldName]) { - foPairs.keys.add(fieldName); - foPairs.fields[fieldName] = new Map([[condition.id, [subfilter]]]); - } else if ((entries = foPairs.fields[fieldName].get(condition.id)) !== undefined) { - entries.push(subfilter); - - // skip the shape insertion in the geospatial index - return; - } else { - foPairs.fields[fieldName].set(condition.id, [subfilter]); - } - - storeGeoshape(foPairs.custom.index, geotype, condition.id, value); + this.geospatial(foPairs, subfilter, condition); } } diff --git a/test/api.test.js b/test/api.test.js index eb8a2d7..f07b642 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -144,12 +144,19 @@ describe('DSL API', () => { return dsl.register('i', 'c', {equals: {foo: 'bar'}}); }) .then(subscription => { - let sfs = dsl.storage.filters[subscription.id].subfilters; + const sfs = dsl.storage.filters.get(subscription.id).subfilters; ids.push(subscription.id); - should(sfs).be.an.Array(); - should(sfs.length).be.eql(1); - should(dsl.storage.subfilters.i.c[sfs[0].id].filters.map(f => f.id).sort()).match(ids.sort()); + should(sfs).instanceOf(Set); + should(sfs.size).be.eql(1); + + const filterIds = []; + + for (const sf of sfs.values()) { + sf.filters.forEach(f => filterIds.push(f.id)); + } + + should(filterIds.sort()).match(ids.sort()); }); }); }); @@ -275,20 +282,24 @@ describe('DSL API', () => { return dsl.register('i', 'c', {equals: {foo: 'bar'}}); }) .then(subscription => { - const sfs = dsl.storage.filters[subscription.id].subfilters; + const sfs = dsl.storage.filters.get(subscription.id).subfilters; ids.push(subscription.id); - should(sfs).be.an.Array(); - should(sfs.length).be.eql(1); - should(sfs[0].filters.length).be.eql(2); - should(sfs[0].filters.map(f => f.id).sort()).match(Array.from(ids).sort()); + should(sfs).instanceOf(Set); + should(sfs.size).be.eql(1); + + sf = Array.from(sfs.values())[0]; + should(sf.filters.size).be.eql(2); + + const filterIds = Array.from(sf.filters).map(f => f.id); + should(filterIds.sort()).match(Array.from(ids).sort()); - sf = sfs[0]; return dsl.remove(subscription.id); }) .then(() => { - should(sf.filters.length).be.eql(1); - should(dsl.storage.subfilters.i.c[sf.id].filters.map(f => f.id)).match([ids[0]]); + should(sf.filters.size).be.eql(1); + const fid = Array.from(sf.filters)[0].id; + should(fid).match(ids[0]); }); }); }); diff --git a/test/keywords/equals.test.js b/test/keywords/equals.test.js index 845d464..dd63dbd 100644 --- a/test/keywords/equals.test.js +++ b/test/keywords/equals.test.js @@ -13,6 +13,10 @@ describe('DSL.keyword.equals', () => { dsl = new DSL(); }); + function getSubfilter(id) { + return Array.from(dsl.storage.filters.get(id).subfilters)[0]; + } + describe('#validation', () => { it('should reject empty filters', () => { return should(dsl.validate({equals: ['foo', 'bar']})).be.rejectedWith(BadRequestError); @@ -65,12 +69,14 @@ describe('DSL.keyword.equals', () => { it('should store a single condition correctly', () => { return dsl.register('index', 'collection', {equals: {foo: 'bar'}}) .then(subscription => { - let subfilter = dsl.storage.filters[subscription.id].subfilters[0]; - - should(dsl.storage.foPairs.index.collection.equals).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.equals.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.equals.fields.foo).instanceOf(Map); - should(dsl.storage.foPairs.index.collection.equals.fields.foo.get('bar')).eql([subfilter]); + const + subfilter = getSubfilter(subscription.id), + storage = dsl.storage.foPairs.index.collection.get('equals'); + + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo).instanceOf(Map); + should(storage.fields.foo.get('bar')).eql(new Set([subfilter])); }); }); @@ -79,18 +85,18 @@ describe('DSL.keyword.equals', () => { return dsl.register('index', 'collection', {equals: {foo: 'bar'}}) .then(subscription => { - barSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + barSubfilter = getSubfilter(subscription.id); return dsl.register('index', 'collection', {equals: {foo: 'qux'}}); }) .then(subscription => { - const quxSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; - const equals = dsl.storage.foPairs.index.collection.equals; + const quxSubfilter = getSubfilter(subscription.id); + const equals = dsl.storage.foPairs.index.collection.get('equals'); should(equals).be.an.instanceof(FieldOperand); should(equals.keys).eql(new Set(['foo'])); - should(equals.fields.foo.get('bar')).eql([barSubfilter]); - should(equals.fields.foo.get('qux')).eql([quxSubfilter]); + should(equals.fields.foo.get('bar')).eql(new Set([barSubfilter])); + should(equals.fields.foo.get('qux')).eql(new Set([quxSubfilter])); }); }); @@ -99,18 +105,18 @@ describe('DSL.keyword.equals', () => { return dsl.register('index', 'collection', {equals: {foo: 'bar'}}) .then(subscription => { - barSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + barSubfilter = getSubfilter(subscription.id); return dsl.register('index', 'collection', {and: [{equals: {baz: 'qux'}}, {equals: {foo: 'bar'}}]}); }) .then(subscription => { - const multiSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; - const equals = dsl.storage.foPairs.index.collection.equals; + const multiSubfilter = getSubfilter(subscription.id); + const equals = dsl.storage.foPairs.index.collection.get('equals'); should(equals).be.an.instanceof(FieldOperand); should(equals.keys).eql(new Set(['foo', 'baz'])); - should(equals.fields.foo.get('bar')).eql([barSubfilter, multiSubfilter]); - should(equals.fields.baz.get('qux')).eql([multiSubfilter]); + should(equals.fields.foo.get('bar')).eql(new Set([barSubfilter, multiSubfilter])); + should(equals.fields.baz.get('qux')).eql(new Set([multiSubfilter])); }); }); }); @@ -237,46 +243,42 @@ describe('DSL.keyword.equals', () => { }); }) .then(subscription => { - multiSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + multiSubfilter = getSubfilter(subscription.id); return dsl.remove(idToRemove); }) .then(() => { - const equals = dsl.storage.foPairs.index.collection.equals; + const equals = dsl.storage.foPairs.index.collection.get('equals'); should(equals).be.an.instanceof(FieldOperand); should(equals.keys).eql(new Set(['foo', 'baz'])); - should(equals.fields.foo.get('bar')).eql([multiSubfilter]); - should(equals.fields.baz.get('qux')).eql([multiSubfilter]); + should(equals.fields.foo.get('bar')).eql(new Set([multiSubfilter])); + should(equals.fields.baz.get('qux')).eql(new Set([multiSubfilter])); }); }); it('should remove a value from the list if its last subfilter is removed', () => { let - idToRemove, + equals, barSubfilter; return dsl.register('index', 'collection', {equals: {foo: 'bar'}}) .then(subscription => { - barSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + equals = dsl.storage.foPairs.index.collection.get('equals'); - return dsl.register('index', 'collection', { - and: [ - {equals: {baz: 'qux'}}, - {equals: {foo: 'bar'}} - ] - }); + barSubfilter = getSubfilter(subscription.id); + + return dsl.register('index', 'collection', {equals: {foo: 'qux'}}); }) .then(subscription => { - idToRemove = subscription.id; - return dsl.remove(idToRemove); + should(equals.fields.foo.get('bar')).eql(new Set([barSubfilter])); + should(equals.fields.foo.get('qux')).eql(new Set([getSubfilter(subscription.id)])); + return dsl.remove(subscription.id); }) .then(() => { - const equals = dsl.storage.foPairs.index.collection.equals; - should(equals).be.an.instanceof(FieldOperand); should(equals.keys).eql(new Set(['foo'])); - should(equals.fields.foo.get('bar')).eql([barSubfilter]); + should(equals.fields.foo.get('bar')).eql(new Set([barSubfilter])); should(equals.fields.foo.get('qux')).be.undefined(); }); }); @@ -289,24 +291,38 @@ describe('DSL.keyword.equals', () => { return dsl.register('index', 'collection', {equals: {foo: 'bar'}}) .then(subscription => { - barSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + barSubfilter = getSubfilter(subscription.id); return dsl.register('index', 'collection', {equals: {baz: 'qux'}}); }) .then(subscription => { - equals = dsl.storage.foPairs.index.collection.equals; + equals = dsl.storage.foPairs.index.collection.get('equals'); should(equals.keys).eql(new Set(['foo', 'baz'])); - should(equals.fields.baz.get('qux')).be.an.Array().and.not.empty(); + should(equals.fields.baz.get('qux')).eql(new Set([getSubfilter(subscription.id)])); idToRemove = subscription.id; return dsl.remove(idToRemove); }) .then(() => { should(equals).be.an.instanceof(FieldOperand); should(equals.keys).eql(new Set(['foo'])); - should(equals.fields.foo.get('bar')).eql([barSubfilter]); + should(equals.fields.foo.get('bar')).eql(new Set([barSubfilter])); should(equals.fields.baz).be.undefined(); }); }); + + it('should remove a single collection if other collections are registered', () => { + return dsl.register('index', 'collection', {equals: {foo: 'bar'}}) + .then(() => dsl.register('index', 'collection2', {equals: {foo: 'bar'}})) + .then(subscription => { + should(dsl.storage.foPairs.index.collection).not.undefined(); + should(dsl.storage.foPairs.index.collection2).not.undefined(); + return dsl.remove(subscription.id); + }) + .then(() => { + should(dsl.storage.foPairs.index.collection).not.undefined(); + should(dsl.storage.foPairs.index.collection2).undefined(); + }); + }); }); }); diff --git a/test/keywords/everything.test.js b/test/keywords/everything.test.js index 2d8d3e2..216be41 100644 --- a/test/keywords/everything.test.js +++ b/test/keywords/everything.test.js @@ -30,12 +30,12 @@ describe('DSL.keyword.everything', () => { it('should register an empty filter correctly', () => { return dsl.register('index', 'collection', {}) .then(subscription => { - const storeEntry = dsl.storage.foPairs.index.collection.everything; + const storeEntry = dsl.storage.foPairs.index.collection.get('everything'); should(storeEntry) .be.instanceof(FieldOperand); should(storeEntry.fields.all) - .eql([dsl.storage.filters[subscription.id].subfilters[0]]); + .eql(Array.from(dsl.storage.filters.get(subscription.id).subfilters)); }); }); }); diff --git a/test/keywords/exists.test.js b/test/keywords/exists.test.js index a127a37..fd24ad8 100644 --- a/test/keywords/exists.test.js +++ b/test/keywords/exists.test.js @@ -116,12 +116,12 @@ describe('DSL.keyword.exists', () => { return dsl.register('index', 'collection', {exists: 'foo'}) .then(subscription => { const - subfilter = dsl.storage.filters[subscription.id].subfilters[0], - storage = dsl.storage.foPairs.index.collection.exists; + subfilter = dsl.storage.filters.get(subscription.id).subfilters, + storage = dsl.storage.foPairs.index.collection.get('exists'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.subfilters).eql([subfilter]); + should(storage.fields.foo.subfilters).eql(subfilter); should(storage.fields.foo.values).instanceOf(Map); should(storage.fields.foo.values.size).eql(0); }); @@ -132,7 +132,7 @@ describe('DSL.keyword.exists', () => { return dsl.register('index', 'collection', {exists: 'foo'}) .then(subscription => { - barSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + barSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', { and: [ @@ -143,12 +143,12 @@ describe('DSL.keyword.exists', () => { }) .then(subscription => { const - quxSubfilter = dsl.storage.filters[subscription.id].subfilters[0], - storage = dsl.storage.foPairs.index.collection.exists; + quxSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('exists'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.subfilters).eql([barSubfilter, quxSubfilter]); + should(storage.fields.foo.subfilters).eql(new Set([barSubfilter, quxSubfilter])); should(storage.fields.foo.values).instanceOf(Map); should(storage.fields.foo.values.size).eql(0); }); @@ -158,15 +158,15 @@ describe('DSL.keyword.exists', () => { return dsl.register('index', 'collection', {exists: 'foo["bar"]'}) .then(subscription => { const - subfilter = dsl.storage.filters[subscription.id].subfilters[0], - storage = dsl.storage.foPairs.index.collection.exists; + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('exists'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.subfilters).Array().and.empty(); + should(storage.fields.foo.subfilters.size).eql(0); should(storage.fields.foo.values).instanceOf(Map); should(storage.fields.foo.values.size).eql(1); - should(storage.fields.foo.values.get('bar')).eql([subfilter]); + should(storage.fields.foo.values.get('bar')).eql(new Set([subfilter])); }); }); @@ -175,7 +175,7 @@ describe('DSL.keyword.exists', () => { return dsl.register('index', 'collection', {exists: 'foo["bar"]'}) .then(subscription => { - barSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + barSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', { and: [ @@ -186,16 +186,16 @@ describe('DSL.keyword.exists', () => { }) .then(subscription => { const - quxSubfilter = dsl.storage.filters[subscription.id].subfilters[0], - storage = dsl.storage.foPairs.index.collection.exists; + quxSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('exists'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo', 'qux'])); - should(storage.fields.foo.subfilters).Array().and.empty(); + should(storage.fields.foo.subfilters.size).eql(0); should(storage.fields.foo.values).instanceOf(Map); should(storage.fields.foo.values.size).eql(1); - should(storage.fields.foo.values.get('bar')).eql([barSubfilter, quxSubfilter]); - should(storage.fields.qux.values.get('bar')).eql([quxSubfilter]); + should(storage.fields.foo.values.get('bar')).eql(new Set([barSubfilter, quxSubfilter])); + should(storage.fields.qux.values.get('bar')).eql(new Set([quxSubfilter])); }); }); }); @@ -289,41 +289,75 @@ describe('DSL.keyword.exists', () => { }); }) .then(subscription => { - multiSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + multiSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.remove(idToRemove); }) .then(() => { - const storage = dsl.storage.foPairs.index.collection.exists; + const storage = dsl.storage.foPairs.index.collection.get('exists'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.subfilters).match([multiSubfilter]); + should(storage.fields.foo.subfilters).match(new Set([multiSubfilter])); should(storage.fields.foo.values).be.instanceOf(Map); should(storage.fields.foo.values.size).eql(0); }); }); + it('should remove a single subfilter from a multi-filter array condition', () => { + let + storage, + idToRemove, + singleSubfilter, + multiSubfilter; + + return dsl.register('index', 'collection', {exists: 'foo["bar"]'}) + .then(subscription => { + idToRemove = subscription.id; + storage = dsl.storage.foPairs.index.collection.get('exists'); + singleSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + + return dsl.register('index', 'collection', { + and: [ + {equals: {foo: 'qux'}}, + {exists: {field: 'foo["bar"]'}} + ] + }); + }) + .then(subscription => { + multiSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + should(storage.fields.foo.values.get('bar')).match(new Set([singleSubfilter, multiSubfilter])); + should(storage.fields.foo.subfilters.size).eql(0); + return dsl.remove(idToRemove); + }) + .then(() => { + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.subfilters.size).eql(0); + should(storage.fields.foo.values.get('bar')).match(new Set([multiSubfilter])); + }); + }); + it('should remove a field from the list if its last subfilter is removed', () => { let fooSubfilter; return dsl.register('index', 'collection', {exists: 'foo'}) .then(subscription => { - fooSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + fooSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', {exists: 'bar'}); }) .then(subscription => { - should(dsl.storage.foPairs.index.collection.exists.keys).eql(new Set(['foo', 'bar'])); + should(dsl.storage.foPairs.index.collection.get('exists').keys).eql(new Set(['foo', 'bar'])); return dsl.remove(subscription.id); }) .then(() => { - const storage = dsl.storage.foPairs.index.collection.exists; + const storage = dsl.storage.foPairs.index.collection.get('exists'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.subfilters).match([fooSubfilter]); + should(storage.fields.foo.subfilters).match(new Set([fooSubfilter])); should(storage.fields.foo.values).be.instanceOf(Map); should(storage.fields.foo.values.size).eql(0); should(storage.fields.bar).be.undefined(); @@ -336,17 +370,17 @@ describe('DSL.keyword.exists', () => { return dsl.register('index', 'collection', {exists: 'foo'}) .then(subscription => { - fooSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + fooSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', {exists: 'bar["foo"]'}); }) .then(subscription => dsl.remove(subscription.id)) .then(() => { - const storage = dsl.storage.foPairs.index.collection.exists; + const storage = dsl.storage.foPairs.index.collection.get('exists'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.subfilters).match([fooSubfilter]); + should(storage.fields.foo.subfilters).match(new Set([fooSubfilter])); should(storage.fields.foo.values).be.instanceOf(Map); should(storage.fields.foo.values.size).eql(0); should(storage.fields.bar).be.undefined(); @@ -359,17 +393,17 @@ describe('DSL.keyword.exists', () => { return dsl.register('index', 'collection', {exists: 'foo'}) .then(subscription => { - fooSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + fooSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', {exists: 'foo["bar"]'}); }) .then(subscription => dsl.remove(subscription.id)) .then(() => { - const storage = dsl.storage.foPairs.index.collection.exists; + const storage = dsl.storage.foPairs.index.collection.get('exists'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.subfilters).match([fooSubfilter]); + should(storage.fields.foo.subfilters).match(new Set([fooSubfilter])); should(storage.fields.foo.values).be.instanceOf(Map); should(storage.fields.foo.values.size).eql(0); }); @@ -381,20 +415,20 @@ describe('DSL.keyword.exists', () => { return dsl.register('index', 'collection', {exists: 'foo["bar"]'}) .then(subscription => { - fooSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + fooSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', {exists: 'foo'}); }) .then(subscription => dsl.remove(subscription.id)) .then(() => { - const storage = dsl.storage.foPairs.index.collection.exists; + const storage = dsl.storage.foPairs.index.collection.get('exists'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.subfilters).Array().empty(); + should(storage.fields.foo.subfilters.size).eql(0); should(storage.fields.foo.values).be.instanceOf(Map); should(storage.fields.foo.values.size).eql(1); - should(storage.fields.foo.values.get('bar')).eql([fooSubfilter]); + should(storage.fields.foo.values.get('bar')).eql(new Set([fooSubfilter])); }); }); }); diff --git a/test/keywords/geoBoundingBox.test.js b/test/keywords/geoBoundingBox.test.js index eade96b..b26596e 100644 --- a/test/keywords/geoBoundingBox.test.js +++ b/test/keywords/geoBoundingBox.test.js @@ -9,7 +9,8 @@ const describe('DSL.keyword.geoBoundingBox', () => { let dsl, - standardize, + standardize; + const bbox = { topLeft: { lat: 43.6331979, lon: 3.8433703 }, bottomRight: { lat: 43.5810609, lon: 3.9282093 } @@ -42,7 +43,7 @@ describe('DSL.keyword.geoBoundingBox', () => { }); it('should validate a {top, left, bottom, right} bbox', () => { - let box = { + const box = { bottom: 43.5810609, left: 3.8433703, top: 43.6331979, @@ -53,7 +54,7 @@ describe('DSL.keyword.geoBoundingBox', () => { }); it('should validate a {"top", "left", "bottom", "right"} bbox', () => { - let box = { + const box = { bottom: '43.5810609', left: '3.8433703', top: '43.6331979', @@ -68,7 +69,7 @@ describe('DSL.keyword.geoBoundingBox', () => { }); it('should validate a {top_left: {lat, lon}, bottom_right: {lat, lon}} bbox', () => { - let box = { + const box = { top_left: { lat: 43.6331979, lon: 3.8433703 }, bottom_right: { lat: 43.5810609, lon: 3.9282093 } }; @@ -77,7 +78,7 @@ describe('DSL.keyword.geoBoundingBox', () => { }); it('should validate a {topLeft: [lat, lon], bottomRight: [lat, lon]} bbox', () => { - let box = { + const box = { topLeft: [43.6331979, 3.8433703], bottomRight: [43.5810609, 3.9282093] }; @@ -86,7 +87,7 @@ describe('DSL.keyword.geoBoundingBox', () => { }); it('should validate a {top_left: [lat, lon], bottom_right: [lat, lon]} bbox', () => { - let box = { + const box = { top_left: [43.6331979, 3.8433703], bottom_right: [43.5810609, 3.9282093] }; @@ -95,7 +96,7 @@ describe('DSL.keyword.geoBoundingBox', () => { }); it('should validate a {topLeft: "lat, lon", bottomRight: "lat, lon" bbox', () => { - let box = { + const box = { topLeft: '43.6331979, 3.8433703', bottomRight: '43.5810609, 3.9282093' }; @@ -104,7 +105,7 @@ describe('DSL.keyword.geoBoundingBox', () => { }); it('should validate a {top_left: "lat, lon", bottom_right: "lat, lon" bbox', () => { - let box = { + const box = { top_left: '43.6331979, 3.8433703', bottom_right: '43.5810609, 3.9282093' }; @@ -112,10 +113,10 @@ describe('DSL.keyword.geoBoundingBox', () => { return should(standardize({geoBoundingBox: {foo: box}})).be.fulfilledWith(bboxStandardized); }); - it('should validate a {topLeft: "geohash", bottomRight: "geohash"} bbox', (done) => { - standardize({geoBoundingBox: {foo: {topLeft: 'spf8prntv18e', bottomRight: 'spdzcmsqjft4'}}}) + it('should validate a {topLeft: "geohash", bottomRight: "geohash"} bbox', () => { + return standardize({geoBoundingBox: {foo: {topLeft: 'spf8prntv18e', bottomRight: 'spdzcmsqjft4'}}}) .then(result => { - let box = bboxStandardized.geospatial.geoBoundingBox.foo; + const box = bboxStandardized.geospatial.geoBoundingBox.foo; should(result).be.an.Object(); should(result.geospatial).be.an.Object(); @@ -125,15 +126,13 @@ describe('DSL.keyword.geoBoundingBox', () => { should(result.geospatial.geoBoundingBox.foo.bottom).be.approximately(box.bottom, 10e-7); should(result.geospatial.geoBoundingBox.foo.left).be.approximately(box.left, 10e-7); should(result.geospatial.geoBoundingBox.foo.right).be.approximately(box.right, 10e-7); - done(); - }) - .catch(e => done(e)); + }); }); - it('should validate a {top_left: "geohash", bottom_right: "geohash"} bbox', (done) => { - standardize({geoBoundingBox: {foo: {top_left: 'spf8prntv18e', bottom_right: 'spdzcmsqjft4'}}}) + it('should validate a {top_left: "geohash", bottom_right: "geohash"} bbox', () => { + return standardize({geoBoundingBox: {foo: {top_left: 'spf8prntv18e', bottom_right: 'spdzcmsqjft4'}}}) .then(result => { - let box = bboxStandardized.geospatial.geoBoundingBox.foo; + const box = bboxStandardized.geospatial.geoBoundingBox.foo; should(result).be.an.Object(); should(result.geospatial).be.an.Object(); @@ -143,13 +142,11 @@ describe('DSL.keyword.geoBoundingBox', () => { should(result.geospatial.geoBoundingBox.foo.bottom).be.approximately(box.bottom, 10e-7); should(result.geospatial.geoBoundingBox.foo.left).be.approximately(box.left, 10e-7); should(result.geospatial.geoBoundingBox.foo.right).be.approximately(box.right, 10e-7); - done(); - }) - .catch(e => done(e)); + }); }); it('should reject an unrecognized bbox format', () => { - let box = { + const box = { top_left: '40.73 / -74.1', bottom_right: '40.01 / -71.12' }; @@ -158,7 +155,7 @@ describe('DSL.keyword.geoBoundingBox', () => { }); it('should reject a non-convertible bbox point string', () => { - let box = { + const box = { bottom: '43.5810609', left: '3.8433703', top: '43.6331979', @@ -173,11 +170,13 @@ describe('DSL.keyword.geoBoundingBox', () => { it('should store a single geoBoundingBox correctly', () => { return dsl.register('index', 'collection', {geoBoundingBox: {foo: bbox}}) .then(subscription => { - let subfilter = dsl.storage.filters[subscription.id].subfilters[0]; + const + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[subfilter.conditions[0].id]).match([subfilter]); + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(Array.from(subfilter.conditions)[0].id)).match(new Set([subfilter])); }); }); @@ -185,15 +184,17 @@ describe('DSL.keyword.geoBoundingBox', () => { let sf1; return dsl.register('index', 'collection', {geoBoundingBox: {foo: bbox}}) .then(subscription => { - sf1 = dsl.storage.filters[subscription.id].subfilters[0]; + sf1 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', {and: [{geoBoundingBox: {foo: bbox}}, {equals: {foo: 'bar'}}]}); }) .then(subscription => { - let sf2 = dsl.storage.filters[subscription.id].subfilters[0]; + const + sf2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[sf1.conditions[0].id]).match([sf1, sf2]); + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(Array.from(sf1.conditions)[0].id)).match(new Set([sf1, sf2])); }); }); @@ -202,17 +203,19 @@ describe('DSL.keyword.geoBoundingBox', () => { return dsl.register('index', 'collection', {geoBoundingBox: {foo: bbox}}) .then(subscription => { - sf1 = dsl.storage.filters[subscription.id].subfilters[0]; - cond1 = sf1.conditions[0].id; + sf1 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + cond1 = Array.from(sf1.conditions)[0].id; return dsl.register('index', 'collection', {geoBoundingBox: {foo: {topLeft: 'dr5r9ydj2y73', bottomRight: 'drj7teegpus6'}}}); }) .then(subscription => { - let sf2 = dsl.storage.filters[subscription.id].subfilters[0]; - - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[cond1]).match([sf1]); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[sf2.conditions[0].id]).match([sf2]); + const + sf2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); + + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(cond1)).match(new Set([sf1])); + should(storage.fields.foo.get(Array.from(sf2.conditions)[0].id)).match(new Set([sf2])); }); }); }); @@ -221,47 +224,43 @@ describe('DSL.keyword.geoBoundingBox', () => { it('should match a point inside the bbox', () => { return dsl.register('index', 'collection', {geoBoundingBox: {foo: bbox}}) .then(subscription => { - var result = dsl.test('index', 'collection', {foo: {latLon: [43.6073913, 3.9109057]}}); + const result = dsl.test('index', 'collection', {foo: {latLon: [43.6073913, 3.9109057]}}); - should(result).be.an.Array().and.not.empty(); - should(result[0]).be.eql(subscription.id); + should(result).eql([subscription.id]); }); }); it('should convert points to float before trying to match them', () => { return dsl.register('index', 'collection', {geoBoundingBox: {foo: bbox}}) .then(subscription => { - var result = dsl.test('index', 'collection', {foo: {latLon: ['43.6073913', '3.9109057']}}); + const result = dsl.test('index', 'collection', {foo: {latLon: ['43.6073913', '3.9109057']}}); - should(result).be.an.Array().and.not.empty(); - should(result[0]).be.eql(subscription.id); + should(result).eql([subscription.id]); }); }); it('should match a point exactly on a bbox corner', () => { return dsl.register('index', 'collection', {geoBoundingBox: {foo: bbox}}) .then(subscription => { - var result = dsl.test('index', 'collection', {foo: bbox.topLeft}); + const result = dsl.test('index', 'collection', {foo: bbox.topLeft}); - should(result).be.an.Array().and.not.empty(); - should(result[0]).be.eql(subscription.id); + should(result).eql([subscription.id]); }); }); it('should match a point on one of the bbox border', () => { return dsl.register('index', 'collection', {geoBoundingBox: {foo: bbox}}) .then(subscription => { - var result = dsl.test('index', 'collection', {foo: {lat: bbox.topLeft.lat, lon: 3.9}}); + const result = dsl.test('index', 'collection', {foo: {lat: bbox.topLeft.lat, lon: 3.9}}); - should(result).be.an.Array().and.not.empty(); - should(result[0]).be.eql(subscription.id); + should(result).eql([subscription.id]); }); }); it('should not match if a point is outside the bbox', () => { return dsl.register('index', 'collection', {geoBoundingBox: {foo: bbox}}) .then(() => { - var result = dsl.test('index', 'collection', {foo: {lat: bbox.topLeft.lat + 10e-6, lon: 3.9}}); + const result = dsl.test('index', 'collection', {foo: {lat: bbox.topLeft.lat + 10e-6, lon: 3.9}}); should(result).be.an.Array().and.be.empty(); }); @@ -270,7 +269,7 @@ describe('DSL.keyword.geoBoundingBox', () => { it('should return an empty array if the document does not contain a geopoint', () => { return dsl.register('index', 'collection', {geoBoundingBox: {foo: bbox}}) .then(() => { - var result = dsl.test('index', 'collection', {bar: {lat: bbox.topLeft.lat + 10e-6, lon: 3.9}}); + const result = dsl.test('index', 'collection', {bar: {lat: bbox.topLeft.lat + 10e-6, lon: 3.9}}); should(result).be.an.Array().and.be.empty(); }); @@ -279,7 +278,7 @@ describe('DSL.keyword.geoBoundingBox', () => { it('should return an empty array if the document contain an invalid geopoint', () => { return dsl.register('index', 'collection', {geoBoundingBox: {foo: bbox}}) .then(() => { - var result = dsl.test('index', 'collection', {foo: '43.6331979 / 3.8433703'}); + const result = dsl.test('index', 'collection', {foo: '43.6331979 / 3.8433703'}); should(result).be.an.Array().and.be.empty(); }); diff --git a/test/keywords/geoDistance.test.js b/test/keywords/geoDistance.test.js index 0f815b6..0302e83 100644 --- a/test/keywords/geoDistance.test.js +++ b/test/keywords/geoDistance.test.js @@ -9,7 +9,8 @@ const describe('DSL.keyword.geoDistance', () => { let dsl, - standardize, + standardize; + const point = { lat: 43.6331979, lon: 3.8433703 }, distanceStandardized = { geospatial: { @@ -71,10 +72,10 @@ describe('DSL.keyword.geoDistance', () => { return should(standardize({geoDistance: {foo: p, distance: '1km'}})).be.fulfilledWith(distanceStandardized); }); - it('should validate a {latLon: "geohash"} point', (done) => { - let p = {latLon: 'spf8prntv18e'}; + it('should validate a {latLon: "geohash"} point', () => { + const p = {latLon: 'spf8prntv18e'}; - standardize({geoDistance: {foo: p, distance: '1km'}}) + return standardize({geoDistance: {foo: p, distance: '1km'}}) .then(result => { should(result).be.an.Object(); should(result.geospatial).be.an.Object(); @@ -83,15 +84,13 @@ describe('DSL.keyword.geoDistance', () => { should(result.geospatial.geoDistance.foo.distance).be.eql(1000); should(result.geospatial.geoDistance.foo.lat).be.approximately(point.lat, 10e-7); should(result.geospatial.geoDistance.foo.lon).be.approximately(point.lon, 10e-7); - done(); - }) - .catch(e => done(e)); + }); }); - it('should validate a {lat_lon: "geohash"} point', (done) => { - let p = {lat_lon: 'spf8prntv18e'}; + it('should validate a {lat_lon: "geohash"} point', () => { + const p = {lat_lon: 'spf8prntv18e'}; - standardize({geoDistance: {foo: p, distance: '1km'}}) + return standardize({geoDistance: {foo: p, distance: '1km'}}) .then(result => { should(result).be.an.Object(); should(result.geospatial).be.an.Object(); @@ -100,23 +99,21 @@ describe('DSL.keyword.geoDistance', () => { should(result.geospatial.geoDistance.foo.distance).be.eql(1000); should(result.geospatial.geoDistance.foo.lat).be.approximately(point.lat, 10e-7); should(result.geospatial.geoDistance.foo.lon).be.approximately(point.lon, 10e-7); - done(); - }) - .catch(e => done(e)); + }); }); it('should reject an unrecognized point format', () => { - let p = {foo: 'bar'}; + const p = {foo: 'bar'}; return should(standardize({geoDistance: {foo: p, distance: '1km'}})).be.rejectedWith(BadRequestError); }); it('should reject an invalid latLon argument type', () => { - let p = {latLon: 42}; + const p = {latLon: 42}; return should(standardize({geoDistance: {foo: p, distance: '1km'}})).be.rejectedWith(BadRequestError); }); it('should reject an invalid latLon argument string', () => { - let p = {latLon: '[10, 10]'}; + const p = {latLon: '[10, 10]'}; return should(standardize({geoDistance: {foo: p, distance: '1km'}})).be.rejectedWith(BadRequestError); }); @@ -133,11 +130,13 @@ describe('DSL.keyword.geoDistance', () => { it('should store a single geoDistance correctly', () => { return dsl.register('index', 'collection', {geoDistance: {foo: point, distance: '1km'}}) .then(subscription => { - let subfilter = dsl.storage.filters[subscription.id].subfilters[0]; + const + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[subfilter.conditions[0].id]).match([subfilter]); + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(Array.from(subfilter.conditions)[0].id)).match(new Set([subfilter])); }); }); @@ -145,15 +144,17 @@ describe('DSL.keyword.geoDistance', () => { let sf1; return dsl.register('index', 'collection', {geoDistance: {foo: point, distance: '1km'}}) .then(subscription => { - sf1 = dsl.storage.filters[subscription.id].subfilters[0]; + sf1 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', {and: [{geoDistance: {foo: point, distance: '1km'}}, {equals: {foo: 'bar'}}]}); }) .then(subscription => { - let sf2 = dsl.storage.filters[subscription.id].subfilters[0]; + const + sf2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[sf1.conditions[0].id]).match([sf1, sf2]); + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(Array.from(sf1.conditions)[0].id)).match(new Set([sf1, sf2])); }); }); @@ -162,17 +163,19 @@ describe('DSL.keyword.geoDistance', () => { return dsl.register('index', 'collection', {geoDistance: {foo: point, distance: '1km'}}) .then(subscription => { - sf1 = dsl.storage.filters[subscription.id].subfilters[0]; - cond1 = sf1.conditions[0].id; + sf1 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + cond1 = Array.from(sf1.conditions)[0].id; return dsl.register('index', 'collection', {geoDistance: {foo: point, distance: '10km'}}); }) .then(subscription => { - let sf2 = dsl.storage.filters[subscription.id].subfilters[0]; - - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[cond1]).match([sf1]); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[sf2.conditions[0].id]).match([sf2]); + const + sf2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); + + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(cond1)).match(new Set([sf1])); + should(storage.fields.foo.get(Array.from(sf2.conditions)[0].id)).match(new Set([sf2])); }); }); }); @@ -181,17 +184,16 @@ describe('DSL.keyword.geoDistance', () => { it('should match a point inside the circle', () => { return dsl.register('index', 'collection', {geoDistance: {foo: point, distance: '1km'}}) .then(subscription => { - var result = dsl.test('index', 'collection', {foo: {lat: 43.634, lon: 3.8432 }}); + const result = dsl.test('index', 'collection', {foo: {lat: 43.634, lon: 3.8432 }}); - should(result).be.an.Array().and.not.empty(); - should(result[0]).be.eql(subscription.id); + should(result).eql([subscription.id]); }); }); it('should not match if a point is outside the circle', () => { return dsl.register('index', 'collection', {geoDistance: {foo: point, distance: '1km'}}) .then(() => { - var result = dsl.test('index', 'collection', {foo: {lat: point.lat, lon: 3.9}}); + const result = dsl.test('index', 'collection', {foo: {lat: point.lat, lon: 3.9}}); should(result).be.an.Array().and.be.empty(); }); @@ -200,7 +202,7 @@ describe('DSL.keyword.geoDistance', () => { it('should return an empty array if the document does not contain the searched geopoint', () => { return dsl.register('index', 'collection', {geoDistance: {foo: point, distance: '1km'}}) .then(() => { - var result = dsl.test('index', 'collection', {bar: point}); + const result = dsl.test('index', 'collection', {bar: point}); should(result).be.an.Array().and.be.empty(); }); @@ -209,7 +211,7 @@ describe('DSL.keyword.geoDistance', () => { it('should return an empty array if the document contain an invalid geopoint', () => { return dsl.register('index', 'collection', {geoDistance: {foo: point, distance: '1km'}}) .then(() => { - var result = dsl.test('index', 'collection', {foo: '43.6331979 / 3.8433703'}); + const result = dsl.test('index', 'collection', {foo: '43.6331979 / 3.8433703'}); should(result).be.an.Array().and.be.empty(); }); diff --git a/test/keywords/geoDistanceRange.test.js b/test/keywords/geoDistanceRange.test.js index 4887e30..bf5de0d 100644 --- a/test/keywords/geoDistanceRange.test.js +++ b/test/keywords/geoDistanceRange.test.js @@ -9,7 +9,8 @@ const describe('DSL.keyword.geoDistanceRange', () => { let dsl, - standardize, + standardize; + const point = { lat: 43.6331979, lon: 3.8433703 }, distanceStandardized = { geospatial: { @@ -43,39 +44,39 @@ describe('DSL.keyword.geoDistanceRange', () => { }); it('should validate a {latLon: [lat, lon]} point', () => { - let p = {latLon: [point.lat, point.lon]}; + const p = {latLon: [point.lat, point.lon]}; return should(standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}})).be.fulfilledWith(distanceStandardized); }); it('should validate a {lat_lon: [lat, lon]} point', () => { - let p = {lat_lon: [point.lat, point.lon]}; + const p = {lat_lon: [point.lat, point.lon]}; return should(standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}})).be.fulfilledWith(distanceStandardized); }); it('should validate a {latLon: {lat: lat, lon: lon}} point', () => { - let p = {latLon: {lat: point.lat, lon: point.lon}}; + const p = {latLon: {lat: point.lat, lon: point.lon}}; return should(standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}})).be.fulfilledWith(distanceStandardized); }); it('should validate a {lat_lon: {lat: lat, lon: lon}} point', () => { - let p = {lat_lon: {lat: point.lat, lon: point.lon}}; + const p = {lat_lon: {lat: point.lat, lon: point.lon}}; return should(standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}})).be.fulfilledWith(distanceStandardized); }); it('should validate a {latLon: "lat, lon"} point', () => { - let p = {latLon: `${point.lat}, ${point.lon}`}; + const p = {latLon: `${point.lat}, ${point.lon}`}; return should(standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}})).be.fulfilledWith(distanceStandardized); }); it('should validate a {lat_lon: "lat, lon"} point', () => { - let p = {lat_lon: `${point.lat}, ${point.lon}`}; + const p = {lat_lon: `${point.lat}, ${point.lon}`}; return should(standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}})).be.fulfilledWith(distanceStandardized); }); - it('should validate a {latLon: "geohash"} point', (done) => { - let p = {latLon: 'spf8prntv18e'}; + it('should validate a {latLon: "geohash"} point', () => { + const p = {latLon: 'spf8prntv18e'}; - standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}}) + return standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}}) .then(result => { should(result).be.an.Object(); should(result.geospatial).be.an.Object(); @@ -85,15 +86,13 @@ describe('DSL.keyword.geoDistanceRange', () => { should(result.geospatial.geoDistanceRange.foo.to).be.eql(10000); should(result.geospatial.geoDistanceRange.foo.lat).be.approximately(point.lat, 10e-7); should(result.geospatial.geoDistanceRange.foo.lon).be.approximately(point.lon, 10e-7); - done(); - }) - .catch(e => done(e)); + }); }); - it('should validate a {lat_lon: "geohash"} point', (done) => { - let p = {lat_lon: 'spf8prntv18e'}; + it('should validate a {lat_lon: "geohash"} point', () => { + const p = {lat_lon: 'spf8prntv18e'}; - standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}}) + return standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}}) .then(result => { should(result).be.an.Object(); should(result.geospatial).be.an.Object(); @@ -103,23 +102,21 @@ describe('DSL.keyword.geoDistanceRange', () => { should(result.geospatial.geoDistanceRange.foo.to).be.eql(10000); should(result.geospatial.geoDistanceRange.foo.lat).be.approximately(point.lat, 10e-7); should(result.geospatial.geoDistanceRange.foo.lon).be.approximately(point.lon, 10e-7); - done(); - }) - .catch(e => done(e)); + }); }); it('should reject an unrecognized point format', () => { - let p = {foo: 'bar'}; + const p = {foo: 'bar'}; return should(standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}})).be.rejectedWith(BadRequestError); }); it('should reject an invalid latLon argument type', () => { - let p = {latLon: 42}; + const p = {latLon: 42}; return should(standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}})).be.rejectedWith(BadRequestError); }); it('should reject an invalid latLon argument string', () => { - let p = {latLon: '[10, 10]'}; + const p = {latLon: '[10, 10]'}; return should(standardize({geoDistanceRange: {foo: p, from: '1km', to: '10km'}})).be.rejectedWith(BadRequestError); }); @@ -148,11 +145,13 @@ describe('DSL.keyword.geoDistanceRange', () => { it('should store a single geoDistanceRange correctly', () => { return dsl.register('index', 'collection', {geoDistanceRange: {foo: point, from: '1km', to: '10km'}}) .then(subscription => { - let subfilter = dsl.storage.filters[subscription.id].subfilters[0]; + const + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[subfilter.conditions[0].id]).match([subfilter]); + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(Array.from(subfilter.conditions)[0].id)).match(new Set([subfilter])); }); }); @@ -160,15 +159,17 @@ describe('DSL.keyword.geoDistanceRange', () => { let sf1; return dsl.register('index', 'collection', {geoDistanceRange: {foo: point, from: '1km', to: '10km'}}) .then(subscription => { - sf1 = dsl.storage.filters[subscription.id].subfilters[0]; + sf1 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', {and: [{geoDistanceRange: {foo: point, from: '1km', to: '10km'}}, {equals: {foo: 'bar'}}]}); }) .then(subscription => { - let sf2 = dsl.storage.filters[subscription.id].subfilters[0]; + const + sf2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[sf1.conditions[0].id]).match([sf1, sf2]); + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(Array.from(sf1.conditions)[0].id)).match(new Set([sf1, sf2])); }); }); @@ -177,17 +178,19 @@ describe('DSL.keyword.geoDistanceRange', () => { return dsl.register('index', 'collection', {geoDistanceRange: {foo: point, from: '1km', to: '10km'}}) .then(subscription => { - sf1 = dsl.storage.filters[subscription.id].subfilters[0]; - cond1 = sf1.conditions[0].id; + sf1 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + cond1 = Array.from(sf1.conditions)[0].id; return dsl.register('index', 'collection', {geoDistanceRange: {foo: point, from: '10km', to: '100km'}}); }) .then(subscription => { - let sf2 = dsl.storage.filters[subscription.id].subfilters[0]; - - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[cond1]).match([sf1]); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[sf2.conditions[0].id]).match([sf2]); + const + sf2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); + + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(cond1)).match(new Set([sf1])); + should(storage.fields.foo.get(Array.from(sf2.conditions)[0].id)).match(new Set([sf2])); }); }); }); @@ -196,17 +199,16 @@ describe('DSL.keyword.geoDistanceRange', () => { it('should match a point inside the circle', () => { return dsl.register('index', 'collection', {geoDistanceRange: {foo: point, from: '1km', to: '10km'}}) .then(subscription => { - var result = dsl.test('index', 'collection', {foo: {lat: 43.634, lon: 3.9 }}); + const result = dsl.test('index', 'collection', {foo: {lat: 43.634, lon: 3.9 }}); - should(result).be.an.Array().and.not.empty(); - should(result[0]).be.eql(subscription.id); + should(result).eql([subscription.id]); }); }); it('should not match if a point is outside the outer radius', () => { return dsl.register('index', 'collection', {geoDistanceRange: {foo: point, from: '1km', to: '10km'}}) .then(() => { - var result = dsl.test('index', 'collection', {foo: {lat: point.lat, lon: 5}}); + const result = dsl.test('index', 'collection', {foo: {lat: point.lat, lon: 5}}); should(result).be.an.Array().and.be.empty(); }); @@ -215,7 +217,7 @@ describe('DSL.keyword.geoDistanceRange', () => { it('should not match if a point is inside the inner radius', () => { return dsl.register('index', 'collection', {geoDistanceRange: {foo: point, from: '1km', to: '10km'}}) .then(() => { - var result = dsl.test('index', 'collection', {foo: point}); + const result = dsl.test('index', 'collection', {foo: point}); should(result).be.an.Array().and.be.empty(); }); @@ -224,7 +226,7 @@ describe('DSL.keyword.geoDistanceRange', () => { it('should return an empty array if the document does not contain the searched geopoint', () => { return dsl.register('index', 'collection', {geoDistanceRange: {foo: point, from: '1km', to: '10km'}}) .then(() => { - var result = dsl.test('index', 'collection', {bar: point}); + const result = dsl.test('index', 'collection', {bar: point}); should(result).be.an.Array().and.be.empty(); }); @@ -233,7 +235,7 @@ describe('DSL.keyword.geoDistanceRange', () => { it('should return an empty array if the document contain an invalid geopoint', () => { return dsl.register('index', 'collection', {geoDistanceRange: {foo: point, from: '1km', to: '10km'}}) .then(() => { - var result = dsl.test('index', 'collection', {foo: '43.6331979 / 3.8433703'}); + const result = dsl.test('index', 'collection', {foo: '43.6331979 / 3.8433703'}); should(result).be.an.Array().and.be.empty(); }); diff --git a/test/keywords/geoPolygon.test.js b/test/keywords/geoPolygon.test.js index a8730a4..3099571 100644 --- a/test/keywords/geoPolygon.test.js +++ b/test/keywords/geoPolygon.test.js @@ -9,7 +9,8 @@ const describe('DSL.keyword.geoPolygon', () => { let dsl, - standardize, + standardize; + const polygon = { points: [ {lat: 43.6021299, lon: 3.8989713}, @@ -72,13 +73,13 @@ describe('DSL.keyword.geoPolygon', () => { }); it('should reject a polygon containing an invalid point format', () => { - var p = polygon.points.slice(); + const p = polygon.points.slice(); p.push(42); return should(standardize({geoPolygon: {foo: {points: p}}})).be.rejectedWith(BadRequestError); }); - it('should standardize all geopoint types in a single points array', (done) => { - let points = { + it('should standardize all geopoint types in a single points array', () => { + const points = { points: [ {lat: 43.6021299, lon: 3.8989713}, {latLon: [43.6057389, 3.8968173]}, @@ -92,7 +93,7 @@ describe('DSL.keyword.geoPolygon', () => { ] }; - standardize({geoPolygon: {foo: points}}) + return standardize({geoPolygon: {foo: points}}) .then(result => { should(result.geospatial).be.an.Object(); should(result.geospatial.geoPolygon).be.an.Object(); @@ -102,9 +103,7 @@ describe('DSL.keyword.geoPolygon', () => { should(p[0]).be.approximately(polygonStandardized.geospatial.geoPolygon.foo[i][0], 10e-6); should(p[1]).be.approximately(polygonStandardized.geospatial.geoPolygon.foo[i][1], 10e-6); }); - done(); - }) - .catch(e => done(e)); + }); }); }); @@ -112,11 +111,13 @@ describe('DSL.keyword.geoPolygon', () => { it('should store a single geoPolygon correctly', () => { return dsl.register('index', 'collection', {geoPolygon: {foo: polygon}}) .then(subscription => { - let subfilter = dsl.storage.filters[subscription.id].subfilters[0]; + const + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[subfilter.conditions[0].id]).match([subfilter]); + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(Array.from(subfilter.conditions)[0].id)).match(new Set([subfilter])); }); }); @@ -124,15 +125,17 @@ describe('DSL.keyword.geoPolygon', () => { let sf1; return dsl.register('index', 'collection', {geoPolygon: {foo: polygon}}) .then(subscription => { - sf1 = dsl.storage.filters[subscription.id].subfilters[0]; + sf1 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', {and: [{geoPolygon: {foo: polygon}}, {equals: {foo: 'bar'}}]}); }) .then(subscription => { - let sf2 = dsl.storage.filters[subscription.id].subfilters[0]; + const + sf2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[sf1.conditions[0].id]).match([sf1, sf2]); + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(Array.from(sf1.conditions)[0].id)).match(new Set([sf1, sf2])); }); }); @@ -141,17 +144,19 @@ describe('DSL.keyword.geoPolygon', () => { return dsl.register('index', 'collection', {geoPolygon: {foo: polygon}}) .then(subscription => { - sf1 = dsl.storage.filters[subscription.id].subfilters[0]; - cond1 = sf1.conditions[0].id; + sf1 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + cond1 = Array.from(sf1.conditions)[0].id; return dsl.register('index', 'collection', {geoBoundingBox: {foo: {topLeft: 'dr5r9ydj2y73', bottomRight: 'drj7teegpus6'}}}); }) .then(subscription => { - let sf2 = dsl.storage.filters[subscription.id].subfilters[0]; - - should(dsl.storage.foPairs.index.collection.geospatial).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[cond1]).match([sf1]); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[sf2.conditions[0].id]).match([sf2]); + const + sf2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + storage = dsl.storage.foPairs.index.collection.get('geospatial'); + + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(cond1)).match(new Set([sf1])); + should(storage.fields.foo.get(Array.from(sf2.conditions)[0].id)).match(new Set([sf2])); }); }); }); @@ -160,27 +165,25 @@ describe('DSL.keyword.geoPolygon', () => { it('should match a point inside the polygon', () => { return dsl.register('index', 'collection', {geoPolygon: {foo: polygon}}) .then(subscription => { - var result = dsl.test('index', 'collection', {foo: {latLon: [43.6073913, 3.9109057]}}); + const result = dsl.test('index', 'collection', {foo: {latLon: [43.6073913, 3.9109057]}}); - should(result).be.an.Array().and.not.empty(); - should(result[0]).be.eql(subscription.id); + should(result).eql([subscription.id]); }); }); it('should match a point exactly on a polygon corner', () => { return dsl.register('index', 'collection', {geoPolygon: {foo: polygon}}) .then(subscription => { - var result = dsl.test('index', 'collection', {foo: {latLon: polygon.points[0]}}); + const result = dsl.test('index', 'collection', {foo: {latLon: polygon.points[0]}}); - should(result).be.an.Array().and.not.empty(); - should(result[0]).be.eql(subscription.id); + should(result).eql([subscription.id]); }); }); it('should not match if a point is outside the bbox', () => { return dsl.register('index', 'collection', {geoPolygon: {foo: polygon}}) .then(() => { - var result = dsl.test('index', 'collection', {foo: {lat: polygon.points[0][0] + 10e-6, lon: polygon.points[0][1] + 10e-6}}); + const result = dsl.test('index', 'collection', {foo: {lat: polygon.points[0][0] + 10e-6, lon: polygon.points[0][1] + 10e-6}}); should(result).be.an.Array().and.be.empty(); }); @@ -189,7 +192,7 @@ describe('DSL.keyword.geoPolygon', () => { it('should return an empty array if the document does not contain a geopoint', () => { return dsl.register('index', 'collection', {geoPolygon: {foo: polygon}}) .then(() => { - var result = dsl.test('index', 'collection', {bar: {latLon: polygon.points[0]}}); + const result = dsl.test('index', 'collection', {bar: {latLon: polygon.points[0]}}); should(result).be.an.Array().and.be.empty(); }); @@ -198,7 +201,7 @@ describe('DSL.keyword.geoPolygon', () => { it('should return an empty array if the document contain an invalid geopoint', () => { return dsl.register('index', 'collection', {geoPolygon: {foo: polygon}}) .then(() => { - var result = dsl.test('index', 'collection', {foo: '43.6331979 / 3.8433703'}); + const result = dsl.test('index', 'collection', {foo: '43.6331979 / 3.8433703'}); should(result).be.an.Array().and.be.empty(); }); diff --git a/test/keywords/geospatial.test.js b/test/keywords/geospatial.test.js index cfaf724..4ea795c 100644 --- a/test/keywords/geospatial.test.js +++ b/test/keywords/geospatial.test.js @@ -8,8 +8,8 @@ const * Mutualizes filter removal for all 4 geospatial keywords */ describe('DSL.keyword.geospatial', () => { - let - dsl, + let dsl; + const geoFilter = { geoBoundingBox: { foo: { @@ -39,9 +39,11 @@ describe('DSL.keyword.geospatial', () => { .then(() => dsl.register('index', 'collection', geoFilter)) .then(subscription => dsl.remove(subscription.id)) .then(() => { - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['bar'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.bar).be.an.Object(); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo).be.undefined(); + const storage = dsl.storage.foPairs.index.collection.get('geospatial'); + + should(storage.keys).eql(new Set(['bar'])); + should(storage.fields.bar).be.an.Object(); + should(storage.fields.foo).be.undefined(); }); }); @@ -50,14 +52,16 @@ describe('DSL.keyword.geospatial', () => { return dsl.register('index', 'collection', {geoDistance: {foo: {lat: 13, lon: 42}, distance: '1km'}}) .then(subscription => { - sf = dsl.storage.filters[subscription.id].subfilters[0]; - cond = sf.conditions[0].id; + sf = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + cond = Array.from(sf.conditions)[0].id; return dsl.register('index', 'collection', geoFilter); }) .then(subscription => dsl.remove(subscription.id)) .then(() => { - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[cond]).match([sf]); + const storage = dsl.storage.foPairs.index.collection.get('geospatial'); + + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(cond)).match(new Set([sf])); }); }); @@ -66,15 +70,17 @@ describe('DSL.keyword.geospatial', () => { return dsl.register('index', 'collection', geoFilter) .then(subscription => { - sf = dsl.storage.filters[subscription.id].subfilters[0]; - cond = sf.conditions[0].id; + sf = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + cond = Array.from(sf.conditions)[0].id; return dsl.register('index', 'collection', {and: [geoFilter, {exists: {field: 'bar'}}]}); }) .then(subscription => dsl.remove(subscription.id)) .then(() => { - should(dsl.storage.foPairs.index.collection.geospatial.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.geospatial.fields.foo[cond]).match([sf]); + const storage = dsl.storage.foPairs.index.collection.get('geospatial'); + + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(cond)).match(new Set([sf])); }); }); }); diff --git a/test/keywords/notequals.test.js b/test/keywords/notequals.test.js index e4c10d4..c513c32 100644 --- a/test/keywords/notequals.test.js +++ b/test/keywords/notequals.test.js @@ -31,28 +31,25 @@ describe('DSL.keyword.notequals', () => { it('should match if the document contains the field with another value', () => { return dsl.register('index', 'collection', {not: {equals: {foo: 'bar'}}}) .then(subscription => { - let result = dsl.test('index', 'collection', {foo: 'qux'}); - should(result).be.an.Array().and.not.empty(); - should(result).match([subscription.id]); + const result = dsl.test('index', 'collection', {foo: 'qux'}); + should(result).eql([subscription.id]); }); }); it('should match if the document do not contain the registered field', () => { return dsl.register('index', 'collection', {not: {equals: {foo: 'bar'}}}) .then(subscription => { - let result = dsl.test('index', 'collection', {qux: 'bar'}); - should(result).be.an.Array().and.not.empty(); - should(result).match([subscription.id]); + const result = dsl.test('index', 'collection', {qux: 'bar'}); + should(result).eql([subscription.id]); }); }); it('should match a document with the subscribed nested keyword', () => { return dsl.register('index', 'collection', {not: {equals: {'foo.bar.baz': 'qux'}}}) .then(subscription => { - var result = dsl.test('index', 'collection', {foo: {bar: {baz: 'foobar'}}}); + const result = dsl.test('index', 'collection', {foo: {bar: {baz: 'foobar'}}}); - should(result).be.an.Array().and.not.empty(); - should(result[0]).be.eql(subscription.id); + should(result).be.eql([subscription.id]); }); }); @@ -121,8 +118,7 @@ describe('DSL.keyword.notequals', () => { it('should remove a single subfilter from a multi-filter condition', () => { let idToRemove, - barSubfilter, - quxSubfilter; + subfilter; return dsl.register('index', 'collection', {not: {equals: {foo: 'bar'}}}) .then(subscription => { @@ -130,19 +126,19 @@ describe('DSL.keyword.notequals', () => { return dsl.register('index', 'collection', {and: [{not: {equals: {foo: 'qux'}}}, {not: {equals: {foo: 'bar'}}}]}); }) .then(subscription => { - barSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; - quxSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.remove(idToRemove); }) .then(() => { - const storage = dsl.storage.foPairs.index.collection.notequals; + const storage = dsl.storage.foPairs.index.collection.get('notequals'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); should(storage.fields.foo).instanceOf(Map); + should(storage.fields.foo.size).eql(2); - should(storage.fields.foo.get('bar')).eql([barSubfilter]); - should(storage.fields.foo.get('qux')).eql([quxSubfilter]); + should(storage.fields.foo.get('bar')).eql(new Set([subfilter])); + should(storage.fields.foo.get('qux')).eql(new Set([subfilter])); }); }); @@ -151,16 +147,16 @@ describe('DSL.keyword.notequals', () => { return dsl.register('index', 'collection', {not: {equals: {foo: 'bar'}}}) .then(subscription => { - barSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + barSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', {and: [{not: {equals: {foo: 'qux'}}}, {not: {equals: {foo: 'bar'}}}]}); }) .then(subscription => dsl.remove(subscription.id)) .then(() => { - const storage = dsl.storage.foPairs.index.collection.notequals; + const storage = dsl.storage.foPairs.index.collection.get('notequals'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.get('bar')).match([barSubfilter]); + should(storage.fields.foo.get('bar')).match(new Set([barSubfilter])); should(storage.fields.foo.get('qux')).undefined(); }); }); @@ -170,19 +166,19 @@ describe('DSL.keyword.notequals', () => { return dsl.register('index', 'collection', {not: {equals: {foo: 'bar'}}}) .then(subscription => { - barSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; + barSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', {not: {equals: {baz: 'qux'}}}); }) .then(subscription => { - should(dsl.storage.foPairs.index.collection.notequals.keys).eql(new Set(['foo', 'baz'])); + should(dsl.storage.foPairs.index.collection.get('notequals').keys).eql(new Set(['foo', 'baz'])); return dsl.remove(subscription.id); }) .then(() => { - const storage = dsl.storage.foPairs.index.collection.notequals; + const storage = dsl.storage.foPairs.index.collection.get('notequals'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.get('bar')).match([barSubfilter]); + should(storage.fields.foo.get('bar')).match(new Set([barSubfilter])); should(storage.fields.baz).be.undefined(); }); }); diff --git a/test/keywords/notgeospatial.test.js b/test/keywords/notgeospatial.test.js index a670cf1..2d1c744 100644 --- a/test/keywords/notgeospatial.test.js +++ b/test/keywords/notgeospatial.test.js @@ -25,16 +25,16 @@ describe('DSL.keyword.notgeospatial', () => { return dsl.register('index', 'collection', {not: {geoDistance: {foo: {lat: 13, lon: 42}, distance: '1000m'}}}) .then(subscription => { const - storage = dsl.storage.foPairs.index.collection.notgeospatial, - id = dsl.storage.filters[subscription.id].subfilters[0].conditions[0].id, - subfilter = dsl.storage.filters[subscription.id].subfilters[0]; + storage = dsl.storage.foPairs.index.collection.get('notgeospatial'), + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + id = Array.from(subfilter.conditions)[0].id; should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); should(storage.custom.index).be.an.Object(); should(storage.fields.foo).be.instanceOf(Map); should(storage.fields.foo.size).be.eql(1); - should(storage.fields.foo.get(id)).eql([subfilter]); + should(storage.fields.foo.get(id)).eql(new Set([subfilter])); }); }); @@ -43,16 +43,16 @@ describe('DSL.keyword.notgeospatial', () => { return dsl.register('index', 'collection', {not: {geoDistance: {foo: {lat: 13, lon: 42}, distance: '1000m'}}}) .then(subscription => { - id1 = dsl.storage.filters[subscription.id].subfilters[0].conditions[0].id; - subfilter1 = dsl.storage.filters[subscription.id].subfilters[0]; + subfilter1 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + id1 = Array.from(subfilter1.conditions)[0].id; return dsl.register('index', 'collection', {not: {geoBoundingBox: {foo: {top: 13, left: 0, right: 42, bottom: -14}}}}); }) .then(subscription => { const - id2 = dsl.storage.filters[subscription.id].subfilters[0].conditions[0].id, - subfilter2 = dsl.storage.filters[subscription.id].subfilters[0], - storage = dsl.storage.foPairs.index.collection.notgeospatial; + subfilter2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + id2 = Array.from(subfilter2.conditions)[0].id, + storage = dsl.storage.foPairs.index.collection.get('notgeospatial'); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); @@ -60,8 +60,8 @@ describe('DSL.keyword.notgeospatial', () => { should(storage.fields.foo).be.instanceOf(Map); should(storage.fields.foo.size).be.eql(2); - should(storage.fields.foo.get(id1)).eql([subfilter1]); - should(storage.fields.foo.get(id2)).eql([subfilter2]); + should(storage.fields.foo.get(id1)).eql(new Set([subfilter1])); + should(storage.fields.foo.get(id2)).eql(new Set([subfilter2])); }); }); @@ -73,20 +73,22 @@ describe('DSL.keyword.notgeospatial', () => { return dsl.register('index', 'collection', filter) .then(subscription => { - id = dsl.storage.filters[subscription.id].subfilters[0].conditions[0].id; - subfilter = dsl.storage.filters[subscription.id].subfilters[0]; + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + id = Array.from(subfilter.conditions)[0].id; return dsl.register('index', 'collection', {and: [{equals: {bar: 'baz'}}, filter]}); }) .then(subscription => { - const storage = dsl.storage.foPairs.index.collection.notgeospatial; + const + storage = dsl.storage.foPairs.index.collection.get('notgeospatial'), + subfilter2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); should(storage.custom.index).be.an.Object(); should(storage.fields.foo).be.instanceOf(Map); should(storage.fields.foo.size).be.eql(1); - should(storage.fields.foo.get(id)).eql([subfilter, dsl.storage.filters[subscription.id].subfilters[0]]); + should(storage.fields.foo.get(id)).eql(new Set([subfilter, subfilter2])); }); }); }); @@ -183,7 +185,7 @@ describe('DSL.keyword.notgeospatial', () => { return dsl.register('index', 'collection', filter) .then(subscription => { filterId = subscription.id; - storage = dsl.storage.foPairs.index.collection.notgeospatial; + storage = dsl.storage.foPairs.index.collection.get('notgeospatial'); }); }); @@ -197,14 +199,14 @@ describe('DSL.keyword.notgeospatial', () => { return dsl.register('index', 'collection', {and: [filter, {equals: {foo: 'bar'}}]}) .then(subscription => { - sf = dsl.storage.filters[subscription.id].subfilters[0]; + sf = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.remove(filterId); }) .then(() => { should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.get(sf.conditions[1].id)).match([sf]); + should(storage.fields.foo.get(Array.from(sf.conditions)[1].id)).match(new Set([sf])); }); }); @@ -224,14 +226,14 @@ describe('DSL.keyword.notgeospatial', () => { let id, subfilter; return dsl.register('index', 'collection', geofilter) .then(subscription => { - subfilter = dsl.storage.filters[subscription.id].subfilters[0]; - id = subfilter.conditions[0].id; + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + id = Array.from(subfilter.conditions)[0].id; return dsl.remove(filterId); }) .then(() => { should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); - should(storage.fields.foo.get(id)).match([subfilter]); + should(storage.fields.foo.get(id)).match(new Set([subfilter])); }); }); @@ -251,16 +253,16 @@ describe('DSL.keyword.notgeospatial', () => { let id, subfilter; return dsl.register('index', 'collection', geofilter) .then(subscription => { - subfilter = dsl.storage.filters[subscription.id].subfilters[0]; - id = subfilter.conditions[0].id; + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + id = Array.from(subfilter.conditions)[0].id; - should(dsl.storage.foPairs.index.collection.notgeospatial.keys).eql(new Set(['foo', 'bar'])); + should(dsl.storage.foPairs.index.collection.get('notgeospatial').keys).eql(new Set(['foo', 'bar'])); return dsl.remove(filterId); }) .then(() => { should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['bar'])); - should(storage.fields.bar.get(id)).match([subfilter]); + should(storage.fields.bar.get(id)).match(new Set([subfilter])); }); }); }); diff --git a/test/keywords/nothing.test.js b/test/keywords/nothing.test.js index 351105b..de54c9d 100644 --- a/test/keywords/nothing.test.js +++ b/test/keywords/nothing.test.js @@ -14,12 +14,12 @@ describe('DSL.keyword.nothing', () => { it('should register in the store', () => { return dsl.register('index', 'collection', {nothing: 'anything'}) .then(subscription => { - const storeEntry = dsl.storage.foPairs.index.collection.nothing; + const storeEntry = dsl.storage.foPairs.index.collection.get('nothing'); should(storeEntry) .be.instanceof(FieldOperand); should(storeEntry.fields.all) - .eql([dsl.storage.filters[subscription.id].subfilters[0]]); + .eql([Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]]); }); }); }); diff --git a/test/keywords/notrange.test.js b/test/keywords/notrange.test.js index 01aef6b..78152c6 100644 --- a/test/keywords/notrange.test.js +++ b/test/keywords/notrange.test.js @@ -3,7 +3,8 @@ const should = require('should'), FieldOperand = require('../../lib/storage/objects/fieldOperand'), - DSL = require('../../'); + DSL = require('../../'), + RangeCondition = require('../../lib/storage/objects/rangeCondition'); describe('DSL.keyword.notrange', () => { let dsl; @@ -17,20 +18,18 @@ describe('DSL.keyword.notrange', () => { return dsl.register('index', 'collection', {not: {range: {foo: {gt: 42, lt: 100}}}}) .then(subscription => { const - subfilter = dsl.storage.filters[subscription.id].subfilters[0], - store = dsl.storage.foPairs.index.collection.notrange; + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + store = dsl.storage.foPairs.index.collection.get('notrange'); should(store).be.instanceOf(FieldOperand); should(store.keys).eql(new Set(['foo'])); - should(store.fields.foo.count).be.eql(1); - should(store.fields.foo.subfilters) - .be.an.Object() - .have.property(subfilter.id); - - const conditionId = Object.keys(store.fields.foo.subfilters[subfilter.id])[0]; - should(store.fields.foo.subfilters[subfilter.id][conditionId].subfilter).match(subfilter); - should(store.fields.foo.subfilters[subfilter.id][conditionId].low).approximately(42, 1e-9); - should(store.fields.foo.subfilters[subfilter.id][conditionId].high).approximately(100, 1e-9); + should(store.fields.foo.conditions.size).be.eql(1); + + const rangeCondition = Array.from(store.fields.foo.conditions.values())[0]; + should(rangeCondition).instanceOf(RangeCondition); + should(rangeCondition.subfilters).eql(new Set([subfilter])); + should(rangeCondition.low).approximately(42, 1e-9); + should(rangeCondition.high).approximately(100, 1e-9); should(store.fields.foo.tree).be.an.Object(); }); }); @@ -40,7 +39,7 @@ describe('DSL.keyword.notrange', () => { return dsl.register('index', 'collection', {not: {range: {foo: {gt: 42, lt: 100}}}}) .then(subscription => { - sf1 = dsl.storage.filters[subscription.id].subfilters[0]; + sf1 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', { and: [ @@ -51,39 +50,33 @@ describe('DSL.keyword.notrange', () => { }) .then(subscription => { const - sf2 = dsl.storage.filters[subscription.id].subfilters[0], - store = dsl.storage.foPairs.index.collection.notrange; + sf2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + store = dsl.storage.foPairs.index.collection.get('notrange'); should(store).be.instanceOf(FieldOperand); should(store.keys).eql(new Set(['foo'])); - should(store.fields.foo.count).be.eql(3); - - let conditionId = Object.keys(store.fields.foo.subfilters[sf1.id])[0]; - - should(Object.keys(store.fields.foo.subfilters[sf1.id]).length) - .eql(1); - should(store.fields.foo.subfilters[sf1.id][conditionId].subfilter).match(sf1); - should(store.fields.foo.subfilters[sf1.id][conditionId].low) - .be.exactly(42); - should(store.fields.foo.subfilters[sf1.id][conditionId].high) - .be.exactly(100); - - should(Object.keys(store.fields.foo.subfilters[sf2.id]).length) - .be.eql(2); - - conditionId = Object.keys(store.fields.foo.subfilters[sf2.id])[0]; - should(store.fields.foo.subfilters[sf2.id][conditionId].subfilter).match(sf2); - should(store.fields.foo.subfilters[sf2.id][conditionId].low) - .be.approximately(10, 1e-9); - should(store.fields.foo.subfilters[sf2.id][conditionId].high) - .be.approximately(78, 1e-9); - - conditionId = Object.keys(store.fields.foo.subfilters[sf2.id])[1]; - should(store.fields.foo.subfilters[sf2.id][conditionId].subfilter).match(sf2); - should(store.fields.foo.subfilters[sf2.id][conditionId].low) - .be.exactly(0); - should(store.fields.foo.subfilters[sf2.id][conditionId].high) - .be.exactly(50); + should(store.fields.foo.conditions.size).be.eql(3); + + const cd1 = store.fields.foo.conditions.get(Array.from(sf1.conditions)[0].id); + + should(cd1).instanceOf(RangeCondition); + should(cd1.subfilters).eql(new Set([sf1])); + should(cd1.low).exactly(42); + should(cd1.high).exactly(100); + + const cd2 = store.fields.foo.conditions.get(Array.from(sf2.conditions)[0].id); + + should(cd2).instanceOf(RangeCondition); + should(cd2.subfilters).eql(new Set([sf2])); + should(cd2.low).approximately(10, 1e-9); + should(cd2.high).approximately(78, 1e-9); + + const cd3 = store.fields.foo.conditions.get(Array.from(sf2.conditions)[1].id); + + should(cd3).instanceOf(RangeCondition); + should(cd3.subfilters).eql(new Set([sf2])); + should(cd3.low).exactly(0); + should(cd3.high).exactly(50); should(store.fields.foo.tree).be.an.Object(); }); @@ -171,30 +164,36 @@ describe('DSL.keyword.notrange', () => { it('should remove a single subfilter from a multi-filter condition', () => { let - idToRemove, + storage, multiSubfilter; return dsl.register('index', 'collection', {not: {range: {foo: {gte: 42, lte: 110}}}}) .then(subscription => { - idToRemove = subscription.id; + storage = dsl.storage.foPairs.index.collection.get('notrange'); + multiSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; - return dsl.register('index', 'collection', {not: {range: {foo: {gt: 42, lt: 110}}}}); + return dsl.register('index', 'collection', { + and: [ + {not: {range: {foo: {lt: 50}}}}, + {not: {range: {foo: {gt: 2}}}}, + {not: {range: {foo: {gte: 42, lte: 110}}}} + ] + }); }) .then(subscription => { - multiSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; - - return dsl.remove(idToRemove); + should(storage.fields.foo.conditions.size).eql(3); + return dsl.remove(subscription.id); }) .then(() => { - should(dsl.storage.foPairs.index.collection.notrange).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.notrange.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.notrange.fields.foo.count).eql(1); - - const conditionId = Object.keys(dsl.storage.foPairs.index.collection.notrange.fields.foo.subfilters[multiSubfilter.id])[0]; - should(dsl.storage.foPairs.index.collection.notrange.fields.foo.subfilters[multiSubfilter.id][conditionId].subfilter).match(multiSubfilter); - should(dsl.storage.foPairs.index.collection.notrange.fields.foo.subfilters[multiSubfilter.id][conditionId].low).eql(42); - should(dsl.storage.foPairs.index.collection.notrange.fields.foo.subfilters[multiSubfilter.id][conditionId].high).eql(110); - should(dsl.storage.foPairs.index.collection.notrange.fields.foo.subfilters[idToRemove]).be.undefined(); + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.conditions.size).eql(1); + + const rcd = storage.fields.foo.conditions.get(Array.from(multiSubfilter.conditions)[0].id); + should(rcd).instanceOf(RangeCondition); + should(rcd.subfilters).match(new Set([multiSubfilter])); + should(rcd.low).approximately(42, 1e-9); + should(rcd.high).approximately(110, 1e-9); }); }); @@ -210,20 +209,24 @@ describe('DSL.keyword.notrange', () => { return dsl.register('index', 'collection', {not: {range: {foo: {gt: 42, lt: 110}}}}); }) .then(subscription => { - multiSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; - should(dsl.storage.foPairs.index.collection.notrange.keys).eql(new Set(['bar', 'foo'])); + multiSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + should(dsl.storage.foPairs.index.collection.get('notrange').keys).eql(new Set(['bar', 'foo'])); return dsl.remove(idToRemove); }) .then(() => { - should(dsl.storage.foPairs.index.collection.notrange).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.notrange.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.notrange.fields.foo.count).eql(1); - - const conditionId = Object.keys(dsl.storage.foPairs.index.collection.notrange.fields.foo.subfilters[multiSubfilter.id])[0]; - should(dsl.storage.foPairs.index.collection.notrange.fields.foo.subfilters[multiSubfilter.id][conditionId].subfilter).match(multiSubfilter); - should(dsl.storage.foPairs.index.collection.notrange.fields.foo.subfilters[multiSubfilter.id][conditionId].low).eql(42); - should(dsl.storage.foPairs.index.collection.notrange.fields.foo.subfilters[multiSubfilter.id][conditionId].high).eql(110); - should(dsl.storage.foPairs.index.collection.notrange.fields.bar).be.undefined(); + const storage = dsl.storage.foPairs.index.collection.get('notrange'); + + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.conditions.size).eql(1); + + const rcd = storage.fields.foo.conditions.get(Array.from(multiSubfilter.conditions)[0].id); + + should(rcd).instanceOf(RangeCondition); + should(rcd.subfilters).match(new Set([multiSubfilter])); + should(rcd.low).eql(42); + should(rcd.high).eql(110); + should(storage.fields.bar).be.undefined(); }); }); }); diff --git a/test/keywords/notregexp.test.js b/test/keywords/notregexp.test.js index 4d0f576..0c8389e 100644 --- a/test/keywords/notregexp.test.js +++ b/test/keywords/notregexp.test.js @@ -16,13 +16,17 @@ describe('DSL.keyword.notregexp', () => { describe('#storage', () => { it('should invoke regexp storage function', () => { - let spy = sinon.spy(dsl.storage.storeOperand, 'regexp'); + const spy = sinon.spy(dsl.storage.storeOperand, 'regexp'); return dsl.register('index', 'collection', {not: {regexp: {foo: {value: '^\\w{2}oba\\w$', flags: 'i'}}}}) .then(subscription => { const - storage = dsl.storage.foPairs.index.collection.notregexp, - condition = new RegexpCondition('^\\w{2}oba\\w$', dsl.storage.filters[subscription.id].subfilters[0], 'i'); + storage = dsl.storage.foPairs.index.collection.get('notregexp'), + condition = new RegexpCondition( + '^\\w{2}oba\\w$', + Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + 'i' + ); should(spy.called).be.true(); diff --git a/test/keywords/range.test.js b/test/keywords/range.test.js index 92fec9f..641e5d7 100644 --- a/test/keywords/range.test.js +++ b/test/keywords/range.test.js @@ -4,6 +4,7 @@ const should = require('should'), BadRequestError = require('kuzzle-common-objects').errors.BadRequestError, FieldOperand = require('../../lib/storage/objects/fieldOperand'), + RangeCondition = require('../../lib/storage/objects/rangeCondition'), DSL = require('../../'); describe('DSL.keyword.range', () => { @@ -63,18 +64,18 @@ describe('DSL.keyword.range', () => { return dsl.register('index', 'collection', {range: {foo: {gt: 42, lt: 100}}}) .then(subscription => { const - subfilter = dsl.storage.filters[subscription.id].subfilters[0], - store = dsl.storage.foPairs.index.collection.range; + subfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + store = dsl.storage.foPairs.index.collection.get('range'); should(store).be.instanceOf(FieldOperand); should(store.keys).eql(new Set(['foo'])); - should(store.fields.foo.count).be.eql(1); + should(store.fields.foo.conditions.size).be.eql(1); - const conditionId = Object.keys(store.fields.foo.subfilters[subfilter.id])[0]; - should(store.fields.foo.subfilters[subfilter.id][conditionId].subfilter).match(subfilter); - should(store.fields.foo.subfilters[subfilter.id][conditionId].low).approximately(42, 1e-9); - should(store.fields.foo.subfilters[subfilter.id][conditionId].high).approximately(100, 1e-9); - should(store.fields.foo.tree).be.an.Object(); + const rangeInfo = Array.from(store.fields.foo.conditions.values())[0]; + should(rangeInfo).instanceOf(RangeCondition); + should(rangeInfo.subfilters).match(new Set([subfilter])); + should(rangeInfo.low).approximately(42, 1e-9); + should(rangeInfo.high).approximately(100, 1e-9); }); }); @@ -83,30 +84,30 @@ describe('DSL.keyword.range', () => { return dsl.register('index', 'collection', {range: {foo: {gt: 42, lt: 100}}}) .then(subscription => { - sf1 = dsl.storage.filters[subscription.id].subfilters[0]; + sf1 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', {range: {foo: {gte: 10, lte: 78}}}); }) .then(subscription => { const - sf2 = dsl.storage.filters[subscription.id].subfilters[0], - store = dsl.storage.foPairs.index.collection.range; + sf2 = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + store = dsl.storage.foPairs.index.collection.get('range'); should(store).be.instanceOf(FieldOperand); should(store.keys).eql(new Set(['foo'])); - should(store.fields.foo.count).be.eql(2); + should(store.fields.foo.conditions.size).be.eql(2); - let conditionId = Object.keys(store.fields.foo.subfilters[sf1.id])[0]; - should(store.fields.foo.subfilters[sf1.id][conditionId].subfilter).match(sf1); - should(store.fields.foo.subfilters[sf1.id][conditionId].low).approximately(42, 1e-9); - should(store.fields.foo.subfilters[sf1.id][conditionId].high).approximately(100, 1e-9); + let rangeInfo = store.fields.foo.conditions.get(Array.from(sf1.conditions)[0].id); + should(rangeInfo).instanceOf(RangeCondition); + should(rangeInfo.subfilters).match(new Set([sf1])); + should(rangeInfo.low).approximately(42, 1e-9); + should(rangeInfo.high).approximately(100, 1e-9); - conditionId = Object.keys(store.fields.foo.subfilters[sf2.id])[0]; - should(store.fields.foo.subfilters[sf2.id][conditionId].subfilter).match(sf2); - should(store.fields.foo.subfilters[sf2.id][conditionId].low) - .be.exactly(10); - should(store.fields.foo.subfilters[sf2.id][conditionId].high) - .be.exactly(78); + rangeInfo = store.fields.foo.conditions.get(Array.from(sf2.conditions)[0].id); + should(rangeInfo).instanceOf(RangeCondition); + should(rangeInfo.subfilters).match(new Set([sf2])); + should(rangeInfo.low).be.exactly(10); + should(rangeInfo.high).be.exactly(78); should(store.fields.foo.tree).be.an.Object(); }); @@ -197,35 +198,33 @@ describe('DSL.keyword.range', () => { it('should remove a single subfilter from a multi-filter condition', () => { let - idToRemove, + storage, multiSubfilter; return dsl.register('index', 'collection', {range: {foo: {gt: 42, lt: 110}}}) .then(subscription => { - idToRemove = subscription.id; - + storage = dsl.storage.foPairs.index.collection.get('range'); + multiSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; return dsl.register('index', 'collection', { and: [ - {range: {foo: {gte: 42, lte: 110}}}, + {range: {foo: {gt: 42, lt: 110}}}, {range: {foo: {lt: 50}}} ] }); }) .then(subscription => { - multiSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; - - return dsl.remove(idToRemove); + should(storage.fields.foo.conditions.size).eql(2); + return dsl.remove(subscription.id); }) .then(() => { - should(dsl.storage.foPairs.index.collection.range).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.range.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.range.fields.foo.count).eql(2); - - const conditionId = Object.keys(dsl.storage.foPairs.index.collection.range.fields.foo.subfilters[multiSubfilter.id])[0]; - should(dsl.storage.foPairs.index.collection.range.fields.foo.subfilters[multiSubfilter.id][conditionId].subfilter).match(multiSubfilter); - should(dsl.storage.foPairs.index.collection.range.fields.foo.subfilters[multiSubfilter.id][conditionId].low).eql(42); - should(dsl.storage.foPairs.index.collection.range.fields.foo.subfilters[multiSubfilter.id][conditionId].high).eql(110); - should(dsl.storage.foPairs.index.collection.range.fields.foo.subfilters[idToRemove]).be.undefined(); + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.conditions.size).eql(1); + + const rangeInfo = Array.from(storage.fields.foo.conditions.values())[0]; + should(rangeInfo.subfilters).match(new Set([multiSubfilter])); + should(rangeInfo.low).approximately(42, 1e-9); + should(rangeInfo.high).approximately(110, 1e-9); }); }); @@ -241,20 +240,22 @@ describe('DSL.keyword.range', () => { return dsl.register('index', 'collection', {range: {foo: {gte: 42, lte: 110}}}); }) .then(subscription => { - multiSubfilter = dsl.storage.filters[subscription.id].subfilters[0]; - should(dsl.storage.foPairs.index.collection.range.keys).eql(new Set(['bar', 'foo'])); + multiSubfilter = Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]; + should(dsl.storage.foPairs.index.collection.get('range').keys).eql(new Set(['bar', 'foo'])); return dsl.remove(idToRemove); }) .then(() => { - should(dsl.storage.foPairs.index.collection.range).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.range.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.range.fields.foo.count).eql(1); - - const conditionId = Object.keys(dsl.storage.foPairs.index.collection.range.fields.foo.subfilters[multiSubfilter.id])[0]; - should(dsl.storage.foPairs.index.collection.range.fields.foo.subfilters[multiSubfilter.id][conditionId].subfilter).match(multiSubfilter); - should(dsl.storage.foPairs.index.collection.range.fields.foo.subfilters[multiSubfilter.id][conditionId].low).eql(42); - should(dsl.storage.foPairs.index.collection.range.fields.foo.subfilters[multiSubfilter.id][conditionId].high).eql(110); - should(dsl.storage.foPairs.index.collection.range.fields.bar).be.undefined(); + const storage = dsl.storage.foPairs.index.collection.get('range'); + + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.conditions.size).eql(1); + + const rangeInfo = Array.from(storage.fields.foo.conditions.values())[0]; + should(rangeInfo.subfilters).match(new Set([multiSubfilter])); + should(rangeInfo.low).approximately(42, 1e-9); + should(rangeInfo.high).approximately(110, 1e-9); + should(storage.fields.bar).be.undefined(); }); }); }); diff --git a/test/keywords/regexp.test.js b/test/keywords/regexp.test.js index 39354ab..c51e365 100644 --- a/test/keywords/regexp.test.js +++ b/test/keywords/regexp.test.js @@ -98,8 +98,12 @@ describe('DSL.keyword.regexp', () => { return dsl.register('index', 'collection', {regexp: {foo: {value: 'foo', flags: 'i'}}}) .then(subscription => { const - storage = dsl.storage.foPairs.index.collection.regexp, - regexp = new RegexpCondition('foo', dsl.storage.filters[subscription.id].subfilters[0], 'i'); + storage = dsl.storage.foPairs.index.collection.get('regexp'), + regexp = new RegexpCondition( + 'foo', + Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + 'i' + ); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); @@ -112,14 +116,21 @@ describe('DSL.keyword.regexp', () => { return dsl.register('index', 'collection', {regexp: {foo: {value: 'foo', flags: 'i'}}}) .then(subscription => { - cond1 = new RegexpCondition('foo', dsl.storage.filters[subscription.id].subfilters[0], 'i'); + cond1 = new RegexpCondition( + 'foo', + Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + 'i' + ); return dsl.register('index', 'collection', {regexp: {foo: {value: 'bar'}}}); }) .then(subscription => { const - storage = dsl.storage.foPairs.index.collection.regexp, - cond2 = new RegexpCondition('bar', dsl.storage.filters[subscription.id].subfilters[0]); + storage = dsl.storage.foPairs.index.collection.get('regexp'), + cond2 = new RegexpCondition( + 'bar', + Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0] + ); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); @@ -135,13 +146,17 @@ describe('DSL.keyword.regexp', () => { return dsl.register('index', 'collection', filter) .then(subscription => { - cond = new RegexpCondition('foo', dsl.storage.filters[subscription.id].subfilters[0], 'i'); + cond = new RegexpCondition( + 'foo', + Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + 'i' + ); return dsl.register('index', 'collection', {and: [filter, {equals: {foo: 'bar'}}]}); }) .then(subscription => { - const storage = dsl.storage.foPairs.index.collection.regexp; - cond.subfilters.push(dsl.storage.filters[subscription.id].subfilters[0]); + const storage = dsl.storage.foPairs.index.collection.get('regexp'); + cond.subfilters.add(Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0]); should(storage).be.instanceOf(FieldOperand); should(storage.keys).eql(new Set(['foo'])); @@ -219,14 +234,20 @@ describe('DSL.keyword.regexp', () => { return dsl.register('index', 'collection', {and: [filter, {equals: {foo: 'bar'}}]}); }) .then(subscription => { - cond = new RegexpCondition('^\\w{2}oba\\w$', dsl.storage.filters[subscription.id].subfilters[0], 'i'); + cond = new RegexpCondition( + '^\\w{2}oba\\w$', + Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + 'i' + ); return dsl.remove(idToRemove); }) .then(() => { - should(dsl.storage.foPairs.index.collection.regexp).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.regexp.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.regexp.fields.foo.get(cond.stringValue)).match(cond); + const storage = dsl.storage.foPairs.index.collection.get('regexp'); + + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(cond.stringValue)).match(cond); }); }); @@ -237,7 +258,11 @@ describe('DSL.keyword.regexp', () => { return dsl.register('index', 'collection', {regexp: {foo: {value: '^\\w{2}oba\\w$', flags: 'i'}}}) .then(subscription => { - cond = new RegexpCondition('^\\w{2}oba\\w$', dsl.storage.filters[subscription.id].subfilters[0], 'i'); + cond = new RegexpCondition( + '^\\w{2}oba\\w$', + Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + 'i' + ); return dsl.register('index', 'collection', {regexp: {foo: {value: '^$'}}}); }) @@ -246,10 +271,12 @@ describe('DSL.keyword.regexp', () => { return dsl.remove(idToRemove); }) .then(() => { - should(dsl.storage.foPairs.index.collection.regexp).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.regexp.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.regexp.fields.foo.get(cond.stringValue)).match(cond); - should(dsl.storage.foPairs.index.collection.regexp.fields.foo.size).eql(1); + const storage = dsl.storage.foPairs.index.collection.get('regexp'); + + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(cond.stringValue)).match(cond); + should(storage.fields.foo.size).eql(1); }); }); @@ -260,20 +287,26 @@ describe('DSL.keyword.regexp', () => { return dsl.register('index', 'collection', {regexp: {foo: {value: '^\\w{2}oba\\w$', flags: 'i'}}}) .then(subscription => { - cond = new RegexpCondition('^\\w{2}oba\\w$', dsl.storage.filters[subscription.id].subfilters[0], 'i'); + cond = new RegexpCondition( + '^\\w{2}oba\\w$', + Array.from(dsl.storage.filters.get(subscription.id).subfilters)[0], + 'i' + ); return dsl.register('index', 'collection', {regexp: {bar: {value: '^\\w{2}oba\\w$', flags: 'i'}}}); }) .then(subscription => { - should(dsl.storage.foPairs.index.collection.regexp.keys).eql(new Set(['foo', 'bar'])); + should(dsl.storage.foPairs.index.collection.get('regexp').keys).eql(new Set(['foo', 'bar'])); idToRemove = subscription.id; return dsl.remove(idToRemove); }) .then(() => { - should(dsl.storage.foPairs.index.collection.regexp).be.instanceOf(FieldOperand); - should(dsl.storage.foPairs.index.collection.regexp.keys).eql(new Set(['foo'])); - should(dsl.storage.foPairs.index.collection.regexp.fields.foo.get(cond.stringValue)).match(cond); - should(dsl.storage.foPairs.index.collection.regexp.fields.bar).be.undefined(); + const storage = dsl.storage.foPairs.index.collection.get('regexp'); + + should(storage).be.instanceOf(FieldOperand); + should(storage.keys).eql(new Set(['foo'])); + should(storage.fields.foo.get(cond.stringValue)).match(cond); + should(storage.fields.bar).be.undefined(); }); }); }); diff --git a/test/operands/and.test.js b/test/operands/and.test.js index 6daf08b..02493ca 100644 --- a/test/operands/and.test.js +++ b/test/operands/and.test.js @@ -67,10 +67,10 @@ describe('DSL.operands.and', () => { }) .then(() => dsl.remove(id)) .then(() => { - should(dsl.storage.foPairs.index.collection.exists).be.an.Object(); - should(dsl.storage.foPairs.index.collection.equals).be.undefined(); - should(dsl.storage.foPairs.index.collection.notexists).be.undefined(); - should(dsl.storage.foPairs.index.collection.range).be.undefined(); + should(dsl.storage.foPairs.index.collection.get('exists')).be.an.Object(); + should(dsl.storage.foPairs.index.collection.get('equals')).be.undefined(); + should(dsl.storage.foPairs.index.collection.get('notexists')).be.undefined(); + should(dsl.storage.foPairs.index.collection.get('range')).be.undefined(); }); }); }); diff --git a/test/operands/or.test.js b/test/operands/or.test.js index 7f9cb7b..3ef5527 100644 --- a/test/operands/or.test.js +++ b/test/operands/or.test.js @@ -67,10 +67,10 @@ describe('DSL.operands.or', () => { }) .then(() => dsl.remove(id)) .then(() => { - should(dsl.storage.foPairs.index.collection.exists).be.an.Object(); - should(dsl.storage.foPairs.index.collection.equals).be.undefined(); - should(dsl.storage.foPairs.index.collection.notexists).be.undefined(); - should(dsl.storage.foPairs.index.collection.range).be.undefined(); + should(dsl.storage.foPairs.index.collection.get('exists')).be.an.Object(); + should(dsl.storage.foPairs.index.collection.get('equals')).be.undefined(); + should(dsl.storage.foPairs.index.collection.get('notexists')).be.undefined(); + should(dsl.storage.foPairs.index.collection.get('range')).be.undefined(); }); }); }); diff --git a/test/testIndex.test.js b/test/testIndex.test.js deleted file mode 100644 index b0d3d85..0000000 --- a/test/testIndex.test.js +++ /dev/null @@ -1,206 +0,0 @@ -'use strict'; - -require('reify'); - -/** - * These test cases are dedicated to check that subscriptions indexation, - * removal and re-indexation are well-performed - * - * Indexes, stored in dsl.storage.testTables, are vital to track - * how many conditions a subfilter has validated and, if a subfilter - * validated all its conditions, what filters should be notified with - * the tested document/message. - */ - -const - should = require('should'), - sinon = require('sinon'), - Bluebird = require('bluebird'), - DSL = require('../'); - -describe('#TestTables (== DSL filter indexes)', () => { - let dsl; - - beforeEach(() => { - dsl = new DSL(); - }); - - describe('#indexing', () => { - it('should index new filters properly', () => { - return dsl.register('i', 'c', {exists: 'foo'}) - .then(subscription => { - const filter = dsl.storage.filters[subscription.id]; - - should(filter.fidx).be.eql(0); - should(filter.subfilters[0].cidx).be.eql(0); - should(dsl.storage.testTables.i.c.conditions[0]).be.eql(1); - should(dsl.storage.testTables.i.c.clength).be.eql(1); - - return dsl.register('i', 'c', {exists: {field: 'bar'}}); - }) - .then(subscription => { - const filter = dsl.storage.filters[subscription.id]; - should(filter.fidx).be.eql(1); - should(filter.subfilters[0].cidx).be.eql(1); - should(dsl.storage.testTables.i.c.conditions[1]).be.eql(1); - should(dsl.storage.testTables.i.c.clength).be.eql(2); - - return dsl.register('i', 'c', {and: [{exists: {field: 'baz'}}, {exists: {field: 'qux'}}]}); - }) - .then(subscription => { - const filter = dsl.storage.filters[subscription.id]; - should(filter.fidx).be.eql(2); - should(filter.subfilters[0].cidx).be.eql(2); - should(dsl.storage.testTables.i.c.conditions[2]).be.eql(2); - should(dsl.storage.testTables.i.c.clength).be.eql(3); - }); - }); - - it('should reallocate the condition index table when full', () => { - return dsl.register('i', 'c', {exists: 'foo'}) - .then(() => { - const promises = []; - - should(dsl.storage.testTables.i.c.clength).be.eql(1); - should(dsl.storage.testTables.i.c.conditions.length).be.eql(10); - - for(let i = 0; i < 10; ++i) { - promises.push(dsl.register('i', 'c', {exists: `${i}`})); - } - - return Bluebird.all(promises); - }) - .then(() => { - const promises = []; - - should(dsl.storage.testTables.i.c.clength).be.eql(11); - should(dsl.storage.testTables.i.c.conditions.length).be.eql(15); - - for(let i = 0; i < 20; ++i) { - promises.push(dsl.register('i', 'c', {exists: `secondPass_${i}`})); - } - - return Bluebird.all(promises); - }) - .then(() => { - should(dsl.storage.testTables.i.c.clength).be.eql(31); - should(dsl.storage.testTables.i.c.conditions.length).be.eql(33); - }); - }); - - it('should not re-index an already indexed filter', () => { - let filter; - - return dsl.register('i', 'c', {exists: {field: 'foo'}}) - .then(subscription => { - filter = dsl.storage.filters[subscription.id]; - should(filter.fidx).be.eql(0); - - return dsl.register('i', 'c', {or: [{exists: {field: 'foo'}}, {equals: {bar: 'foo'}}]}); - }) - .then(() => { - should(filter.fidx).be.eql(0); - should(filter.subfilters[0].filters[0]).be.eql(filter); - should(filter.subfilters[0].filters[1].fidx).be.eql(1); - }); - }); - }); - - describe('#subfilter removal', () => { - it('should delete the test table if the last filter is removed', () => { - return dsl.register('i', 'c', {exists: {field: 'foo'}}) - .then(subscription => dsl.remove(subscription.id)) - .then(() => { - should(dsl.storage.testTables).be.an.Object().and.be.empty(); - }); - }); - - it('should track removed filters and re-index', () => { - const clock = sinon.useFakeTimers(); - let - id1, - id2; - - return dsl.register('i', 'c', {exists: 'foo'}) - .then(subscription => { - id1 = subscription.id; - return dsl.register('i', 'c', {exists: 'bar'}); - }) - .then(subscription => { - id2 = subscription.id; - return dsl.remove(id1); - }) - .then(() => { - const filter = dsl.storage.filters[id2]; - - should(filter.fidx).be.eql(1); - should(filter.subfilters[0].cidx).be.eql(1); - should(dsl.storage.testTables.i.c.clength).be.eql(2); - should(dsl.storage.testTables.i.c.removedFilters.has(id1)).be.true(); - should(dsl.storage.testTables.i.c.removedFilters.size).be.eql(1); - should(dsl.storage.testTables.i.c.removedConditions.size).be.eql(1); - should(dsl.storage.testTables.i.c.reindexing).be.true(); - - clock.tick(5000); - - should(filter.fidx).be.eql(0); - should(filter.subfilters[0].cidx).be.eql(0); - should(dsl.storage.testTables.i.c.clength).be.eql(1); - should(dsl.storage.testTables.i.c.removedFilters).be.empty(); - should(dsl.storage.testTables.i.c.removedConditions).be.empty(); - should(dsl.storage.testTables.i.c.reindexing).be.false(); - - clock.restore(); - }); - }); - - it('should not trigger a re-index if less than 10% of registered filters are removed', () => { - const promises = []; - - for(let i = 0; i < 10; i++) { - promises.push(dsl.register('i', 'c', {exists: `${i}`})); - } - - return Bluebird.all(promises) - .then(() => dsl.remove(Object.keys(dsl.storage.filters)[0])) - .then(() => should(dsl.storage.testTables.i.c.reindexing).be.false()); - }); - - // https://github.com/kuzzleio/kuzzle/issues/740 - it('issue #740 Unhandled Exception on room removal', () => { - let - room1, - room2; - - return dsl.register('index', 'collection', { - or: [ - {equals: {foo: 'bar'}}, - {exists: 'foo'} - ] - }) - .then(response => { - room1 = response.id; - return dsl.register('index', 'collection', {equals: {foo: 'bar'}}); - }) - .then(response => { - room2 = response.id; - return dsl.remove(room1); - }) - .then(() => dsl.remove(room2)); - }); - - // https://github.com/kuzzleio/kuzzle/issues/824 - it('should remove a filter on which several conditions are set for the same field', () => { - const filter = { - and: [ - { not: { range: { foo: {lt: 42} } } }, - { not: { range: { foo: {lt: 50} } } }, - { not: { range: { foo: {lt: 2} } } } - ] - }; - - return dsl.register('i', 'c', filter) - .then(response => dsl.remove(response.id)); - }); - }); -});