mirror of
https://github.com/redis/redis.git
synced 2026-05-28 04:02:46 -04:00
[vector sets] VRANGE implementation (#14235)
This is basically the Vector Set iteration primitive. It exploits the underlying radix tree implementation. The usage pattern is strongly reminiscent of other Redis commands doing similar things. The command usage is straightforward: ``` > VRANGE word_embeddings_int8 [Redis + 10 1) "Redis" 2) "Rediscover" 3) "Rediscover_Ashland" 4) "Rediscover_Northern_Ireland" 5) "Rediscovered" 6) "Rediscovered_Bookshop" 7) "Rediscovering" 8) "Rediscovering_God" 9) "Rediscovering_Lost" 10) "Rediscovers" ``` The above command returns 10 (or less, if less are available in the specified range) elements from "Redis" (inclusive) to the maximum possible element. The comparison is performed byte by byte, as `memcmp()` would do, in this way the elements have a total order. The start and end range can be either a string, prefixed by `[` or `(` (the prefix is mandatory) to tell the command if the range is inclusive or exclusive, or can be the special symbols `-` and `+` that means the maximum and minimum element. More info can be found in the implementation itself and in the README file change. --------- Co-authored-by: debing.sun <debing.sun@redis.com>
This commit is contained in:
parent
3c1a759954
commit
3de2fdad58
4 changed files with 434 additions and 16 deletions
|
|
@ -189,6 +189,78 @@ This command will return 1 (or true) if the specified element is already in the
|
|||
|
||||
As with other existence check Redis commands, if the key does not exist it is considered as if it was empty, thus the element is reported as non existing.
|
||||
|
||||
**VRANGE: return elements in a lexicographical range
|
||||
|
||||
VRANGE key start end count
|
||||
|
||||
The `VRANGE` command has many different use cases, but its main goal is to
|
||||
provide a stateless iterator for the elements inside a vector set: that is,
|
||||
it allows to retrieve all the elements inside a vector set in small amounts
|
||||
for each call, without an explicit cursor, and with guarantees about what
|
||||
the user will miss in case the vector set is changing (elements added and/or
|
||||
removed) during the iteration.
|
||||
|
||||
The command usage is straightforward:
|
||||
|
||||
```
|
||||
> VRANGE word_embeddings_int8 [Redis + 10
|
||||
1) "Redis"
|
||||
2) "Rediscover"
|
||||
3) "Rediscover_Ashland"
|
||||
4) "Rediscover_Northern_Ireland"
|
||||
5) "Rediscovered"
|
||||
6) "Rediscovered_Bookshop"
|
||||
7) "Rediscovering"
|
||||
8) "Rediscovering_God"
|
||||
9) "Rediscovering_Lost"
|
||||
10) "Rediscovers"
|
||||
```
|
||||
|
||||
The above command returns 10 (or less, if less are available in the specified range) elements from "Redis" (inclusive) to the maximum possible element. The comparison is performed byte by byte, as `memcmp()` would do, in this way the elements have a total order. The start and end range can be either a string, prefixed by `[` or `(` (the prefix is mandatory) to tell the command if the range is inclusive or exclusive, or can be the special symbols `-` and `+` that means the maximum and minimum element.
|
||||
|
||||
So for instance if I want to iterate all the elements, ten elements for each call, I'll proceed as such:
|
||||
|
||||
```
|
||||
> VRANGE mykey - + 10
|
||||
1) "a"
|
||||
2) "a-league"
|
||||
3) "a."
|
||||
4) "a.d."
|
||||
5) "a.k.a."
|
||||
6) "a.m."
|
||||
7) "a1"
|
||||
8) "a2"
|
||||
9) "a3"
|
||||
10) "a7"
|
||||
```
|
||||
|
||||
This will give me the first 10 elements. Then I want the next ten elements
|
||||
starting from the last element in the previous result, but *excluding* it,
|
||||
so the next range will use the `(` prefix with the last element of the
|
||||
previous call, that was `"a7"`:
|
||||
|
||||
```
|
||||
> VRANGE mykey (a7 + 10
|
||||
1) "a930913"
|
||||
2) "aa"
|
||||
3) "aaa"
|
||||
4) "aaron"
|
||||
5) "ab"
|
||||
6) "aba"
|
||||
7) "abandon"
|
||||
8) "abandoned"
|
||||
9) "abandoning"
|
||||
10) "abandonment"
|
||||
```
|
||||
|
||||
And so forth.
|
||||
|
||||
The command count is mandatory, however a negative count means to return all the elements in the set. This means that `VRANGE mykey - + -1` will return every element. Of course, iterating like that means that it is possible to block the server for a long time.
|
||||
|
||||
The command time complexity is O(1) to seek to the element (considering the element would be of reasonable size), since we use a Radix Tree in the underlying implementation, plus the time to yield "M" elements. So if M is small, each call is just executed in constant time. However the iteration of a total set (via multiple calls) of N elements is O(N). Basically: this command, with a small count, will never produce latency issues in the Redis server.
|
||||
|
||||
In case the elements are changing continuously as the set is iterated, the guarantees are very simple: each range will produce exactly the elements that were present in the range in the moment the `VRANGE` command was executed. In other words, an iteration performed in this way is *guaranteed* to return all the elements that stayed within the vector set from the start to the end of the iteration. Elements removed or added in the meantime may be returned or not depending on the moment they were added or removed.
|
||||
|
||||
**VLINKS: introspection command that shows neighbors for a node**
|
||||
|
||||
VLINKS key element [WITHSCORES]
|
||||
|
|
|
|||
|
|
@ -412,5 +412,35 @@
|
|||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"VRANGE": {
|
||||
"summary": "Return elements in a lexicographical range",
|
||||
"complexity": "O(log(K)+M) where K is the number of elements in the start prefix, and M is the number of elements returned. In practical terms, the command is just O(M)",
|
||||
"group": "vector_set",
|
||||
"since": "8.4.0",
|
||||
"arity": -4,
|
||||
"function": "vrangeCommand",
|
||||
"command_flags": [
|
||||
"READONLY"
|
||||
],
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key"
|
||||
},
|
||||
{
|
||||
"name": "start",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "end",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "integer",
|
||||
"optional": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
113
modules/vector-sets/tests/vrange.py
Normal file
113
modules/vector-sets/tests/vrange.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
from test import TestCase, generate_random_vector
|
||||
import struct
|
||||
|
||||
class BasicVRANGE(TestCase):
|
||||
def getname(self):
|
||||
return "VRANGE basic functionality and iteration"
|
||||
|
||||
def test(self):
|
||||
# Add multiple elements with different names for lexicographical ordering
|
||||
elements = [
|
||||
"apple", "apricot", "banana", "cherry", "date",
|
||||
"elderberry", "fig", "grape", "honeydew", "kiwi",
|
||||
"lemon", "mango", "nectarine", "orange", "papaya",
|
||||
"quince", "raspberry", "strawberry", "tangerine", "watermelon"
|
||||
]
|
||||
|
||||
# Add all elements to the vector set
|
||||
for elem in elements:
|
||||
vec = generate_random_vector(4)
|
||||
vec_bytes = struct.pack('4f', *vec)
|
||||
self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, elem)
|
||||
|
||||
# Test 1: Basic range with inclusive boundaries
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '[apple', '[grape', '5')
|
||||
result = [r.decode() for r in result]
|
||||
assert result == ['apple', 'apricot', 'banana', 'cherry', 'date'], f"Expected first 5 elements from apple, got {result}"
|
||||
|
||||
# Test 2: Exclusive start boundary
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '(apple', '[cherry', '10')
|
||||
result = [r.decode() for r in result]
|
||||
assert result == ['apricot', 'banana', 'cherry'], f"Expected elements after apple up to cherry inclusive, got {result}"
|
||||
|
||||
# Test 3: Exclusive end boundary
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '[banana', '(cherry', '10')
|
||||
result = [r.decode() for r in result]
|
||||
assert result == ['banana'], f"Expected only banana (cherry excluded), got {result}"
|
||||
|
||||
# Test 4: Using '-' for minimum element
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '-', '[banana', '10')
|
||||
result = [r.decode() for r in result]
|
||||
assert result[0] == 'apple', "Should start from the first element"
|
||||
assert result[-1] == 'banana', "Should end at banana"
|
||||
|
||||
# Test 5: Using '+' for maximum element
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '[raspberry', '+', '10')
|
||||
result = [r.decode() for r in result]
|
||||
assert 'raspberry' in result and 'strawberry' in result and 'tangerine' in result and 'watermelon' in result, "Should include all elements from raspberry onwards"
|
||||
|
||||
# Test 6: Full range with '-' and '+'
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '-', '+', '100')
|
||||
result = [r.decode() for r in result]
|
||||
assert len(result) == len(elements), f"Should return all {len(elements)} elements"
|
||||
assert result == sorted(elements), "Elements should be in lexicographical order"
|
||||
|
||||
# Test 7: Iterator pattern - verify each element appears exactly once
|
||||
seen = set()
|
||||
batch_size = 3
|
||||
current = '-'
|
||||
|
||||
while True:
|
||||
if current == '-':
|
||||
# First iteration
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '-', '+', str(batch_size))
|
||||
else:
|
||||
# Subsequent iterations - exclusive start from last element
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, f'({current}', '+', str(batch_size))
|
||||
|
||||
result = [r.decode() for r in result]
|
||||
|
||||
if not result:
|
||||
break
|
||||
|
||||
# Check no duplicates in this batch
|
||||
for elem in result:
|
||||
assert elem not in seen, f"Element {elem} appeared more than once"
|
||||
seen.add(elem)
|
||||
|
||||
# Update current to last element
|
||||
current = result[-1]
|
||||
|
||||
# Break if we got less than requested (end of set)
|
||||
if len(result) < batch_size:
|
||||
break
|
||||
|
||||
# Verify we saw all elements exactly once
|
||||
assert seen == set(elements), f"Iterator should visit all elements exactly once. Missing: {set(elements) - seen}, Extra: {seen - set(elements)}"
|
||||
|
||||
# Test 8: Count of 0 returns empty array
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '-', '+', '0')
|
||||
assert result == [], f"Count of 0 should return empty array, got {result}"
|
||||
|
||||
# Test 9: Range with no matching elements
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '[zebra', '+', '10')
|
||||
assert result == [], f"Range beyond all elements should return empty array, got {result}"
|
||||
|
||||
# Test 10: Non-existent key
|
||||
result = self.redis.execute_command('VRANGE', 'nonexistent_key', '-', '+', '10')
|
||||
assert result == [], f"Non-existent key should return empty array, got {result}"
|
||||
|
||||
# Test 11: Partial word boundaries
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '[app', '[apr', '10')
|
||||
result = [r.decode() for r in result]
|
||||
assert 'apple' in result, "Should include 'apple' which starts with 'app'"
|
||||
assert 'apricot' not in result, "Should not include 'apricot' as it's >= 'apr'"
|
||||
|
||||
# Test 12: Single element range
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '[cherry', '[cherry', '10')
|
||||
result = [r.decode() for r in result]
|
||||
assert result == ['cherry'], f"Inclusive single element range should return that element, got {result}"
|
||||
|
||||
# Test 13: Empty range (start > end)
|
||||
result = self.redis.execute_command('VRANGE', self.test_key, '[grape', '[apple', '10')
|
||||
assert result == [], f"Range where start > end should return empty array, got {result}"
|
||||
|
|
@ -1718,6 +1718,183 @@ int VISMEMBER_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int ar
|
|||
return RedisModule_ReplyWithBool(ctx, node != NULL);
|
||||
}
|
||||
|
||||
/* Structure to represent a range boundary. */
|
||||
struct vsetRangeOp {
|
||||
int incl; /* 1 if inclusive ([), 0 if exclusive ((). */
|
||||
int min; /* 1 if this is "-" (minimum). */
|
||||
int max; /* 1 if this is "+" (maximum). */
|
||||
unsigned char *ele; /* The actual element, NULL if min/max. */
|
||||
size_t ele_len; /* Length of the element. */
|
||||
};
|
||||
|
||||
/* Parse a range specification like "[foo" or "(bar" or "-" or "+".
|
||||
* Returns 1 on success, 0 on error. */
|
||||
int vsetParseRangeOp(RedisModuleString *arg, struct vsetRangeOp *op) {
|
||||
size_t len;
|
||||
const char *str = RedisModule_StringPtrLen(arg, &len);
|
||||
|
||||
if (len == 0) return 0;
|
||||
|
||||
/* Initialize the structure. */
|
||||
op->incl = 0;
|
||||
op->min = 0;
|
||||
op->max = 0;
|
||||
op->ele = NULL;
|
||||
op->ele_len = 0;
|
||||
|
||||
/* Check for special cases "-" and "+". */
|
||||
if (len == 1 && str[0] == '-') {
|
||||
op->min = 1;
|
||||
return 1;
|
||||
}
|
||||
if (len == 1 && str[0] == '+') {
|
||||
op->max = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Otherwise, must start with ( or [. */
|
||||
if (str[0] == '[') {
|
||||
op->incl = 1;
|
||||
} else if (str[0] == '(') {
|
||||
op->incl = 0;
|
||||
} else {
|
||||
return 0; /* Invalid format. */
|
||||
}
|
||||
|
||||
/* Extract the string part after the bracket. */
|
||||
if (len > 1) {
|
||||
op->ele = (unsigned char *)(str + 1);
|
||||
op->ele_len = len - 1;
|
||||
} else {
|
||||
return 0; /* Just a bracket with no string. */
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Check if the current element is within the range defined by the end operator.
|
||||
* Returns 1 if the element is within range, 0 if it has passed the end. */
|
||||
int vsetIsElementInRange(const void *ele, size_t ele_len, struct vsetRangeOp *end_op) {
|
||||
/* If end is "+", element is always in range. */
|
||||
if (end_op->max) return 1;
|
||||
|
||||
/* Compare current element with end boundary. */
|
||||
size_t minlen = ele_len < end_op->ele_len ? ele_len : end_op->ele_len;
|
||||
int cmp = memcmp(ele, end_op->ele, minlen);
|
||||
|
||||
if (cmp == 0) {
|
||||
/* If equal up to minlen, shorter string is smaller. */
|
||||
if (ele_len < end_op->ele_len) {
|
||||
cmp = -1;
|
||||
} else if (ele_len > end_op->ele_len) {
|
||||
cmp = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check based on inclusive/exclusive. */
|
||||
if (end_op->incl) {
|
||||
return cmp <= 0; /* Inclusive: element <= end. */
|
||||
} else {
|
||||
return cmp < 0; /* Exclusive: element < end. */
|
||||
}
|
||||
}
|
||||
|
||||
/* VRANGE key start end [count]
|
||||
* Returns elements in the lexicographical range [start, end]
|
||||
*
|
||||
* Elements must be specified in one of the following forms:
|
||||
*
|
||||
* [myelement
|
||||
* (myelement
|
||||
* +
|
||||
* -
|
||||
*
|
||||
* Elements starting with [ are inclusive, so "myelement" would be
|
||||
* returned if present in the set. Elements starting with ( are exclusive
|
||||
* ranges instead. The special - and + elements mean the minimum and maximum
|
||||
* possible element (inclusive), so "VRANGE key - +" will return everything
|
||||
* (depending on COUNT of course). The special - element can be used only
|
||||
* as starting element, the special + element only as ending element. */
|
||||
int VRANGE_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModule_AutoMemory(ctx);
|
||||
|
||||
/* Check arguments. */
|
||||
if (argc < 4 || argc > 5) return RedisModule_WrongArity(ctx);
|
||||
|
||||
/* Parse COUNT if provided. */
|
||||
long long count = -1; /* Default: return all elements. */
|
||||
if (argc == 5) {
|
||||
if (RedisModule_StringToLongLong(argv[4], &count) != REDISMODULE_OK) {
|
||||
return RedisModule_ReplyWithError(ctx, "ERR invalid COUNT value");
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse range operators. */
|
||||
struct vsetRangeOp start_op, end_op;
|
||||
if (!vsetParseRangeOp(argv[2], &start_op)) {
|
||||
return RedisModule_ReplyWithError(ctx, "ERR invalid start range format");
|
||||
}
|
||||
if (!vsetParseRangeOp(argv[3], &end_op)) {
|
||||
return RedisModule_ReplyWithError(ctx, "ERR invalid end range format");
|
||||
}
|
||||
|
||||
/* Validate: "-" can only be first arg, "+" can only be second. */
|
||||
if (start_op.max || end_op.min) {
|
||||
return RedisModule_ReplyWithError(ctx,
|
||||
"ERR '-' can only be used as first argument, '+' only as second");
|
||||
}
|
||||
|
||||
/* Open the key. */
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
|
||||
int type = RedisModule_KeyType(key);
|
||||
|
||||
if (type == REDISMODULE_KEYTYPE_EMPTY) {
|
||||
return RedisModule_ReplyWithEmptyArray(ctx);
|
||||
}
|
||||
|
||||
if (RedisModule_ModuleTypeGetType(key) != VectorSetType) {
|
||||
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||
}
|
||||
|
||||
struct vsetObject *vset = RedisModule_ModuleTypeGetValue(key);
|
||||
|
||||
/* Start the iterator. */
|
||||
RedisModuleDictIter *iter;
|
||||
if (start_op.min) {
|
||||
/* Start from the beginning. */
|
||||
iter = RedisModule_DictIteratorStartC(vset->dict, "^", NULL, 0);
|
||||
} else {
|
||||
/* Start from the specified element. */
|
||||
const char *op = start_op.incl ? ">=" : ">";
|
||||
iter = RedisModule_DictIteratorStartC(vset->dict, op, start_op.ele, start_op.ele_len);
|
||||
}
|
||||
|
||||
/* Collect results. */
|
||||
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_LEN);
|
||||
long long returned = 0;
|
||||
|
||||
void *key_data;
|
||||
size_t key_len;
|
||||
while ((key_data = RedisModule_DictNextC(iter, &key_len, NULL)) != NULL) {
|
||||
/* Check if we've collected enough elements. */
|
||||
if (count >= 0 && returned >= count) break;
|
||||
|
||||
/* Check if we've passed the end range. */
|
||||
if (!vsetIsElementInRange(key_data, key_len, &end_op)) break;
|
||||
|
||||
/* Add this element to the result. */
|
||||
RedisModule_ReplyWithStringBuffer(ctx, key_data, key_len);
|
||||
returned++;
|
||||
}
|
||||
|
||||
RedisModule_ReplySetArrayLength(ctx, returned);
|
||||
|
||||
/* Cleanup. */
|
||||
RedisModule_DictIteratorStop(iter);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* ============================== vset type methods ========================= */
|
||||
|
||||
#define SAVE_FLAG_HAS_PROJMATRIX (1<<0)
|
||||
|
|
@ -2082,13 +2259,13 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
{ .name = "reduce", .type = REDISMODULE_ARG_TYPE_BLOCK, .token = "REDUCE", .flags = REDISMODULE_CMD_ARG_OPTIONAL,
|
||||
.subargs = (RedisModuleCommandArg[]) {
|
||||
{ .name = "dim", .type = REDISMODULE_ARG_TYPE_INTEGER },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
}
|
||||
},
|
||||
{ .name = "format", .type = REDISMODULE_ARG_TYPE_ONEOF, .subargs = (RedisModuleCommandArg[]) {
|
||||
{ .name = "fp32", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "FP32" },
|
||||
{ .name = "values", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "VALUES" },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
}
|
||||
},
|
||||
{ .name = "vector", .type = REDISMODULE_ARG_TYPE_STRING },
|
||||
|
|
@ -2098,13 +2275,13 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
{ .name = "noquant", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "NOQUANT" },
|
||||
{ .name = "bin", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "BIN" },
|
||||
{ .name = "q8", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "Q8" },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
}
|
||||
},
|
||||
{ .name = "build-exploration-factor", .type = REDISMODULE_ARG_TYPE_INTEGER, .token = "EF", .flags = REDISMODULE_CMD_ARG_OPTIONAL },
|
||||
{ .name = "attributes", .type = REDISMODULE_ARG_TYPE_STRING, .token = "SETATTR", .flags = REDISMODULE_CMD_ARG_OPTIONAL },
|
||||
{ .name = "numlinks", .type = REDISMODULE_ARG_TYPE_INTEGER, .token = "M", .flags = REDISMODULE_CMD_ARG_OPTIONAL },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vadd_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2126,7 +2303,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
RedisModuleCommandArg vrem_args[] = {
|
||||
{ .name = "key", .type = REDISMODULE_ARG_TYPE_KEY, .key_spec_index = 0 },
|
||||
{ .name = "element", .type = REDISMODULE_ARG_TYPE_STRING },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vrem_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2151,7 +2328,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
{ .name = "ele", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "ELE" },
|
||||
{ .name = "fp32", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "FP32" },
|
||||
{ .name = "values", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "VALUES" },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
}
|
||||
},
|
||||
{ .name = "vector_or_element", .type = REDISMODULE_ARG_TYPE_STRING },
|
||||
|
|
@ -2164,7 +2341,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
{ .name = "max-filtering-effort", .type = REDISMODULE_ARG_TYPE_INTEGER, .token = "FILTER-EF", .flags = REDISMODULE_CMD_ARG_OPTIONAL },
|
||||
{ .name = "truth", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "TRUTH", .flags = REDISMODULE_CMD_ARG_OPTIONAL },
|
||||
{ .name = "nothread", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "NOTHREAD", .flags = REDISMODULE_CMD_ARG_OPTIONAL },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vsim_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2185,7 +2362,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
|
||||
RedisModuleCommandArg vdim_args[] = {
|
||||
{ .name = "key", .type = REDISMODULE_ARG_TYPE_KEY, .key_spec_index = 0 },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vdim_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2206,7 +2383,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
|
||||
RedisModuleCommandArg vcard_args[] = {
|
||||
{ .name = "key", .type = REDISMODULE_ARG_TYPE_KEY, .key_spec_index = 0 },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vcard_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2229,7 +2406,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
{ .name = "key", .type = REDISMODULE_ARG_TYPE_KEY, .key_spec_index = 0 },
|
||||
{ .name = "element", .type = REDISMODULE_ARG_TYPE_STRING },
|
||||
{ .name = "raw", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "RAW", .flags = REDISMODULE_CMD_ARG_OPTIONAL },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vemb_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2252,7 +2429,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
{ .name = "key", .type = REDISMODULE_ARG_TYPE_KEY, .key_spec_index = 0 },
|
||||
{ .name = "element", .type = REDISMODULE_ARG_TYPE_STRING },
|
||||
{ .name = "withscores", .type = REDISMODULE_ARG_TYPE_PURE_TOKEN, .token = "WITHSCORES", .flags = REDISMODULE_CMD_ARG_OPTIONAL },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vlinks_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2273,7 +2450,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
|
||||
RedisModuleCommandArg vinfo_args[] = {
|
||||
{ .name = "key", .type = REDISMODULE_ARG_TYPE_KEY, .key_spec_index = 0 },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vinfo_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2296,7 +2473,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
{ .name = "key", .type = REDISMODULE_ARG_TYPE_KEY, .key_spec_index = 0 },
|
||||
{ .name = "element", .type = REDISMODULE_ARG_TYPE_STRING },
|
||||
{ .name = "json", .type = REDISMODULE_ARG_TYPE_STRING },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vsetattr_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2318,7 +2495,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
RedisModuleCommandArg vgetattr_args[] = {
|
||||
{ .name = "key", .type = REDISMODULE_ARG_TYPE_KEY, .key_spec_index = 0 },
|
||||
{ .name = "element", .type = REDISMODULE_ARG_TYPE_STRING },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vgetattr_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2340,7 +2517,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
RedisModuleCommandArg vrandmember_args[] = {
|
||||
{ .name = "key", .type = REDISMODULE_ARG_TYPE_KEY, .key_spec_index = 0 },
|
||||
{ .name = "count", .type = REDISMODULE_ARG_TYPE_INTEGER, .flags = REDISMODULE_CMD_ARG_OPTIONAL },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vrandmember_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2362,7 +2539,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
RedisModuleCommandArg vismember_args[] = {
|
||||
{ .name = "key", .type = REDISMODULE_ARG_TYPE_KEY, .key_spec_index = 0 },
|
||||
{ .name = "element", .type = REDISMODULE_ARG_TYPE_STRING },
|
||||
{ NULL }
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vismember_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
|
|
@ -2373,6 +2550,32 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
};
|
||||
if (RedisModule_SetCommandInfo(vismember_cmd, &vismember_info) == REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
// Register command VRANGE
|
||||
if (RedisModule_CreateCommand(ctx, "VRANGE",
|
||||
VRANGE_RedisCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
RedisModuleCommand *vrange_cmd = RedisModule_GetCommand(ctx, "VRANGE");
|
||||
if (vrange_cmd == NULL) return REDISMODULE_ERR;
|
||||
|
||||
RedisModuleCommandArg vrange_args[] = {
|
||||
{ .name = "key", .type = REDISMODULE_ARG_TYPE_KEY, .key_spec_index = 0 },
|
||||
{ .name = "start", .type = REDISMODULE_ARG_TYPE_STRING },
|
||||
{ .name = "end", .type = REDISMODULE_ARG_TYPE_STRING },
|
||||
{ .name = "count", .type = REDISMODULE_ARG_TYPE_INTEGER, .flags = REDISMODULE_CMD_ARG_OPTIONAL },
|
||||
{ .name = NULL }
|
||||
};
|
||||
RedisModuleCommandInfo vrange_info = {
|
||||
.version = REDISMODULE_COMMAND_INFO_VERSION,
|
||||
.summary = "Return vector set elements in a lex range",
|
||||
.since = "8.4.0",
|
||||
.arity = -4,
|
||||
.args = vrange_args,
|
||||
};
|
||||
if (RedisModule_SetCommandInfo(vrange_cmd, &vrange_info) == REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
// Set the allocator for the HNSW library, so that memory tracking
|
||||
// is correct in Redis.
|
||||
hnsw_set_allocator(RedisModule_Free, RedisModule_Alloc,
|
||||
RedisModule_Realloc);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue