mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 08:08:53 -05:00
Modules: Add remaining list API functions (#8439)
List functions operating on elements by index: * RM_ListGet * RM_ListSet * RM_ListInsert * RM_ListDelete Iteration is done using a simple for loop over indices. The index based functions use an internal iterator as an optimization. This is explained in the docs: ``` * Many of the list functions access elements by index. Since a list is in * essence a doubly-linked list, accessing elements by index is generally an * O(N) operation. However, if elements are accessed sequentially or with * indices close together, the functions are optimized to seek the index from * the previous index, rather than seeking from the ends of the list. * * This enables iteration to be done efficiently using a simple for loop: * * long n = RM_ValueLength(key); * for (long i = 0; i < n; i++) { * RedisModuleString *elem = RedisModule_ListGet(key, i); * // Do stuff... * } ```
This commit is contained in:
parent
1376d83363
commit
ea36d4de17
@ -36,6 +36,7 @@ $TCLSH tests/test_helper.tcl \
|
|||||||
--single unit/moduleapi/defrag \
|
--single unit/moduleapi/defrag \
|
||||||
--single unit/moduleapi/hash \
|
--single unit/moduleapi/hash \
|
||||||
--single unit/moduleapi/zset \
|
--single unit/moduleapi/zset \
|
||||||
|
--single unit/moduleapi/list \
|
||||||
--single unit/moduleapi/stream \
|
--single unit/moduleapi/stream \
|
||||||
--single unit/moduleapi/datatype2 \
|
--single unit/moduleapi/datatype2 \
|
||||||
"${@}"
|
"${@}"
|
||||||
|
299
src/module.c
299
src/module.c
@ -184,6 +184,11 @@ struct RedisModuleKey {
|
|||||||
int mode; /* Opening mode. */
|
int mode; /* Opening mode. */
|
||||||
|
|
||||||
union {
|
union {
|
||||||
|
struct {
|
||||||
|
/* List, use only if value->type == OBJ_LIST */
|
||||||
|
listTypeEntry entry; /* Current entry in iteration. */
|
||||||
|
long index; /* Current 0-based index in iteration. */
|
||||||
|
} list;
|
||||||
struct {
|
struct {
|
||||||
/* Zset iterator, use only if value->type == OBJ_ZSET */
|
/* Zset iterator, use only if value->type == OBJ_ZSET */
|
||||||
uint32_t type; /* REDISMODULE_ZSET_RANGE_* */
|
uint32_t type; /* REDISMODULE_ZSET_RANGE_* */
|
||||||
@ -538,6 +543,17 @@ int moduleCreateEmptyKey(RedisModuleKey *key, int type) {
|
|||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Frees key->iter and sets it to NULL. */
|
||||||
|
static void moduleFreeKeyIterator(RedisModuleKey *key) {
|
||||||
|
serverAssert(key->iter != NULL);
|
||||||
|
switch (key->value->type) {
|
||||||
|
case OBJ_LIST: listTypeReleaseIterator(key->iter); break;
|
||||||
|
case OBJ_STREAM: zfree(key->iter); break;
|
||||||
|
default: serverAssert(0); /* No key->iter for other types. */
|
||||||
|
}
|
||||||
|
key->iter = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* This function is called in low-level API implementation functions in order
|
/* This function is called in low-level API implementation functions in order
|
||||||
* to check if the value associated with the key remained empty after an
|
* to check if the value associated with the key remained empty after an
|
||||||
* operation that removed elements from an aggregate data type.
|
* operation that removed elements from an aggregate data type.
|
||||||
@ -563,6 +579,7 @@ int moduleDelKeyIfEmpty(RedisModuleKey *key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isempty) {
|
if (isempty) {
|
||||||
|
if (key->iter) moduleFreeKeyIterator(key);
|
||||||
dbDelete(key->db,key->key);
|
dbDelete(key->db,key->key);
|
||||||
key->value = NULL;
|
key->value = NULL;
|
||||||
return 1;
|
return 1;
|
||||||
@ -2436,13 +2453,20 @@ static void moduleCloseKey(RedisModuleKey *key) {
|
|||||||
int signal = SHOULD_SIGNAL_MODIFIED_KEYS(key->ctx);
|
int signal = SHOULD_SIGNAL_MODIFIED_KEYS(key->ctx);
|
||||||
if ((key->mode & REDISMODULE_WRITE) && signal)
|
if ((key->mode & REDISMODULE_WRITE) && signal)
|
||||||
signalModifiedKey(key->ctx->client,key->db,key->key);
|
signalModifiedKey(key->ctx->client,key->db,key->key);
|
||||||
if (key->iter) zfree(key->iter);
|
if (key->value) {
|
||||||
RM_ZsetRangeStop(key);
|
if (key->iter) moduleFreeKeyIterator(key);
|
||||||
if (key && key->value && key->value->type == OBJ_STREAM &&
|
switch (key->value->type) {
|
||||||
key->u.stream.signalready) {
|
case OBJ_ZSET:
|
||||||
/* One of more RM_StreamAdd() have been done. */
|
RM_ZsetRangeStop(key);
|
||||||
signalKeyAsReady(key->db, key->key, OBJ_STREAM);
|
break;
|
||||||
|
case OBJ_STREAM:
|
||||||
|
if (key->u.stream.signalready)
|
||||||
|
/* One or more RM_StreamAdd() have been done. */
|
||||||
|
signalKeyAsReady(key->db, key->key, OBJ_STREAM);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
serverAssert(key->iter == NULL);
|
||||||
decrRefCount(key->key);
|
decrRefCount(key->key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2735,16 +2759,108 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) {
|
|||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
* ## Key API for List type
|
* ## Key API for List type
|
||||||
*
|
*
|
||||||
|
* Many of the list functions access elements by index. Since a list is in
|
||||||
|
* essence a doubly-linked list, accessing elements by index is generally an
|
||||||
|
* O(N) operation. However, if elements are accessed sequentially or with
|
||||||
|
* indices close together, the functions are optimized to seek the index from
|
||||||
|
* the previous index, rather than seeking from the ends of the list.
|
||||||
|
*
|
||||||
|
* This enables iteration to be done efficiently using a simple for loop:
|
||||||
|
*
|
||||||
|
* long n = RM_ValueLength(key);
|
||||||
|
* for (long i = 0; i < n; i++) {
|
||||||
|
* RedisModuleString *elem = RedisModule_ListGet(key, i);
|
||||||
|
* // Do stuff...
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Note that after modifying a list using RM_ListPop, RM_ListSet or
|
||||||
|
* RM_ListInsert, the internal iterator is invalidated so the next operation
|
||||||
|
* will require a linear seek.
|
||||||
|
*
|
||||||
|
* Modifying a list in any another way, for examle using RM_Call(), while a key
|
||||||
|
* is open will confuse the internal iterator and may cause trouble if the key
|
||||||
|
* is used after such modifications. The key must be reopened in this case.
|
||||||
|
*
|
||||||
* See also RM_ValueLength(), which returns the length of a list.
|
* See also RM_ValueLength(), which returns the length of a list.
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/* Push an element into a list, on head or tail depending on 'where' argument.
|
/* Seeks the key's internal list iterator to the given index. On success, 1 is
|
||||||
* If the key pointer is about an empty key opened for writing, the key
|
* returned and key->iter, key->u.list.entry and key->u.list.index are set. On
|
||||||
* is created. On error (key opened for read-only operations or of the wrong
|
* failure, 0 is returned and errno is set as required by the list API
|
||||||
* type) REDISMODULE_ERR is returned, otherwise REDISMODULE_OK is returned. */
|
* functions. */
|
||||||
|
int moduleListIteratorSeek(RedisModuleKey *key, long index, int mode) {
|
||||||
|
if (!key) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return 0;
|
||||||
|
} else if (!key->value || key->value->type != OBJ_LIST) {
|
||||||
|
errno = ENOTSUP;
|
||||||
|
return 0;
|
||||||
|
} if (!(key->mode & mode)) {
|
||||||
|
errno = EBADF;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long length = listTypeLength(key->value);
|
||||||
|
if (index < -length || index >= length) {
|
||||||
|
errno = EDOM; /* Invalid index */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key->iter == NULL) {
|
||||||
|
/* No existing iterator. Create one. */
|
||||||
|
key->iter = listTypeInitIterator(key->value, index, LIST_TAIL);
|
||||||
|
serverAssert(key->iter != NULL);
|
||||||
|
serverAssert(listTypeNext(key->iter, &key->u.list.entry));
|
||||||
|
key->u.list.index = index;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* There's an existing iterator. Make sure the requested index has the same
|
||||||
|
* sign as the iterator's index. */
|
||||||
|
if (index < 0 && key->u.list.index >= 0) index += length;
|
||||||
|
else if (index >= 0 && key->u.list.index < 0) index -= length;
|
||||||
|
|
||||||
|
if (index == key->u.list.index) return 1; /* We're done. */
|
||||||
|
|
||||||
|
/* Seek the iterator to the requested index. */
|
||||||
|
unsigned char dir = key->u.list.index < index ? LIST_TAIL : LIST_HEAD;
|
||||||
|
listTypeSetIteratorDirection(key->iter, dir);
|
||||||
|
while (key->u.list.index != index) {
|
||||||
|
serverAssert(listTypeNext(key->iter, &key->u.list.entry));
|
||||||
|
key->u.list.index += dir == LIST_HEAD ? -1 : 1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Push an element into a list, on head or tail depending on 'where' argument
|
||||||
|
* (REDISMODULE_LIST_HEAD or REDISMODULE_LIST_TAIL). If the key refers to an
|
||||||
|
* empty key opened for writing, the key is created. On success, REDISMODULE_OK
|
||||||
|
* is returned. On failure, REDISMODULE_ERR is returned and `errno` is set as
|
||||||
|
* follows:
|
||||||
|
*
|
||||||
|
* - EINVAL if key or ele is NULL.
|
||||||
|
* - ENOTSUP if the key is of another type than list.
|
||||||
|
* - EBADF if the key is not opened for writing.
|
||||||
|
*
|
||||||
|
* Note: Before Redis 7.0, `errno` was not set by this function. */
|
||||||
int RM_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele) {
|
int RM_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele) {
|
||||||
|
if (!key || !ele) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
} else if (key->value != NULL && key->value->type != OBJ_LIST) {
|
||||||
|
errno = ENOTSUP;
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
} if (!(key->mode & REDISMODULE_WRITE)) {
|
||||||
|
errno = EBADF;
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR;
|
if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR;
|
||||||
if (key->value && key->value->type != OBJ_LIST) return REDISMODULE_ERR;
|
if (key->value && key->value->type != OBJ_LIST) return REDISMODULE_ERR;
|
||||||
|
if (key->iter) {
|
||||||
|
listTypeReleaseIterator(key->iter);
|
||||||
|
key->iter = NULL;
|
||||||
|
}
|
||||||
if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_LIST);
|
if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_LIST);
|
||||||
listTypePush(key->value, ele,
|
listTypePush(key->value, ele,
|
||||||
(where == REDISMODULE_LIST_HEAD) ? LIST_HEAD : LIST_TAIL);
|
(where == REDISMODULE_LIST_HEAD) ? LIST_HEAD : LIST_TAIL);
|
||||||
@ -2753,16 +2869,31 @@ int RM_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele) {
|
|||||||
|
|
||||||
/* Pop an element from the list, and returns it as a module string object
|
/* Pop an element from the list, and returns it as a module string object
|
||||||
* that the user should be free with RM_FreeString() or by enabling
|
* that the user should be free with RM_FreeString() or by enabling
|
||||||
* automatic memory. 'where' specifies if the element should be popped from
|
* automatic memory. The `where` argument specifies if the element should be
|
||||||
* head or tail. The command returns NULL if:
|
* popped from the beginning or the end of the list (REDISMODULE_LIST_HEAD or
|
||||||
|
* REDISMODULE_LIST_TAIL). On failure, the command returns NULL and sets
|
||||||
|
* `errno` as follows:
|
||||||
*
|
*
|
||||||
* 1. The list is empty.
|
* - EINVAL if key is NULL.
|
||||||
* 2. The key was not open for writing.
|
* - ENOTSUP if the key is empty or of another type than list.
|
||||||
* 3. The key is not a list. */
|
* - EBADF if the key is not opened for writing.
|
||||||
|
*
|
||||||
|
* Note: Before Redis 7.0, `errno` was not set by this function. */
|
||||||
RedisModuleString *RM_ListPop(RedisModuleKey *key, int where) {
|
RedisModuleString *RM_ListPop(RedisModuleKey *key, int where) {
|
||||||
if (!(key->mode & REDISMODULE_WRITE) ||
|
if (!key) {
|
||||||
key->value == NULL ||
|
errno = EINVAL;
|
||||||
key->value->type != OBJ_LIST) return NULL;
|
return NULL;
|
||||||
|
} else if (key->value == NULL || key->value->type != OBJ_LIST) {
|
||||||
|
errno = ENOTSUP;
|
||||||
|
return NULL;
|
||||||
|
} else if (!(key->mode & REDISMODULE_WRITE)) {
|
||||||
|
errno = EBADF;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (key->iter) {
|
||||||
|
listTypeReleaseIterator(key->iter);
|
||||||
|
key->iter = NULL;
|
||||||
|
}
|
||||||
robj *ele = listTypePop(key->value,
|
robj *ele = listTypePop(key->value,
|
||||||
(where == REDISMODULE_LIST_HEAD) ? LIST_HEAD : LIST_TAIL);
|
(where == REDISMODULE_LIST_HEAD) ? LIST_HEAD : LIST_TAIL);
|
||||||
robj *decoded = getDecodedObject(ele);
|
robj *decoded = getDecodedObject(ele);
|
||||||
@ -2772,6 +2903,134 @@ RedisModuleString *RM_ListPop(RedisModuleKey *key, int where) {
|
|||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns the element at index `index` in the list stored at `key`, like the
|
||||||
|
* LINDEX command. The element should be free'd using RM_FreeString() or using
|
||||||
|
* automatic memory management.
|
||||||
|
*
|
||||||
|
* The index is zero-based, so 0 means the first element, 1 the second element
|
||||||
|
* and so on. Negative indices can be used to designate elements starting at the
|
||||||
|
* tail of the list. Here, -1 means the last element, -2 means the penultimate
|
||||||
|
* and so forth.
|
||||||
|
*
|
||||||
|
* When no value is found at the given key and index, NULL is returned and
|
||||||
|
* `errno` is set as follows:
|
||||||
|
*
|
||||||
|
* - EINVAL if key is NULL.
|
||||||
|
* - ENOTSUP if the key is not a list.
|
||||||
|
* - EBADF if the key is not opened for reading.
|
||||||
|
* - EDOM if the index is not a valid index in the list.
|
||||||
|
*/
|
||||||
|
RedisModuleString *RM_ListGet(RedisModuleKey *key, long index) {
|
||||||
|
if (moduleListIteratorSeek(key, index, REDISMODULE_READ)) {
|
||||||
|
robj *elem = listTypeGet(&key->u.list.entry);
|
||||||
|
robj *decoded = getDecodedObject(elem);
|
||||||
|
decrRefCount(elem);
|
||||||
|
autoMemoryAdd(key->ctx, REDISMODULE_AM_STRING, decoded);
|
||||||
|
return decoded;
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Replaces the element at index `index` in the list stored at `key`.
|
||||||
|
*
|
||||||
|
* The index is zero-based, so 0 means the first element, 1 the second element
|
||||||
|
* and so on. Negative indices can be used to designate elements starting at the
|
||||||
|
* tail of the list. Here, -1 means the last element, -2 means the penultimate
|
||||||
|
* and so forth.
|
||||||
|
*
|
||||||
|
* On success, REDISMODULE_OK is returned. On failure, REDISMODULE_ERR is
|
||||||
|
* returned and `errno` is set as follows:
|
||||||
|
*
|
||||||
|
* - EINVAL if key or value is NULL.
|
||||||
|
* - ENOTSUP if the key is not a list.
|
||||||
|
* - EBADF if the key is not opened for writing.
|
||||||
|
* - EDOM if the index is not a valid index in the list.
|
||||||
|
*/
|
||||||
|
int RM_ListSet(RedisModuleKey *key, long index, RedisModuleString *value) {
|
||||||
|
if (!value) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
}
|
||||||
|
if (moduleListIteratorSeek(key, index, REDISMODULE_WRITE)) {
|
||||||
|
listTypeReplace(&key->u.list.entry, value);
|
||||||
|
/* A note in quicklist.c forbids use of iterator after insert, so
|
||||||
|
* probably also after replace. */
|
||||||
|
listTypeReleaseIterator(key->iter);
|
||||||
|
key->iter = NULL;
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
} else {
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inserts an element at the given index.
|
||||||
|
*
|
||||||
|
* The index is zero-based, so 0 means the first element, 1 the second element
|
||||||
|
* and so on. Negative indices can be used to designate elements starting at the
|
||||||
|
* tail of the list. Here, -1 means the last element, -2 means the penultimate
|
||||||
|
* and so forth. The index is the element's index after inserting it.
|
||||||
|
*
|
||||||
|
* On success, REDISMODULE_OK is returned. On failure, REDISMODULE_ERR is
|
||||||
|
* returned and `errno` is set as follows:
|
||||||
|
*
|
||||||
|
* - EINVAL if key or value is NULL.
|
||||||
|
* - ENOTSUP if the key of another type than list.
|
||||||
|
* - EBADF if the key is not opened for writing.
|
||||||
|
* - EDOM if the index is not a valid index in the list.
|
||||||
|
*/
|
||||||
|
int RM_ListInsert(RedisModuleKey *key, long index, RedisModuleString *value) {
|
||||||
|
if (!value) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
} else if (key != NULL && key->value == NULL &&
|
||||||
|
(index == 0 || index == -1)) {
|
||||||
|
/* Insert in empty key => push. */
|
||||||
|
return RM_ListPush(key, REDISMODULE_LIST_TAIL, value);
|
||||||
|
} else if (key != NULL && key->value != NULL &&
|
||||||
|
key->value->type == OBJ_LIST &&
|
||||||
|
(index == (long)listTypeLength(key->value) || index == -1)) {
|
||||||
|
/* Insert after the last element => push tail. */
|
||||||
|
return RM_ListPush(key, REDISMODULE_LIST_TAIL, value);
|
||||||
|
} else if (key != NULL && key->value != NULL &&
|
||||||
|
key->value->type == OBJ_LIST &&
|
||||||
|
(index == 0 || index == -(long)listTypeLength(key->value) - 1)) {
|
||||||
|
/* Insert before the first element => push head. */
|
||||||
|
return RM_ListPush(key, REDISMODULE_LIST_HEAD, value);
|
||||||
|
}
|
||||||
|
if (moduleListIteratorSeek(key, index, REDISMODULE_WRITE)) {
|
||||||
|
int where = index < 0 ? LIST_TAIL : LIST_HEAD;
|
||||||
|
listTypeInsert(&key->u.list.entry, value, where);
|
||||||
|
/* A note in quicklist.c forbids use of iterator after insert. */
|
||||||
|
listTypeReleaseIterator(key->iter);
|
||||||
|
key->iter = NULL;
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
} else {
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Removes an element at the given index. The index is 0-based. A negative index
|
||||||
|
* can also be used, counting from the end of the list.
|
||||||
|
*
|
||||||
|
* On success, REDISMODULE_OK is returned. On failure, REDISMODULE_ERR is
|
||||||
|
* returned and `errno` is set as follows:
|
||||||
|
*
|
||||||
|
* - EINVAL if key or value is NULL.
|
||||||
|
* - ENOTSUP if the key is not a list.
|
||||||
|
* - EBADF if the key is not opened for writing.
|
||||||
|
* - EDOM if the index is not a valid index in the list.
|
||||||
|
*/
|
||||||
|
int RM_ListDelete(RedisModuleKey *key, long index) {
|
||||||
|
if (moduleListIteratorSeek(key, index, REDISMODULE_WRITE)) {
|
||||||
|
listTypeDelete(key->iter, &key->u.list.entry);
|
||||||
|
moduleDelKeyIfEmpty(key);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
} else {
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
* ## Key API for Sorted Set type
|
* ## Key API for Sorted Set type
|
||||||
*
|
*
|
||||||
@ -9564,6 +9823,10 @@ void moduleRegisterCoreAPI(void) {
|
|||||||
REGISTER_API(ValueLength);
|
REGISTER_API(ValueLength);
|
||||||
REGISTER_API(ListPush);
|
REGISTER_API(ListPush);
|
||||||
REGISTER_API(ListPop);
|
REGISTER_API(ListPop);
|
||||||
|
REGISTER_API(ListGet);
|
||||||
|
REGISTER_API(ListSet);
|
||||||
|
REGISTER_API(ListInsert);
|
||||||
|
REGISTER_API(ListDelete);
|
||||||
REGISTER_API(StringToLongLong);
|
REGISTER_API(StringToLongLong);
|
||||||
REGISTER_API(StringToDouble);
|
REGISTER_API(StringToDouble);
|
||||||
REGISTER_API(StringToLongDouble);
|
REGISTER_API(StringToLongDouble);
|
||||||
|
@ -673,6 +673,15 @@ void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) {
|
|||||||
* quicklistNext() will jump to the next node. */
|
* quicklistNext() will jump to the next node. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Replace quicklist entry by 'data' with length 'sz'. */
|
||||||
|
void quicklistReplaceEntry(quicklist *quicklist, quicklistEntry *entry,
|
||||||
|
void *data, int sz) {
|
||||||
|
/* quicklistNext() and quicklistIndex() provide an uncompressed node */
|
||||||
|
entry->node->zl = ziplistReplace(entry->node->zl, entry->zi, data, sz);
|
||||||
|
quicklistNodeUpdateSz(entry->node);
|
||||||
|
quicklistCompress(quicklist, entry->node);
|
||||||
|
}
|
||||||
|
|
||||||
/* Replace quicklist entry at offset 'index' by 'data' with length 'sz'.
|
/* Replace quicklist entry at offset 'index' by 'data' with length 'sz'.
|
||||||
*
|
*
|
||||||
* Returns 1 if replace happened.
|
* Returns 1 if replace happened.
|
||||||
@ -681,10 +690,7 @@ int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
|
|||||||
int sz) {
|
int sz) {
|
||||||
quicklistEntry entry;
|
quicklistEntry entry;
|
||||||
if (likely(quicklistIndex(quicklist, index, &entry))) {
|
if (likely(quicklistIndex(quicklist, index, &entry))) {
|
||||||
/* quicklistIndex provides an uncompressed node */
|
quicklistReplaceEntry(quicklist, &entry, data, sz);
|
||||||
entry.node->zl = ziplistReplace(entry.node->zl, entry.zi, data, sz);
|
|
||||||
quicklistNodeUpdateSz(entry.node);
|
|
||||||
quicklistCompress(quicklist, entry.node);
|
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
@ -1189,6 +1195,11 @@ int quicklistNext(quicklistIter *iter, quicklistEntry *entry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sets the direction of a quicklist iterator. */
|
||||||
|
void quicklistSetDirection(quicklistIter *iter, int direction) {
|
||||||
|
iter->direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
/* Duplicate the quicklist.
|
/* Duplicate the quicklist.
|
||||||
* On success a copy of the original quicklist is returned.
|
* On success a copy of the original quicklist is returned.
|
||||||
*
|
*
|
||||||
|
@ -169,6 +169,8 @@ void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *entry,
|
|||||||
void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *entry,
|
void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *entry,
|
||||||
void *value, const size_t sz);
|
void *value, const size_t sz);
|
||||||
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry);
|
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry);
|
||||||
|
void quicklistReplaceEntry(quicklist *quicklist, quicklistEntry *entry,
|
||||||
|
void *data, int sz);
|
||||||
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
|
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
|
||||||
int sz);
|
int sz);
|
||||||
int quicklistDelRange(quicklist *quicklist, const long start, const long stop);
|
int quicklistDelRange(quicklist *quicklist, const long start, const long stop);
|
||||||
@ -176,6 +178,7 @@ quicklistIter *quicklistGetIterator(const quicklist *quicklist, int direction);
|
|||||||
quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
|
quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
|
||||||
int direction, const long long idx);
|
int direction, const long long idx);
|
||||||
int quicklistNext(quicklistIter *iter, quicklistEntry *entry);
|
int quicklistNext(quicklistIter *iter, quicklistEntry *entry);
|
||||||
|
void quicklistSetDirection(quicklistIter *iter, int direction);
|
||||||
void quicklistReleaseIterator(quicklistIter *iter);
|
void quicklistReleaseIterator(quicklistIter *iter);
|
||||||
quicklist *quicklistDup(quicklist *orig);
|
quicklist *quicklistDup(quicklist *orig);
|
||||||
int quicklistIndex(const quicklist *quicklist, const long long index,
|
int quicklistIndex(const quicklist *quicklist, const long long index,
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
* Avoid touching the LRU/LFU of the key when opened. */
|
* Avoid touching the LRU/LFU of the key when opened. */
|
||||||
#define REDISMODULE_OPEN_KEY_NOTOUCH (1<<16)
|
#define REDISMODULE_OPEN_KEY_NOTOUCH (1<<16)
|
||||||
|
|
||||||
|
/* List push and pop */
|
||||||
#define REDISMODULE_LIST_HEAD 0
|
#define REDISMODULE_LIST_HEAD 0
|
||||||
#define REDISMODULE_LIST_TAIL 1
|
#define REDISMODULE_LIST_TAIL 1
|
||||||
|
|
||||||
@ -615,6 +616,10 @@ REDISMODULE_API int (*RedisModule_KeyType)(RedisModuleKey *kp) REDISMODULE_ATTR;
|
|||||||
REDISMODULE_API size_t (*RedisModule_ValueLength)(RedisModuleKey *kp) REDISMODULE_ATTR;
|
REDISMODULE_API size_t (*RedisModule_ValueLength)(RedisModuleKey *kp) REDISMODULE_ATTR;
|
||||||
REDISMODULE_API int (*RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele) REDISMODULE_ATTR;
|
REDISMODULE_API int (*RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele) REDISMODULE_ATTR;
|
||||||
REDISMODULE_API RedisModuleString * (*RedisModule_ListPop)(RedisModuleKey *key, int where) REDISMODULE_ATTR;
|
REDISMODULE_API RedisModuleString * (*RedisModule_ListPop)(RedisModuleKey *key, int where) REDISMODULE_ATTR;
|
||||||
|
REDISMODULE_API RedisModuleString * (*RedisModule_ListGet)(RedisModuleKey *key, long index) REDISMODULE_ATTR;
|
||||||
|
REDISMODULE_API int (*RedisModule_ListSet)(RedisModuleKey *key, long index, RedisModuleString *value) REDISMODULE_ATTR;
|
||||||
|
REDISMODULE_API int (*RedisModule_ListInsert)(RedisModuleKey *key, long index, RedisModuleString *value) REDISMODULE_ATTR;
|
||||||
|
REDISMODULE_API int (*RedisModule_ListDelete)(RedisModuleKey *key, long index) REDISMODULE_ATTR;
|
||||||
REDISMODULE_API RedisModuleCallReply * (*RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR;
|
REDISMODULE_API RedisModuleCallReply * (*RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR;
|
||||||
REDISMODULE_API const char * (*RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len) REDISMODULE_ATTR;
|
REDISMODULE_API const char * (*RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len) REDISMODULE_ATTR;
|
||||||
REDISMODULE_API void (*RedisModule_FreeCallReply)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
|
REDISMODULE_API void (*RedisModule_FreeCallReply)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
|
||||||
@ -941,6 +946,10 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(ValueLength);
|
REDISMODULE_GET_API(ValueLength);
|
||||||
REDISMODULE_GET_API(ListPush);
|
REDISMODULE_GET_API(ListPush);
|
||||||
REDISMODULE_GET_API(ListPop);
|
REDISMODULE_GET_API(ListPop);
|
||||||
|
REDISMODULE_GET_API(ListGet);
|
||||||
|
REDISMODULE_GET_API(ListSet);
|
||||||
|
REDISMODULE_GET_API(ListInsert);
|
||||||
|
REDISMODULE_GET_API(ListDelete);
|
||||||
REDISMODULE_GET_API(StringToLongLong);
|
REDISMODULE_GET_API(StringToLongLong);
|
||||||
REDISMODULE_GET_API(StringToDouble);
|
REDISMODULE_GET_API(StringToDouble);
|
||||||
REDISMODULE_GET_API(StringToLongDouble);
|
REDISMODULE_GET_API(StringToLongDouble);
|
||||||
|
@ -1988,9 +1988,11 @@ robj *listTypePop(robj *subject, int where);
|
|||||||
unsigned long listTypeLength(const robj *subject);
|
unsigned long listTypeLength(const robj *subject);
|
||||||
listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char direction);
|
listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char direction);
|
||||||
void listTypeReleaseIterator(listTypeIterator *li);
|
void listTypeReleaseIterator(listTypeIterator *li);
|
||||||
|
void listTypeSetIteratorDirection(listTypeIterator *li, unsigned char direction);
|
||||||
int listTypeNext(listTypeIterator *li, listTypeEntry *entry);
|
int listTypeNext(listTypeIterator *li, listTypeEntry *entry);
|
||||||
robj *listTypeGet(listTypeEntry *entry);
|
robj *listTypeGet(listTypeEntry *entry);
|
||||||
void listTypeInsert(listTypeEntry *entry, robj *value, int where);
|
void listTypeInsert(listTypeEntry *entry, robj *value, int where);
|
||||||
|
void listTypeReplace(listTypeEntry *entry, robj *value);
|
||||||
int listTypeEqual(listTypeEntry *entry, robj *o);
|
int listTypeEqual(listTypeEntry *entry, robj *o);
|
||||||
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
|
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
|
||||||
void listTypeConvert(robj *subject, int enc);
|
void listTypeConvert(robj *subject, int enc);
|
||||||
|
21
src/t_list.c
21
src/t_list.c
@ -103,6 +103,13 @@ listTypeIterator *listTypeInitIterator(robj *subject, long index,
|
|||||||
return li;
|
return li;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sets the direction of an iterator. */
|
||||||
|
void listTypeSetIteratorDirection(listTypeIterator *li, unsigned char direction) {
|
||||||
|
li->direction = direction;
|
||||||
|
int dir = direction == LIST_HEAD ? AL_START_TAIL : AL_START_HEAD;
|
||||||
|
quicklistSetDirection(li->iter, dir);
|
||||||
|
}
|
||||||
|
|
||||||
/* Clean up the iterator. */
|
/* Clean up the iterator. */
|
||||||
void listTypeReleaseIterator(listTypeIterator *li) {
|
void listTypeReleaseIterator(listTypeIterator *li) {
|
||||||
zfree(li->iter);
|
zfree(li->iter);
|
||||||
@ -159,6 +166,20 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Replaces entry at the current position of the iterator. */
|
||||||
|
void listTypeReplace(listTypeEntry *entry, robj *value) {
|
||||||
|
if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||||
|
value = getDecodedObject(value);
|
||||||
|
sds str = value->ptr;
|
||||||
|
size_t len = sdslen(str);
|
||||||
|
quicklistReplaceEntry((quicklist *)entry->entry.quicklist,
|
||||||
|
&entry->entry, str, len);
|
||||||
|
decrRefCount(value);
|
||||||
|
} else {
|
||||||
|
serverPanic("Unknown list encoding");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Compare the given object with the entry at the current position. */
|
/* Compare the given object with the entry at the current position. */
|
||||||
int listTypeEqual(listTypeEntry *entry, robj *o) {
|
int listTypeEqual(listTypeEntry *entry, robj *o) {
|
||||||
if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) {
|
if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||||
|
@ -40,6 +40,7 @@ TEST_MODULES = \
|
|||||||
hash.so \
|
hash.so \
|
||||||
zset.so \
|
zset.so \
|
||||||
stream.so \
|
stream.so \
|
||||||
|
list.so
|
||||||
|
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
|
236
tests/modules/list.c
Normal file
236
tests/modules/list.c
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
#include "redismodule.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
|
/* LIST.GETALL key [REVERSE] */
|
||||||
|
int list_getall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc < 2 || argc > 3) return RedisModule_WrongArity(ctx);
|
||||||
|
int reverse = (argc == 3 &&
|
||||||
|
!strcasecmp(RedisModule_StringPtrLen(argv[2], NULL),
|
||||||
|
"REVERSE"));
|
||||||
|
RedisModule_AutoMemory(ctx);
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
|
||||||
|
if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_LIST) {
|
||||||
|
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||||
|
}
|
||||||
|
long n = RedisModule_ValueLength(key);
|
||||||
|
RedisModule_ReplyWithArray(ctx, n);
|
||||||
|
if (!reverse) {
|
||||||
|
for (long i = 0; i < n; i++) {
|
||||||
|
RedisModuleString *elem = RedisModule_ListGet(key, i);
|
||||||
|
RedisModule_ReplyWithString(ctx, elem);
|
||||||
|
RedisModule_FreeString(ctx, elem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (long i = -1; i >= -n; i--) {
|
||||||
|
RedisModuleString *elem = RedisModule_ListGet(key, i);
|
||||||
|
RedisModule_ReplyWithString(ctx, elem);
|
||||||
|
RedisModule_FreeString(ctx, elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test error condition: index out of bounds */
|
||||||
|
assert(RedisModule_ListGet(key, n) == NULL);
|
||||||
|
assert(errno == EDOM); /* no more elements in list */
|
||||||
|
|
||||||
|
/* RedisModule_CloseKey(key); //implicit, done by auto memory */
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LIST.EDIT key [REVERSE] cmdstr [value ..]
|
||||||
|
*
|
||||||
|
* cmdstr is a string of the following characters:
|
||||||
|
*
|
||||||
|
* k -- keep
|
||||||
|
* d -- delete
|
||||||
|
* i -- insert value from args
|
||||||
|
* r -- replace with value from args
|
||||||
|
*
|
||||||
|
* The number of occurrences of "i" and "r" in cmdstr) should correspond to the
|
||||||
|
* number of args after cmdstr.
|
||||||
|
*
|
||||||
|
* The reply is the number of edits (inserts + replaces + deletes) performed.
|
||||||
|
*/
|
||||||
|
int list_edit(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc < 3) return RedisModule_WrongArity(ctx);
|
||||||
|
RedisModule_AutoMemory(ctx);
|
||||||
|
int argpos = 1; /* the next arg */
|
||||||
|
|
||||||
|
/* key */
|
||||||
|
int keymode = REDISMODULE_READ | REDISMODULE_WRITE;
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[argpos++], keymode);
|
||||||
|
if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_LIST) {
|
||||||
|
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* REVERSE */
|
||||||
|
int reverse = 0;
|
||||||
|
if (argc >= 4 &&
|
||||||
|
!strcasecmp(RedisModule_StringPtrLen(argv[argpos], NULL), "REVERSE")) {
|
||||||
|
reverse = 1;
|
||||||
|
argpos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cmdstr */
|
||||||
|
size_t cmdstr_len;
|
||||||
|
const char *cmdstr = RedisModule_StringPtrLen(argv[argpos++], &cmdstr_len);
|
||||||
|
|
||||||
|
/* validate cmdstr vs. argc */
|
||||||
|
long num_req_args = 0;
|
||||||
|
long min_list_length = 0;
|
||||||
|
for (size_t cmdpos = 0; cmdpos < cmdstr_len; cmdpos++) {
|
||||||
|
char c = cmdstr[cmdpos];
|
||||||
|
if (c == 'i' || c == 'r') num_req_args++;
|
||||||
|
if (c == 'd' || c == 'r' || c == 'k') min_list_length++;
|
||||||
|
}
|
||||||
|
if (argc < argpos + num_req_args) {
|
||||||
|
return RedisModule_ReplyWithError(ctx, "ERR too few args");
|
||||||
|
}
|
||||||
|
if ((long)RedisModule_ValueLength(key) < min_list_length) {
|
||||||
|
return RedisModule_ReplyWithError(ctx, "ERR list too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Iterate over the chars in cmdstr (edit instructions) */
|
||||||
|
long long num_edits = 0;
|
||||||
|
long index = reverse ? -1 : 0;
|
||||||
|
RedisModuleString *value;
|
||||||
|
|
||||||
|
for (size_t cmdpos = 0; cmdpos < cmdstr_len; cmdpos++) {
|
||||||
|
switch (cmdstr[cmdpos]) {
|
||||||
|
case 'i': /* insert */
|
||||||
|
value = argv[argpos++];
|
||||||
|
assert(RedisModule_ListInsert(key, index, value) == REDISMODULE_OK);
|
||||||
|
index += reverse ? -1 : 1;
|
||||||
|
num_edits++;
|
||||||
|
break;
|
||||||
|
case 'd': /* delete */
|
||||||
|
assert(RedisModule_ListDelete(key, index) == REDISMODULE_OK);
|
||||||
|
num_edits++;
|
||||||
|
break;
|
||||||
|
case 'r': /* replace */
|
||||||
|
value = argv[argpos++];
|
||||||
|
assert(RedisModule_ListSet(key, index, value) == REDISMODULE_OK);
|
||||||
|
index += reverse ? -1 : 1;
|
||||||
|
num_edits++;
|
||||||
|
break;
|
||||||
|
case 'k': /* keep */
|
||||||
|
index += reverse ? -1 : 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, num_edits);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reply based on errno as set by the List API functions. */
|
||||||
|
static int replyByErrno(RedisModuleCtx *ctx) {
|
||||||
|
switch (errno) {
|
||||||
|
case EDOM:
|
||||||
|
return RedisModule_ReplyWithError(ctx, "ERR index out of bounds");
|
||||||
|
case ENOTSUP:
|
||||||
|
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||||
|
default: assert(0); /* Can't happen */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LIST.GET key index */
|
||||||
|
int list_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 3) return RedisModule_WrongArity(ctx);
|
||||||
|
long long index;
|
||||||
|
if (RedisModule_StringToLongLong(argv[2], &index) != REDISMODULE_OK) {
|
||||||
|
return RedisModule_ReplyWithError(ctx, "ERR index must be a number");
|
||||||
|
}
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
|
||||||
|
RedisModuleString *value = RedisModule_ListGet(key, index);
|
||||||
|
if (value) {
|
||||||
|
RedisModule_ReplyWithString(ctx, value);
|
||||||
|
RedisModule_FreeString(ctx, value);
|
||||||
|
} else {
|
||||||
|
replyByErrno(ctx);
|
||||||
|
}
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LIST.SET key index value */
|
||||||
|
int list_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 4) return RedisModule_WrongArity(ctx);
|
||||||
|
long long index;
|
||||||
|
if (RedisModule_StringToLongLong(argv[2], &index) != REDISMODULE_OK) {
|
||||||
|
RedisModule_ReplyWithError(ctx, "ERR index must be a number");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||||
|
if (RedisModule_ListSet(key, index, argv[3]) == REDISMODULE_OK) {
|
||||||
|
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||||
|
} else {
|
||||||
|
replyByErrno(ctx);
|
||||||
|
}
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LIST.INSERT key index value
|
||||||
|
*
|
||||||
|
* If index is negative, value is inserted after, otherwise before the element
|
||||||
|
* at index.
|
||||||
|
*/
|
||||||
|
int list_insert(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 4) return RedisModule_WrongArity(ctx);
|
||||||
|
long long index;
|
||||||
|
if (RedisModule_StringToLongLong(argv[2], &index) != REDISMODULE_OK) {
|
||||||
|
RedisModule_ReplyWithError(ctx, "ERR index must be a number");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||||
|
if (RedisModule_ListInsert(key, index, argv[3]) == REDISMODULE_OK) {
|
||||||
|
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||||
|
} else {
|
||||||
|
replyByErrno(ctx);
|
||||||
|
}
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LIST.DELETE key index */
|
||||||
|
int list_delete(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 3) return RedisModule_WrongArity(ctx);
|
||||||
|
long long index;
|
||||||
|
if (RedisModule_StringToLongLong(argv[2], &index) != REDISMODULE_OK) {
|
||||||
|
RedisModule_ReplyWithError(ctx, "ERR index must be a number");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||||
|
if (RedisModule_ListDelete(key, index) == REDISMODULE_OK) {
|
||||||
|
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||||
|
} else {
|
||||||
|
replyByErrno(ctx);
|
||||||
|
}
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
if (RedisModule_Init(ctx, "list", 1, REDISMODULE_APIVER_1) == REDISMODULE_OK &&
|
||||||
|
RedisModule_CreateCommand(ctx, "list.getall", list_getall, "",
|
||||||
|
1, 1, 1) == REDISMODULE_OK &&
|
||||||
|
RedisModule_CreateCommand(ctx, "list.edit", list_edit, "",
|
||||||
|
1, 1, 1) == REDISMODULE_OK &&
|
||||||
|
RedisModule_CreateCommand(ctx, "list.get", list_get, "",
|
||||||
|
1, 1, 1) == REDISMODULE_OK &&
|
||||||
|
RedisModule_CreateCommand(ctx, "list.set", list_set, "",
|
||||||
|
1, 1, 1) == REDISMODULE_OK &&
|
||||||
|
RedisModule_CreateCommand(ctx, "list.insert", list_insert, "",
|
||||||
|
1, 1, 1) == REDISMODULE_OK &&
|
||||||
|
RedisModule_CreateCommand(ctx, "list.delete", list_delete, "",
|
||||||
|
1, 1, 1) == REDISMODULE_OK) {
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
} else {
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
}
|
||||||
|
}
|
66
tests/unit/moduleapi/list.tcl
Normal file
66
tests/unit/moduleapi/list.tcl
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
set testmodule [file normalize tests/modules/list.so]
|
||||||
|
|
||||||
|
start_server {tags {"modules"}} {
|
||||||
|
r module load $testmodule
|
||||||
|
|
||||||
|
test {Module list set, get, insert, delete} {
|
||||||
|
r del k
|
||||||
|
r rpush k x
|
||||||
|
# insert, set, get
|
||||||
|
r list.insert k 0 foo
|
||||||
|
r list.insert k -1 bar
|
||||||
|
r list.set k 1 xyz
|
||||||
|
assert_equal {foo xyz bar} [r list.getall k]
|
||||||
|
assert_equal {foo} [r list.get k 0]
|
||||||
|
assert_equal {xyz} [r list.get k 1]
|
||||||
|
assert_equal {bar} [r list.get k 2]
|
||||||
|
assert_equal {bar} [r list.get k -1]
|
||||||
|
assert_equal {foo} [r list.get k -3]
|
||||||
|
assert_error {ERR index out*} {r list.get k -4}
|
||||||
|
assert_error {ERR index out*} {r list.get k 3}
|
||||||
|
# remove
|
||||||
|
assert_error {ERR index out*} {r list.delete k -4}
|
||||||
|
assert_error {ERR index out*} {r list.delete k 3}
|
||||||
|
r list.delete k 0
|
||||||
|
r list.delete k -1
|
||||||
|
assert_equal {xyz} [r list.getall k]
|
||||||
|
# removing the last element deletes the list
|
||||||
|
r list.delete k 0
|
||||||
|
assert_equal 0 [r exists k]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Module list iteration} {
|
||||||
|
r del k
|
||||||
|
r rpush k x y z
|
||||||
|
assert_equal {x y z} [r list.getall k]
|
||||||
|
assert_equal {z y x} [r list.getall k REVERSE]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Module list insert & delete} {
|
||||||
|
r del k
|
||||||
|
r rpush k x y z
|
||||||
|
r list.edit k ikikdi foo bar baz
|
||||||
|
r list.getall k
|
||||||
|
} {foo x bar y baz}
|
||||||
|
|
||||||
|
test {Module list insert & delete, neg index} {
|
||||||
|
r del k
|
||||||
|
r rpush k x y z
|
||||||
|
r list.edit k REVERSE ikikdi foo bar baz
|
||||||
|
r list.getall k
|
||||||
|
} {baz y bar z foo}
|
||||||
|
|
||||||
|
test {Module list set while iterating} {
|
||||||
|
r del k
|
||||||
|
r rpush k x y z
|
||||||
|
r list.edit k rkr foo bar
|
||||||
|
r list.getall k
|
||||||
|
} {foo y bar}
|
||||||
|
|
||||||
|
test {Module list set while iterating, neg index} {
|
||||||
|
r del k
|
||||||
|
r rpush k x y z
|
||||||
|
r list.edit k reverse rkr foo bar
|
||||||
|
r list.getall k
|
||||||
|
} {bar y foo}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user