Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions packages/client/lib/commands/BLMOVEM.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL, BLOCKING_MIN_VALUE } from '../test-utils';
import BLMOVEM from './BLMOVEM';
import { parseArgs } from './generic-transformers';

describe('BLMOVEM', () => {
testUtils.isVersionGreaterThanHook([8, 10]);

describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
parseArgs(BLMOVEM, 'source', 'destination', 'LEFT', 'RIGHT', 0),
['BLMOVEM', 'source', 'destination', 'LEFT', 'RIGHT', '0']
);
});

it('with COUNT and ORDER', () => {
assert.deepEqual(
parseArgs(BLMOVEM, 'source', 'destination', 'LEFT', 'LEFT', 0, {
COUNT: 3,
ORDER: 'OBO'
}),
['BLMOVEM', 'source', 'destination', 'LEFT', 'LEFT', '0', 'COUNT', '3', 'OBO']
);
});

it('with EXACTLY and ORDER', () => {
assert.deepEqual(
parseArgs(BLMOVEM, 'source', 'destination', 'LEFT', 'RIGHT', 0, {
EXACTLY: 2,
ORDER: 'BULK'
}),
['BLMOVEM', 'source', 'destination', 'LEFT', 'RIGHT', '0', 'EXACTLY', '2', 'BULK']
);
});
});

testUtils.testAll('blMoveM - null on timeout', async client => {
assert.equal(
await client.blMoveM('{tag}source', '{tag}destination', 'LEFT', 'RIGHT', BLOCKING_MIN_VALUE),
null
);
}, {
client: GLOBAL.SERVERS.OPEN,
cluster: GLOBAL.CLUSTERS.OPEN
});

testUtils.testAll('blMoveM - with elements', async client => {
const [, reply] = await Promise.all([
client.rPush('{tag}source', ['1', '2', '3']),
client.blMoveM('{tag}source', '{tag}destination', 'LEFT', 'LEFT', BLOCKING_MIN_VALUE, {
COUNT: 2,
ORDER: 'BULK'
})
]);
assert.deepEqual(reply, ['1', '2']);
}, {
client: GLOBAL.SERVERS.OPEN,
cluster: GLOBAL.CLUSTERS.OPEN
});
});
23 changes: 23 additions & 0 deletions packages/client/lib/commands/BLMOVEM.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, Command } from '../RESP/types';
import { ListSide } from './generic-transformers';
import LMOVEM, { LMoveMOptions, parseLMoveMOptions } from './LMOVEM';

export default {
IS_READ_ONLY: false,
parseCommand(
parser: CommandParser,
source: RedisArgument,
destination: RedisArgument,
sourceSide: ListSide,
destinationSide: ListSide,
timeout: number,
options?: LMoveMOptions
) {
parser.push('BLMOVEM');
parser.pushKeys([source, destination]);
parser.push(sourceSide, destinationSide, timeout.toString());
parseLMoveMOptions(parser, options);
},
transformReply: LMOVEM.transformReply
} as const satisfies Command;
145 changes: 145 additions & 0 deletions packages/client/lib/commands/LMOVEM.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import LMOVEM from './LMOVEM';
import { parseArgs } from './generic-transformers';

describe('LMOVEM', () => {
testUtils.isVersionGreaterThanHook([8, 10]);

describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
parseArgs(LMOVEM, 'source', 'destination', 'LEFT', 'RIGHT'),
['LMOVEM', 'source', 'destination', 'LEFT', 'RIGHT']
);
});

it('with COUNT', () => {
assert.deepEqual(
parseArgs(LMOVEM, 'source', 'destination', 'LEFT', 'RIGHT', {
COUNT: 3
}),
['LMOVEM', 'source', 'destination', 'LEFT', 'RIGHT', 'COUNT', '3']
);
});

it('with COUNT and ORDER', () => {
assert.deepEqual(
parseArgs(LMOVEM, 'source', 'destination', 'LEFT', 'LEFT', {
COUNT: 3,
ORDER: 'OBO'
}),
['LMOVEM', 'source', 'destination', 'LEFT', 'LEFT', 'COUNT', '3', 'OBO']
);
});

it('with EXACTLY and ORDER', () => {
assert.deepEqual(
parseArgs(LMOVEM, 'source', 'destination', 'LEFT', 'RIGHT', {
EXACTLY: 2,
ORDER: 'BULK'
}),
['LMOVEM', 'source', 'destination', 'LEFT', 'RIGHT', 'EXACTLY', '2', 'BULK']
);
});
});

