Skip to content

Commit

Permalink
feat(commands): add new command LMOVE (#1292)
Browse files Browse the repository at this point in the history
Co-authored-by: Philip Nicholls <[email protected]>
  • Loading branch information
philios33 and philip-nicholls authored Jul 14, 2023
1 parent b3a6578 commit 7222ca8
Show file tree
Hide file tree
Showing 3 changed files with 181 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 @@ -55,6 +55,7 @@ export * from './lastsave'
export * from './lindex'
export * from './linsert'
export * from './llen'
export * from './lmove'
export * from './lolwut'
export * from './lpop'
export * from './lpush'
Expand Down
48 changes: 48 additions & 0 deletions src/commands/lmove.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export function lmove(listKey1, listKey2, position1, position2) {

if (this.data.has(listKey1) && !(this.data.get(listKey1) instanceof Array)) {
throw new Error(`Key ${listKey1} does not contain a list`)
}
if (this.data.has(listKey2) && !(this.data.get(listKey2) instanceof Array)) {
throw new Error(`Key ${listKey2} does not contain a list`)
}

if (position1 !== "LEFT" && position1 !== "RIGHT") {
throw new Error("Position1 argument must be 'LEFT' or 'RIGHT'");
}
if (position2 !== "LEFT" && position2 !== "RIGHT") {
throw new Error("Position2 argument must be 'LEFT' or 'RIGHT'");
}

const list1 = this.data.get(listKey1) || []
let list2 = list1; // Operate on the same list
if (listKey1 !== listKey2) {
// Operate on two different lists
list2 = this.data.get(listKey2) || []
}

if (list1.length === 0) {
return null;
}

let value;
if (position1 === "LEFT") {
value = list1.shift();
} else {
value = list1.pop();
}

if (position2 === "LEFT") {
list2.unshift(value);
} else {
list2.push(value);
}

this.data.set(listKey1, list1);
if (listKey2 !== listKey1) {
this.data.set(listKey2, list2);
}

return value

}
132 changes: 132 additions & 0 deletions test/integration/commands/lmove.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import Redis from 'ioredis'

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

runTwinSuite('lmove', command => {
describe(command, () => {
const redis = new Redis()
afterAll(() => {
redis.disconnect()
})

const listId1 = "LIST1";
const listId2 = "LIST2";
const emptyList = "EMPTY";
const notalist = "NOTALIST";

beforeEach(async () => {
await redis.del(listId1);
await redis.del(listId2);
await redis.del(notalist);

await redis.lpush(emptyList, "TEST");
await redis.lpop(emptyList);
const membersEmpty = await redis.lrange(listId1, 0, -1);
expect(membersEmpty).toEqual([]);

await redis.lpush(listId1, ['two', 'one']);
await redis.lpush(listId2, ['four', 'three']);
await redis.set(notalist, "TEST");
const members1 = await redis.lrange(listId1, 0, -1);
const members2 = await redis.lrange(listId2, 0, -1);
expect(members1).toEqual(['one', 'two']);
expect(members2).toEqual(['three', 'four']);
});

it('should move the value from LEFT of list1 to LEFT of list2', async () => {
const result = await redis.lmove(listId1, listId2, "LEFT", "LEFT");
expect(result).toEqual('one');

const current1 = await redis.lrange(listId1, 0, -1);
const current2 = await redis.lrange(listId2, 0, -1);
expect(current1).toEqual(['two']);
expect(current2).toEqual(['one', 'three', 'four']);
});

it('should move the value from RIGHT of list1 to LEFT of list2', async () => {
const result = await redis.lmove(listId1, listId2, "RIGHT", "LEFT");
expect(result).toEqual('two');

const current1 = await redis.lrange(listId1, 0, -1);
const current2 = await redis.lrange(listId2, 0, -1);
expect(current1).toEqual(['one']);
expect(current2).toEqual(['two', 'three', 'four']);
});

it('should move the value from LEFT of list1 to RIGHT of list2', async () => {
const result = await redis.lmove(listId1, listId2, "LEFT", "RIGHT");
expect(result).toEqual('one');

const current1 = await redis.lrange(listId1, 0, -1);
const current2 = await redis.lrange(listId2, 0, -1);
expect(current1).toEqual(['two']);
expect(current2).toEqual(['three', 'four', 'one']);
});

it('should move the value from RIGHT of list1 to RIGHT of list2', async () => {
const result = await redis.lmove(listId1, listId2, "RIGHT", "RIGHT");
expect(result).toEqual('two');

const current1 = await redis.lrange(listId1, 0, -1);
const current2 = await redis.lrange(listId2, 0, -1);
expect(current1).toEqual(['one']);
expect(current2).toEqual(['three', 'four', 'two']);
});

it('should rotate the list if the source and destination are the same', async () => {
const result = await redis.lmove(listId2, listId2, "LEFT", "RIGHT");
expect(result).toEqual('three');

const current1 = await redis.lrange(listId1, 0, -1);
const current2 = await redis.lrange(listId2, 0, -1);
expect(current1).toEqual(['one', 'two']);
expect(current2).toEqual(['four', 'three']);
});

it('should perform no operation if the source is an empty list', async () => {
const result = await redis.lmove(emptyList, listId2, "LEFT", "LEFT");
expect(result).toEqual(null);

const current1 = await redis.lrange(listId1, 0, -1);
const current2 = await redis.lrange(listId2, 0, -1);
expect(current1).toEqual(['one', 'two']);
expect(current2).toEqual(['three', 'four']);
});

it('should perform no operation if the source and destination are the same and both positions are LEFT', async () => {
const result = await redis.lmove(listId2, listId2, "LEFT", "LEFT");
expect(result).toEqual('three');

const current1 = await redis.lrange(listId1, 0, -1);
const current2 = await redis.lrange(listId2, 0, -1);
expect(current1).toEqual(['one', 'two']);
expect(current2).toEqual(['three', 'four']);
});

it('should perform no operation if the source and destination are the same and both positions are RIGHT', async () => {
const result = await redis.lmove(listId2, listId2, "RIGHT", "RIGHT");
expect(result).toEqual('four');

const current1 = await redis.lrange(listId1, 0, -1);
const current2 = await redis.lrange(listId2, 0, -1);
expect(current1).toEqual(['one', 'two']);
expect(current2).toEqual(['three', 'four']);
});


it('should perform no operation and return nil when source does not exist', async () => {
const value = await redis.get("unknown");
expect(value).toEqual(null); // Ensures nil is being represented by null

const result = await redis.lmove("unknown", listId2, "LEFT", "LEFT");
expect(result).toEqual(null);
});

it('should error if the value is not a list', async () => {
expect(async () => {
await redis.lmove(notalist, listId2, "LEFT", "LEFT");
}).rejects.toThrow("Key NOTALIST does not contain a list");
});
})
})

0 comments on commit 7222ca8

Please sign in to comment.