kvao: implement key filter

This commit is contained in:
Miroslav Bajtoš 2016-08-17 14:24:20 +02:00
parent 01ce7df60f
commit 3b653a192b
6 changed files with 67 additions and 6 deletions

View File

@ -9,3 +9,4 @@ benchmark.js
analyse.r analyse.r
docs/html docs/html
npm-debug.log npm-debug.log
.travis.yml

View File

@ -3,6 +3,7 @@
var assert = require('assert'); var assert = require('assert');
var Connector = require('loopback-connector').Connector; var Connector = require('loopback-connector').Connector;
var debug = require('debug')('loopback:connector:kv-memory'); var debug = require('debug')('loopback:connector:kv-memory');
var minimatch = require('minimatch');
var util = require('util'); var util = require('util');
exports.initialize = function initializeDataSource(dataSource, cb) { exports.initialize = function initializeDataSource(dataSource, cb) {
@ -160,8 +161,10 @@ KeyValueMemoryConnector.prototype.iterateKeys =
function(modelName, filter, options, callback) { function(modelName, filter, options, callback) {
var store = this._getStoreForModel(modelName); var store = this._getStoreForModel(modelName);
var self = this; var self = this;
var checkFilter = createMatcher(filter.match);
var keys = Object.keys(store).filter(function(key) { var keys = Object.keys(store).filter(function(key) {
return !self._removeIfExpired(modelName, key); return !self._removeIfExpired(modelName, key) && checkFilter(key);
}); });
debug('ITERATE KEYS %j -> %s keys', modelName, keys.length); debug('ITERATE KEYS %j -> %s keys', modelName, keys.length);
@ -175,6 +178,18 @@ function(modelName, filter, options, callback) {
}; };
}; };
function createMatcher(pattern) {
if (!pattern) return function matchAll() { return true; };
return minimatch.filter(pattern, {
nobrace: true,
noglobstar: true,
dot: true,
noext: true,
nocomment: true,
});
}
KeyValueMemoryConnector.prototype.disconnect = function(callback) { KeyValueMemoryConnector.prototype.disconnect = function(callback) {
if (this._cleanupTimer) if (this._cleanupTimer)
clearInterval(this._cleanupTimer); clearInterval(this._cleanupTimer);

View File

@ -9,6 +9,10 @@ var utils = require('../utils');
* @param {Object} filter An optional filter object with the following * @param {Object} filter An optional filter object with the following
* properties: * properties:
* - `match` - glob string to use to filter returned keys, e.g. 'userid.*' * - `match` - glob string to use to filter returned keys, e.g. 'userid.*'
* All connectors are required to support `*` and `?`.
* They may also support additional special characters that are specific
* to the backing store.
*
* @param {Object} options * @param {Object} options
* *
* @returns {AsyncIterator} An object implementing "next(cb) -> Promise" * @returns {AsyncIterator} An object implementing "next(cb) -> Promise"

View File

@ -13,6 +13,9 @@ var utils = require('../utils');
* @param {Object} filter An optional filter object with the following * @param {Object} filter An optional filter object with the following
* properties: * properties:
* - `match` - glob string to use to filter returned keys, e.g. 'userid.*' * - `match` - glob string to use to filter returned keys, e.g. 'userid.*'
* All connectors are required to support `*` and `?`.
* They may also support additional special characters that are specific
* to the backing store.
* @param {Object} options * @param {Object} options
* @callback callback * @callback callback
* @param {Error=} err * @param {Error=} err

View File

@ -45,6 +45,7 @@
"depd": "^1.0.0", "depd": "^1.0.0",
"inflection": "^1.6.0", "inflection": "^1.6.0",
"loopback-connector": "^2.1.0", "loopback-connector": "^2.1.0",
"minimatch": "^3.0.3",
"node-uuid": "^1.4.2", "node-uuid": "^1.4.2",
"qs": "^3.1.0", "qs": "^3.1.0",
"strong-globalize": "^2.6.2", "strong-globalize": "^2.6.2",

View File

@ -9,6 +9,12 @@ module.exports = function(dataSourceFactory, connectorCapabilities) {
var CacheItem; var CacheItem;
beforeEach(function unpackContext() { beforeEach(function unpackContext() {
CacheItem = helpers.givenCacheItem(dataSourceFactory); CacheItem = helpers.givenCacheItem(dataSourceFactory);
CacheItem.sortedKeys = function(filter, options) {
return this.keys(filter, options).then(function(keys) {
keys.sort();
return keys;
});
};
}); });
it('returns all keys - Callback API', function(done) { it('returns all keys - Callback API', function(done) {
@ -41,10 +47,9 @@ module.exports = function(dataSourceFactory, connectorCapabilities) {
return helpers.givenKeys(AnotherModel, ['otherKey1', 'otherKey2']); return helpers.givenKeys(AnotherModel, ['otherKey1', 'otherKey2']);
}) })
.then(function() { .then(function() {
return CacheItem.keys(); return CacheItem.sortedKeys();
}) })
.then(function(keys) { .then(function(keys) {
keys.sort();
should(keys).eql(['key1', 'key2']); should(keys).eql(['key1', 'key2']);
}); });
}); });
@ -53,16 +58,48 @@ module.exports = function(dataSourceFactory, connectorCapabilities) {
var expectedKeys = []; var expectedKeys = [];
for (var ix = 0; ix < 1000; ix++) for (var ix = 0; ix < 1000; ix++)
expectedKeys.push('key-' + ix); expectedKeys.push('key-' + ix);
expectedKeys.sort();
return helpers.givenKeys(CacheItem, expectedKeys) return helpers.givenKeys(CacheItem, expectedKeys)
.then(function() { .then(function() {
return CacheItem.keys(); return CacheItem.sortedKeys();
}) })
.then(function(keys) { .then(function(keys) {
keys.sort();
expectedKeys.sort();
should(keys).eql(expectedKeys); should(keys).eql(expectedKeys);
}); });
}); });
context('with "filter.match"', function() {
beforeEach(function createTestData() {
return helpers.givenKeys(CacheItem, [
'hallo',
'hello',
'hxllo',
'hllo',
'heeello',
'foo',
'bar',
]);
});
it('supports "?" operator', function() {
return CacheItem.sortedKeys({ match: 'h?llo' }).then(function(keys) {
should(keys).eql(['hallo', 'hello', 'hxllo']);
});
});
it('supports "*" operator', function() {
return CacheItem.sortedKeys({ match: 'h*llo' }).then(function(keys) {
should(keys).eql(['hallo', 'heeello', 'hello', 'hllo', 'hxllo']);
});
});
it('handles no matches found', function() {
return CacheItem.sortedKeys({ match: 'not-found' })
.then(function(keys) {
should(keys).eql([]);
});
});
});
}); });
}; };