testUtils.testAll('lMoveM - null when source empty', async client => {
assert.equal(
await client.lMoveM('{tag}source', '{tag}destination', 'LEFT', 'RIGHT'),
null
);
}, {
client: GLOBAL.SERVERS.OPEN,
cluster: GLOBAL.CLUSTERS.OPEN
});

testUtils.testAll('lMoveM - COUNT with OBO ordering', async client => {
await client.rPush('{tag}source', ['1', '2', '3', '4', '5']);
assert.deepEqual(
await client.lMoveM('{tag}source', '{tag}destination', 'LEFT', 'LEFT', {
COUNT: 3,
ORDER: 'OBO'
}),
['3', '2', '1']
);
assert.deepEqual(
await client.lRange('{tag}destination', 0, -1),
['3', '2', '1']
);
assert.deepEqual(
await client.lRange('{tag}source', 0, -1),
['4', '5']
);
}, {
client: GLOBAL.SERVERS.OPEN,
cluster: GLOBAL.CLUSTERS.OPEN
});

testUtils.testAll('lMoveM - COUNT with BULK ordering', async client => {
await client.rPush('{tag}source', ['1', '2', '3', '4', '5']);
assert.deepEqual(
await client.lMoveM('{tag}source', '{tag}destination', 'LEFT', 'LEFT', {
COUNT: 3,
ORDER: 'BULK'
}),
['1', '2', '3']
);
assert.deepEqual(
await client.lRange('{tag}destination', 0, -1),
['1', '2', '3']
);
}, {
client: GLOBAL.SERVERS.OPEN,
cluster: GLOBAL.CLUSTERS.OPEN
});

testUtils.testAll('lMoveM - no options moves a single element as an array', async client => {
await client.rPush('{tag}source', ['1', '2', '3']);
assert.deepEqual(
await client.lMoveM('{tag}source', '{tag}destination', 'LEFT', 'RIGHT'),
['1']
);
assert.deepEqual(
await client.lRange('{tag}source', 0, -1),
['2', '3']
);
}, {
client: GLOBAL.SERVERS.OPEN,
cluster: GLOBAL.CLUSTERS.OPEN
});

testUtils.testAll('lMoveM - COUNT clamps to available elements', async client => {
await client.rPush('{tag}source', ['1', '2', '3']);
assert.deepEqual(
await client.lMoveM('{tag}source', '{tag}destination', 'LEFT', 'LEFT', {
COUNT: 10,
ORDER: 'BULK'
}),
['1', '2', '3']
);
assert.equal(await client.exists('{tag}source'), 0);
}, {
client: GLOBAL.SERVERS.OPEN,
cluster: GLOBAL.CLUSTERS.OPEN
});

testUtils.testAll('lMoveM - EXACTLY with too few elements moves nothing', async client => {
await client.rPush('{tag}source', ['1', '2']);
assert.equal(
await client.lMoveM('{tag}source', '{tag}destination', 'LEFT', 'RIGHT', {
EXACTLY: 3,
ORDER: 'BULK'
}),
null
);
Comment thread
cursor[bot] marked this conversation as resolved.
assert.deepEqual(
await client.lRange('{tag}source', 0, -1),
['1', '2']
);
assert.equal(await client.exists('{tag}destination'), 0);
}, {
client: GLOBAL.SERVERS.OPEN,
cluster: GLOBAL.CLUSTERS.OPEN
});
});
59 changes: 59 additions & 0 deletions packages/client/lib/commands/LMOVEM.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { CommandParser } from '../client/parser';
import { RedisArgument, ArrayReply, BlobStringReply, NullReply, Command } from '../RESP/types';
import { ListSide } from './generic-transformers';

/**
* Ordering of the moved elements at the destination.
*
* - `OBO` (one-by-one): push each element as it is popped, reversing the
* moved block's relative order (stack semantics).
* - `BULK`: preserve the moved block's original relative order (queue semantics).
*/
export type LMoveMOrder = 'OBO' | 'BULK';

export type LMoveMOptions = {
/** Move up to `COUNT` elements (fewer if the source has fewer). */
COUNT: number;
ORDER?: LMoveMOrder;
} | {
/** Move exactly `EXACTLY` elements; if the source has fewer, move nothing and reply with null. */
EXACTLY: number;
ORDER?: LMoveMOrder;
};

