This commit is contained in:
Rogger Valverde 2024-04-15 15:00:07 +01:00 committed by GitHub
commit 8732fee816
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 190 additions and 41 deletions

View File

@ -3805,7 +3805,9 @@ struct COMMAND_ARG PFMERGE_Args[] = {
#ifndef SKIP_CMD_HISTORY_TABLE
/* BLMOVE history */
#define BLMOVE_History NULL
commandHistory BLMOVE_History[] = {
{"7.3.0","Added the `count` argument."},
};
#endif
#ifndef SKIP_CMD_TIPS_TABLE
@ -3839,6 +3841,7 @@ struct COMMAND_ARG BLMOVE_Args[] = {
{MAKE_ARG("wherefrom",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=BLMOVE_wherefrom_Subargs},
{MAKE_ARG("whereto",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=BLMOVE_whereto_Subargs},
{MAKE_ARG("timeout",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,"7.3.0",CMD_ARG_OPTIONAL,0,NULL)},
};
/********** BLMPOP ********************/
@ -4043,7 +4046,9 @@ struct COMMAND_ARG LLEN_Args[] = {
#ifndef SKIP_CMD_HISTORY_TABLE
/* LMOVE history */
#define LMOVE_History NULL
commandHistory LMOVE_History[] = {
{"7.3.0","Added the `count` argument."},
};
#endif
#ifndef SKIP_CMD_TIPS_TABLE
@ -4076,6 +4081,7 @@ struct COMMAND_ARG LMOVE_Args[] = {
{MAKE_ARG("destination",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("wherefrom",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=LMOVE_wherefrom_Subargs},
{MAKE_ARG("whereto",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=LMOVE_whereto_Subargs},
{MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,"7.3.0",CMD_ARG_OPTIONAL,0,NULL)},
};
/********** LMPOP ********************/
@ -10731,7 +10737,7 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("pfmerge","Merges one or more HyperLogLog values into a single key.","O(N) to merge N HyperLogLogs, but with high constant times.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFMERGE_History,0,PFMERGE_Tips,0,pfmergeCommand,-2,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_HYPERLOGLOG,PFMERGE_Keyspecs,2,NULL,2),.args=PFMERGE_Args},
{MAKE_CMD("pfselftest","An internal command for testing HyperLogLog values.","N/A","2.8.9",CMD_DOC_SYSCMD,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFSELFTEST_History,0,PFSELFTEST_Tips,0,pfselftestCommand,1,CMD_ADMIN,ACL_CATEGORY_HYPERLOGLOG,PFSELFTEST_Keyspecs,0,NULL,0)},
/* list */
{MAKE_CMD("blmove","Pops an element from a list, pushes it to another list and returns it. Blocks until an element is available otherwise. Deletes the list if the last element was moved.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BLMOVE_History,0,BLMOVE_Tips,0,blmoveCommand,6,CMD_WRITE|CMD_DENYOOM|CMD_BLOCKING,ACL_CATEGORY_LIST,BLMOVE_Keyspecs,2,NULL,5),.args=BLMOVE_Args},
{MAKE_CMD("blmove","Pops an element from a list, pushes it to another list and returns it. Blocks until an element is available otherwise. Deletes the list if the last element was moved.","O(N) where N is the number of elements returned","6.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BLMOVE_History,1,BLMOVE_Tips,0,blmoveCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_BLOCKING,ACL_CATEGORY_LIST,BLMOVE_Keyspecs,2,NULL,6),.args=BLMOVE_Args},
{MAKE_CMD("blmpop","Pops the first element from one of multiple lists. Blocks until an element is available otherwise. Deletes the list if the last element was popped.","O(N+M) where N is the number of provided keys and M is the number of elements returned.","7.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BLMPOP_History,0,BLMPOP_Tips,0,blmpopCommand,-5,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_LIST,BLMPOP_Keyspecs,1,blmpopGetKeys,5),.args=BLMPOP_Args},
{MAKE_CMD("blpop","Removes and returns the first element in a list. Blocks until an element is available otherwise. Deletes the list if the last element was popped.","O(N) where N is the number of provided keys.","2.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BLPOP_History,1,BLPOP_Tips,0,blpopCommand,-3,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_LIST,BLPOP_Keyspecs,1,NULL,2),.args=BLPOP_Args},
{MAKE_CMD("brpop","Removes and returns the last element in a list. Blocks until an element is available otherwise. Deletes the list if the last element was popped.","O(N) where N is the number of provided keys.","2.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BRPOP_History,1,BRPOP_Tips,0,brpopCommand,-3,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_LIST,BRPOP_Keyspecs,1,NULL,2),.args=BRPOP_Args},
@ -10739,7 +10745,7 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("lindex","Returns an element from a list by its index.","O(N) where N is the number of elements to traverse to get to the element at index. This makes asking for the first or the last element of the list O(1).","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LINDEX_History,0,LINDEX_Tips,0,lindexCommand,3,CMD_READONLY,ACL_CATEGORY_LIST,LINDEX_Keyspecs,1,NULL,2),.args=LINDEX_Args},
{MAKE_CMD("linsert","Inserts an element before or after another element in a list.","O(N) where N is the number of elements to traverse before seeing the value pivot. This means that inserting somewhere on the left end on the list (head) can be considered O(1) and inserting somewhere on the right end (tail) is O(N).","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LINSERT_History,0,LINSERT_Tips,0,linsertCommand,5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_LIST,LINSERT_Keyspecs,1,NULL,4),.args=LINSERT_Args},
{MAKE_CMD("llen","Returns the length of a list.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LLEN_History,0,LLEN_Tips,0,llenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_LIST,LLEN_Keyspecs,1,NULL,1),.args=LLEN_Args},
{MAKE_CMD("lmove","Returns an element after popping it from one list and pushing it to another. Deletes the list if the last element was moved.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LMOVE_History,0,LMOVE_Tips,0,lmoveCommand,5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_LIST,LMOVE_Keyspecs,2,NULL,4),.args=LMOVE_Args},
{MAKE_CMD("lmove","Returns an element after popping it from one list and pushing it to another. Deletes the list if the last element was moved.","O(N) where N is the number of elements returned","6.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LMOVE_History,1,LMOVE_Tips,0,lmoveCommand,-5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_LIST,LMOVE_Keyspecs,2,NULL,5),.args=LMOVE_Args},
{MAKE_CMD("lmpop","Returns multiple elements from a list after removing them. Deletes the list if the last element was popped.","O(N+M) where N is the number of provided keys and M is the number of elements returned.","7.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LMPOP_History,0,LMPOP_Tips,0,lmpopCommand,-4,CMD_WRITE,ACL_CATEGORY_LIST,LMPOP_Keyspecs,1,lmpopGetKeys,4),.args=LMPOP_Args},
{MAKE_CMD("lpop","Returns the first elements in a list after removing it. Deletes the list if the last element was popped.","O(N) where N is the number of elements returned","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LPOP_History,1,LPOP_Tips,0,lpopCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_LIST,LPOP_Keyspecs,1,NULL,2),.args=LPOP_Args},
{MAKE_CMD("lpos","Returns the index of matching elements in a list.","O(N) where N is the number of elements in the list, for the average case. When searching for elements near the head or the tail of the list, or when the MAXLEN option is provided, the command may run in constant time.","6.0.6",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LPOS_History,0,LPOS_Tips,0,lposCommand,-3,CMD_READONLY,ACL_CATEGORY_LIST,LPOS_Keyspecs,1,NULL,5),.args=LPOS_Args},

View File

@ -1,16 +1,22 @@
{
"BLMOVE": {
"summary": "Pops an element from a list, pushes it to another list and returns it. Blocks until an element is available otherwise. Deletes the list if the last element was moved.",
"complexity": "O(1)",
"complexity": "O(N) where N is the number of elements returned",
"group": "list",
"since": "6.2.0",
"arity": 6,
"arity": -6,
"function": "blmoveCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"BLOCKING"
],
"history": [
[
"7.3.0",
"Added the `count` argument."
]
],
"acl_categories": [
"LIST"
],
@ -56,9 +62,16 @@
"reply_schema": {
"oneOf": [
{
"description": "The popped element.",
"description": "In case `count` argument was not given, the popped element.",
"type": "string"
},
{
"description": "In case `count` argument was given, a list of elements being popped and pushed.",
"type": "array",
"items": {
"type": "string"
}
},
{
"description": "Operation timed-out",
"type": "null"
@ -111,6 +124,12 @@
{
"name": "timeout",
"type": "double"
},
{
"name": "count",
"type": "integer",
"optional": true,
"since": "7.3.0"
}
]
}

View File

@ -1,15 +1,21 @@
{
"LMOVE": {
"summary": "Returns an element after popping it from one list and pushing it to another. Deletes the list if the last element was moved.",
"complexity": "O(1)",
"complexity": "O(N) where N is the number of elements returned",
"group": "list",
"since": "6.2.0",
"arity": 5,
"arity": -5,
"function": "lmoveCommand",
"command_flags": [
"WRITE",
"DENYOOM"
],
"history": [
[
"7.3.0",
"Added the `count` argument."
]
],
"acl_categories": [
"LIST"
],
@ -53,8 +59,19 @@
}
],
"reply_schema": {
"description": "The element being popped and pushed.",
"type": "string"
"oneOf": [
{
"description": "In case `count` argument was not given, the element being popped and pushed.",
"type": "string"
},
{
"description": "In case `count` argument was given, a list of elements being popped and pushed.",
"type": "array",
"items": {
"type": "string"
}
}
]
},
"arguments": [
{
@ -98,6 +115,12 @@
"token": "RIGHT"
}
]
},
{
"name": "count",
"type": "integer",
"optional": true,
"since": "7.3.0"
}
]
}

View File

@ -1115,46 +1115,101 @@ robj *getStringObjectFromListPosition(int position) {
}
}
void lmoveGenericCommand(client *c, int wherefrom, int whereto) {
robj *sobj, *value;
if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
void lmoveGenericCommand(client *c, int wherefrom, int whereto, int countIndex) {
robj *sobj;
int hascount = (c->argc == (countIndex + 1));
long count = 0;
if (hascount) {
/* Parse the optional count argument. */
if (getPositiveLongFromObjectOrReply(c,c->argv[countIndex],&count,NULL) != C_OK)
return;
}
if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],hascount ? shared.nullarray[c->resp]: shared.null[c->resp]))
== NULL || checkType(c,sobj,OBJ_LIST)) return;
if (listTypeLength(sobj) == 0) {
/* This may only happen after loading very old RDB files. Recent
* versions of Redis delete keys of empty lists. */
addReplyNull(c);
} else {
robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
robj *touchedkey = c->argv[1];
if (hascount && !count) {
/* Fast exit path. */
addReply(c,shared.emptyarray);
return;
}
if (checkType(c,dobj,OBJ_LIST)) return;
value = listTypePop(sobj,wherefrom);
serverAssert(value); /* assertion for valgrind (avoid NPD) */
lmoveHandlePush(c,c->argv[2],dobj,value,whereto);
listElementsRemoved(c,touchedkey,wherefrom,sobj,1,1,NULL);
if (!count) {
robj *value;
if (listTypeLength(sobj) == 0) {
/* This may only happen after loading very old RDB files. Recent
* versions of Redis delete keys of empty lists. */
addReplyNull(c);
} else {
robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
robj *touchedkey = c->argv[1];
/* listTypePop returns an object with its refcount incremented */
decrRefCount(value);
if (checkType(c,dobj,OBJ_LIST)) return;
value = listTypePop(sobj,wherefrom);
serverAssert(value); /* assertion for valgrind (avoid NPD) */
/* Create the list if the key does not exist */
if (!dobj) {
dobj = createListListpackObject();
dbAdd(c->db,c->argv[2],dobj);
}
lmoveHandlePush(c,c->argv[2],dobj,value,whereto);
listElementsRemoved(c,touchedkey,wherefrom,sobj,1,1,NULL);
if (c->cmd->proc == blmoveCommand) {
rewriteClientCommandVector(c,5,shared.lmove,
c->argv[1],c->argv[2],c->argv[3],c->argv[4]);
} else if (c->cmd->proc == brpoplpushCommand) {
rewriteClientCommandVector(c,3,shared.rpoplpush,
c->argv[1],c->argv[2]);
/* listTypePop returns an object with its refcount incremented */
decrRefCount(value);
}
} else {
if (listTypeLength(sobj) == 0) {
/* This may only happen after loading very old RDB files. Recent
* versions of Redis delete keys of empty lists. */
addReply(c,shared.emptyarray);
} else {
robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
robj *touchedkey = c->argv[1];
if (checkType(c,dobj,OBJ_LIST)) return;
long llen = listTypeLength(sobj);
long rangelen = (count > llen) ? llen : count;
addReplyArrayLen(c,rangelen);
long int j;
if (!dobj) {
dobj = createListListpackObject();
dbAdd(c->db,c->argv[2],dobj);
}
for (j = 0; j < rangelen; j++) {
robj *value;
value = listTypePop(sobj,wherefrom);
serverAssert(value); /* assertion for valgrind (avoid NPD) */
lmoveHandlePush(c,c->argv[2],dobj,value,whereto);
listElementsRemoved(c,touchedkey,wherefrom,sobj,1,1,NULL);
/* listTypePop returns an object with its refcount incremented */
decrRefCount(value);
}
}
}
if (c->cmd->proc == blmoveCommand) {
rewriteClientCommandVector(c,5,shared.lmove,
c->argv[1],c->argv[2],c->argv[3],c->argv[4]);
} else if (c->cmd->proc == brpoplpushCommand) {
rewriteClientCommandVector(c,3,shared.rpoplpush,
c->argv[1],c->argv[2]);
}
}
/* LMOVE <source> <destination> (LEFT|RIGHT) (LEFT|RIGHT) */
/* LMOVE <source> <destination> (LEFT|RIGHT) (LEFT|RIGHT) [count]*/
void lmoveCommand(client *c) {
int wherefrom, whereto;
if (getListPositionFromObjectOrReply(c,c->argv[3],&wherefrom)
!= C_OK) return;
if (getListPositionFromObjectOrReply(c,c->argv[4],&whereto)
!= C_OK) return;
lmoveGenericCommand(c, wherefrom, whereto);
lmoveGenericCommand(c, wherefrom, whereto, 5);
}
/* This is the semantic of this command:
@ -1173,7 +1228,7 @@ void lmoveCommand(client *c) {
* as well. This command was originally proposed by Ezra Zygmuntowicz.
*/
void rpoplpushCommand(client *c) {
lmoveGenericCommand(c, LIST_TAIL, LIST_HEAD);
lmoveGenericCommand(c, LIST_TAIL, LIST_HEAD, -1);
}
/* Blocking RPOP/LPOP/LMPOP
@ -1277,11 +1332,11 @@ void blmoveGenericCommand(client *c, int wherefrom, int whereto, mstime_t timeou
/* The list exists and has elements, so
* the regular lmoveCommand is executed. */
serverAssertWithInfo(c,key,listTypeLength(key) > 0);
lmoveGenericCommand(c,wherefrom,whereto);
lmoveGenericCommand(c,wherefrom,whereto,6);
}
}
/* BLMOVE <source> <destination> (LEFT|RIGHT) (LEFT|RIGHT) <timeout> */
/* BLMOVE <source> <destination> (LEFT|RIGHT) (LEFT|RIGHT) <timeout> [count]*/
void blmoveCommand(client *c) {
mstime_t timeout;
int wherefrom, whereto;

View File

@ -55,8 +55,8 @@
"SCAN 2 MATCH " "pattern [COUNT count] [TYPE type]"
"SCAN 2 COUNT " "count [MATCH pattern] [TYPE type]"
# One-of choices: BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout
"BLMOVE src dst LEFT " "LEFT|RIGHT timeout"
# One-of choices: BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout [count]
"BLMOVE src dst LEFT " "LEFT|RIGHT timeout [count]"
# Optional args can be in any order: ZRANGE key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
"ZRANGE k 1 2 " "[BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]"

View File

@ -166,6 +166,24 @@ foreach type {listpack quicklist} {
r debug quicklist-packed-threshold 0
} {OK} {needs:debug}
test {Test LMOVE with the optional count argument} {
r flushdb
r debug quicklist-packed-threshold 1b
r RPUSH lst2{t} "aa"
r RPUSH lst2{t} "bb"
r LSET lst2{t} 0 xxxxxxxxxxx
r RPUSH lst2{t} "cc"
r RPUSH lst2{t} "dd"
r LMOVE lst2{t} lst{t} RIGHT LEFT 2
assert_equal [r llen lst{t}] 2
assert_equal [r llen lst2{t}] 2
assert_equal [r lpop lst2{t}] "xxxxxxxxxxx"
assert_equal [r lpop lst2{t}] "bb"
assert_equal [r lpop lst{t}] "cc"
assert_equal [r lpop lst{t}] "dd"
r debug quicklist-packed-threshold 0
} {OK} {needs:debug}
# testing LSET with combinations of node types
# plain->packed , packed->plain, plain->plain, packed->packed
test {Test LSET with packed / plain combinations} {
@ -760,6 +778,34 @@ foreach {type large} [array get largevalue] {
}
$rd close
}
test "BLMOVE $wherefrom $whereto - $type with the optional count argument" {
r del target{t}
r rpush target{t} bar
set rd [redis_deferring_client]
create_$type blist{t} "a b $large c d"
$rd blmove blist{t} target{t} $wherefrom $whereto 1 2
set poppedelements [$rd read]
if {$wherefrom eq "right"} {
assert_equal "d c" $poppedelements
assert_equal "a b $large" [r lrange blist{t} 0 -1]
} else {
assert_equal "a b" $poppedelements
assert_equal "$large c d" [r lrange blist{t} 0 -1]
}
if {$whereto eq "right"} {
r lpop target{t}
assert_equal $poppedelements [r lpop target{t} 2]
} else {
r rpop target{t}
assert_equal $poppedelements [r rpop target{t} 2]
}
$rd close
}
}
}
}