Skip to content

Commit

Permalink
feat: zinterstore support(#863)
Browse files Browse the repository at this point in the history
Fixes #862
  • Loading branch information
mishan authored and stipsan committed Oct 7, 2019
1 parent eeb3905 commit 74d0d8e
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export * from './zadd';
export * from './zcard';
export * from './zcount';
export * from './zincrby';
export * from './zinterstore';
export * from './zrange';
export * from './zrangebyscore';
export * from './zrank';
Expand Down
55 changes: 55 additions & 0 deletions src/commands/zinterstore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Map from 'es6-map';
import arrayFrom from 'array-from';
import { intersectionBy } from 'lodash';

export function zinterstore(destKey, numKeys, ...keys) {
const srcMaps = [];

if (parseInt(numKeys, 10) !== keys.length) {
throw new Error('ERR syntax error');
}

for (let i = 0; i < keys.length; i += 1) {
// @TODO investigate a more stable way to detect sorted lists
if (!this.data.has(keys[i]) || this.data.get(keys[i]) instanceof Map) {
srcMaps.push(this.data.get(keys[i]));
}
}

// deep copy inputs
const inputs = srcMaps.map(x =>
JSON.parse(JSON.stringify(arrayFrom(x.values())))
);

const intersected = intersectionBy(...inputs, 'value');

if (intersected.length === 0) {
// make sure we don't have destKey set anymore
this.data.delete(destKey);

return 0;
}

// @TODO: support AGGREGATE option
// @TODO: support WEIGHTS option
// aggregate weights
for (let i = 0; i < intersected.length; i += 1) {
let weightSum = 0;

for (let j = 0; j < srcMaps.length; j += 1) {
if (srcMaps[j].get(intersected[i].value)) {
weightSum += srcMaps[j].get(intersected[i].value).score;
}
}

intersected[i].score = weightSum;
intersected[i] = [intersected[i].value, intersected[i]];
}

const intersectedMap = new Map(intersected);

// store new sorted set
this.data.set(destKey, intersectedMap);

return intersected.length;
}
80 changes: 80 additions & 0 deletions test/commands/zinterstore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import Map from 'es6-map';
import expect from 'expect';

import MockRedis from '../../src';

describe('zinterstore', () => {
const data = {
foo: new Map([
['first', { score: 1, value: 'first' }],
['second', { score: 2, value: 'second' }],
['third', { score: 3, value: 'third' }],
['fourth', { score: 4, value: 'fourth' }],
['fifth', { score: 5, value: 'fifth' }],
]),
bar: new Map([
['first', { score: 1, value: 'first' }],
['third', { score: 3, value: 'third' }],
['fourth', { score: 4, value: 'fourth' }],
['tenth', { score: 10, value: 'tenth' }],
]),
baz: new Map([
['twentieth', { score: 20, value: 'twentieth' }],
['thirtieth', { score: 30, value: 'thirtiest' }],
]),
};

it('should intersect two sorted sets and return the number of resulting elements', () => {
const redis = new MockRedis({ data });

return redis
.zinterstore('dest', 2, 'foo', 'bar')
.then(res => expect(res).toEqual(3));
});

it('should return 0 if the intersection is an empty set', () => {
const redis = new MockRedis({ data });

return redis
.zinterstore('dest', 2, 'foo', 'baz')
.then(res => expect(res).toEqual(0));
});

it('should not create a sorted set if the intersection is an empty set', () => {
const redis = new MockRedis({ data });

return redis.zinterstore('dest', 2, 'foo', 'baz').then(() => {
expect(redis.data.get('dest')).toBe(undefined);
});
});

it('should intersect two sorted sets with the correct data in the specified key', () => {
const redis = new MockRedis({ data });

return redis.zinterstore('dest', 2, 'foo', 'bar').then(() => {
expect(redis.data.get('dest')).toEqual(
new Map([
['first', { score: 2, value: 'first' }],
['third', { score: 6, value: 'third' }],
['fourth', { score: 8, value: 'fourth' }],
])
);
});
});

it('should throw a syntax error if more keys specified than numKeys', () => {
const redis = new MockRedis({ data });

return redis
.zinterstore('dest', 2, 'foo', 'bar', 'baz')
.catch(err => expect(err.message).toBe('ERR syntax error'));
});

it('should throw a syntax error if fewer keys specified than numKeys', () => {
const redis = new MockRedis({ data });

return redis
.zinterstore('dest', 3, 'foo', 'bar')
.catch(err => expect(err.message).toBe('ERR syntax error'));
});
});

0 comments on commit 74d0d8e

Please sign in to comment.