/**
* Pushes the trailing `[<COUNT|EXACTLY> count [OBO|BULK]]` arguments shared by
* LMOVEM and BLMOVEM. Kept separate from the command bodies because BLMOVEM's
* `timeout` sits between the sides and these options, so only the tail is shared.
*/
export function parseLMoveMOptions(parser: CommandParser, options?: LMoveMOptions) {
if (options) {
if ('EXACTLY' in options) {
parser.push('EXACTLY', options.EXACTLY.toString());
} else {
parser.push('COUNT', options.COUNT.toString());
}

if (options.ORDER !== undefined) {
parser.push(options.ORDER);
}
}
}

export default {
IS_READ_ONLY: false,
parseCommand(
parser: CommandParser,
source: RedisArgument,
destination: RedisArgument,
sourceSide: ListSide,
destinationSide: ListSide,
options?: LMoveMOptions
) {
parser.push('LMOVEM');
parser.pushKeys([source, destination]);
parser.push(sourceSide, destinationSide);
parseLMoveMOptions(parser, options);
},
transformReply: undefined as unknown as () => ArrayReply<BlobStringReply> | NullReply
} as const satisfies Command;
50 changes: 50 additions & 0 deletions packages/client/lib/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import BITFIELD from './BITFIELD';
import BITOP from './BITOP';
import BITPOS from './BITPOS';
import BLMOVE from './BLMOVE';
import BLMOVEM from './BLMOVEM';
import BLMPOP from './BLMPOP';
import BLPOP from './BLPOP';
import BRPOP from './BRPOP';
Expand Down Expand Up @@ -208,6 +209,7 @@ import LINDEX from './LINDEX';
import LINSERT from './LINSERT';
import LLEN from './LLEN';
import LMOVE from './LMOVE';
import LMOVEM from './LMOVEM';
import LMPOP from './LMPOP';
import LOLWUT from './LOLWUT';
import LPOP_COUNT from './LPOP_COUNT';
Expand Down Expand Up @@ -951,6 +953,30 @@ export default {
* @param timeout - Timeout in seconds, 0 to block indefinitely
*/
blMove: BLMOVE,
/**
* Moves multiple elements from one list to another; or blocks until the source has elements to move
* @param source - Key of the source list
* @param destination - Key of the destination list
* @param sourceSide - Side of source list to pop from (LEFT or RIGHT)
* @param destinationSide - Side of destination list to push to (LEFT or RIGHT)
* @param timeout - Timeout in seconds, 0 to block indefinitely
* @param options - Optional COUNT/EXACTLY count and OBO/BULK ordering
* @see https://redis.io/commands/blmovem/
* @since 8.10
*/
BLMOVEM,
/**
* Moves multiple elements from one list to another; or blocks until the source has elements to move
* @param source - Key of the source list
* @param destination - Key of the destination list
* @param sourceSide - Side of source list to pop from (LEFT or RIGHT)
* @param destinationSide - Side of destination list to push to (LEFT or RIGHT)
* @param timeout - Timeout in seconds, 0 to block indefinitely
* @param options - Optional COUNT/EXACTLY count and OBO/BULK ordering
* @see https://redis.io/commands/blmovem/
* @since 8.10
*/
blMoveM: BLMOVEM,
/**
* Pops elements from multiple lists; blocks until elements are available
* @param timeout - Timeout in seconds, 0 to block indefinitely
Expand Down Expand Up @@ -3037,6 +3063,30 @@ export default {
* @see https://redis.io/commands/lmove/
*/
lMove: LMOVE,
/**
* Moves multiple elements from one list to another
*
* @param source - The source list key
* @param destination - The destination list key
* @param sourceSide - The side to pop from (LEFT or RIGHT)
* @param destinationSide - The side to push to (LEFT or RIGHT)
* @param options - Optional COUNT/EXACTLY count and OBO/BULK ordering
* @see https://redis.io/commands/lmovem/
* @since 8.10
*/
LMOVEM,
/**
* Moves multiple elements from one list to another
*
* @param source - The source list key
* @param destination - The destination list key
* @param sourceSide - The side to pop from (LEFT or RIGHT)
* @param destinationSide - The side to push to (LEFT or RIGHT)
* @param options - Optional COUNT/EXACTLY count and OBO/BULK ordering
* @see https://redis.io/commands/lmovem/
* @since 8.10
*/
lMoveM: LMOVEM,
/**
* Constructs the LMPOP command
*
Expand Down