Skip to content

Commit

Permalink
feat: add zdiff (#1312)
Browse files Browse the repository at this point in the history
  • Loading branch information
mxr authored Sep 29, 2023
1 parent a20c172 commit 49a3b11
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 1 deletion.
2 changes: 1 addition & 1 deletion compat.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@
| [zadd] | :white_check_mark: | :white_check_mark: |
| [zcard] | :white_check_mark: | :white_check_mark: |
| [zcount] | :white_check_mark: | :white_check_mark: |
| [zdiff] | :white_check_mark: | :x: |
| [zdiff] | :white_check_mark: | :white_check_mark: |
| [zdiffstore] | :white_check_mark: | :x: |
| [zincrby] | :white_check_mark: | :white_check_mark: |
| [zinter] | :white_check_mark: | :x: |
Expand Down
1 change: 1 addition & 0 deletions src/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export * from './xrevrange'
export * from './zadd'
export * from './zcard'
export * from './zcount'
export * from './zdiff'
export * from './zincrby'
export * from './zinterstore'
export * from './zpopmax'
Expand Down
49 changes: 49 additions & 0 deletions src/commands/zdiff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { convertStringToBuffer } from '../commands-utils/convertStringToBuffer'
import { zrange } from './zrange'
import { zscore } from './zscore'

export function zdiff(numkeys, ...vals) {
if (vals.length === 0) {
throw new Error("ERR wrong number of arguments for 'zdiff' command")
}

const [keys, withScores] =
vals[vals.length - 1] === 'WITHSCORES'
? [vals.slice(0, -1), true]
: [vals, false]

if (
(Number.isInteger(numkeys) ? numkeys : parseInt(numkeys, 10)) !==
keys.length
) {
throw new Error(
'ERR numkeys must match the number of keys. ' +
`numkeys===${numkeys} keys but got ${keys.length} keys`
)
}

const firstKeyMembers = zrange.call(this, keys[0], 0, -1)
const otherMembers = new Set(
keys
.slice(1)
.map(key => zrange.call(this, key, 0, -1))
.flat()
)

const diffedMembers = firstKeyMembers.filter(
member => !otherMembers.has(member)
)

if (!withScores) {
return diffedMembers
}

return diffedMembers
.map(member => [member, zscore.call(this, keys[0], member)])
.flat()
}

export function zdiffBuffer(numkeys, ...vals) {
const val = zdiff.apply(this, numkeys, vals)
return convertStringToBuffer(val)
}
118 changes: 118 additions & 0 deletions test/integration/commands/zdiff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import Redis from 'ioredis'

// eslint-disable-next-line import/no-relative-parent-imports
import { runTwinSuite } from '../../../test-utils'

runTwinSuite('zdiff', command => {
describe(command, () => {
const redis = new Redis()

afterAll(() => {
redis.disconnect()
})

it('throws if not enough keys', () => {
redis
.zdiff(1, 'key1')
.catch(err =>
expect(err.toString()).toContain(
"ERR wrong number of arguments for 'zdiff' command"
)
)
})

it('throws if not enough keys with scores', () => {
redis
.zdiff(1, 'key1', 'WITHSCORES')
.catch(err =>
expect(err.toString()).toContain(
"ERR wrong number of arguments for 'zdiff' command"
)
)
})

it('throws if numkeys is wrong', () => {
redis
.zdiff(1, 'key1', 'key2')
.catch(err =>
expect(err.toString()).toContain(
'ERR numkeys must match the number of keys'
)
)
redis
.zdiff(2, 'key1', 'key2')
.catch(err =>
expect(err.toString()).toContain(
'ERR numkeys must match the number of keys'
)
)
})

it('throws if numkeys is wrong with scores', () => {
redis
.zdiff(1, 'key1', 'key2', 'WITHSCORES')
.catch(err =>
expect(err.toString()).toContain(
'ERR numkeys must match the number of keys'
)
)
redis
.zdiff(2, 'key1', 'key2', 'WITHSCORES')
.catch(err =>
expect(err.toString()).toContain(
'ERR numkeys must match the number of keys'
)
)
})

it('should return diff between two keys', async () => {
await redis.zadd('key1', 1, 'a', 2, 'b', 3, 'c', 4, 'd')
await redis.zadd('key2', 3, 'a', 4, 'd')

const members = await redis.zdiff(2, 'key1', 'key2')
expect(members).toEqual(['b', 'c'])
})

it('should return diff between two keys with scores', async () => {
await redis.zadd('key1', 1, 'a', 2, 'b', 3, 'c', 4, 'd')
await redis.zadd('key2', 3, 'a', 4, 'd')

const members = await redis.zdiff(2, 'key1', 'key2', 'WITHSCORES')
expect(members).toEqual(['b', '2', 'c', '3'])
})

it('should return diff between two keys with no overlap', async () => {
await redis.zadd('key1', 1, 'a', 2, 'b', 3, 'c', 4, 'd')
await redis.zadd('key2', 3, 'e', 4, 'f')

const members = await redis.zdiff(2, 'key1', 'key2')
expect(members).toEqual(['a', 'b', 'c', 'd'])
})

it('should return diff between two keys with no overlap with scores', async () => {
await redis.zadd('key1', 1, 'a', 2, 'b', 3, 'c', 4, 'd')
await redis.zadd('key2', 3, 'e', 4, 'f')

const members = await redis.zdiff(2, 'key1', 'key2', 'WITHSCORES')
expect(members).toEqual(['a', '1', 'b', '2', 'c', '3', 'd', '4'])
})

it('should return diff between three keys', async () => {
await redis.zadd('key1', 1, 'a', 2, 'b', 3, 'c', 4, 'd')
await redis.zadd('key2', 3, 'a', 4, 'd')
await redis.zadd('key2', 5, 'a', 6, 'c')

const members = await redis.zdiff(3, 'key1', 'key2', 'key3')
expect(members).toEqual(['b'])
})

it('should return diff between three keys with scores', async () => {
await redis.zadd('key1', 1, 'a', 2, 'b', 3, 'c', 4, 'd')
await redis.zadd('key2', 3, 'a', 4, 'd')
await redis.zadd('key2', 5, 'a', 6, 'c')

const members = await redis.zdiff(3, 'key1', 'key2', 'key3', 'WITHSCORES')
expect(members).toEqual(['b', '2'])
})
})
})

0 comments on commit 49a3b11

Please sign in to comment.