Implement Valkey >= 8.1 IFEQ set option

Implement the new `IFEQ` `SET` option that will be included in `Valkey`
8.1.

See: valkey-io/valkey#1324
This commit is contained in:
michael-grunder
2025-01-19 09:15:41 -08:00
committed by Michael Grunder
parent faa4bc2086
commit a2eef77f44
4 changed files with 45 additions and 4 deletions

View File

@@ -2293,7 +2293,8 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key = NULL, *exp_type = NULL, *set_type = NULL;
zval *z_value, *z_opts=NULL;
zend_string *ifeq = NULL, *tmp = NULL;
zval *z_value, *z_opts = NULL;
smart_string cmdstr = {0};
zend_long expire = -1;
zend_bool get = 0;
@@ -2312,7 +2313,6 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
return FAILURE;
}
// Check for an options array
if (z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) {
HashTable *kt = Z_ARRVAL_P(z_opts);
@@ -2329,11 +2329,14 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zend_string_equals_literal_ci(zkey, "PXAT"))
) {
if (redis_try_get_expiry(v, &expire) == FAILURE || expire < 1) {
zend_tmp_string_release(tmp);
setExpiryWarning(v);
return FAILURE;
}
exp_type = ZSTR_VAL(zkey);
} else if (zkey && !ifeq && zend_string_equals_literal_ci(zkey, "IFEQ")) {
ifeq = zval_get_tmp_string(v, &tmp);
} else if (Z_TYPE_P(v) == IS_STRING) {
if (zend_string_equals_literal_ci(Z_STR_P(v), "KEEPTTL")) {
keep_ttl = 1;
@@ -2348,6 +2351,7 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
} ZEND_HASH_FOREACH_END();
} else if (z_opts && Z_TYPE_P(z_opts) != IS_NULL) {
if (redis_try_get_expiry(z_opts, &expire) == FAILURE || expire < 1) {
zend_tmp_string_release(tmp);
setExpiryWarning(z_opts);
return FAILURE;
}
@@ -2356,6 +2360,14 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
/* Protect the user from syntax errors but give them some info about what's wrong */
if (exp_type && keep_ttl) {
php_error_docref(NULL, E_WARNING, "KEEPTTL can't be combined with EX or PX option");
zend_tmp_string_release(tmp);
return FAILURE;
}
/* You can't use IFEQ with NX or XX */
if (set_type && ifeq) {
php_error_docref(NULL, E_WARNING, "IFEQ can't be combined with NX or XX option");
zend_tmp_string_release(tmp);
return FAILURE;
}
@@ -2363,11 +2375,13 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
* actually execute a SETEX command */
if (expire > 0 && !exp_type && !set_type && !keep_ttl) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SETEX", "klv", key, key_len, expire, z_value);
zend_tmp_string_release(tmp);
return SUCCESS;
}
/* Calculate argc based on options set */
int argc = 2 + (exp_type ? 2 : 0) + (set_type != NULL) + (keep_ttl != 0) + get;
int argc = 2 + (ifeq ? 2 : 0) + (exp_type ? 2 : 0) + (set_type != NULL) +
(keep_ttl != 0) + get;
/* Initial SET <key> <value> */
redis_cmd_init_sstr(&cmdstr, argc, "SET", 3);
@@ -2379,8 +2393,13 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
redis_cmd_append_sstr_long(&cmdstr, (long)expire);
}
if (set_type)
if (ifeq) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "IFEQ");
redis_cmd_append_sstr_zstr(&cmdstr, ifeq);
} else if (set_type) {
redis_cmd_append_sstr(&cmdstr, set_type, strlen(set_type));
}
if (keep_ttl)
redis_cmd_append_sstr(&cmdstr, "KEEPTTL", 7);
if (get) {
@@ -2388,6 +2407,8 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
*ctx = PHPREDIS_CTX_PTR;
}
zend_tmp_string_release(tmp);
/* Push command and length to the caller */
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;

View File

@@ -133,6 +133,7 @@ class Redis_Cluster_Test extends Redis_Test {
$info = $this->redis->info(uniqid());
$this->version = $info['redis_version'] ?? '0.0.0';
$this->is_keydb = $this->detectKeyDB($info);
$this->is_valkey = $this->detectValkey($info);
}
/* Override newInstance as we want a RedisCluster object */

View File

@@ -67,11 +67,16 @@ class Redis_Test extends TestSuite {
isset($info['mvcc_depth']);
}
protected function detectValkey(array $info) {
return isset($info['server_name']) && $info['server_name'] === 'valkey';
}
public function setUp() {
$this->redis = $this->newInstance();
$info = $this->redis->info();
$this->version = (isset($info['redis_version'])?$info['redis_version']:'0.0.0');
$this->is_keydb = $this->detectKeyDB($info);
$this->is_valkey = $this->detectValKey($info);
}
protected function minVersionCheck($version) {
@@ -629,6 +634,19 @@ class Redis_Test extends TestSuite {
$this->assertEquals('bar', $this->redis->set('foo', 'baz', ['GET']));
}
/* Test Valkey >= 8.1 IFEQ SET option */
public function testValkeyIfEq() {
if ( ! $this->is_valkey || ! $this->minVersionCheck('8.1.0'))
$this->markTestSkipped();
$this->redis->del('foo');
$this->assertTrue($this->redis->set('foo', 'bar'));
$this->assertTrue($this->redis->set('foo', 'bar2', ['IFEQ' => 'bar']));
$this->assertFalse($this->redis->set('foo', 'bar4', ['IFEQ' => 'bar3']));
$this->assertEquals('bar2', $this->redis->set('foo', 'bar3', ['IFEQ' => 'bar2', 'GET']));
}
public function testGetSet() {
$this->redis->del('key');
$this->assertFalse($this->redis->getSet('key', '42'));

View File

@@ -16,6 +16,7 @@ class TestSuite
/* Redis server version */
protected $version;
protected bool $is_keydb;
protected bool $is_valkey;
private static bool $colorize = false;