Skip to content

Commit

Permalink
Refactor flush to deleteAll
Browse files Browse the repository at this point in the history
- Rename `flush` to `deleteAll`
- Add `delete`
- Detect `delete/deleteAll` before running downstream test suites
- Fall back to unoptimized `deleteAll` when connector does not support
  `deleteAll` but supports `delete`
- Return 501 for connectors not supporting `delete` or `deleteAll`
  • Loading branch information
simonhoibm committed Jan 9, 2017
1 parent 1e05640 commit 2320df1
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 41 deletions.
13 changes: 11 additions & 2 deletions lib/connectors/kv-memory.js
Expand Up @@ -201,9 +201,18 @@ KeyValueMemoryConnector.prototype.disconnect = function(callback) {
process.nextTick(callback);
};

KeyValueMemoryConnector.prototype.flush =
KeyValueMemoryConnector.prototype.delete =
function(modelName, key, options, callback) {
var store = this._getStoreForModel(modelName);
delete store[key];
callback();
};

KeyValueMemoryConnector.prototype.deleteAll =
function(modelName, options, callback) {
this._store = Object.create(null);
var modelStore = this._getStoreForModel(modelName);
for (var key in modelStore)
delete modelStore[key];
callback();
};

Expand Down
63 changes: 63 additions & 0 deletions lib/kvao/delete-all.js
@@ -0,0 +1,63 @@
'use strict';

var assert = require('assert');
var async = require('async');
var debug = require('debug')('loopback:kvao:delete-all');
var utils = require('../utils');

/**
* Delete all keys (and values) associated to the current model.
*
* @options {Object} options Unused ATM, placeholder for future options.
* @callback {Function} callback
* @param {Error} err Error object.
* @promise
*
* @header KVAO.prototype.deleteAll([options, ]cb)
*/
module.exports = function deleteAll(options, callback) {
if (callback == undefined && typeof options === 'function') {
callback = options;
options = {};
} else if (!options) {
options = {};
}

assert(typeof options === 'object', 'options must be an object');

callback = callback || utils.createPromiseCallback();

var connector = this.getConnector();
if (typeof connector.deleteAll === 'function') {
connector.deleteAll(this.modelName, options, callback);
} else if (typeof connector.delete === 'function') {
debug('Falling back to unoptimized key-value pair deletion');
iterateAndDelete(connector, this.modelName, options, callback);
} else {
var errMsg = 'Connector does not support key-value pair deletion';
debug(errMsg);
process.nextTick(function() {
var err = new Error(errMsg);
err.statusCode = 501;
callback(err);
});
}
return callback.promise;
};

function iterateAndDelete(connector, modelName, options, callback) {
var iter = connector.iterateKeys(modelName, {});
var keys = [];
iter.next(onNextKey);

function onNextKey(err, key) {
if (err) return callback(err);
if (key === undefined) return callback();
connector.delete(modelName, key, options, onDeleted);
}

function onDeleted(err) {
if (err) return callback(err);
iter.next(onNextKey);
}
}
45 changes: 45 additions & 0 deletions lib/kvao/delete.js
@@ -0,0 +1,45 @@
'use strict';

var assert = require('assert');
var debug = require('debug')('loopback:kvao:delete');
var utils = require('../utils');

/**
* Delete the key-value pair associated to the given key.
*
* @param {String} key Key to use when searching the database.
* @options {Object} options
* @callback {Function} callback
* @param {Error} err Error object.
* @param {*} result Value associated with the given key.
* @promise
*
* @header KVAO.prototype.delete(key[, options], cb)
*/
module.exports = function keyValueDelete(key, options, callback) {
if (callback == undefined && typeof options === 'function') {
callback = options;
options = {};
} else if (!options) {
options = {};
}

assert(typeof key === 'string' && key, 'key must be a non-empty string');

callback = callback || utils.createPromiseCallback();

var connector = this.getConnector();
if (typeof connector.delete === 'function') {
connector.delete(this.modelName, key, options, callback);
} else {
var errMsg = 'Connector does not support key-value pair deletion';
debug(errMsg);
process.nextTick(function() {
var err = new Error(errMsg);
err.statusCode = 501;
callback(err);
});
}

return callback.promise;
};
30 changes: 0 additions & 30 deletions lib/kvao/flush.js

