Implement VEMB and slightly rework VINFO

Unfortunately `VEMB` has a unique `RESP2` reply as far as I can tell,
where it sends the embedding mode (int8, bin, fp32) as a simple string.

This would cause any of PhpRedis' generic reply handlers to turn that
into `true` which isn't useful. For that reason we need a custom reply
handler.

Additionally slightly rework `VINFO` to short circuit and return failure
if we read anything other than a bulk string or an integer reply type.
Otherwise we may get out of sync on the socket.

See #2543
This commit is contained in:
michael-grunder
2025-07-31 12:50:29 -07:00
committed by Michael Grunder
parent 8f8a49bec2
commit 96378b70fd
13 changed files with 194 additions and 15 deletions
+75 -11
View File
@@ -2491,21 +2491,18 @@ redis_read_vinfo_response(RedisSock *redis_sock, zval *z_ret, long long count) {
size_t klen, vlen;
long lval;
if (count < 0 || count % 2 != 0) {
if (count < 0 || count % 2 != 0 || Z_TYPE_P(z_ret) != IS_ARRAY) {
zend_error_noreturn(E_ERROR, "Internal finfo handler error");
}
for (long long i = 0; i < count; i += 2) {
if (redis_read_reply_type(redis_sock, &type, &lval) < 0 ||
type != TYPE_LINE)
type != TYPE_LINE ||
redis_sock_gets(redis_sock, kbuf, sizeof(kbuf), &klen) < 0)
{
return FAILURE;
}
if (redis_sock_gets(redis_sock, kbuf, sizeof(kbuf), &klen) < 0) {
return FAILURE;
}
if (redis_read_reply_type(redis_sock, &type, &lval) < 0) {
return FAILURE;
}
@@ -2521,10 +2518,8 @@ redis_read_vinfo_response(RedisSock *redis_sock, zval *z_ret, long long count) {
add_assoc_long_ex(z_ret, kbuf, klen, lval);
break;
default:
add_assoc_null_ex(z_ret, kbuf, klen);
break;
}
return FAILURE;
}
}
return SUCCESS;
@@ -2558,8 +2553,77 @@ redis_vinfo_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
return SUCCESS;
}
/* Unfortunately VEMB decided to use +string\r\n for the encoding when RAW is
* sent which PhpRedis will parse as `(true)` so we need a specific handler for
* it */
PHP_REDIS_API int
redis_xinfo_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
redis_read_vemb_response(RedisSock *redis_sock, zval *z_ret, long long count) {
REDIS_REPLY_TYPE type;
char kbuf[256], *str;
size_t klen;
double dval;
long tlen;
if (count < 0 || Z_TYPE_P(z_ret) != IS_ARRAY) {
zend_error_noreturn(E_ERROR, "Internal vemb handler error");
}
for (long long i = 0; i < count; i++) {
if (redis_read_reply_type(redis_sock, &type, &tlen) < 0) {
return FAILURE;
}
if (type == TYPE_LINE) {
if (redis_sock_gets(redis_sock, kbuf, sizeof(kbuf), &klen) < 0)
return FAILURE;
add_next_index_stringl(z_ret, kbuf, klen);
} else if (type == TYPE_BULK) {
if ((str = redis_sock_read_bulk_reply(redis_sock, tlen)) == NULL)
return FAILURE;
if (is_numeric_string(str, tlen, NULL, &dval, 0) == IS_DOUBLE) {
add_next_index_double(z_ret, dval);
} else {
add_next_index_stringl(z_ret, str, tlen);
}
efree(str);
} else {
return FAILURE;
}
}
return SUCCESS;
}
PHP_REDIS_API int
redis_vemb_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, void *ctx)
{
zval z_ret;
int count;
if (read_mbulk_header(redis_sock, &count) < 0 || count < 1) {
REDIS_RESPONSE_ERROR(redis_sock, z_tab);
return FAILURE;
}
array_init_size(&z_ret, count);
if (redis_read_vemb_response(redis_sock, &z_ret, count) != SUCCESS) {
zval_dtor(&z_ret);
REDIS_RESPONSE_ERROR(redis_sock, z_tab);
return FAILURE;
}
REDIS_RETURN_ZVAL(redis_sock, z_tab, z_ret);
return SUCCESS;
}
PHP_REDIS_API int
redis_xinfo_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, void *ctx)
{
zval z_ret;
int elements;
+5
View File
@@ -128,6 +128,11 @@ PHP_REDIS_API int redis_vinfo_reply(INTERNAL_FUNCTION_PARAMETERS,
PHP_REDIS_API int redis_read_vinfo_response(RedisSock *redis_sock,
zval *z_ret, long long count);
PHP_REDIS_API int redis_vemb_reply(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_read_vemb_response(RedisSock *redis_sock, zval *z_ret,
long long count);
PHP_REDIS_API int redis_pubsub_response(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS,
+4
View File
@@ -3195,6 +3195,10 @@ PHP_METHOD(Redis, vinfo) {
REDIS_PROCESS_KW_CMD("VINFO", redis_key_cmd, redis_vinfo_reply);
}
PHP_METHOD(Redis, vemb) {
REDIS_PROCESS_CMD(vemb, redis_vemb_reply);
}
/*
* Streams
*/
+11
View File
@@ -4270,6 +4270,17 @@ class Redis {
*/
public function vinfo(string $key): Redis|array|false;
/**
* Get the embeddings for a specific member
*
* @param string $key The vector set to query.
* @param mixed $member The member to query.
* @param bool $raw If set to `true`, the raw embeddings will be returned
*
* @return Redis|array|false An array of embeddings for the member or false on failure.
*/
public function vemb(string $key, mixed $member, bool $raw = false): Redis|array|false;
/**
* Truncate a STREAM key in various ways.
*
+9 -1
View File
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: ef50011ff095df387f4b0f043d381e092253c8e8 */
* Stub hash: 8cbcd94f8610fdfd08566cdf137ce63dbcfbf609 */
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null")
@@ -1082,6 +1082,12 @@ ZEND_END_ARG_INFO()
#define arginfo_class_Redis_vinfo arginfo_class_Redis_getWithMeta
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_vemb, 0, 2, Redis, MAY_BE_ARRAY|MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, member, IS_MIXED, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, raw, _IS_BOOL, 0, "false")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_xtrim, 0, 2, Redis, MAY_BE_LONG|MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, threshold, IS_STRING, 0)
@@ -1498,6 +1504,7 @@ ZEND_METHOD(Redis, vsim);
ZEND_METHOD(Redis, vcard);
ZEND_METHOD(Redis, vdim);
ZEND_METHOD(Redis, vinfo);
ZEND_METHOD(Redis, vemb);
ZEND_METHOD(Redis, xtrim);
ZEND_METHOD(Redis, zAdd);
ZEND_METHOD(Redis, zCard);
@@ -1779,6 +1786,7 @@ static const zend_function_entry class_Redis_methods[] = {
ZEND_ME(Redis, vcard, arginfo_class_Redis_vcard, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, vdim, arginfo_class_Redis_vdim, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, vinfo, arginfo_class_Redis_vinfo, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, vemb, arginfo_class_Redis_vemb, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, xtrim, arginfo_class_Redis_xtrim, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, zAdd, arginfo_class_Redis_zAdd, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, zCard, arginfo_class_Redis_zCard, ZEND_ACC_PUBLIC)
+4
View File
@@ -3199,6 +3199,10 @@ PHP_METHOD(RedisCluster, vinfo) {
CLUSTER_PROCESS_KW_CMD("VINFO", redis_key_cmd, cluster_vinfo_resp, 1);
}
PHP_METHOD(RedisCluster, vemb) {
CLUSTER_PROCESS_CMD(vemb, cluster_variant_resp, 1);
}
/* {{{ proto long RedisCluster::xack(string key, string group, array ids) }}} */
PHP_METHOD(RedisCluster, xack) {
CLUSTER_PROCESS_CMD(xack, cluster_long_resp, 0);
+5
View File
@@ -1088,6 +1088,11 @@ class RedisCluster {
*/
public function vinfo(string $key): RedisCluster|array|false;
/**
* @see Redis::vemb
*/
public function vemb(string $key, mixed $member, bool $raw = false): RedisCluster|array|false;
/**
* @see Redis::xack
*/
+9 -1
View File
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 6833953df5e7260f7791abe42c5ae9cd9a0125d2 */
* Stub hash: 0a9dbef0d65b9c674457ff335ec637544141c4e5 */
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster___construct, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 1)
@@ -898,6 +898,12 @@ ZEND_END_ARG_INFO()
#define arginfo_class_RedisCluster_vinfo arginfo_class_RedisCluster_getWithMeta
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_vemb, 0, 2, RedisCluster, MAY_BE_ARRAY|MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, member, IS_MIXED, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, raw, _IS_BOOL, 0, "false")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_xack, 0, 3, RedisCluster, MAY_BE_LONG|MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, group, IS_STRING, 0)
@@ -1338,6 +1344,7 @@ ZEND_METHOD(RedisCluster, vsim);
ZEND_METHOD(RedisCluster, vcard);
ZEND_METHOD(RedisCluster, vdim);
ZEND_METHOD(RedisCluster, vinfo);
ZEND_METHOD(RedisCluster, vemb);
ZEND_METHOD(RedisCluster, xack);
ZEND_METHOD(RedisCluster, xadd);
ZEND_METHOD(RedisCluster, xclaim);
@@ -1587,6 +1594,7 @@ static const zend_function_entry class_RedisCluster_methods[] = {
ZEND_ME(RedisCluster, vcard, arginfo_class_RedisCluster_vcard, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, vdim, arginfo_class_RedisCluster_vdim, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, vinfo, arginfo_class_RedisCluster_vinfo, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, vemb, arginfo_class_RedisCluster_vemb, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, xack, arginfo_class_RedisCluster_xack, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, xadd, arginfo_class_RedisCluster_xadd, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, xclaim, arginfo_class_RedisCluster_xclaim, ZEND_ACC_PUBLIC)
+9 -1
View File
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 6833953df5e7260f7791abe42c5ae9cd9a0125d2 */
* Stub hash: 0a9dbef0d65b9c674457ff335ec637544141c4e5 */
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster___construct, 0, 0, 1)
ZEND_ARG_INFO(0, name)
@@ -761,6 +761,12 @@ ZEND_END_ARG_INFO()
#define arginfo_class_RedisCluster_vinfo arginfo_class_RedisCluster__prefix
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_vemb, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, member)
ZEND_ARG_INFO(0, raw)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_xack, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, group)
@@ -1173,6 +1179,7 @@ ZEND_METHOD(RedisCluster, vsim);
ZEND_METHOD(RedisCluster, vcard);
ZEND_METHOD(RedisCluster, vdim);
ZEND_METHOD(RedisCluster, vinfo);
ZEND_METHOD(RedisCluster, vemb);
ZEND_METHOD(RedisCluster, xack);
ZEND_METHOD(RedisCluster, xadd);
ZEND_METHOD(RedisCluster, xclaim);
@@ -1422,6 +1429,7 @@ static const zend_function_entry class_RedisCluster_methods[] = {
ZEND_ME(RedisCluster, vcard, arginfo_class_RedisCluster_vcard, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, vdim, arginfo_class_RedisCluster_vdim, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, vinfo, arginfo_class_RedisCluster_vinfo, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, vemb, arginfo_class_RedisCluster_vemb, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, xack, arginfo_class_RedisCluster_xack, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, xadd, arginfo_class_RedisCluster_xadd, ZEND_ACC_PUBLIC)
ZEND_ME(RedisCluster, xclaim, arginfo_class_RedisCluster_xclaim, ZEND_ACC_PUBLIC)
+32
View File
@@ -7007,6 +7007,38 @@ redis_vsim_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
return SUCCESS;
}
int
redis_vemb_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
smart_string cmdstr = {0};
zend_bool raw = 0;
zend_string *key;
zval *member;
ZEND_PARSE_PARAMETERS_START(2, 3) {
Z_PARAM_STR(key)
Z_PARAM_ZVAL(member);
Z_PARAM_OPTIONAL
Z_PARAM_BOOL(raw)
} ZEND_PARSE_PARAMETERS_END_EX(return FAILURE);
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 2 + !!raw, "VEMB");
redis_cmd_append_sstr_key_zstr(&cmdstr, key, redis_sock, slot);
redis_cmd_append_sstr_zval(&cmdstr, member, redis_sock);
if (raw) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "RAW");
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/*
* Redis commands that don't deal with the server at all. The RedisSock*
* pointer is the only thing retrieved differently, so we just take that
+3
View File
@@ -352,6 +352,9 @@ int redis_vadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
int redis_vsim_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_vemb_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_xadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
+9 -1
View File
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: ef50011ff095df387f4b0f043d381e092253c8e8 */
* Stub hash: 8cbcd94f8610fdfd08566cdf137ce63dbcfbf609 */
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0)
ZEND_ARG_INFO(0, options)
@@ -962,6 +962,12 @@ ZEND_END_ARG_INFO()
#define arginfo_class_Redis_vinfo arginfo_class_Redis__prefix
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_vemb, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, member)
ZEND_ARG_INFO(0, raw)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_xtrim, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, threshold)
@@ -1336,6 +1342,7 @@ ZEND_METHOD(Redis, vsim);
ZEND_METHOD(Redis, vcard);
ZEND_METHOD(Redis, vdim);
ZEND_METHOD(Redis, vinfo);
ZEND_METHOD(Redis, vemb);
ZEND_METHOD(Redis, xtrim);
ZEND_METHOD(Redis, zAdd);
ZEND_METHOD(Redis, zCard);
@@ -1617,6 +1624,7 @@ static const zend_function_entry class_Redis_methods[] = {
ZEND_ME(Redis, vcard, arginfo_class_Redis_vcard, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, vdim, arginfo_class_Redis_vdim, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, vinfo, arginfo_class_Redis_vinfo, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, vemb, arginfo_class_Redis_vemb, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, xtrim, arginfo_class_Redis_xtrim, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, zAdd, arginfo_class_Redis_zAdd, ZEND_ACC_PUBLIC)
ZEND_ME(Redis, zCard, arginfo_class_Redis_zCard, ZEND_ACC_PUBLIC)
+19
View File
@@ -7754,6 +7754,25 @@ class Redis_Test extends TestSuite {
$this->assertEquals(1, $this->redis->del('v'));
}
public function testVEmb() {
if ( ! $this->minVersionCheck('8.0'))
$this->markTestSkipped();
$this->assertIsInt($this->redis->del('v'));
$this->assertEquals(1, $this->redis->vadd('v', [0.5, 1.0], 'e'));
$res = $this->redis->vemb('v', 'e');
$this->assertIsArray($res);
$this->assertTrue(filter_var($res[0], FILTER_VALIDATE_FLOAT) !== false);
$res = $this->redis->vemb('v', 'e', true);
$this->assertIsArray($res);
$this->assertEquals('int8', $res[0]);
$this->assertEquals(1, $this->redis->del('v'));
}
public function testInvalidAuthArgs() {
$client = $this->newInstance();