diff --git a/local_examples/cmds_hash/NRedisStack/CmdsHashExample.cs b/local_examples/cmds_hash/NRedisStack/CmdsHashExample.cs index 038c5b5b0..ba0f226fe 100644 --- a/local_examples/cmds_hash/NRedisStack/CmdsHashExample.cs +++ b/local_examples/cmds_hash/NRedisStack/CmdsHashExample.cs @@ -1,6 +1,7 @@ // EXAMPLE: cmds_hash using StackExchange.Redis; using Xunit; +using System.Linq; namespace Doc; @@ -110,5 +111,40 @@ public void Run() // >>> Hello, World // STEP_END Assert.Equal("Hello, World", string.Join(", ", hValsResult)); + db.KeyDelete("myhash"); + + // STEP_START hexpire + // Set up hash with fields + db.HashSet("myhash", + [ + new("field1", "Hello"), + new("field2", "World") + ] + ); + + // Set expiration on hash fields using raw Execute + RedisResult hexpireRes1 = db.Execute("HEXPIRE", "myhash", 10, "FIELDS", 2, "field1", "field2"); + Console.WriteLine(string.Join(", ", (RedisValue[])hexpireRes1)); + // >>> 1, 1 + + // Check TTL of the fields using raw Execute + RedisResult hexpireRes2 = db.Execute("HTTL", "myhash", "FIELDS", 2, "field1", "field2"); + RedisValue[] ttlValues = (RedisValue[])hexpireRes2; + Console.WriteLine(ttlValues.Length); + // >>> 2 + + // Try to set expiration on non-existent field + RedisResult hexpireRes3 = db.Execute("HEXPIRE", "myhash", 10, "FIELDS", 1, "nonexistent"); + Console.WriteLine(string.Join(", ", (RedisValue[])hexpireRes3)); + // >>> -2 + // STEP_END + + RedisValue[] expireResult1 = (RedisValue[])hexpireRes1; + RedisValue[] expireResult3 = (RedisValue[])hexpireRes3; + Assert.Equal("1, 1", string.Join(", ", expireResult1)); + Assert.Equal(2, ttlValues.Length); + Assert.True(ttlValues.All(ttl => (int)ttl > 0)); // TTL should be positive + Assert.Equal("-2", string.Join(", ", expireResult3)); + db.KeyDelete("myhash"); } } diff --git a/local_examples/cmds_hash/go-redis/cmds_hash_test.go b/local_examples/cmds_hash/go-redis/cmds_hash_test.go index 8dcd85d81..e2ba1e5d6 100644 --- a/local_examples/cmds_hash/go-redis/cmds_hash_test.go +++ b/local_examples/cmds_hash/go-redis/cmds_hash_test.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "sort" + "time" "github.com/redis/go-redis/v9" ) @@ -293,4 +294,54 @@ func ExampleClient_hdel() { // 1 // 1 // 0 +} + +func ExampleClient_hexpire() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password + DB: 0, // use default DB + }) + + // STEP_START hexpire + // Set up hash with fields + rdb.HSet(ctx, "myhash", "field1", "Hello", "field2", "World") + + // Set expiration on hash fields + res1, err := rdb.HExpire(ctx, "myhash", 10*time.Second, "field1", "field2").Result() + + if err != nil { + fmt.Println(err) + } + + fmt.Println(res1) // >>> [1 1] + + // Check TTL of the fields + res2, err := rdb.HTTL(ctx, "myhash", "field1", "field2").Result() + + if err != nil { + fmt.Println(err) + } + + fmt.Println(len(res2)) // >>> 2 + + // Try to set expiration on non-existent field + res3, err := rdb.HExpire(ctx, "myhash", 10*time.Second, "nonexistent").Result() + + if err != nil { + fmt.Println(err) + } + + fmt.Println(res3) // >>> [-2] + + // Clean up + rdb.Del(ctx, "myhash") + // STEP_END + + // Output: + // [1 1] + // 2 + // [-2] } \ No newline at end of file diff --git a/local_examples/cmds_hash/jedis/CmdsHashExample.java b/local_examples/cmds_hash/jedis/CmdsHashExample.java index 3124a26d7..b52b88af2 100644 --- a/local_examples/cmds_hash/jedis/CmdsHashExample.java +++ b/local_examples/cmds_hash/jedis/CmdsHashExample.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.List; import java.util.Collections; +import java.util.Arrays; // HIDE_START import redis.clients.jedis.UnifiedJedis; @@ -17,6 +18,7 @@ import static java.util.stream.Collectors.toList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; // HIDE_START public class CmdsHashExample { @@ -159,13 +161,41 @@ public void run() { System.out.println(hValsResult2); // >>> [Hello, World] // STEP_END - // REMOVE_START + // REMOVE_START // Tests for 'hvals' step. assertEquals(2, hValsResult1); assertEquals("[Hello, World]", hValsResult2.toString()); jedis.del("myhash"); // REMOVE_END + // STEP_START hexpire + // Set up hash with fields + Map hExpireExampleParams = new HashMap<>(); + hExpireExampleParams.put("field1", "Hello"); + hExpireExampleParams.put("field2", "World"); + jedis.hset("myhash", hExpireExampleParams); + + // Set expiration on hash fields + List hExpireResult1 = jedis.hexpire("myhash", 10, "field1", "field2"); + System.out.println(hExpireResult1); // >>> [1, 1] + + // Check TTL of the fields + List hExpireResult2 = jedis.httl("myhash", "field1", "field2"); + System.out.println(hExpireResult2.size()); // >>> 2 + + // Try to set expiration on non-existent field + List hExpireResult3 = jedis.hexpire("myhash", 10, "nonexistent"); + System.out.println(hExpireResult3); // >>> [-2] + // STEP_END + // REMOVE_START + // Tests for 'hexpire' step. + assertEquals(Arrays.asList(1L, 1L), hExpireResult1); + assertEquals(2, hExpireResult2.size()); + assertTrue(hExpireResult2.stream().allMatch(ttl -> ttl > 0)); // TTL should be positive + assertEquals(Arrays.asList(-2L), hExpireResult3); + jedis.del("myhash"); + // REMOVE_END + // HIDE_START jedis.close(); } diff --git a/local_examples/cmds_hash/lettuce-async/CmdsHashExample.java b/local_examples/cmds_hash/lettuce-async/CmdsHashExample.java index ea580b190..fb0c096b0 100644 --- a/local_examples/cmds_hash/lettuce-async/CmdsHashExample.java +++ b/local_examples/cmds_hash/lettuce-async/CmdsHashExample.java @@ -184,6 +184,49 @@ public void run() { // REMOVE_START asyncCommands.del("myhash").toCompletableFuture().join(); // REMOVE_END + + // STEP_START hexpire + // Set up hash with fields + Map hExpireExampleParams = new HashMap<>(); + hExpireExampleParams.put("field1", "Hello"); + hExpireExampleParams.put("field2", "World"); + + CompletableFuture hExpireExample = asyncCommands.hset("myhash", hExpireExampleParams).thenCompose(res1 -> { + // REMOVE_START + assertThat(res1).isEqualTo(2L); + // REMOVE_END + // Set expiration on hash fields + return asyncCommands.hexpire("myhash", 10, "field1", "field2"); + }).thenCompose(res2 -> { + System.out.println(res2); + // >>> [1, 1] + // REMOVE_START + assertThat(res2).isEqualTo(Arrays.asList(1L, 1L)); + // REMOVE_END + // Check TTL of the fields + return asyncCommands.httl("myhash", "field1", "field2"); + }).thenCompose(res3 -> { + System.out.println(res3.size()); + // >>> 2 + // REMOVE_START + assertThat(res3.size()).isEqualTo(2); + assertThat(res3.stream().allMatch(ttl -> ttl > 0)).isTrue(); // TTL should be positive + // REMOVE_END + // Try to set expiration on non-existent field + return asyncCommands.hexpire("myhash", 10, "nonexistent"); + }).thenAccept(res4 -> { + System.out.println(res4); + // >>> [-2] + // REMOVE_START + assertThat(res4).isEqualTo(Arrays.asList(-2L)); + // REMOVE_END + }).toCompletableFuture(); + // STEP_END + + hExpireExample.join(); + // REMOVE_START + asyncCommands.del("myhash").toCompletableFuture().join(); + // REMOVE_END } finally { redisClient.shutdown(); } diff --git a/local_examples/cmds_hash/lettuce-reactive/CmdsHashExample.java b/local_examples/cmds_hash/lettuce-reactive/CmdsHashExample.java index c42580283..9c60b5b78 100644 --- a/local_examples/cmds_hash/lettuce-reactive/CmdsHashExample.java +++ b/local_examples/cmds_hash/lettuce-reactive/CmdsHashExample.java @@ -224,6 +224,58 @@ public void run() { // REMOVE_START reactiveCommands.del("myhash").block(); // REMOVE_END + + // STEP_START hexpire + // Set up hash with fields + Map hExpireExampleParams = new HashMap<>(); + hExpireExampleParams.put("field1", "Hello"); + hExpireExampleParams.put("field2", "World"); + + Mono hExpireExample1 = reactiveCommands.hset("myhash", hExpireExampleParams).doOnNext(result -> { + // REMOVE_START + assertThat(result).isEqualTo(2L); + // REMOVE_END + }); + + hExpireExample1.block(); + + // Set expiration on hash fields + Mono> hExpireExample2 = reactiveCommands.hexpire("myhash", 10, "field1", "field2").collectList().doOnNext(result -> { + System.out.println(result); + // >>> [1, 1] + // REMOVE_START + assertThat(result).isEqualTo(Arrays.asList(1L, 1L)); + // REMOVE_END + }); + + hExpireExample2.block(); + + // Check TTL of the fields + Mono> hExpireExample3 = reactiveCommands.httl("myhash", "field1", "field2").collectList().doOnNext(result -> { + System.out.println(result.size()); + // >>> 2 + // REMOVE_START + assertThat(result.size()).isEqualTo(2); + assertThat(result.stream().allMatch(ttl -> ttl > 0)).isTrue(); // TTL should be positive + // REMOVE_END + }); + + hExpireExample3.block(); + + // Try to set expiration on non-existent field + Mono> hExpireExample4 = reactiveCommands.hexpire("myhash", 10, "nonexistent").collectList().doOnNext(result -> { + System.out.println(result); + // >>> [-2] + // REMOVE_START + assertThat(result).isEqualTo(Arrays.asList(-2L)); + // REMOVE_END + }); + // STEP_END + + hExpireExample4.block(); + // REMOVE_START + reactiveCommands.del("myhash").block(); + // REMOVE_END } finally { redisClient.shutdown(); } diff --git a/local_examples/cmds_hash/node-redis/cmds-hash.js b/local_examples/cmds_hash/node-redis/cmds-hash.js index e48eaad6b..8cc913b93 100644 --- a/local_examples/cmds_hash/node-redis/cmds-hash.js +++ b/local_examples/cmds_hash/node-redis/cmds-hash.js @@ -122,6 +122,33 @@ await client.del('myhash') // REMOVE_END // STEP_END +// STEP_START hexpire +// Set up hash with fields +await client.hSet('myhash', { + 'field1': 'Hello', + 'field2': 'World' +}) + +// Set expiration on hash fields +const res14 = await client.hExpire('myhash', ['field1', 'field2'], 10) +console.log(res14) // [1, 1] + +// Check TTL of the fields +const res15 = await client.hTTL('myhash', ['field1', 'field2']) +console.log(res15) // [10, 10] (or close to 10) + +// Try to set expiration on non-existent field +const res16 = await client.hExpire('myhash', ['nonexistent'], 10) +console.log(res16) // [-2] + +// REMOVE_START +assert.deepEqual(res14, [1, 1]); +assert(res15.every(ttl => ttl > 0)); // TTL should be positive +assert.deepEqual(res16, [-2]); +await client.del('myhash') +// REMOVE_END +// STEP_END + // HIDE_START await client.close(); // HIDE_END diff --git a/local_examples/cmds_hash/predis/CmdsHashTest.php b/local_examples/cmds_hash/predis/CmdsHashTest.php index 1772579f6..6cc345921 100644 --- a/local_examples/cmds_hash/predis/CmdsHashTest.php +++ b/local_examples/cmds_hash/predis/CmdsHashTest.php @@ -122,6 +122,34 @@ public function testCmdsHash(): void $this->assertEquals('OK', $hValsResult1); $this->assertEquals(['Hello', 'World'], $hValsResult2); + // STEP_START hexpire + echo "\n--- HEXPIRE Command ---\n"; + // Clean up first + $this->redis->del('myhash'); + + // Set up hash with fields + $hExpireResult1 = $this->redis->hmset('myhash', ['field1' => 'Hello', 'field2' => 'World']); + echo "HMSET myhash field1 Hello field2 World: " . ($hExpireResult1 ? 'OK' : 'FAIL') . "\n"; // >>> OK + + // Set expiration on hash fields + $hExpireResult2 = $this->redis->hexpire('myhash', 10, ['field1', 'field2']); + echo "HEXPIRE myhash 10 FIELDS field1 field2: " . json_encode($hExpireResult2) . "\n"; // >>> [1,1] + + // Check TTL of the fields + $hExpireResult3 = $this->redis->httl('myhash', ['field1', 'field2']); + echo "HTTL myhash FIELDS field1 field2 count: " . count($hExpireResult3) . "\n"; // >>> 2 + + // Try to set expiration on non-existent field + $hExpireResult4 = $this->redis->hexpire('myhash', 10, ['nonexistent']); + echo "HEXPIRE myhash 10 FIELDS nonexistent: " . json_encode($hExpireResult4) . "\n"; // >>> [-2] + // STEP_END + + $this->assertEquals('OK', $hExpireResult1); + $this->assertEquals([1, 1], $hExpireResult2); + $this->assertEquals(2, count($hExpireResult3)); + $this->assertTrue(array_reduce($hExpireResult3, function($carry, $ttl) { return $carry && $ttl > 0; }, true)); // TTL should be positive + $this->assertEquals([-2], $hExpireResult4); + echo "\n=== All Hash Commands Tests Passed! ===\n"; } diff --git a/local_examples/cmds_hash/redis-py/cmds_hash.py b/local_examples/cmds_hash/redis-py/cmds_hash.py index fd876b719..434436e5d 100644 --- a/local_examples/cmds_hash/redis-py/cmds_hash.py +++ b/local_examples/cmds_hash/redis-py/cmds_hash.py @@ -105,4 +105,28 @@ assert res11 == [ "Hello", "World" ] r.delete("myhash") # REMOVE_END +# STEP_END + +# STEP_START hexpire +# Set up hash with fields +r.hset("myhash", mapping={"field1": "Hello", "field2": "World"}) + +# Set expiration on hash fields +res12 = r.hexpire("myhash", 10, "field1", "field2") +print(res12) # >>> [1, 1] + +# Check TTL of the fields +res13 = r.httl("myhash", "field1", "field2") +print(res13) # >>> [10, 10] (or close to 10) + +# Try to set expiration on non-existent field +res14 = r.hexpire("myhash", 10, "nonexistent") +print(res14) # >>> [-2] + +# REMOVE_START +assert res12 == [1, 1] +assert all(ttl > 0 for ttl in res13) # TTL should be positive +assert res14 == [-2] +r.delete("myhash") +# REMOVE_END # STEP_END \ No newline at end of file diff --git a/local_examples/cmds_hash/rust-async/cmds_hash.rs b/local_examples/cmds_hash/rust-async/cmds_hash.rs index e9c1a86b8..58e10c5d8 100644 --- a/local_examples/cmds_hash/rust-async/cmds_hash.rs +++ b/local_examples/cmds_hash/rust-async/cmds_hash.rs @@ -349,5 +349,64 @@ mod cmds_hash_tests { let _: Result = r.del("myhash").await; // REMOVE_END // STEP_END + + // STEP_START hexpire + // Set up hash with fields + let hash_fields = vec![("field1", "Hello"), ("field2", "World")]; + if let Ok(res) = r.hset_multiple("myhash", &hash_fields).await { + let res: String = res; + println!("{res}"); // >>> OK + } + + // Set expiration on hash fields + match r.hexpire("myhash", 10, redis::ExpireOption::NONE, &["field1", "field2"]).await { + Ok(res1) => { + let res1: Vec = res1; + println!("{:?}", res1); // >>> [1, 1] + // REMOVE_START + assert_eq!(res1, vec![1, 1]); + // REMOVE_END + }, + Err(e) => { + println!("Error setting expiration: {e}"); + return; + } + } + + // Check TTL of the fields + match r.httl("myhash", &["field1", "field2"]).await { + Ok(res2) => { + let res2: Vec = res2; + println!("{}", res2.len()); // >>> 2 + // REMOVE_START + assert_eq!(res2.len(), 2); + assert!(res2.iter().all(|&ttl| ttl > 0)); // TTL should be positive + // REMOVE_END + }, + Err(e) => { + println!("Error getting TTL: {e}"); + return; + } + } + + // Try to set expiration on non-existent field + match r.hexpire("myhash", 10, redis::ExpireOption::NONE, &["nonexistent"]).await { + Ok(res3) => { + let res3: Vec = res3; + println!("{:?}", res3); // >>> [-2] + // REMOVE_START + assert_eq!(res3, vec![-2]); + // REMOVE_END + }, + Err(e) => { + println!("Error setting expiration on non-existent field: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("myhash").await; + // REMOVE_END + // STEP_END } } diff --git a/local_examples/cmds_hash/rust-sync/cmds_hash.rs b/local_examples/cmds_hash/rust-sync/cmds_hash.rs index 5ad056bae..cf6bd0ae1 100644 --- a/local_examples/cmds_hash/rust-sync/cmds_hash.rs +++ b/local_examples/cmds_hash/rust-sync/cmds_hash.rs @@ -349,5 +349,64 @@ mod cmds_hash_tests { let _: Result = r.del("myhash"); // REMOVE_END // STEP_END + + // STEP_START hexpire + // Set up hash with fields + let hash_fields = vec![("field1", "Hello"), ("field2", "World")]; + if let Ok(res) = r.hset_multiple("myhash", &hash_fields) { + let res: String = res; + println!("{res}"); // >>> OK + } + + // Set expiration on hash fields + match r.hexpire("myhash", 10, redis::ExpireOption::NONE, &["field1", "field2"]) { + Ok(res1) => { + let res1: Vec = res1; + println!("{:?}", res1); // >>> [1, 1] + // REMOVE_START + assert_eq!(res1, vec![1, 1]); + // REMOVE_END + }, + Err(e) => { + println!("Error setting expiration: {e}"); + return; + } + } + + // Check TTL of the fields + match r.httl("myhash", &["field1", "field2"]) { + Ok(res2) => { + let res2: Vec = res2; + println!("{}", res2.len()); // >>> 2 + // REMOVE_START + assert_eq!(res2.len(), 2); + assert!(res2.iter().all(|&ttl| ttl > 0)); // TTL should be positive + // REMOVE_END + }, + Err(e) => { + println!("Error getting TTL: {e}"); + return; + } + } + + // Try to set expiration on non-existent field + match r.hexpire("myhash", 10, redis::ExpireOption::NONE, &["nonexistent"]) { + Ok(res3) => { + let res3: Vec = res3; + println!("{:?}", res3); // >>> [-2] + // REMOVE_START + assert_eq!(res3, vec![-2]); + // REMOVE_END + }, + Err(e) => { + println!("Error setting expiration on non-existent field: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("myhash"); + // REMOVE_END + // STEP_END } }