Skip to content

Commit

Permalink
Support zdiff
Browse files Browse the repository at this point in the history
  • Loading branch information
mxr committed Aug 24, 2023
1 parent 114919d commit 496afe2
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 1 deletion.
2 changes: 1 addition & 1 deletion compat.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,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
44 changes: 44 additions & 0 deletions src/commands/zdiff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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 (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 [key1Members, ...key2toNSortedSets] = keys.map(key =>
zrange.call(this, key, 0, -1)
)

const otherMembers = new Set(key2toNSortedSets.flat())
const members = key1Members.filter(member => !otherMembers.has(member))

if (!withScores) {
return members
}

const membersWithScores = []
for (const member of members) {
membersWithScores.push(member)
membersWithScores.push(zscore.call(this, keys[0], member))
}
return membersWithScores
}

export function zdiffBuffer(numkeys, ...keys) {
const val = zdiff.apply(this, numkeys, keys)
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 496afe2

Please sign in to comment.