This file was deleted.

3 changes: 2 additions & 1 deletion lib/kvao/index.js
Expand Up @@ -5,10 +5,11 @@ function KeyValueAccessObject() {

module.exports = KeyValueAccessObject;

KeyValueAccessObject.delete = require('./delete');
KeyValueAccessObject.deleteAll = require('./delete-all');
KeyValueAccessObject.get = require('./get');
KeyValueAccessObject.set = require('./set');
KeyValueAccessObject.expire = require('./expire');
KeyValueAccessObject.flush = require('./flush');
KeyValueAccessObject.ttl = require('./ttl');
KeyValueAccessObject.iterateKeys = require('./iterate-keys');
KeyValueAccessObject.keys = require('./keys');
Expand Down
15 changes: 14 additions & 1 deletion test/kv-memory.js
Expand Up @@ -2,10 +2,23 @@
var kvMemory = require('../lib/connectors/kv-memory');
var DataSource = require('..').DataSource;

describe('KeyValue-Memory connector', function() {
describe('Optimized KeyValue-Memory connector', function() {
var dataSourceFactory = function() {
return new DataSource({connector: kvMemory});
};

require('./kvao.suite')(dataSourceFactory);
});

describe('Unoptimized KeyValue-Memory connector', function() {
var dataSourceFactory = function() {
var ds = new DataSource({connector: kvMemory});

// disable optimized methods
ds.connector.deleteAll = false;

return ds;
};

require('./kvao.suite')(dataSourceFactory);
});
36 changes: 36 additions & 0 deletions test/kvao/delete-all.suite.js
@@ -0,0 +1,36 @@
'use strict';

const bdd = require('../helpers/bdd-if');
const helpers = require('./_helpers');
const should = require('should');

module.exports = function(dataSourceFactory, connectorCapabilities) {
var supportsDeleteAll = 'deleteAll' in dataSourceFactory().connector;

bdd.describeIf(supportsDeleteAll, 'deleteAll', function() {
let CacheItem;
beforeEach(function unpackContext() {
CacheItem = helpers.givenCacheItem(dataSourceFactory);
});

it('removes all key-value pairs for the given model', function() {
return helpers.givenKeys(CacheItem, ['key1', 'key2'])
.then(() => CacheItem.deleteAll())
.then(() => CacheItem.keys())
.then((keys) => {
should(keys).eql([]);
});
});

it('does not remove data from other existing models', function() {
var AnotherModel = dataSourceFactory().createModel('AnotherModel');
return helpers.givenKeys(CacheItem, ['key1', 'key2'])
.then(() => helpers.givenKeys(AnotherModel, ['key3', 'key4']))
.then(() => CacheItem.deleteAll())
.then(() => AnotherModel.keys())
.then((keys) => {
should(keys).eql(['key3', 'key4']);
});
});
});
};
14 changes: 7 additions & 7 deletions test/kvao/flush.suite.js → test/kvao/delete.suite.js
Expand Up @@ -5,21 +5,21 @@ const helpers = require('./_helpers');
const should = require('should');

module.exports = function(dataSourceFactory, connectorCapabilities) {
var supportsFlushOperation =
connectorCapabilities.supportsFlushOperation !== false;
var supportsDelete = 'delete' in dataSourceFactory().connector;

bdd.describeIf(supportsFlushOperation, 'flush', function() {
bdd.describeIf(supportsDelete, 'delete', function() {
let CacheItem;
beforeEach(function unpackContext() {
CacheItem = helpers.givenCacheItem(dataSourceFactory);
return CacheItem.deleteAll();
});

it('removes all associated keys for a given model', function() {
it('removes the key-value pair for the given key', function() {
return helpers.givenKeys(CacheItem, ['key1', 'key2'])
.then(() => CacheItem.flush())
.then(() => CacheItem.delete('key1'))
.then(() => CacheItem.keys())
.done((keys) => {
should(keys).eql([]);
.then((keys) => {
keys.should.eql(['key2']);
});
});
});
Expand Down

0 comments on commit 2320df1

Please sign in to comment.