diff --git a/java/integTest/src/test/java/compatibility/jedis/JedisTest.java b/java/integTest/src/test/java/compatibility/jedis/JedisTest.java index 61557d0d408..0003c2a5a38 100644 --- a/java/integTest/src/test/java/compatibility/jedis/JedisTest.java +++ b/java/integTest/src/test/java/compatibility/jedis/JedisTest.java @@ -27,7 +27,6 @@ import redis.clients.jedis.args.ExpiryOption; import redis.clients.jedis.args.ListDirection; import redis.clients.jedis.args.ListPosition; -import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.params.BitPosParams; import redis.clients.jedis.params.GetExParams; import redis.clients.jedis.params.HGetExParams; @@ -35,9 +34,8 @@ import redis.clients.jedis.params.LPosParams; import redis.clients.jedis.params.ScanParams; import redis.clients.jedis.params.SetParams; -import redis.clients.jedis.resps.AccessControlLogEntry; -import redis.clients.jedis.resps.AccessControlUser; import redis.clients.jedis.resps.ScanResult; +import redis.clients.jedis.resps.Tuple; import redis.clients.jedis.util.KeyValue; /** @@ -3376,283 +3374,1451 @@ void list_edge_cases() { 0, jedis.rpushx("non_existent_key", "value"), "RPUSHX on non-existent key should return 0"); } - // --- ACL command integration tests --- + // ======================================== + // Sorted Set Commands Tests + // ======================================== @Test - void acl_cat_without_category() { - List categories = jedis.aclCat(); - assertNotNull(categories); - assertTrue(categories.size() > 0); - assertTrue(categories.contains("string")); - assertTrue(categories.contains("list")); - assertTrue(categories.contains("hash")); + void sortedset_zadd_single_member() { + String key = UUID.randomUUID().toString(); + + // Test ZADD with single member + long result = jedis.zadd(key, 1.0, "member1"); + assertEquals(1, result, "ZADD should return 1 for new member"); + + // Verify member was added + Double score = jedis.zscore(key, "member1"); + assertEquals(1.0, score, 0.001, "Member should have correct score"); + + // Test ZADD updating existing member + result = jedis.zadd(key, 2.0, "member1"); + assertEquals(0, result, "ZADD should return 0 when updating existing member"); + + score = jedis.zscore(key, "member1"); + assertEquals(2.0, score, 0.001, "Member score should be updated"); } @Test - void acl_cat_with_category() { - List stringCommands = jedis.aclCat("string"); - assertNotNull(stringCommands); - assertTrue(stringCommands.size() > 0); - assertTrue(stringCommands.contains("get")); - assertTrue(stringCommands.contains("set")); + void sortedset_zadd_multiple_members() { + String key = UUID.randomUUID().toString(); + + // Test ZADD with multiple members + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + + long result = jedis.zadd(key, members); + assertEquals(3, result, "ZADD should return 3 for three new members"); - List listCommands = jedis.aclCat("list"); - assertNotNull(listCommands); - assertTrue(listCommands.size() > 0); - assertTrue(listCommands.contains("lpush")); - assertTrue(listCommands.contains("rpush")); + // Verify all members were added + long cardinality = jedis.zcard(key); + assertEquals(3, cardinality, "Sorted set should have 3 members"); + + // Verify individual scores + assertEquals(1.0, jedis.zscore(key, "member1"), 0.001); + assertEquals(2.0, jedis.zscore(key, "member2"), 0.001); + assertEquals(3.0, jedis.zscore(key, "member3"), 0.001); } @Test - void acl_cat_invalid_category() { - JedisException exception = - assertThrows(JedisException.class, () -> jedis.aclCat("nonexistent")); - String message = exception.getMessage().toLowerCase(); - assertTrue( - message.contains("unknown category") - || message.contains("category") - || (exception.getCause() != null - && exception.getCause().getMessage().toLowerCase().contains("category"))); + void sortedset_zadd_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + byte[] member = "member1".getBytes(); + + // Test binary ZADD + long result = jedis.zadd(key, 5.5, member); + assertEquals(1, result, "Binary ZADD should return 1 for new member"); + + // Verify member was added + Double score = jedis.zscore(key, member); + assertEquals(5.5, score, 0.001, "Binary member should have correct score"); + + // Cleanup + jedis.del(key); } @Test - void acl_setuser_and_deluser() { - String username = "testuser_" + UUID.randomUUID().toString().replace("-", ""); + void sortedset_zaddIncr() { + String key = UUID.randomUUID().toString(); - try { - String setResult = jedis.aclSetUser(username, "on", "+get", "~*"); - assertEquals("OK", setResult); + // Test ZADD INCR on non-existing member + double score = jedis.zaddIncr(key, 5.0, "member1"); + assertEquals(5.0, score, 0.001, "ZADD INCR should return 5.0 for new member"); + + // Test ZADD INCR on existing member + score = jedis.zaddIncr(key, 2.5, "member1"); + assertEquals(7.5, score, 0.001, "ZADD INCR should return 7.5 after increment"); - List users = jedis.aclUsers(); - assertTrue(users.contains(username)); + // Verify final score + Double finalScore = jedis.zscore(key, "member1"); + assertEquals(7.5, finalScore, 0.001, "Member should have incremented score"); + } - long deleteCount = jedis.aclDelUser(username); - assertEquals(1L, deleteCount); + @Test + void sortedset_zaddIncr_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + byte[] member = "member1".getBytes(); - users = jedis.aclUsers(); - assertFalse(users.contains(username)); - } finally { - try { - jedis.aclDelUser(username); - } catch (Exception ignored) { - } - } + // Test binary ZADD INCR + double score = jedis.zaddIncr(key, 3.14, member); + assertEquals(3.14, score, 0.001, "Binary ZADD INCR should return correct score"); + + // Cleanup + jedis.del(key); } @Test - void acl_deluser_multiple_users() { - String username1 = "testuser1_" + UUID.randomUUID().toString().replace("-", ""); - String username2 = "testuser2_" + UUID.randomUUID().toString().replace("-", ""); + void sortedset_zrem() { + String key = UUID.randomUUID().toString(); - try { - jedis.aclSetUser(username1, "on"); - jedis.aclSetUser(username2, "on"); + // Setup test data + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(key, members); - long deleteCount = jedis.aclDelUser(username1, username2); - assertEquals(2L, deleteCount); + // Test ZREM single member + long result = jedis.zrem(key, "member1"); + assertEquals(1, result, "ZREM should return 1 for removed member"); - List users = jedis.aclUsers(); - assertFalse(users.contains(username1)); - assertFalse(users.contains(username2)); - } finally { - try { - jedis.aclDelUser(username1, username2); - } catch (Exception ignored) { - } - } + // Test ZREM multiple members + result = jedis.zrem(key, "member2", "member3"); + assertEquals(2, result, "ZREM should return 2 for two removed members"); + + // Verify all members removed + long cardinality = jedis.zcard(key); + assertEquals(0, cardinality, "Sorted set should be empty"); + + // Test ZREM on non-existing member + result = jedis.zrem(key, "nonexistent"); + assertEquals(0, result, "ZREM should return 0 for non-existing member"); } @Test - void acl_getuser() { - String username = "testuser_" + UUID.randomUUID().toString().replace("-", ""); + void sortedset_zrem_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + byte[] member1 = "member1".getBytes(); + byte[] member2 = "member2".getBytes(); - try { - jedis.aclSetUser(username, "on", "+get", "+set", "~key*"); - - AccessControlUser userInfo = jedis.aclGetUser(username); - assertNotNull(userInfo); - assertFalse(userInfo.getFlags().isEmpty()); - assertTrue(userInfo.getFlags().contains("on")); - assertNotNull(userInfo.getCommands()); - assertTrue(userInfo.getCommands().contains("+get")); - assertTrue(userInfo.getCommands().contains("+set")); - assertFalse(userInfo.getKeys().isEmpty()); - - AccessControlUser nonExistent = jedis.aclGetUser("nonexistent_user_12345"); - assertNull(nonExistent); - } finally { - try { - jedis.aclDelUser(username); - } catch (Exception ignored) { - } - } + // Setup + Map members = new HashMap<>(); + members.put(member1, 1.0); + members.put(member2, 2.0); + jedis.zadd(key, members); + + // Test binary ZREM + long result = jedis.zrem(key, member1, member2); + assertEquals(2, result, "Binary ZREM should return 2 for two removed members"); + + // Cleanup + jedis.del(key); } @Test - void acl_list() { - List aclList = jedis.aclList(); - assertNotNull(aclList); - assertTrue(aclList.size() > 0); + void sortedset_zcard() { + String key = UUID.randomUUID().toString(); - boolean hasDefaultUser = false; - for (String rule : aclList) { - if (rule.contains("user default")) { - hasDefaultUser = true; - break; - } - } - assertTrue(hasDefaultUser); + // Test ZCARD on non-existing key + long result = jedis.zcard(key); + assertEquals(0, result, "ZCARD should return 0 for non-existing key"); + + // Add members + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(key, members); + + // Test ZCARD on existing sorted set + result = jedis.zcard(key); + assertEquals(3, result, "ZCARD should return 3 for three members"); } @Test - void acl_users() { - List users = jedis.aclUsers(); - assertNotNull(users); - assertTrue(users.size() > 0); - assertTrue(users.contains("default")); + void sortedset_zcard_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + + // Test binary ZCARD + long result = jedis.zcard(key); + assertEquals(0, result, "Binary ZCARD should return 0 for non-existing key"); + + // Add member and test again + jedis.zadd(key, 1.0, "member1".getBytes()); + result = jedis.zcard(key); + assertEquals(1, result, "Binary ZCARD should return 1"); + + // Cleanup + jedis.del(key); } @Test - void acl_whoami() { - String username = jedis.aclWhoAmI(); - assertNotNull(username); - assertEquals("default", username); + void sortedset_zscore() { + String key = UUID.randomUUID().toString(); + + // Test ZSCORE on non-existing key + Double result = jedis.zscore(key, "member1"); + assertNull(result, "ZSCORE should return null for non-existing key"); + + // Add member + jedis.zadd(key, 5.5, "member1"); + + // Test ZSCORE on existing member + result = jedis.zscore(key, "member1"); + assertEquals(5.5, result, 0.001, "ZSCORE should return correct score"); + + // Test ZSCORE on non-existing member + result = jedis.zscore(key, "nonexistent"); + assertNull(result, "ZSCORE should return null for non-existing member"); } @Test - void acl_dryrun() { - assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "ACL DRYRUN added in version 7.0"); + void sortedset_zscore_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + byte[] member = "member1".getBytes(); - String username = "testuser_" + UUID.randomUUID().toString().replace("-", ""); + // Add member + jedis.zadd(key, 7.7, member); - try { - jedis.aclSetUser(username, "on", "+get", "~*", "nopass"); + // Test binary ZSCORE + Double result = jedis.zscore(key, member); + assertEquals(7.7, result, 0.001, "Binary ZSCORE should return correct score"); - String result = jedis.aclDryRun(username, "get", "key"); - assertEquals("OK", result); + // Cleanup + jedis.del(key); + } - String deniedResult = jedis.aclDryRun(username, "set", "key", "value"); - String denied = deniedResult.toLowerCase(); - assertTrue( - denied.contains("permission") - || denied.contains("denied") - || denied.contains("noperm") - || denied.contains("user") - || denied.contains("command")); - } finally { - try { - jedis.aclDelUser(username); - } catch (Exception ignored) { - } - } + @Test + void sortedset_zmscore() { + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo("6.2.0"), + "ZMSCORE command requires Valkey 6.2.0 or higher"); + + String key = UUID.randomUUID().toString(); + + // Setup test data + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(key, members); + + // Test ZMSCORE + List scores = jedis.zmscore(key, "member1", "nonexistent", "member3"); + assertEquals(3, scores.size(), "ZMSCORE should return 3 scores"); + assertEquals(1.0, scores.get(0), 0.001, "First score should be correct"); + assertNull(scores.get(1), "Non-existing member should have null score"); + assertEquals(3.0, scores.get(2), 0.001, "Third score should be correct"); } @Test - void acl_genpass_default() { - String password = jedis.aclGenPass(); - assertNotNull(password); - assertEquals(64, password.length()); - assertTrue(password.matches("[0-9a-f]+")); + void sortedset_zmscore_binary() { + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo("6.2.0"), + "ZMSCORE command requires Valkey 6.2.0 or higher"); + + byte[] key = UUID.randomUUID().toString().getBytes(); + byte[] member1 = "member1".getBytes(); + byte[] member2 = "member2".getBytes(); + + // Setup + Map members = new HashMap<>(); + members.put(member1, 1.5); + members.put(member2, 2.5); + jedis.zadd(key, members); + + // Test binary ZMSCORE + List scores = jedis.zmscore(key, member1, member2); + assertEquals(2, scores.size(), "Binary ZMSCORE should return 2 scores"); + assertEquals(1.5, scores.get(0), 0.001); + assertEquals(2.5, scores.get(1), 0.001); + + // Cleanup + jedis.del(key); } @Test - void acl_genpass_with_bits() { - String password = jedis.aclGenPass(128); - assertNotNull(password); - assertEquals(32, password.length()); - assertTrue(password.matches("[0-9a-f]+")); + void sortedset_zrange() { + String key = UUID.randomUUID().toString(); + + // Setup test data + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + members.put("member4", 4.0); + jedis.zadd(key, members); - String password256 = jedis.aclGenPass(256); - assertNotNull(password256); - assertEquals(64, password256.length()); - assertTrue(password256.matches("[0-9a-f]+")); + // Test ZRANGE full range + List range = jedis.zrange(key, 0, -1); + assertEquals(4, range.size(), "ZRANGE should return all 4 members"); + assertEquals("member1", range.get(0), "First member should be lowest score"); + assertEquals("member4", range.get(3), "Last member should be highest score"); + + // Test ZRANGE partial range + range = jedis.zrange(key, 1, 2); + assertEquals(2, range.size(), "ZRANGE should return 2 members"); + assertEquals("member2", range.get(0)); + assertEquals("member3", range.get(1)); + + // Test ZRANGE with negative indices + range = jedis.zrange(key, -2, -1); + assertEquals(2, range.size(), "ZRANGE should support negative indices"); + assertEquals("member3", range.get(0)); + assertEquals("member4", range.get(1)); + + // Test ZRANGE on non-existing key + range = jedis.zrange("nonexistent", 0, -1); + assertTrue(range.isEmpty(), "ZRANGE should return empty list for non-existing key"); } @Test - void acl_log() { - List log = jedis.aclLog(); - assertNotNull(log); - assertTrue(log.size() >= 0); + void sortedset_zrange_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + + // Setup + Map members = new HashMap<>(); + members.put("member1".getBytes(), 1.0); + members.put("member2".getBytes(), 2.0); + jedis.zadd(key, members); + + // Test binary ZRANGE + List range = jedis.zrange(key, 0, -1); + assertEquals(2, range.size(), "Binary ZRANGE should return 2 members"); + + // Cleanup + jedis.del(key); } @Test - void acl_log_with_count() { - List log = jedis.aclLog(5); - assertNotNull(log); - assertTrue(log.size() <= 5); + void sortedset_zrangeWithScores() { + String key = UUID.randomUUID().toString(); + + // Setup test data + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(key, members); + + // Test ZRANGE WITHSCORES + List rangeWithScores = jedis.zrangeWithScores(key, 0, -1); + assertEquals(3, rangeWithScores.size(), "ZRANGE WITHSCORES should return 3 members"); + assertEquals("member1", rangeWithScores.get(0).getElement()); + assertEquals(1.0, rangeWithScores.get(0).getScore(), 0.001); + assertEquals("member2", rangeWithScores.get(1).getElement()); + assertEquals(2.0, rangeWithScores.get(1).getScore(), 0.001); + assertEquals("member3", rangeWithScores.get(2).getElement()); + assertEquals(3.0, rangeWithScores.get(2).getScore(), 0.001); + + // Test partial range with scores + List partialRange = jedis.zrangeWithScores(key, 0, 1); + assertEquals(2, partialRange.size(), "ZRANGE WITHSCORES should return 2 members"); + assertEquals("member1", partialRange.get(0).getElement()); + assertEquals("member2", partialRange.get(1).getElement()); } @Test - void acl_log_reset() { - String result = jedis.aclLogReset(); - assertEquals("OK", result); + void sortedset_zrangeWithScores_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + + // Setup + Map members = new HashMap<>(); + members.put("member1".getBytes(), 10.0); + members.put("member2".getBytes(), 20.0); + jedis.zadd(key, members); - List log = jedis.aclLog(); - assertNotNull(log); - assertEquals(0, log.size()); + // Test binary ZRANGE WITHSCORES + List rangeWithScores = jedis.zrangeWithScores(key, 0, -1); + assertEquals(2, rangeWithScores.size(), "Binary ZRANGE WITHSCORES should return 2 members"); + + // Cleanup + jedis.del(key); } @Test - void acl_setuser_complex_rules() { - String username = "complexuser_" + UUID.randomUUID().toString().replace("-", ""); + void sortedset_zrank() { + String key = UUID.randomUUID().toString(); - try { - String result = jedis.aclSetUser(username, "on", "+get", "+set", "+del", "~key:*", "nopass"); - assertEquals("OK", result); + // Setup test data with distinct scores + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(key, members); - List users = jedis.aclUsers(); - assertTrue(users.contains(username)); + // Test ZRANK + Long rank = jedis.zrank(key, "member1"); + assertEquals(0L, rank, "member1 should have rank 0 (lowest score)"); - AccessControlUser userInfo = jedis.aclGetUser(username); - assertNotNull(userInfo); - } finally { - try { - jedis.aclDelUser(username); - } catch (Exception ignored) { - } - } + rank = jedis.zrank(key, "member2"); + assertEquals(1L, rank, "member2 should have rank 1"); + + rank = jedis.zrank(key, "member3"); + assertEquals(2L, rank, "member3 should have rank 2 (highest score)"); + + // Test ZRANK on non-existing member + rank = jedis.zrank(key, "nonexistent"); + assertNull(rank, "ZRANK should return null for non-existing member"); + + // Test ZRANK on non-existing key + rank = jedis.zrank("nonexistent_key", "member1"); + assertNull(rank, "ZRANK should return null for non-existing key"); } @Test - void acl_load() { - assumeTrue(isAclFileConfigured(), "Skipping test: ACL file not configured on server"); + void sortedset_zrank_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + byte[] member = "member1".getBytes(); + + // Setup + jedis.zadd(key, 1.0, member); + jedis.zadd(key, 2.0, "member2".getBytes()); - String result = jedis.aclLoad(); - assertEquals("OK", result); + // Test binary ZRANK + Long rank = jedis.zrank(key, member); + assertEquals(0L, rank, "Binary ZRANK should return correct rank"); + + // Cleanup + jedis.del(key); } @Test - void acl_save() { - assumeTrue(isAclFileConfigured(), "Skipping test: ACL file not configured on server"); + void sortedset_zrevrank() { + String key = UUID.randomUUID().toString(); - String result = jedis.aclSave(); - assertEquals("OK", result); + // Setup test data + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(key, members); + + // Test ZREVRANK (reverse order: high to low) + Long revrank = jedis.zrevrank(key, "member3"); + assertEquals(0L, revrank, "member3 should have revrank 0 (highest score)"); + + revrank = jedis.zrevrank(key, "member2"); + assertEquals(1L, revrank, "member2 should have revrank 1"); + + revrank = jedis.zrevrank(key, "member1"); + assertEquals(2L, revrank, "member1 should have revrank 2 (lowest score)"); + + // Test ZREVRANK on non-existing member + revrank = jedis.zrevrank(key, "nonexistent"); + assertNull(revrank, "ZREVRANK should return null for non-existing member"); } - private boolean isAclFileConfigured() { - try { - jedis.aclLoad(); - return true; - } catch (JedisException e) { - String message = getFullExceptionMessage(e).toLowerCase(); - if (message.contains("no acl file") - || message.contains("aclfile") - || message.contains("not configured")) { - return false; - } - throw e; + @Test + void sortedset_zrevrank_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + byte[] member = "member1".getBytes(); + + // Setup + jedis.zadd(key, 1.0, member); + jedis.zadd(key, 2.0, "member2".getBytes()); + + // Test binary ZREVRANK + Long revrank = jedis.zrevrank(key, "member2".getBytes()); + assertEquals(0L, revrank, "Binary ZREVRANK should return 0 for highest score"); + + // Cleanup + jedis.del(key); + } + + @Test + void sortedset_zcount() { + String key = UUID.randomUUID().toString(); + + // Setup test data + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + members.put("member4", 4.0); + members.put("member5", 5.0); + jedis.zadd(key, members); + + // Test ZCOUNT with inclusive range + long count = jedis.zcount(key, 2.0, 4.0); + assertEquals(3, count, "ZCOUNT should return 3 for range [2.0, 4.0]"); + + // Test ZCOUNT with full range + count = jedis.zcount(key, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); + assertEquals(5, count, "ZCOUNT should return 5 for full range"); + + // Test ZCOUNT with no matches + count = jedis.zcount(key, 10.0, 20.0); + assertEquals(0, count, "ZCOUNT should return 0 for range with no members"); + + // Test ZCOUNT on non-existing key + count = jedis.zcount("nonexistent", 0.0, 10.0); + assertEquals(0, count, "ZCOUNT should return 0 for non-existing key"); + } + + @Test + void sortedset_zcount_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + + // Setup + Map members = new HashMap<>(); + members.put("member1".getBytes(), 1.0); + members.put("member2".getBytes(), 2.0); + jedis.zadd(key, members); + + // Test binary ZCOUNT + long count = jedis.zcount(key, 1.0, 2.0); + assertEquals(2, count, "Binary ZCOUNT should return 2"); + + // Cleanup + jedis.del(key); + } + + @Test + void sortedset_zincrby() { + String key = UUID.randomUUID().toString(); + + // Test ZINCRBY on non-existing member (creates with increment as score) + double score = jedis.zincrby(key, 5.0, "member1"); + assertEquals(5.0, score, 0.001, "ZINCRBY should return 5.0 for new member"); + + // Test ZINCRBY on existing member + score = jedis.zincrby(key, 2.5, "member1"); + assertEquals(7.5, score, 0.001, "ZINCRBY should return 7.5 after increment"); + + // Test ZINCRBY with negative increment + score = jedis.zincrby(key, -3.0, "member1"); + assertEquals(4.5, score, 0.001, "ZINCRBY should support negative increments"); + + // Verify final score + Double finalScore = jedis.zscore(key, "member1"); + assertEquals(4.5, finalScore, 0.001, "Member should have final incremented score"); + } + + @Test + void sortedset_zincrby_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + byte[] member = "member1".getBytes(); + + // Test binary ZINCRBY + double score = jedis.zincrby(key, 10.5, member); + assertEquals(10.5, score, 0.001, "Binary ZINCRBY should return correct score"); + + score = jedis.zincrby(key, 5.0, member); + assertEquals(15.5, score, 0.001, "Binary ZINCRBY should increment correctly"); + + // Cleanup + jedis.del(key); + } + + @Test + void sortedset_zpopmin() { + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo("5.0.0"), + "ZPOPMIN command requires Valkey 5.0.0 or higher"); + + String key = UUID.randomUUID().toString(); + + // Setup test data + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + members.put("member4", 4.0); + jedis.zadd(key, members); + + // Test ZPOPMIN with count + List popped = jedis.zpopmin(key, 2); + assertEquals(2, popped.size(), "ZPOPMIN should return 2 members"); + assertEquals( + "member1", popped.get(0).getElement(), "ZPOPMIN should include lowest scored member"); + assertEquals(1.0, popped.get(0).getScore(), 0.001); + assertEquals("member2", popped.get(1).getElement(), "ZPOPMIN should include second lowest"); + assertEquals(2.0, popped.get(1).getScore(), 0.001); + + // Verify members were removed + long cardinality = jedis.zcard(key); + assertEquals(2, cardinality, "Sorted set should have 2 members remaining"); + + // Test ZPOPMIN on non-existing key + List poppedEmpty = jedis.zpopmin("nonexistent", 1); + assertTrue(poppedEmpty.isEmpty(), "ZPOPMIN should return empty list for non-existing key"); + } + + @Test + void sortedset_zpopmin_binary() { + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo("5.0.0"), + "ZPOPMIN command requires Valkey 5.0.0 or higher"); + + byte[] key = UUID.randomUUID().toString().getBytes(); + + // Setup + Map members = new HashMap<>(); + members.put("member1".getBytes(), 1.0); + members.put("member2".getBytes(), 2.0); + jedis.zadd(key, members); + + // Test binary ZPOPMIN + List popped = jedis.zpopmin(key, 1); + assertEquals(1, popped.size(), "Binary ZPOPMIN should return 1 member"); + + // Cleanup + jedis.del(key); + } + + @Test + void sortedset_zpopmax() { + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo("5.0.0"), + "ZPOPMAX command requires Valkey 5.0.0 or higher"); + + String key = UUID.randomUUID().toString(); + + // Setup test data + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + members.put("member4", 4.0); + jedis.zadd(key, members); + + // Test ZPOPMAX with count + List popped = jedis.zpopmax(key, 2); + assertEquals(2, popped.size(), "ZPOPMAX should return 2 members"); + assertEquals( + "member4", popped.get(0).getElement(), "ZPOPMAX should include highest scored member"); + assertEquals(4.0, popped.get(0).getScore(), 0.001); + assertEquals("member3", popped.get(1).getElement(), "ZPOPMAX should include second highest"); + assertEquals(3.0, popped.get(1).getScore(), 0.001); + + // Verify members were removed + long cardinality = jedis.zcard(key); + assertEquals(2, cardinality, "Sorted set should have 2 members remaining"); + + // Test ZPOPMAX on non-existing key + List poppedEmpty = jedis.zpopmax("nonexistent", 1); + assertTrue(poppedEmpty.isEmpty(), "ZPOPMAX should return empty list for non-existing key"); + } + + @Test + void sortedset_zpopmax_binary() { + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo("5.0.0"), + "ZPOPMAX command requires Valkey 5.0.0 or higher"); + + byte[] key = UUID.randomUUID().toString().getBytes(); + + // Setup + Map members = new HashMap<>(); + members.put("member1".getBytes(), 1.0); + members.put("member2".getBytes(), 2.0); + jedis.zadd(key, members); + + // Test binary ZPOPMAX + List popped = jedis.zpopmax(key, 1); + assertEquals(1, popped.size(), "Binary ZPOPMAX should return 1 member"); + + // Cleanup + jedis.del(key); + } + + @Test + void sortedset_zunionstore() { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String destKey = UUID.randomUUID().toString(); + + // Setup test data + Map members1 = new HashMap<>(); + members1.put("member1", 1.0); + members1.put("member2", 2.0); + jedis.zadd(key1, members1); + + Map members2 = new HashMap<>(); + members2.put("member2", 3.0); + members2.put("member3", 4.0); + jedis.zadd(key2, members2); + + // Test ZUNIONSTORE + long result = jedis.zunionstore(destKey, key1, key2); + assertEquals(3, result, "ZUNIONSTORE should return 3 (union has 3 unique members)"); + + // Verify union result + long cardinality = jedis.zcard(destKey); + assertEquals(3, cardinality, "Destination should have 3 members"); + + // Verify scores (default aggregation is SUM) + Double score = jedis.zscore(destKey, "member2"); + assertEquals(5.0, score, 0.001, "member2 score should be sum of both sets (2.0 + 3.0)"); + + // Cleanup + jedis.del(key1, key2, destKey); + } + + @Test + void sortedset_zunionstore_binary() { + byte[] key1 = UUID.randomUUID().toString().getBytes(); + byte[] key2 = UUID.randomUUID().toString().getBytes(); + byte[] destKey = UUID.randomUUID().toString().getBytes(); + + // Setup + Map members1 = new HashMap<>(); + members1.put("member1".getBytes(), 1.0); + jedis.zadd(key1, members1); + + Map members2 = new HashMap<>(); + members2.put("member2".getBytes(), 2.0); + jedis.zadd(key2, members2); + + // Test binary ZUNIONSTORE + long result = jedis.zunionstore(destKey, key1, key2); + assertEquals(2, result, "Binary ZUNIONSTORE should return 2"); + + // Cleanup + jedis.del(key1); + jedis.del(key2); + jedis.del(destKey); + } + + @Test + void sortedset_zinterstore() { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String destKey = UUID.randomUUID().toString(); + + // Setup test data + Map members1 = new HashMap<>(); + members1.put("member1", 1.0); + members1.put("member2", 2.0); + members1.put("member3", 3.0); + jedis.zadd(key1, members1); + + Map members2 = new HashMap<>(); + members2.put("member2", 4.0); + members2.put("member3", 5.0); + members2.put("member4", 6.0); + jedis.zadd(key2, members2); + + // Test ZINTERSTORE + long result = jedis.zinterstore(destKey, key1, key2); + assertEquals(2, result, "ZINTERSTORE should return 2 (intersection has 2 common members)"); + + // Verify intersection result + long cardinality = jedis.zcard(destKey); + assertEquals(2, cardinality, "Destination should have 2 members"); + + // Verify scores (default aggregation is SUM) + Double score = jedis.zscore(destKey, "member2"); + assertEquals(6.0, score, 0.001, "member2 score should be sum (2.0 + 4.0)"); + + score = jedis.zscore(destKey, "member3"); + assertEquals(8.0, score, 0.001, "member3 score should be sum (3.0 + 5.0)"); + + // Verify non-common members are not in result + assertNull(jedis.zscore(destKey, "member1"), "member1 should not be in intersection"); + assertNull(jedis.zscore(destKey, "member4"), "member4 should not be in intersection"); + + // Cleanup + jedis.del(key1, key2, destKey); + } + + @Test + void sortedset_zinterstore_binary() { + byte[] key1 = UUID.randomUUID().toString().getBytes(); + byte[] key2 = UUID.randomUUID().toString().getBytes(); + byte[] destKey = UUID.randomUUID().toString().getBytes(); + + // Setup + Map members1 = new HashMap<>(); + members1.put("common".getBytes(), 1.0); + jedis.zadd(key1, members1); + + Map members2 = new HashMap<>(); + members2.put("common".getBytes(), 2.0); + jedis.zadd(key2, members2); + + // Test binary ZINTERSTORE + long result = jedis.zinterstore(destKey, key1, key2); + assertEquals(1, result, "Binary ZINTERSTORE should return 1"); + + // Cleanup + jedis.del(key1); + jedis.del(key2); + jedis.del(destKey); + } + + @Test + void sortedset_zremrangebyrank() { + String key = UUID.randomUUID().toString(); + + // Setup test data + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + members.put("member4", 4.0); + members.put("member5", 5.0); + jedis.zadd(key, members); + + // Test ZREMRANGEBYRANK + long removed = jedis.zremrangebyrank(key, 0, 2); + assertEquals(3, removed, "ZREMRANGEBYRANK should remove 3 members (ranks 0, 1, 2)"); + + // Verify remaining members + long cardinality = jedis.zcard(key); + assertEquals(2, cardinality, "Sorted set should have 2 members remaining"); + + List remaining = jedis.zrange(key, 0, -1); + assertEquals("member4", remaining.get(0), "member4 should remain"); + assertEquals("member5", remaining.get(1), "member5 should remain"); + + // Test ZREMRANGEBYRANK with negative indices + jedis.del(key); + jedis.zadd(key, members); + removed = jedis.zremrangebyrank(key, -2, -1); + assertEquals(2, removed, "ZREMRANGEBYRANK should remove last 2 members"); + + // Test ZREMRANGEBYRANK on non-existing key + removed = jedis.zremrangebyrank("nonexistent", 0, 10); + assertEquals(0, removed, "ZREMRANGEBYRANK should return 0 for non-existing key"); + } + + @Test + void sortedset_zremrangebyrank_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + + // Setup + Map members = new HashMap<>(); + members.put("member1".getBytes(), 1.0); + members.put("member2".getBytes(), 2.0); + members.put("member3".getBytes(), 3.0); + jedis.zadd(key, members); + + // Test binary ZREMRANGEBYRANK + long removed = jedis.zremrangebyrank(key, 0, 1); + assertEquals(2, removed, "Binary ZREMRANGEBYRANK should remove 2 members"); + + // Cleanup + jedis.del(key); + } + + @Test + void sortedset_zremrangebyscore() { + String key = UUID.randomUUID().toString(); + + // Setup test data + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + members.put("member4", 4.0); + members.put("member5", 5.0); + jedis.zadd(key, members); + + // Test ZREMRANGEBYSCORE + long removed = jedis.zremrangebyscore(key, 2.0, 4.0); + assertEquals(3, removed, "ZREMRANGEBYSCORE should remove 3 members (scores 2.0, 3.0, 4.0)"); + + // Verify remaining members + long cardinality = jedis.zcard(key); + assertEquals(2, cardinality, "Sorted set should have 2 members remaining"); + + List remaining = jedis.zrange(key, 0, -1); + assertEquals("member1", remaining.get(0), "member1 should remain"); + assertEquals("member5", remaining.get(1), "member5 should remain"); + + // Test ZREMRANGEBYSCORE with no matches + removed = jedis.zremrangebyscore(key, 10.0, 20.0); + assertEquals(0, removed, "ZREMRANGEBYSCORE should return 0 for range with no members"); + + // Test ZREMRANGEBYSCORE on non-existing key + removed = jedis.zremrangebyscore("nonexistent", 0.0, 10.0); + assertEquals(0, removed, "ZREMRANGEBYSCORE should return 0 for non-existing key"); + } + + @Test + void sortedset_zremrangebyscore_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + + // Setup + Map members = new HashMap<>(); + members.put("member1".getBytes(), 1.0); + members.put("member2".getBytes(), 2.0); + members.put("member3".getBytes(), 3.0); + jedis.zadd(key, members); + + // Test binary ZREMRANGEBYSCORE + long removed = jedis.zremrangebyscore(key, 1.0, 2.0); + assertEquals(2, removed, "Binary ZREMRANGEBYSCORE should remove 2 members"); + + // Cleanup + jedis.del(key); + } + + @Test + void sortedset_zscan() { + String key = UUID.randomUUID().toString(); + + // Setup test data + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(key, members); + + // Test ZSCAN + ScanResult result = jedis.zscan(key, "0"); + assertNotNull(result, "ZSCAN should return non-null result"); + assertNotNull(result.getCursor(), "ZSCAN should return cursor"); + assertNotNull(result.getResult(), "ZSCAN should return results"); + + List entries = result.getResult(); + assertEquals(3, entries.size(), "ZSCAN should return 3 entries"); + + // Verify all members are present + Set memberNames = new HashSet<>(); + for (Tuple tuple : entries) { + memberNames.add(tuple.getElement()); } + assertTrue(memberNames.contains("member1"), "ZSCAN should include member1"); + assertTrue(memberNames.contains("member2"), "ZSCAN should include member2"); + assertTrue(memberNames.contains("member3"), "ZSCAN should include member3"); + + // Test ZSCAN on non-existing key + ScanResult resultEmpty = jedis.zscan("nonexistent", "0"); + assertTrue( + resultEmpty.getResult().isEmpty(), "ZSCAN should return empty result for non-existing key"); } - private static String getFullExceptionMessage(Throwable t) { - StringBuilder sb = new StringBuilder(); - while (t != null) { - if (t.getMessage() != null) { - if (sb.length() > 0) sb.append(" "); - sb.append(t.getMessage()); - } - t = t.getCause(); + @Test + void sortedset_zscan_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + + // Setup + Map members = new HashMap<>(); + members.put("member1".getBytes(), 1.0); + members.put("member2".getBytes(), 2.0); + jedis.zadd(key, members); + + // Test binary ZSCAN + ScanResult result = jedis.zscan(key, "0".getBytes()); + assertNotNull(result, "Binary ZSCAN should return non-null result"); + assertEquals(2, result.getResult().size(), "Binary ZSCAN should return 2 entries"); + + // Cleanup + jedis.del(key); + } + + @Test + void sortedset_edge_cases() { + String key = UUID.randomUUID().toString(); + + // Test operations on non-existing sorted set + assertEquals(0, jedis.zcard(key), "ZCARD should return 0 for non-existing key"); + assertNull(jedis.zscore(key, "member"), "ZSCORE should return null for non-existing key"); + assertNull(jedis.zrank(key, "member"), "ZRANK should return null for non-existing key"); + assertNull(jedis.zrevrank(key, "member"), "ZREVRANK should return null for non-existing key"); + + List emptyRange = jedis.zrange(key, 0, -1); + assertTrue(emptyRange.isEmpty(), "ZRANGE should return empty list for non-existing key"); + + List emptyRangeWithScores = jedis.zrangeWithScores(key, 0, -1); + assertTrue( + emptyRangeWithScores.isEmpty(), + "ZRANGE WITHSCORES should return empty list for non-existing key"); + + // Test ZREM on non-existing key + long removed = jedis.zrem(key, "member1", "member2"); + assertEquals(0, removed, "ZREM should return 0 for non-existing key"); + + // Test ZCOUNT on non-existing key + long count = jedis.zcount(key, 0.0, 10.0); + assertEquals(0, count, "ZCOUNT should return 0 for non-existing key"); + + // Test operations with duplicate members + jedis.zadd(key, 1.0, "member1"); + jedis.zadd(key, 2.0, "member1"); // Update score + assertEquals(1, jedis.zcard(key), "Sorted set should still have 1 member after update"); + assertEquals(2.0, jedis.zscore(key, "member1"), 0.001, "Score should be updated"); + + // Test ZINCRBY creating new member + double score = jedis.zincrby(key, 5.0, "new_member"); + assertEquals(5.0, score, 0.001, "ZINCRBY should create member with increment as score"); + + // Cleanup + jedis.del(key); + } + + @Test + void sortedset_comprehensive_workflow() { + String key = UUID.randomUUID().toString(); + + // Build a leaderboard + jedis.zadd(key, 100.0, "player1"); + jedis.zadd(key, 200.0, "player2"); + jedis.zadd(key, 150.0, "player3"); + + // Check leaderboard size + assertEquals(3, jedis.zcard(key), "Leaderboard should have 3 players"); + + // Get top 2 players + List topPlayers = jedis.zrange(key, -2, -1); + assertEquals(2, topPlayers.size(), "Should get top 2 players"); + assertEquals("player3", topPlayers.get(0), "player3 should be second"); + assertEquals("player2", topPlayers.get(1), "player2 should be first"); + + // Get rankings + assertEquals(0L, jedis.zrank(key, "player1"), "player1 should be rank 0"); + assertEquals(1L, jedis.zrank(key, "player3"), "player3 should be rank 1"); + assertEquals(2L, jedis.zrank(key, "player2"), "player2 should be rank 2"); + + // Reverse rankings + assertEquals(2L, jedis.zrevrank(key, "player1"), "player1 should be revrank 2"); + assertEquals(0L, jedis.zrevrank(key, "player2"), "player2 should be revrank 0"); + + // Increment a player's score + double newScore = jedis.zincrby(key, 100.0, "player1"); + assertEquals(200.0, newScore, 0.001, "player1 should now have score 200"); + + // Count players in score range + long count = jedis.zcount(key, 150.0, 250.0); + assertEquals(3, count, "Should have 3 players in range [150, 250]"); + + // Remove bottom player + List removed = jedis.zpopmin(key, 1); + assertEquals(1, removed.size(), "Should remove 1 player"); + assertEquals("player3", removed.get(0).getElement(), "Should remove player3"); + + // Final verification + assertEquals(2, jedis.zcard(key), "Leaderboard should have 2 players remaining"); + + // Cleanup + jedis.del(key); + } + + @Test + void sortedset_zrangestore() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String sourceKey = UUID.randomUUID().toString(); + String destKey = UUID.randomUUID().toString(); + + // Setup source sorted set + jedis.zadd(sourceKey, 1.0, "one"); + jedis.zadd(sourceKey, 2.0, "two"); + jedis.zadd(sourceKey, 3.0, "three"); + + // Store range in destination + Long count = jedis.zrangestore(destKey, sourceKey, 0, 1); + assertEquals(2L, count, "Should store 2 elements"); + + // Verify destination + List result = jedis.zrange(destKey, 0, -1); + assertEquals(2, result.size()); + assertEquals("one", result.get(0)); + assertEquals("two", result.get(1)); + + // Cleanup + jedis.del(sourceKey, destKey); + } + + @Test + void sortedset_zrangestore_binary() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + byte[] sourceKey = UUID.randomUUID().toString().getBytes(); + byte[] destKey = UUID.randomUUID().toString().getBytes(); + + jedis.zadd(sourceKey, 1.0, "one".getBytes()); + jedis.zadd(sourceKey, 2.0, "two".getBytes()); + + Long count = jedis.zrangestore(destKey, sourceKey, 0, 1); + assertEquals(2L, count); + + jedis.del(sourceKey, destKey); + } + + @Test + void sortedset_zrankWithScore() { + String minVersion = "7.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key = UUID.randomUUID().toString(); + + jedis.zadd(key, 1.0, "one"); + jedis.zadd(key, 2.0, "two"); + jedis.zadd(key, 3.0, "three"); + + redis.clients.jedis.resps.KeyValue result = jedis.zrankWithScore(key, "two"); + assertNotNull(result); + assertEquals(1L, result.getKey()); + assertEquals(2.0, result.getValue(), 0.001); + + jedis.del(key); + } + + @Test + void sortedset_zrevrankWithScore() { + String minVersion = "7.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key = UUID.randomUUID().toString(); + + jedis.zadd(key, 1.0, "one"); + jedis.zadd(key, 2.0, "two"); + jedis.zadd(key, 3.0, "three"); + + redis.clients.jedis.resps.KeyValue result = jedis.zrevrankWithScore(key, "two"); + assertNotNull(result); + assertEquals(1L, result.getKey()); + assertEquals(2.0, result.getValue(), 0.001); + + jedis.del(key); + } + + @Test + void sortedset_zlexcount() { + String key = UUID.randomUUID().toString(); + + // Add members with same score for lexicographical ordering + jedis.zadd(key, 0.0, "a"); + jedis.zadd(key, 0.0, "b"); + jedis.zadd(key, 0.0, "c"); + jedis.zadd(key, 0.0, "d"); + + Long count = jedis.zlexcount(key, "[a", "[c"); + assertEquals(3L, count, "Should count 3 members (a, b, c)"); + + count = jedis.zlexcount(key, "(a", "(d"); + assertEquals(2L, count, "Should count 2 members (b, c)"); + + jedis.del(key); + } + + @Test + void sortedset_zlexcount_binary() { + byte[] key = UUID.randomUUID().toString().getBytes(); + + jedis.zadd(key, 0.0, "a".getBytes()); + jedis.zadd(key, 0.0, "b".getBytes()); + jedis.zadd(key, 0.0, "c".getBytes()); + + Long count = jedis.zlexcount(key, "[a".getBytes(), "[c".getBytes()); + assertEquals(3L, count); + + jedis.del(key); + } + + @Test + void sortedset_zdiff() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + + jedis.zadd(key1, 1.0, "one"); + jedis.zadd(key1, 2.0, "two"); + jedis.zadd(key1, 3.0, "three"); + + jedis.zadd(key2, 1.0, "one"); + jedis.zadd(key2, 2.0, "two"); + + List diff = jedis.zdiff(key1, key2); + assertEquals(1, diff.size()); + assertEquals("three", diff.get(0)); + + jedis.del(key1, key2); + } + + @Test + void sortedset_zdiffWithScores() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + + jedis.zadd(key1, 1.0, "one"); + jedis.zadd(key1, 2.0, "two"); + jedis.zadd(key1, 3.0, "three"); + + jedis.zadd(key2, 1.0, "one"); + + List diff = jedis.zdiffWithScores(key1, key2); + assertEquals(2, diff.size()); + assertEquals("two", diff.get(0).getElement()); + assertEquals(2.0, diff.get(0).getScore(), 0.001); + assertEquals("three", diff.get(1).getElement()); + assertEquals(3.0, diff.get(1).getScore(), 0.001); + + jedis.del(key1, key2); + } + + @Test + void sortedset_zdiffstore() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String destKey = UUID.randomUUID().toString(); + + jedis.zadd(key1, 1.0, "one"); + jedis.zadd(key1, 2.0, "two"); + jedis.zadd(key2, 1.0, "one"); + + Long count = jedis.zdiffstore(destKey, key1, key2); + assertEquals(1L, count); + + List result = jedis.zrange(destKey, 0, -1); + assertEquals(1, result.size()); + assertEquals("two", result.get(0)); + + jedis.del(key1, key2, destKey); + } + + @Test + void sortedset_zunion() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + + jedis.zadd(key1, 1.0, "one"); + jedis.zadd(key1, 2.0, "two"); + jedis.zadd(key2, 1.0, "one"); + jedis.zadd(key2, 3.0, "three"); + + List union = jedis.zunion(key1, key2); + assertEquals(3, union.size()); + assertTrue(union.contains("one")); + assertTrue(union.contains("two")); + assertTrue(union.contains("three")); + + jedis.del(key1, key2); + } + + @Test + void sortedset_zunionWithScores() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + + jedis.zadd(key1, 1.0, "one"); + jedis.zadd(key1, 2.0, "two"); + jedis.zadd(key2, 1.0, "one"); + jedis.zadd(key2, 3.0, "three"); + + List union = jedis.zunionWithScores(key1, key2); + assertEquals(3, union.size()); + // Results are sorted by score + assertEquals("one", union.get(0).getElement()); + assertEquals(2.0, union.get(0).getScore(), 0.001); // 1.0 + 1.0 + assertEquals("two", union.get(1).getElement()); + assertEquals(2.0, union.get(1).getScore(), 0.001); + assertEquals("three", union.get(2).getElement()); + assertEquals(3.0, union.get(2).getScore(), 0.001); + + jedis.del(key1, key2); + } + + @Test + void sortedset_zinter() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + + jedis.zadd(key1, 1.0, "one"); + jedis.zadd(key1, 2.0, "two"); + jedis.zadd(key2, 1.0, "one"); + jedis.zadd(key2, 3.0, "three"); + + List inter = jedis.zinter(key1, key2); + assertEquals(1, inter.size()); + assertEquals("one", inter.get(0)); + + jedis.del(key1, key2); + } + + @Test + void sortedset_zinterWithScores() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + + jedis.zadd(key1, 1.0, "one"); + jedis.zadd(key1, 2.0, "two"); + jedis.zadd(key2, 1.0, "one"); + jedis.zadd(key2, 3.0, "three"); + + List inter = jedis.zinterWithScores(key1, key2); + assertEquals(1, inter.size()); + assertEquals("one", inter.get(0).getElement()); + assertEquals(2.0, inter.get(0).getScore(), 0.001); // 1.0 + 1.0 + + jedis.del(key1, key2); + } + + @Test + void sortedset_zintercard() { + String minVersion = "7.0.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + + jedis.zadd(key1, 1.0, "one"); + jedis.zadd(key1, 2.0, "two"); + jedis.zadd(key1, 3.0, "three"); + jedis.zadd(key2, 1.0, "one"); + jedis.zadd(key2, 2.0, "two"); + + Long cardinality = jedis.zintercard(key1, key2); + assertEquals(2L, cardinality); + + jedis.del(key1, key2); + } + + @Test + void sortedset_zmpop() { + String minVersion = "7.0.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + + jedis.zadd(key1, 1.0, "one"); + jedis.zadd(key1, 2.0, "two"); + jedis.zadd(key2, 3.0, "three"); + + // Pop min from first non-empty key + redis.clients.jedis.resps.KeyValue> result = + jedis.zmpop(redis.clients.jedis.args.SortedSetOption.MIN, key1.getBytes(), key2.getBytes()); + assertNotNull(result); + assertArrayEquals(key1.getBytes(), result.getKey()); + + jedis.del(key1, key2); + } + + @Test + void sortedset_zremrangebylex() { + String key = UUID.randomUUID().toString(); + + jedis.zadd(key, 0.0, "a"); + jedis.zadd(key, 0.0, "b"); + jedis.zadd(key, 0.0, "c"); + jedis.zadd(key, 0.0, "d"); + + Long removed = jedis.zremrangebylex(key, "[a", "[c"); + assertEquals(3L, removed, "Should remove 3 members (a, b, c)"); + + Long remaining = jedis.zcard(key); + assertEquals(1L, remaining, "Should have 1 member remaining"); + + jedis.del(key); + } + + @Test + void sortedset_zrandmember() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key = UUID.randomUUID().toString(); + + jedis.zadd(key, 1.0, "one"); + jedis.zadd(key, 2.0, "two"); + jedis.zadd(key, 3.0, "three"); + + String member = jedis.zrandmember(key); + assertNotNull(member); + assertTrue(List.of("one", "two", "three").contains(member)); + + jedis.del(key); + } + + @Test + void sortedset_zrandmemberWithCount() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key = UUID.randomUUID().toString(); + + jedis.zadd(key, 1.0, "one"); + jedis.zadd(key, 2.0, "two"); + jedis.zadd(key, 3.0, "three"); + + List members = jedis.zrandmemberWithCount(key, 2); + assertNotNull(members); + assertEquals(2, members.size()); + + jedis.del(key); + } + + @Test + void sortedset_zrandmemberWithCountWithScores() { + String minVersion = "6.2.0"; + assumeTrue( + SERVER_VERSION.isGreaterThanOrEqualTo(minVersion), + "Valkey version required >= " + minVersion); + + String key = UUID.randomUUID().toString(); + + jedis.zadd(key, 1.0, "one"); + jedis.zadd(key, 2.0, "two"); + jedis.zadd(key, 3.0, "three"); + + List membersWithScores = jedis.zrandmemberWithCountWithScores(key, 2); + assertNotNull(membersWithScores); + assertEquals(2, membersWithScores.size()); + + // Each tuple should have member and score + for (Tuple tuple : membersWithScores) { + assertNotNull(tuple.getElement()); // member + assertNotNull(tuple.getScore()); // score } - return sb.toString(); + + jedis.del(key); } } diff --git a/java/integTest/src/test/java/compatibility/jedis/SortedSetCommandsTest.java b/java/integTest/src/test/java/compatibility/jedis/SortedSetCommandsTest.java new file mode 100644 index 00000000000..63e574a07dd --- /dev/null +++ b/java/integTest/src/test/java/compatibility/jedis/SortedSetCommandsTest.java @@ -0,0 +1,465 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package compatibility.jedis; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; +import org.junit.jupiter.api.*; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.resps.ScanResult; +import redis.clients.jedis.resps.Tuple; + +/** + * Integration tests for Jedis sorted set commands. These tests require a running Valkey/Redis + * instance. + */ +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class SortedSetCommandsTest { + + private static Jedis jedis; + private static final String TEST_KEY = "test:sortedset"; + private static final String TEST_KEY_2 = "test:sortedset2"; + private static final String DEST_KEY = "test:sortedset:dest"; + + @BeforeAll + public static void setUp() { + // These tests require a running Valkey/Redis instance + // They will be skipped if no instance is available + try { + jedis = new Jedis("localhost", 6379); + jedis.ping(); + } catch (Exception e) { + // Connection failed - tests will be skipped + jedis = null; + } + } + + @AfterAll + public static void tearDown() { + if (jedis != null) { + try { + jedis.close(); + } catch (Exception e) { + // Ignore cleanup errors + } + } + } + + @BeforeEach + public void cleanUp() { + if (jedis != null) { + try { + jedis.del(TEST_KEY); + jedis.del(TEST_KEY_2); + jedis.del(DEST_KEY); + } catch (Exception e) { + // Ignore cleanup errors + } + } + } + + @Test + @Order(1) + public void testZaddSingleMember() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + long result = jedis.zadd(TEST_KEY, 1.0, "member1"); + assertEquals(1L, result); + + Double score = jedis.zscore(TEST_KEY, "member1"); + assertEquals(1.0, score); + } + + @Test + @Order(2) + public void testZaddMultipleMembers() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + + long result = jedis.zadd(TEST_KEY, members); + assertEquals(3L, result); + + long cardinality = jedis.zcard(TEST_KEY); + assertEquals(3L, cardinality); + } + + @Test + @Order(3) + public void testZaddIncr() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + jedis.zadd(TEST_KEY, 1.0, "member1"); + + double newScore = jedis.zaddIncr(TEST_KEY, 2.5, "member1"); + assertEquals(3.5, newScore, 0.001); + + Double score = jedis.zscore(TEST_KEY, "member1"); + assertEquals(3.5, score, 0.001); + } + + @Test + @Order(4) + public void testZrem() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(TEST_KEY, members); + + long removed = jedis.zrem(TEST_KEY, "member1", "member2"); + assertEquals(2L, removed); + + long cardinality = jedis.zcard(TEST_KEY); + assertEquals(1L, cardinality); + } + + @Test + @Order(5) + public void testZcard() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + long emptyCard = jedis.zcard(TEST_KEY); + assertEquals(0L, emptyCard); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + jedis.zadd(TEST_KEY, members); + + long cardinality = jedis.zcard(TEST_KEY); + assertEquals(2L, cardinality); + } + + @Test + @Order(6) + public void testZscore() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + jedis.zadd(TEST_KEY, 5.5, "member1"); + + Double score = jedis.zscore(TEST_KEY, "member1"); + assertEquals(5.5, score, 0.001); + + Double nonExistent = jedis.zscore(TEST_KEY, "nonexistent"); + assertNull(nonExistent); + } + + @Test + @Order(7) + public void testZmscore() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(TEST_KEY, members); + + List scores = jedis.zmscore(TEST_KEY, "member1", "nonexistent", "member3"); + assertEquals(3, scores.size()); + assertEquals(1.0, scores.get(0), 0.001); + assertNull(scores.get(1)); + assertEquals(3.0, scores.get(2), 0.001); + } + + @Test + @Order(8) + public void testZrange() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(TEST_KEY, members); + + List range = jedis.zrange(TEST_KEY, 0, -1); + assertEquals(3, range.size()); + assertEquals("member1", range.get(0)); + assertEquals("member2", range.get(1)); + assertEquals("member3", range.get(2)); + + List partialRange = jedis.zrange(TEST_KEY, 0, 1); + assertEquals(2, partialRange.size()); + } + + @Test + @Order(9) + public void testZrangeWithScores() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(TEST_KEY, members); + + List rangeWithScores = jedis.zrangeWithScores(TEST_KEY, 0, -1); + assertEquals(3, rangeWithScores.size()); + assertEquals("member1", rangeWithScores.get(0).getElement()); + assertEquals(1.0, rangeWithScores.get(0).getScore(), 0.001); + assertEquals("member2", rangeWithScores.get(1).getElement()); + assertEquals(2.0, rangeWithScores.get(1).getScore(), 0.001); + assertEquals("member3", rangeWithScores.get(2).getElement()); + assertEquals(3.0, rangeWithScores.get(2).getScore(), 0.001); + } + + @Test + @Order(10) + public void testZrank() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(TEST_KEY, members); + + Long rank = jedis.zrank(TEST_KEY, "member2"); + assertEquals(1L, rank); + + Long nonExistent = jedis.zrank(TEST_KEY, "nonexistent"); + assertNull(nonExistent); + } + + @Test + @Order(11) + public void testZrevrank() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(TEST_KEY, members); + + Long revrank = jedis.zrevrank(TEST_KEY, "member2"); + assertEquals(1L, revrank); + + Long nonExistent = jedis.zrevrank(TEST_KEY, "nonexistent"); + assertNull(nonExistent); + } + + @Test + @Order(12) + public void testZcount() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + members.put("member4", 4.0); + jedis.zadd(TEST_KEY, members); + + long count = jedis.zcount(TEST_KEY, 1.5, 3.5); + assertEquals(2L, count); + + long allCount = jedis.zcount(TEST_KEY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); + assertEquals(4L, allCount); + } + + @Test + @Order(13) + public void testZincrby() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + jedis.zadd(TEST_KEY, 1.0, "member1"); + + double newScore = jedis.zincrby(TEST_KEY, 2.5, "member1"); + assertEquals(3.5, newScore, 0.001); + + // Test incrementing non-existent member + double newMemberScore = jedis.zincrby(TEST_KEY, 5.0, "member2"); + assertEquals(5.0, newMemberScore, 0.001); + } + + @Test + @Order(14) + public void testZpopmin() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(TEST_KEY, members); + + List popped = jedis.zpopmin(TEST_KEY, 2); + assertEquals(2, popped.size()); + assertEquals("member1", popped.get(0).getElement()); + assertEquals(1.0, popped.get(0).getScore(), 0.001); + assertEquals("member2", popped.get(1).getElement()); + assertEquals(2.0, popped.get(1).getScore(), 0.001); + + long remaining = jedis.zcard(TEST_KEY); + assertEquals(1L, remaining); + } + + @Test + @Order(15) + public void testZpopmax() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(TEST_KEY, members); + + List popped = jedis.zpopmax(TEST_KEY, 2); + assertEquals(2, popped.size()); + assertEquals("member3", popped.get(0).getElement()); + assertEquals(3.0, popped.get(0).getScore(), 0.001); + assertEquals("member2", popped.get(1).getElement()); + assertEquals(2.0, popped.get(1).getScore(), 0.001); + + long remaining = jedis.zcard(TEST_KEY); + assertEquals(1L, remaining); + } + + @Test + @Order(16) + public void testZunionstore() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members1 = new HashMap<>(); + members1.put("member1", 1.0); + members1.put("member2", 2.0); + jedis.zadd(TEST_KEY, members1); + + Map members2 = new HashMap<>(); + members2.put("member2", 3.0); + members2.put("member3", 4.0); + jedis.zadd(TEST_KEY_2, members2); + + long result = jedis.zunionstore(DEST_KEY, TEST_KEY, TEST_KEY_2); + assertEquals(3L, result); + + long cardinality = jedis.zcard(DEST_KEY); + assertEquals(3L, cardinality); + } + + @Test + @Order(17) + public void testZinterstore() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members1 = new HashMap<>(); + members1.put("member1", 1.0); + members1.put("member2", 2.0); + jedis.zadd(TEST_KEY, members1); + + Map members2 = new HashMap<>(); + members2.put("member2", 3.0); + members2.put("member3", 4.0); + jedis.zadd(TEST_KEY_2, members2); + + long result = jedis.zinterstore(DEST_KEY, TEST_KEY, TEST_KEY_2); + assertEquals(1L, result); + + long cardinality = jedis.zcard(DEST_KEY); + assertEquals(1L, cardinality); + + Double score = jedis.zscore(DEST_KEY, "member2"); + assertNotNull(score); + } + + @Test + @Order(18) + public void testZremrangebyrank() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + members.put("member4", 4.0); + jedis.zadd(TEST_KEY, members); + + long removed = jedis.zremrangebyrank(TEST_KEY, 0, 1); + assertEquals(2L, removed); + + long remaining = jedis.zcard(TEST_KEY); + assertEquals(2L, remaining); + } + + @Test + @Order(19) + public void testZremrangebyscore() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + members.put("member4", 4.0); + jedis.zadd(TEST_KEY, members); + + long removed = jedis.zremrangebyscore(TEST_KEY, 1.5, 3.5); + assertEquals(2L, removed); + + long remaining = jedis.zcard(TEST_KEY); + assertEquals(2L, remaining); + } + + @Test + @Order(20) + public void testZscan() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + Map members = new HashMap<>(); + members.put("member1", 1.0); + members.put("member2", 2.0); + members.put("member3", 3.0); + jedis.zadd(TEST_KEY, members); + + ScanResult result = jedis.zscan(TEST_KEY, "0"); + assertNotNull(result); + assertNotNull(result.getCursor()); + assertNotNull(result.getResult()); + assertEquals(3, result.getResult().size()); + } + + @Test + @Order(21) + public void testBinaryZadd() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + byte[] key = "test:binary:sortedset".getBytes(); + byte[] member = "member1".getBytes(); + + long result = jedis.zadd(key, 1.0, member); + assertEquals(1L, result); + + Double score = jedis.zscore(key, member); + assertEquals(1.0, score); + + // Cleanup + jedis.del(key); + } + + @Test + @Order(22) + public void testBinaryZrange() { + Assumptions.assumeTrue(jedis != null, "Jedis connection not available"); + + byte[] key = "test:binary:sortedset".getBytes(); + Map members = new HashMap<>(); + members.put("member1".getBytes(), 1.0); + members.put("member2".getBytes(), 2.0); + jedis.zadd(key, members); + + List range = jedis.zrange(key, 0, -1); + assertEquals(2, range.size()); + + // Cleanup + jedis.del(key); + } +} diff --git a/java/jedis-compatibility/compatibility-layer-migration-guide.md b/java/jedis-compatibility/compatibility-layer-migration-guide.md index 0b800bb23b4..1821bf5871e 100644 --- a/java/jedis-compatibility/compatibility-layer-migration-guide.md +++ b/java/jedis-compatibility/compatibility-layer-migration-guide.md @@ -70,7 +70,6 @@ blockingSocketTimeoutMillis - ⚠️ Sorted set operations (ZADD, ZREM, ZRANGE) - **Available via `sendCommand()` only** - ✅ Key operations (DEL, EXISTS, EXPIRE, TTL) - ✅ Connection commands (PING, SELECT) -- ✅ ACL commands (ACL LIST, ACL GETUSER, ACL SETUSER, ACL DELUSER, ACL CAT, ACL GENPASS, ACL LOG, ACL LOG RESET, ACL WHOAMI, ACL USERS, ACL SAVE, ACL LOAD, ACL DRYRUN) - ✅ Generic commands via `sendCommand()` (Protocol.Command types only) ### Client Types @@ -100,7 +99,6 @@ blockingSocketTimeoutMillis - **Pub/Sub**: Redis publish/subscribe not implemented - **Lua scripting**: EVAL/EVALSHA commands not supported - **Modules**: Redis module commands not available -- **Typed sorted set methods**: No dedicated methods like `zadd()`, `zrem()` - use `sendCommand()` instead ### Configuration Limitations - **Complex SSL configurations**: Jedis `JedisClientConfig` SSL parameters cannot be mapped to Valkey GLIDE `GlideClientConfiguration` diff --git a/java/jedis-compatibility/src/main/java/redis/clients/jedis/Jedis.java b/java/jedis-compatibility/src/main/java/redis/clients/jedis/Jedis.java index 21c0e43b0f1..e59782a3dae 100644 --- a/java/jedis-compatibility/src/main/java/redis/clients/jedis/Jedis.java +++ b/java/jedis-compatibility/src/main/java/redis/clients/jedis/Jedis.java @@ -7,9 +7,20 @@ import glide.api.models.commands.GetExOptions; import glide.api.models.commands.LInsertOptions.InsertPosition; import glide.api.models.commands.LPosOptions; +import glide.api.models.commands.RangeOptions; +import glide.api.models.commands.RangeOptions.InfLexBound; +import glide.api.models.commands.RangeOptions.LexBoundary; +import glide.api.models.commands.RangeOptions.LexRange; +import glide.api.models.commands.RangeOptions.RangeByIndex; +import glide.api.models.commands.RangeOptions.RangeQuery; +import glide.api.models.commands.ScoreFilter; import glide.api.models.commands.SetOptions; import glide.api.models.commands.SortBaseOptions; import glide.api.models.commands.SortOptions; +import glide.api.models.commands.WeightAggregateOptions; +import glide.api.models.commands.WeightAggregateOptions.KeyArray; +import glide.api.models.commands.WeightAggregateOptions.KeyArrayBinary; +import glide.api.models.commands.ZAddOptions; import glide.api.models.commands.bitmap.BitFieldOptions.BitFieldGet; import glide.api.models.commands.bitmap.BitFieldOptions.BitFieldIncrby; import glide.api.models.commands.bitmap.BitFieldOptions.BitFieldOverflow; @@ -43,6 +54,7 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Logger; +import java.util.stream.Collectors; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocketFactory; @@ -51,6 +63,7 @@ import redis.clients.jedis.args.ExpiryOption; import redis.clients.jedis.args.ListDirection; import redis.clients.jedis.args.ListPosition; +import redis.clients.jedis.args.SortedSetOption; import redis.clients.jedis.commands.ProtocolCommand; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisException; @@ -61,9 +74,10 @@ import redis.clients.jedis.params.LPosParams; import redis.clients.jedis.params.ScanParams; import redis.clients.jedis.params.SetParams; -import redis.clients.jedis.resps.AccessControlLogEntry; -import redis.clients.jedis.resps.AccessControlUser; +import redis.clients.jedis.params.ZAddParams; +import redis.clients.jedis.params.ZIncrByParams; import redis.clients.jedis.resps.ScanResult; +import redis.clients.jedis.resps.Tuple; import redis.clients.jedis.util.KeyValue; import redis.clients.jedis.util.Pool; @@ -665,307 +679,6 @@ public String auth(String user, String password) { return "OK"; } - /** - * Return the list of ACL rules in ACL configuration file format. - * - * @return list of user rule definitions - */ - public List aclList() { - return executeCommandWithGlide( - "ACL", - () -> { - String[] result = glideClient.aclList().get(); - return result == null ? Collections.emptyList() : Arrays.asList(result); - }); - } - - /** - * Return the ACL rules defined for the given user. - * - * @param name the username - * @return the user's ACL rules, or null if the user does not exist - */ - public AccessControlUser aclGetUser(String name) { - return executeCommandWithGlide( - "ACL", - () -> { - Object result = glideClient.aclGetUser(name).get(); - if (result == null) { - return null; - } - return parseAclGetUserResponse(result); - }); - } - - /** - * Create or modify an ACL user with default (no) rules. - * - * @param name the username - * @return "OK" if successful - */ - public String aclSetUser(String name) { - return executeCommandWithGlide("ACL", () -> glideClient.aclSetUser(name, new String[0]).get()); - } - - /** - * Create or modify an ACL user with the given rules. - * - * @param name the username - * @param rules the ACL rule strings (e.g. "on", "+@all", "~*") - * @return "OK" if successful - */ - public String aclSetUser(String name, String... rules) { - return executeCommandWithGlide("ACL", () -> glideClient.aclSetUser(name, rules).get()); - } - - /** - * Delete the specified ACL users and terminate their connections. - * - * @param usernames the usernames to delete - * @return the number of users deleted - */ - public long aclDelUser(String... usernames) { - return executeCommandWithGlide( - "ACL", - () -> { - Long result = glideClient.aclDelUser(usernames).get(); - return result != null ? result : 0L; - }); - } - - /** - * Return the list of ACL categories. - * - * @return list of category names - */ - public List aclCat() { - return executeCommandWithGlide( - "ACL", - () -> { - String[] result = glideClient.aclCat().get(); - return result == null ? Collections.emptyList() : Arrays.asList(result); - }); - } - - /** - * Return the list of commands in the given ACL category. - * - * @param category the category name (e.g. "string", "list") - * @return list of command names in the category - */ - public List aclCat(String category) { - return executeCommandWithGlide( - "ACL", - () -> { - String[] result = glideClient.aclCat(category).get(); - return result == null ? Collections.emptyList() : Arrays.asList(result); - }); - } - - /** - * Generate a random password for ACL users (default bit length). - * - * @return the generated password string - */ - public String aclGenPass() { - return executeCommandWithGlide("ACL", () -> glideClient.aclGenPass().get()); - } - - /** - * Generate a random password with the specified number of bits for ACL users. - * - * @param bits the number of bits (e.g. 256 for default) - * @return the generated password string - */ - public String aclGenPass(int bits) { - return executeCommandWithGlide("ACL", () -> glideClient.aclGenPass(bits).get()); - } - - /** - * Return recent ACL security events (failed auth, violated rules). - * - * @return list of ACL log entries - */ - public List aclLog() { - return executeCommandWithGlide("ACL", () -> parseAclLogResponse(glideClient.aclLog().get())); - } - - /** - * Return the specified number of recent ACL security events. - * - * @param count the maximum number of entries to return - * @return list of ACL log entries - */ - public List aclLog(int count) { - return executeCommandWithGlide( - "ACL", () -> parseAclLogResponse(glideClient.aclLog(count).get())); - } - - /** - * Clear the ACL security events log. - * - * @return "OK" if successful - */ - public String aclLogReset() { - return executeCommandWithGlide( - "ACL", - () -> { - Object result = glideClient.customCommand(new String[] {"ACL", "LOG", "RESET"}).get(); - return result != null ? result.toString() : null; - }); - } - - /** - * Return the username the current connection is authenticated as. - * - * @return the username (e.g. "default") - */ - public String aclWhoAmI() { - return executeCommandWithGlide("ACL", () -> glideClient.aclWhoami().get()); - } - - /** - * Return the list of ACL usernames. - * - * @return list of usernames - */ - public List aclUsers() { - return executeCommandWithGlide( - "ACL", - () -> { - String[] result = glideClient.aclUsers().get(); - return result == null ? Collections.emptyList() : Arrays.asList(result); - }); - } - - /** - * Save the current ACL rules to the configured ACL file. - * - * @return "OK" if successful - */ - public String aclSave() { - return executeCommandWithGlide("ACL", () -> glideClient.aclSave().get()); - } - - /** - * Reload ACL rules from the configured ACL file. - * - * @return "OK" if successful - */ - public String aclLoad() { - return executeCommandWithGlide("ACL", () -> glideClient.aclLoad().get()); - } - - /** - * Simulate execution of a command by a user without executing it. - * - * @param username the username to simulate - * @param command the command name - * @param args the command arguments - * @return "OK" if the user could execute the command, otherwise an error string - */ - public String aclDryRun(String username, String command, String... args) { - return executeCommandWithGlide( - "ACL", () -> glideClient.aclDryRun(username, command, args).get()); - } - - private static AccessControlUser parseAclGetUserResponse(Object result) { - Object[] arr = (Object[]) result; - AccessControlUser user = new AccessControlUser(); - for (int i = 0; i + 1 < arr.length; i += 2) { - String field = arr[i] != null ? arr[i].toString() : null; - Object value = arr[i + 1]; - if (field == null) { - continue; - } - switch (field) { - case "flags": - if (value instanceof Object[]) { - for (Object f : (Object[]) value) { - if (f != null) user.addFlag(f.toString()); - } - } else if (value != null) { - user.addFlag(value.toString()); - } - break; - case "passwords": - if (value instanceof Object[]) { - for (Object p : (Object[]) value) { - if (p != null) user.addPassword(p.toString()); - } - } else if (value != null) { - user.addPassword(value.toString()); - } - break; - case "commands": - if (value != null) user.setCommands(value.toString()); - break; - case "keys": - if (value instanceof Object[]) { - for (Object k : (Object[]) value) { - if (k != null) user.addKey(k.toString()); - } - } else if (value != null) { - user.addKey(value.toString()); - } - break; - case "channels": - if (value instanceof Object[]) { - for (Object c : (Object[]) value) { - if (c != null) user.addChannel(c.toString()); - } - } else if (value != null) { - user.addChannel(value.toString()); - } - break; - default: - break; - } - } - return user; - } - - private static List parseAclLogResponse(Object result) { - if (result == null) { - return Collections.emptyList(); - } - Object[] entries = (Object[]) result; - List list = new ArrayList<>(entries.length); - for (Object entryObj : entries) { - Map map = flatArrayToMap(entryObj); - map.putIfAbsent(AccessControlLogEntry.ENTRY_ID, 0L); - map.putIfAbsent(AccessControlLogEntry.TIMESTAMP_CREATED, 0L); - map.putIfAbsent(AccessControlLogEntry.TIMESTAMP_LAST_UPDATED, 0L); - Object clientInfo = map.get(AccessControlLogEntry.CLIENT_INFO); - if (clientInfo == null) { - map.put(AccessControlLogEntry.CLIENT_INFO, ""); - } - list.add(new AccessControlLogEntry(map)); - } - return list; - } - - private static Map flatArrayToMap(Object entryObj) { - Map map = new HashMap<>(); - if (!(entryObj instanceof Object[])) { - return map; - } - Object[] pairs = (Object[]) entryObj; - for (int i = 0; i + 1 < pairs.length; i += 2) { - String key = pairs[i] != null ? pairs[i].toString() : null; - Object value = pairs[i + 1]; - if (key == null) { - continue; - } - if (value instanceof Number) { - map.put(key, ((Number) value).longValue()); - } else { - map.put(key, value != null ? value.toString() : null); - } - } - return map; - } - /** * Check if the connection is closed. * @@ -7299,159 +7012,2051 @@ public byte[] brpoplpush(final byte[] source, final byte[] destination, int time return blmove(source, destination, ListDirection.RIGHT, ListDirection.LEFT, timeout); } + // ======================================== + // Sorted Set Commands + // ======================================== + /** - * Adds the specified members to the set stored at key. + * Adds one or more members to a sorted set, or updates the score if it already exists. * - * @param key the key of the set - * @param members the members to add - * @return the number of elements that were added to the set + * @param key the key of the sorted set + * @param score the score of the member + * @param member the member to add + * @return the number of elements added to the sorted set + * @see valkey.io + * @since Valkey 1.2.0 */ - public long sadd(String key, String... members) { - return executeCommandWithGlide("SADD", () -> glideClient.sadd(key, members).get()); + public long zadd(String key, double score, String member) { + return executeCommandWithGlide( + "ZADD", () -> glideClient.zadd(key, Map.of(member, score)).get()); } /** - * Adds the specified members to the set stored at key (binary version). + * Adds one or more members to a sorted set, or updates the score if it already exists (binary + * version). * - * @param key the key of the set - * @param members the members to add - * @return the number of elements that were added to the set + * @param key the key of the sorted set + * @param score the score of the member + * @param member the member to add + * @return the number of elements added to the sorted set */ - public long sadd(final byte[] key, final byte[]... members) { + public long zadd(final byte[] key, double score, final byte[] member) { return executeCommandWithGlide( - "SADD", - () -> { - GlideString[] glideMembers = convertToGlideStringArray(members); - return glideClient.sadd(GlideString.of(key), glideMembers).get(); - }); + "ZADD", + () -> glideClient.zadd(GlideString.of(key), Map.of(GlideString.of(member), score)).get()); } /** - * Removes the specified members from the set stored at key. + * Adds multiple members to a sorted set, or updates the score if they already exist. * - * @param key the key of the set - * @param members the members to remove - * @return the number of elements that were removed from the set + * @param key the key of the sorted set + * @param scoreMembers a map of members to their scores + * @return the number of elements added to the sorted set + * @see valkey.io + * @since Valkey 1.2.0 */ - public long srem(String key, String... members) { - return executeCommandWithGlide("SREM", () -> glideClient.srem(key, members).get()); + public long zadd(String key, Map scoreMembers) { + return executeCommandWithGlide("ZADD", () -> glideClient.zadd(key, scoreMembers).get()); } /** - * Removes the specified members from the set stored at key (binary version). + * Adds multiple members to a sorted set, or updates the score if they already exist (binary + * version). * - * @param key the key of the set - * @param members the members to remove - * @return the number of elements that were removed from the set + * @param key the key of the sorted set + * @param scoreMembers a map of members to their scores + * @return the number of elements added to the sorted set */ - public long srem(final byte[] key, final byte[]... members) { + public long zadd(final byte[] key, Map scoreMembers) { return executeCommandWithGlide( - "SREM", + "ZADD", () -> { - GlideString[] glideMembers = convertToGlideStringArray(members); - return glideClient.srem(GlideString.of(key), glideMembers).get(); + Map glideMap = new HashMap<>(); + for (Map.Entry entry : scoreMembers.entrySet()) { + glideMap.put(GlideString.of(entry.getKey()), entry.getValue()); + } + return glideClient.zadd(GlideString.of(key), glideMap).get(); }); } /** - * Returns all the members of the set value stored at key. + * Adds multiple members to a sorted set with additional options, or updates the score if they + * already exist. * - * @param key the key of the set - * @return all members of the set, or an empty set when key does not exist + * @param key the key of the sorted set + * @param scoreMembers a map of members to their scores + * @param params additional options for the ZADD command + * @return the number of elements added or updated, depending on the CH option + * @see valkey.io + * @since Valkey 3.0.2 */ - public Set smembers(String key) { - return executeCommandWithGlide("SMEMBERS", () -> glideClient.smembers(key).get()); + public long zadd(String key, Map scoreMembers, ZAddParams params) { + return executeCommandWithGlide( + "ZADD", + () -> { + ZAddOptions options = convertZAddParams(params); + boolean changed = params.getCh() != null && params.getCh(); + return glideClient.zadd(key, scoreMembers, options, changed).get(); + }); } /** - * Returns all the members of the set value stored at key (binary version). + * Adds multiple members to a sorted set with additional options, or updates the score if they + * already exist (binary version). * - * @param key the key of the set - * @return all members of the set, or an empty set when key does not exist + * @param key the key of the sorted set + * @param scoreMembers a map of members to their scores + * @param params additional options for the ZADD command + * @return the number of elements added or updated, depending on the CH option */ - public Set smembers(final byte[] key) { + public long zadd(final byte[] key, Map scoreMembers, ZAddParams params) { return executeCommandWithGlide( - "SMEMBERS", + "ZADD", () -> { - Set result = glideClient.smembers(GlideString.of(key)).get(); - return convertGlideStringsToByteArraySet(result); + Map glideMap = new HashMap<>(); + for (Map.Entry entry : scoreMembers.entrySet()) { + glideMap.put(GlideString.of(entry.getKey()), entry.getValue()); + } + ZAddOptions options = convertZAddParams(params); + boolean changed = params.getCh() != null && params.getCh(); + return glideClient.zadd(GlideString.of(key), glideMap, options, changed).get(); }); } /** - * Returns the set cardinality (number of elements) of the set stored at key. + * Increments the score of a member in a sorted set. If the member does not exist, it is added + * with the increment as its score. * - * @param key the key of the set - * @return the cardinality of the set, or 0 if key does not exist + * @param key the key of the sorted set + * @param increment the amount to increment the score by + * @param member the member whose score to increment + * @return the new score of the member + * @see valkey.io + * @since Valkey 1.2.0 */ - public long scard(String key) { - return executeCommandWithGlide("SCARD", () -> glideClient.scard(key).get()); + public double zaddIncr(String key, double increment, String member) { + return executeCommandWithGlide( + "ZADD", () -> glideClient.zaddIncr(key, member, increment).get()); } /** - * Returns the set cardinality (number of elements) of the set stored at key (binary version). + * Increments the score of a member in a sorted set (binary version). If the member does not + * exist, it is added with the increment as its score. * - * @param key the key of the set - * @return the cardinality of the set, or 0 if key does not exist + * @param key the key of the sorted set + * @param increment the amount to increment the score by + * @param member the member whose score to increment + * @return the new score of the member */ - public long scard(final byte[] key) { - return executeCommandWithGlide("SCARD", () -> glideClient.scard(GlideString.of(key)).get()); + public double zaddIncr(final byte[] key, double increment, final byte[] member) { + return executeCommandWithGlide( + "ZADD", + () -> glideClient.zaddIncr(GlideString.of(key), GlideString.of(member), increment).get()); } /** - * Returns if member is a member of the set stored at key. + * Removes one or more members from a sorted set. * - * @param key the key of the set - * @param member the member to check - * @return true if the element is a member of the set, false otherwise + * @param key the key of the sorted set + * @param members the members to remove + * @return the number of members removed from the sorted set + * @see valkey.io + * @since Valkey 1.2.0 */ - public boolean sismember(String key, String member) { - return executeCommandWithGlide("SISMEMBER", () -> glideClient.sismember(key, member).get()); + public long zrem(String key, String... members) { + return executeCommandWithGlide("ZREM", () -> glideClient.zrem(key, members).get()); } /** - * Returns if member is a member of the set stored at key (binary version). + * Removes one or more members from a sorted set (binary version). * - * @param key the key of the set - * @param member the member to check - * @return true if the element is a member of the set, false otherwise + * @param key the key of the sorted set + * @param members the members to remove + * @return the number of members removed from the sorted set */ - public boolean sismember(final byte[] key, final byte[] member) { + public long zrem(final byte[] key, final byte[]... members) { return executeCommandWithGlide( - "SISMEMBER", - () -> glideClient.sismember(GlideString.of(key), GlideString.of(member)).get()); + "ZREM", + () -> { + GlideString[] glideMembers = convertToGlideStringArray(members); + return glideClient.zrem(GlideString.of(key), glideMembers).get(); + }); } /** - * Returns whether each member is a member of the set stored at key. + * Adds the specified members to the set stored at key. * * @param key the key of the set - * @param members the members to check - * @return list of Boolean values, one for each member + * @param members the members to add + * @return the number of elements that were added to the set */ - public List smismember(String key, String... members) { - return executeCommandWithGlide( - "SMISMEMBER", - () -> { - Boolean[] result = glideClient.smismember(key, members).get(); - return result != null ? Arrays.asList(result) : Collections.emptyList(); - }); + public long sadd(String key, String... members) { + return executeCommandWithGlide("SADD", () -> glideClient.sadd(key, members).get()); } /** - * Returns whether each member is a member of the set stored at key (binary version). + * Adds the specified members to the set stored at key (binary version). * * @param key the key of the set - * @param members the members to check - * @return list of Boolean values, one for each member + * @param members the members to add + * @return the number of elements that were added to the set */ - public List smismember(final byte[] key, final byte[]... members) { + public long sadd(final byte[] key, final byte[]... members) { + return executeCommandWithGlide( + "SADD", + () -> { + GlideString[] glideMembers = convertToGlideStringArray(members); + return glideClient.sadd(GlideString.of(key), glideMembers).get(); + }); + } + + /** + * Returns the number of members in a sorted set. + * + * @param key the key of the sorted set + * @return the cardinality (number of elements) of the sorted set, or 0 if key does not exist + * @see valkey.io + * @since Valkey 1.2.0 + */ + public long zcard(String key) { + return executeCommandWithGlide("ZCARD", () -> glideClient.zcard(key).get()); + } + + /** + * Returns the number of members in a sorted set (binary version). + * + * @param key the key of the sorted set + * @return the cardinality (number of elements) of the sorted set, or 0 if key does not exist + */ + public long zcard(final byte[] key) { + return executeCommandWithGlide("ZCARD", () -> glideClient.zcard(GlideString.of(key)).get()); + } + + /** + * Returns the score of a member in a sorted set. + * + * @param key the key of the sorted set + * @param member the member whose score to return + * @return the score of the member, or null if the member does not exist + * @see valkey.io + * @since Valkey 1.2.0 + */ + public Double zscore(String key, String member) { + return executeCommandWithGlide("ZSCORE", () -> glideClient.zscore(key, member).get()); + } + + /** + * Returns the score of a member in a sorted set (binary version). + * + * @param key the key of the sorted set + * @param member the member whose score to return + * @return the score of the member, or null if the member does not exist + */ + public Double zscore(final byte[] key, final byte[] member) { + return executeCommandWithGlide( + "ZSCORE", () -> glideClient.zscore(GlideString.of(key), GlideString.of(member)).get()); + } + + /** + * Returns the scores of multiple members in a sorted set. + * + * @param key the key of the sorted set + * @param members the members whose scores to return + * @return an array of scores corresponding to the members, with null for non-existing members + * @see valkey.io + * @since Valkey 6.2.0 + */ + public List zmscore(String key, String... members) { + return executeCommandWithGlide( + "ZMSCORE", () -> Arrays.asList(glideClient.zmscore(key, members).get())); + } + + /** + * Returns the scores of multiple members in a sorted set (binary version). + * + * @param key the key of the sorted set + * @param members the members whose scores to return + * @return an array of scores corresponding to the members, with null for non-existing members + */ + public List zmscore(final byte[] key, final byte[]... members) { + return executeCommandWithGlide( + "ZMSCORE", + () -> { + GlideString[] glideMembers = convertToGlideStringArray(members); + return Arrays.asList(glideClient.zmscore(GlideString.of(key), glideMembers).get()); + }); + } + + /** + * Removes the specified members from the set stored at key. + * + * @param key the key of the set + * @param members the members to remove + * @return the number of elements that were removed from the set + */ + public long srem(String key, String... members) { + return executeCommandWithGlide("SREM", () -> glideClient.srem(key, members).get()); + } + + /** + * Removes the specified members from the set stored at key (binary version). + * + * @param key the key of the set + * @param members the members to remove + * @return the number of elements that were removed from the set + */ + public long srem(final byte[] key, final byte[]... members) { + return executeCommandWithGlide( + "SREM", + () -> { + GlideString[] glideMembers = convertToGlideStringArray(members); + return glideClient.srem(GlideString.of(key), glideMembers).get(); + }); + } + + /** + * Returns the specified range of elements in a sorted set, by index. + * + * @param key the key of the sorted set + * @param start the starting index (0-based, can be negative to indicate offset from end) + * @param stop the ending index (0-based, can be negative to indicate offset from end) + * @return an array of elements in the specified range + * @see valkey.io + * @since Valkey 1.2.0 + */ + public List zrange(String key, long start, long stop) { + return executeCommandWithGlide( + "ZRANGE", + () -> { + RangeOptions.RangeByIndex rangeQuery = new RangeOptions.RangeByIndex(start, stop); + return Arrays.asList(glideClient.zrange(key, rangeQuery).get()); + }); + } + + /** + * Returns all the members of the set value stored at key. + * + * @param key the key of the set + * @return all members of the set, or an empty set when key does not exist + */ + public Set smembers(String key) { + return executeCommandWithGlide("SMEMBERS", () -> glideClient.smembers(key).get()); + } + + /** + * Returns all the members of the set value stored at key (binary version). + * + * @param key the key of the set + * @return all members of the set, or an empty set when key does not exist + */ + public Set smembers(final byte[] key) { + return executeCommandWithGlide( + "SMEMBERS", + () -> { + Set result = glideClient.smembers(GlideString.of(key)).get(); + return convertGlideStringsToByteArraySet(result); + }); + } + + /** + * Returns the specified range of elements in a sorted set, by index (binary version). + * + * @param key the key of the sorted set + * @param start the starting index (0-based, can be negative to indicate offset from end) + * @param stop the ending index (0-based, can be negative to indicate offset from end) + * @return an array of elements in the specified range + */ + public List zrange(final byte[] key, long start, long stop) { + return executeCommandWithGlide( + "ZRANGE", + () -> { + RangeOptions.RangeByIndex rangeQuery = new RangeOptions.RangeByIndex(start, stop); + GlideString[] result = glideClient.zrange(GlideString.of(key), rangeQuery).get(); + return Arrays.stream(result).map(GlideString::getBytes).collect(Collectors.toList()); + }); + } + + /** + * Returns the set cardinality (number of elements) of the set stored at key. + * + * @param key the key of the set + * @return the cardinality of the set, or 0 if key does not exist + */ + public long scard(String key) { + return executeCommandWithGlide("SCARD", () -> glideClient.scard(key).get()); + } + + /** + * Returns the set cardinality (number of elements) of the set stored at key (binary version). + * + * @param key the key of the set + * @return the cardinality of the set, or 0 if key does not exist + */ + public long scard(final byte[] key) { + return executeCommandWithGlide("SCARD", () -> glideClient.scard(GlideString.of(key)).get()); + } + + /** + * Returns if member is a member of the set stored at key. + * + * @param key the key of the set + * @param member the member to check + * @return true if the element is a member of the set, false otherwise + */ + public boolean sismember(String key, String member) { + return executeCommandWithGlide("SISMEMBER", () -> glideClient.sismember(key, member).get()); + } + + /** + * Returns if member is a member of the set stored at key (binary version). + * + * @param key the key of the set + * @param member the member to check + * @return true if the element is a member of the set, false otherwise + */ + public boolean sismember(final byte[] key, final byte[] member) { + return executeCommandWithGlide( + "SISMEMBER", + () -> glideClient.sismember(GlideString.of(key), GlideString.of(member)).get()); + } + + /** + * Returns whether each member is a member of the set stored at key. + * + * @param key the key of the set + * @param members the members to check + * @return list of Boolean values, one for each member + */ + public List smismember(String key, String... members) { + return executeCommandWithGlide( + "SMISMEMBER", + () -> { + Boolean[] result = glideClient.smismember(key, members).get(); + return result != null ? Arrays.asList(result) : Collections.emptyList(); + }); + } + + /** + * Returns the specified range of elements with their scores in a sorted set, by index. + * + * @param key the key of the sorted set + * @param start the starting index (0-based, can be negative to indicate offset from end) + * @param stop the ending index (0-based, can be negative to indicate offset from end) + * @return a list of Tuples (element-score pairs) in the specified range + * @see valkey.io + * @since Valkey 1.2.0 + */ + public List zrangeWithScores(String key, long start, long stop) { + return executeCommandWithGlide( + "ZRANGE", + () -> { + RangeOptions.RangeByIndex rangeQuery = new RangeOptions.RangeByIndex(start, stop); + Map result = glideClient.zrangeWithScores(key, rangeQuery).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Returns whether each member is a member of the set stored at key (binary version). + * + * @param key the key of the set + * @param members the members to check + * @return list of Boolean values, one for each member + */ + public List smismember(final byte[] key, final byte[]... members) { + return executeCommandWithGlide( + "SMISMEMBER", + () -> { + GlideString[] glideMembers = convertToGlideStringArray(members); + Boolean[] result = glideClient.smismember(GlideString.of(key), glideMembers).get(); + return result != null ? Arrays.asList(result) : Collections.emptyList(); + }); + } + + /** + * Returns the specified range of elements with their scores in a sorted set, by index (binary + * version). + * + * @param key the key of the sorted set + * @param start the starting index (0-based, can be negative to indicate offset from end) + * @param stop the ending index (0-based, can be negative to indicate offset from end) + * @return a list of Tuples (element-score pairs) in the specified range + */ + public List zrangeWithScores(final byte[] key, long start, long stop) { + return executeCommandWithGlide( + "ZRANGE", + () -> { + RangeOptions.RangeByIndex rangeQuery = new RangeOptions.RangeByIndex(start, stop); + Map result = + glideClient.zrangeWithScores(GlideString.of(key), rangeQuery).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey().getBytes(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Returns the rank of a member in a sorted set, with scores ordered from low to high. + * + * @param key the key of the sorted set + * @param member the member whose rank to return + * @return the rank of the member (0-based), or null if the member does not exist + * @see valkey.io + * @since Valkey 2.0.0 + */ + public Long zrank(String key, String member) { + return executeCommandWithGlide("ZRANK", () -> glideClient.zrank(key, member).get()); + } + + /** + * Returns the rank of a member in a sorted set, with scores ordered from low to high (binary + * version). + * + * @param key the key of the sorted set + * @param member the member whose rank to return + * @return the rank of the member (0-based), or null if the member does not exist + */ + public Long zrank(final byte[] key, final byte[] member) { + return executeCommandWithGlide( + "ZRANK", () -> glideClient.zrank(GlideString.of(key), GlideString.of(member)).get()); + } + + /** + * Returns the rank of a member in a sorted set, with scores ordered from high to low. + * + * @param key the key of the sorted set + * @param member the member whose rank to return + * @return the rank of the member (0-based), or null if the member does not exist + * @see valkey.io + * @since Valkey 2.0.0 + */ + public Long zrevrank(String key, String member) { + return executeCommandWithGlide("ZREVRANK", () -> glideClient.zrevrank(key, member).get()); + } + + /** + * Returns the rank of a member in a sorted set, with scores ordered from high to low (binary + * version). + * + * @param key the key of the sorted set + * @param member the member whose rank to return + * @return the rank of the member (0-based), or null if the member does not exist + */ + public Long zrevrank(final byte[] key, final byte[] member) { + return executeCommandWithGlide( + "ZREVRANK", () -> glideClient.zrevrank(GlideString.of(key), GlideString.of(member)).get()); + } + + /** + * Returns the number of members in a sorted set with scores within the given range. + * + * @param key the key of the sorted set + * @param min the minimum score (inclusive by default, use "(" prefix for exclusive) + * @param max the maximum score (inclusive by default, use "(" prefix for exclusive) + * @return the number of elements in the specified score range + * @see valkey.io + * @since Valkey 2.0.0 + */ + public long zcount(String key, double min, double max) { + return executeCommandWithGlide( + "ZCOUNT", + () -> { + RangeOptions.ScoreBoundary minBound = new RangeOptions.ScoreBoundary(min); + RangeOptions.ScoreBoundary maxBound = new RangeOptions.ScoreBoundary(max); + return glideClient.zcount(key, minBound, maxBound).get(); + }); + } + + /** + * Returns the number of members in a sorted set with scores within the given range (binary + * version). + * + * @param key the key of the sorted set + * @param min the minimum score (inclusive by default, use "(" prefix for exclusive) + * @param max the maximum score (inclusive by default, use "(" prefix for exclusive) + * @return the number of elements in the specified score range + */ + public long zcount(final byte[] key, double min, double max) { + return executeCommandWithGlide( + "ZCOUNT", + () -> { + RangeOptions.ScoreBoundary minBound = new RangeOptions.ScoreBoundary(min); + RangeOptions.ScoreBoundary maxBound = new RangeOptions.ScoreBoundary(max); + return glideClient.zcount(GlideString.of(key), minBound, maxBound).get(); + }); + } + + /** + * Returns the number of members in a sorted set with scores within the given range (String + * bounds). + * + * @param key the key of the sorted set + * @param min the minimum score as a string (use "(" prefix for exclusive, e.g., "(1.5") + * @param max the maximum score as a string (use "(" prefix for exclusive, e.g., "(10.0") + * @return the number of elements in the specified score range + * @see valkey.io + * @since Valkey 2.0.0 + */ + public long zcount(String key, String min, String max) { + return executeCommandWithGlide( + "ZCOUNT", + () -> { + RangeOptions.ScoreBoundary minBound = parseScoreBoundary(min); + RangeOptions.ScoreBoundary maxBound = parseScoreBoundary(max); + return glideClient.zcount(key, minBound, maxBound).get(); + }); + } + + /** + * Returns the number of members in a sorted set with scores within the given range (binary + * version with String bounds). + * + * @param key the key of the sorted set + * @param min the minimum score as bytes (use "(" prefix for exclusive) + * @param max the maximum score as bytes (use "(" prefix for exclusive) + * @return the number of elements in the specified score range + */ + public long zcount(final byte[] key, byte[] min, byte[] max) { + return executeCommandWithGlide( + "ZCOUNT", + () -> { + String minStr = new String(min, StandardCharsets.UTF_8); + String maxStr = new String(max, StandardCharsets.UTF_8); + RangeOptions.ScoreBoundary minBound = parseScoreBoundary(minStr); + RangeOptions.ScoreBoundary maxBound = parseScoreBoundary(maxStr); + return glideClient.zcount(GlideString.of(key), minBound, maxBound).get(); + }); + } + + /** + * Increments the score of a member in a sorted set. + * + * @param key the key of the sorted set + * @param increment the amount to increment the score by + * @param member the member whose score to increment + * @return the new score of the member + * @see valkey.io + * @since Valkey 1.2.0 + */ + public double zincrby(String key, double increment, String member) { + return executeCommandWithGlide( + "ZINCRBY", () -> glideClient.zincrby(key, increment, member).get()); + } + + /** + * Increments the score of a member in a sorted set (binary version). + * + * @param key the key of the sorted set + * @param increment the amount to increment the score by + * @param member the member whose score to increment + * @return the new score of the member + */ + public double zincrby(final byte[] key, double increment, final byte[] member) { + return executeCommandWithGlide( + "ZINCRBY", + () -> glideClient.zincrby(GlideString.of(key), increment, GlideString.of(member)).get()); + } + + /** + * Increments the score of a member in a sorted set with additional options. + * + * @param key the key of the sorted set + * @param increment the amount to increment the score by + * @param member the member whose score to increment + * @param params additional options for the ZINCRBY command + * @return the new score of the member, or null if the operation was not performed due to + * conditions + * @see valkey.io + * @since Valkey 3.0.2 + */ + public Double zincrby(String key, double increment, String member, ZIncrByParams params) { + return executeCommandWithGlide( + "ZINCRBY", + () -> { + ZAddOptions options = convertZIncrByParams(params); + return glideClient.zaddIncr(key, member, increment, options).get(); + }); + } + + /** + * Increments the score of a member in a sorted set with additional options (binary version). + * + * @param key the key of the sorted set + * @param increment the amount to increment the score by + * @param member the member whose score to increment + * @param params additional options for the ZINCRBY command + * @return the new score of the member, or null if the operation was not performed due to + * conditions + */ + public Double zincrby( + final byte[] key, double increment, final byte[] member, ZIncrByParams params) { + return executeCommandWithGlide( + "ZINCRBY", + () -> { + ZAddOptions options = convertZIncrByParams(params); + return glideClient + .zaddIncr(GlideString.of(key), GlideString.of(member), increment, options) + .get(); + }); + } + + /** + * Removes and returns the member with the lowest score from the sorted set. + * + * @param key the key of the sorted set + * @return the removed member with its score, or null if the key does not exist + * @see valkey.io + * @since Valkey 5.0.0 + */ + public Tuple zpopmin(String key) { + return executeCommandWithGlide( + "ZPOPMIN", + () -> { + Map result = glideClient.zpopmin(key, 1L).get(); + if (result == null || result.isEmpty()) { + return null; + } + Map.Entry entry = result.entrySet().iterator().next(); + return new Tuple(entry.getKey(), entry.getValue()); + }); + } + + /** + * Removes and returns the member with the lowest score from the sorted set (binary version). + * + * @param key the key of the sorted set + * @return the removed member with its score, or null if the key does not exist + */ + public Tuple zpopmin(final byte[] key) { + return executeCommandWithGlide( + "ZPOPMIN", + () -> { + Map result = glideClient.zpopmin(GlideString.of(key), 1L).get(); + if (result == null || result.isEmpty()) { + return null; + } + Map.Entry entry = result.entrySet().iterator().next(); + return new Tuple(entry.getKey().getBytes(), entry.getValue()); + }); + } + + /** + * Removes and returns up to count members with the lowest scores from the sorted set. + * + * @param key the key of the sorted set + * @param count the maximum number of members to remove + * @return a list of removed members with their scores, ordered from lowest to highest score + * @see valkey.io + * @since Valkey 5.0.0 + */ + public List zpopmin(String key, int count) { + return executeCommandWithGlide( + "ZPOPMIN", + () -> { + Map result = glideClient.zpopmin(key, (long) count).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Removes and returns up to count members with the lowest scores from the sorted set (binary + * version). + * + * @param key the key of the sorted set + * @param count the maximum number of members to remove + * @return a list of removed members with their scores, ordered from lowest to highest score + */ + public List zpopmin(final byte[] key, int count) { + return executeCommandWithGlide( + "ZPOPMIN", + () -> { + Map result = + glideClient.zpopmin(GlideString.of(key), (long) count).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey().getBytes(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Removes and returns the member with the highest score from the sorted set. + * + * @param key the key of the sorted set + * @return the removed member with its score, or null if the key does not exist + * @see valkey.io + * @since Valkey 5.0.0 + */ + public Tuple zpopmax(String key) { + return executeCommandWithGlide( + "ZPOPMAX", + () -> { + Map result = glideClient.zpopmax(key, 1L).get(); + if (result == null || result.isEmpty()) { + return null; + } + Map.Entry entry = result.entrySet().iterator().next(); + return new Tuple(entry.getKey(), entry.getValue()); + }); + } + + /** + * Removes and returns the member with the highest score from the sorted set (binary version). + * + * @param key the key of the sorted set + * @return the removed member with its score, or null if the key does not exist + */ + public Tuple zpopmax(final byte[] key) { + return executeCommandWithGlide( + "ZPOPMAX", + () -> { + Map result = glideClient.zpopmax(GlideString.of(key), 1L).get(); + if (result == null || result.isEmpty()) { + return null; + } + Map.Entry entry = result.entrySet().iterator().next(); + return new Tuple(entry.getKey().getBytes(), entry.getValue()); + }); + } + + /** + * Removes and returns up to count members with the highest scores from the sorted set. + * + * @param key the key of the sorted set + * @param count the maximum number of members to remove + * @return a list of removed members with their scores, ordered from highest to lowest score + * @see valkey.io + * @since Valkey 5.0.0 + */ + public List zpopmax(String key, int count) { + return executeCommandWithGlide( + "ZPOPMAX", + () -> { + Map result = glideClient.zpopmax(key, (long) count).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Removes and returns up to count members with the highest scores from the sorted set (binary + * version). + * + * @param key the key of the sorted set + * @param count the maximum number of members to remove + * @return a list of removed members with their scores, ordered from highest to lowest score + */ + public List zpopmax(final byte[] key, int count) { + return executeCommandWithGlide( + "ZPOPMAX", + () -> { + Map result = + glideClient.zpopmax(GlideString.of(key), (long) count).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey().getBytes(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Computes the union of sorted sets and stores the result in a destination key. + * + * @param dstkey the destination key + * @param sets the keys of the sorted sets to union + * @return the number of elements in the resulting sorted set + * @see valkey.io + * @since Valkey 2.0.0 + */ + public long zunionstore(String dstkey, String... sets) { + return executeCommandWithGlide( + "ZUNIONSTORE", + () -> { + WeightAggregateOptions.KeyArray keyArray = new WeightAggregateOptions.KeyArray(sets); + return glideClient.zunionstore(dstkey, keyArray).get(); + }); + } + + /** + * Computes the union of sorted sets and stores the result in a destination key (binary version). + * + * @param dstkey the destination key + * @param sets the keys of the sorted sets to union + * @return the number of elements in the resulting sorted set + */ + public long zunionstore(final byte[] dstkey, final byte[]... sets) { + return executeCommandWithGlide( + "ZUNIONSTORE", + () -> { + GlideString[] glideSets = convertToGlideStringArray(sets); + WeightAggregateOptions.KeyArrayBinary keyArray = + new WeightAggregateOptions.KeyArrayBinary(glideSets); + return glideClient.zunionstore(GlideString.of(dstkey), keyArray).get(); + }); + } + + /** + * Computes the intersection of sorted sets and stores the result in a destination key. + * + * @param dstkey the destination key + * @param sets the keys of the sorted sets to intersect + * @return the number of elements in the resulting sorted set + * @see valkey.io + * @since Valkey 2.0.0 + */ + public long zinterstore(String dstkey, String... sets) { + return executeCommandWithGlide( + "ZINTERSTORE", + () -> { + WeightAggregateOptions.KeyArray keyArray = new WeightAggregateOptions.KeyArray(sets); + return glideClient.zinterstore(dstkey, keyArray).get(); + }); + } + + /** + * Computes the intersection of sorted sets and stores the result in a destination key (binary + * version). + * + * @param dstkey the destination key + * @param sets the keys of the sorted sets to intersect + * @return the number of elements in the resulting sorted set + */ + public long zinterstore(final byte[] dstkey, final byte[]... sets) { + return executeCommandWithGlide( + "ZINTERSTORE", + () -> { + GlideString[] glideSets = convertToGlideStringArray(sets); + WeightAggregateOptions.KeyArrayBinary keyArray = + new WeightAggregateOptions.KeyArrayBinary(glideSets); + return glideClient.zinterstore(GlideString.of(dstkey), keyArray).get(); + }); + } + + /** + * Removes all members in a sorted set within the given rank range. + * + * @param key the key of the sorted set + * @param start the starting rank (0-based, can be negative to indicate offset from end) + * @param stop the ending rank (0-based, can be negative to indicate offset from end) + * @return the number of members removed + * @see valkey.io + * @since Valkey 2.0.0 + */ + public long zremrangebyrank(String key, long start, long stop) { + return executeCommandWithGlide( + "ZREMRANGEBYRANK", () -> glideClient.zremrangebyrank(key, start, stop).get()); + } + + /** + * Removes all members in a sorted set within the given rank range (binary version). + * + * @param key the key of the sorted set + * @param start the starting rank (0-based, can be negative to indicate offset from end) + * @param stop the ending rank (0-based, can be negative to indicate offset from end) + * @return the number of members removed + */ + public long zremrangebyrank(final byte[] key, long start, long stop) { + return executeCommandWithGlide( + "ZREMRANGEBYRANK", + () -> glideClient.zremrangebyrank(GlideString.of(key), start, stop).get()); + } + + /** + * Removes all members in a sorted set within the given score range. + * + * @param key the key of the sorted set + * @param min the minimum score (inclusive by default, use "(" prefix for exclusive) + * @param max the maximum score (inclusive by default, use "(" prefix for exclusive) + * @return the number of members removed + * @see valkey.io + * @since Valkey 1.2.0 + */ + public long zremrangebyscore(String key, double min, double max) { + return executeCommandWithGlide( + "ZREMRANGEBYSCORE", + () -> { + RangeOptions.ScoreBoundary minBound = new RangeOptions.ScoreBoundary(min); + RangeOptions.ScoreBoundary maxBound = new RangeOptions.ScoreBoundary(max); + return glideClient.zremrangebyscore(key, minBound, maxBound).get(); + }); + } + + /** + * Removes all members in a sorted set within the given score range (binary version). + * + * @param key the key of the sorted set + * @param min the minimum score (inclusive by default, use "(" prefix for exclusive) + * @param max the maximum score (inclusive by default, use "(" prefix for exclusive) + * @return the number of members removed + */ + public long zremrangebyscore(final byte[] key, double min, double max) { + return executeCommandWithGlide( + "ZREMRANGEBYSCORE", + () -> { + RangeOptions.ScoreBoundary minBound = new RangeOptions.ScoreBoundary(min); + RangeOptions.ScoreBoundary maxBound = new RangeOptions.ScoreBoundary(max); + return glideClient.zremrangebyscore(GlideString.of(key), minBound, maxBound).get(); + }); + } + + /** + * Removes all members in a sorted set within the given score range (String bounds). + * + * @param key the key of the sorted set + * @param min the minimum score as a string (use "(" prefix for exclusive, e.g., "(1.5") + * @param max the maximum score as a string (use "(" prefix for exclusive, e.g., "(10.0") + * @return the number of members removed + * @see valkey.io + * @since Valkey 1.2.0 + */ + public long zremrangebyscore(String key, String min, String max) { + return executeCommandWithGlide( + "ZREMRANGEBYSCORE", + () -> { + RangeOptions.ScoreBoundary minBound = parseScoreBoundary(min); + RangeOptions.ScoreBoundary maxBound = parseScoreBoundary(max); + return glideClient.zremrangebyscore(key, minBound, maxBound).get(); + }); + } + + /** + * Removes all members in a sorted set within the given score range (binary version with String + * bounds). + * + * @param key the key of the sorted set + * @param min the minimum score as bytes (use "(" prefix for exclusive) + * @param max the maximum score as bytes (use "(" prefix for exclusive) + * @return the number of members removed + */ + public long zremrangebyscore(final byte[] key, byte[] min, byte[] max) { + return executeCommandWithGlide( + "ZREMRANGEBYSCORE", + () -> { + String minStr = new String(min, StandardCharsets.UTF_8); + String maxStr = new String(max, StandardCharsets.UTF_8); + RangeOptions.ScoreBoundary minBound = parseScoreBoundary(minStr); + RangeOptions.ScoreBoundary maxBound = parseScoreBoundary(maxStr); + return glideClient.zremrangebyscore(GlideString.of(key), minBound, maxBound).get(); + }); + } + + /** + * Iterates over members and scores of a sorted set. + * + * @param key the key of the sorted set + * @param cursor the cursor position to start from (use "0" to start a new iteration) + * @return a ScanResult containing the next cursor position and a list of Tuples + * @see valkey.io + * @since Valkey 2.8.0 + */ + public ScanResult zscan(String key, String cursor) { + return executeCommandWithGlide( + "ZSCAN", + () -> { + Object[] result = glideClient.zscan(key, cursor).get(); + String nextCursor = (String) result[0]; + Object[] membersAndScores = (Object[]) result[1]; + + List tuples = new ArrayList<>(); + for (int i = 0; i < membersAndScores.length; i += 2) { + String member = (String) membersAndScores[i]; + // Score comes back as String when fraction is zero + Object scoreObj = membersAndScores[i + 1]; + Double score = + scoreObj instanceof String + ? Double.parseDouble((String) scoreObj) + : (Double) scoreObj; + tuples.add(new Tuple(member, score)); + } + + return new ScanResult<>(nextCursor, tuples); + }); + } + + /** + * Iterates over members and scores of a sorted set (binary version). + * + * @param key the key of the sorted set + * @param cursor the cursor position to start from (use "0" to start a new iteration) + * @return a ScanResult containing the next cursor position and a list of Tuples + */ + public ScanResult zscan(final byte[] key, final byte[] cursor) { + return executeCommandWithGlide( + "ZSCAN", + () -> { + Object[] result = glideClient.zscan(GlideString.of(key), GlideString.of(cursor)).get(); + GlideString nextCursor = (GlideString) result[0]; + Object[] membersAndScores = (Object[]) result[1]; + + List tuples = new ArrayList<>(); + for (int i = 0; i < membersAndScores.length; i += 2) { + GlideString member = (GlideString) membersAndScores[i]; + // Score comes back as GlideString when fraction is zero + Object scoreObj = membersAndScores[i + 1]; + Double score; + if (scoreObj instanceof GlideString) { + score = Double.parseDouble(((GlideString) scoreObj).getString()); + } else if (scoreObj instanceof String) { + score = Double.parseDouble((String) scoreObj); + } else { + score = (Double) scoreObj; + } + tuples.add(new Tuple(member.getBytes(), score)); + } + + return new ScanResult<>(nextCursor.getBytes(), tuples); + }); + } + + /** + * Stores a range of elements from the sorted set at source into a new sorted set at destination. + * + * @param destination the key of the destination sorted set + * @param source the key of the source sorted set + * @param start the start index (inclusive) + * @param stop the stop index (inclusive) + * @return the number of elements stored in the destination sorted set + * @see valkey.io + * @since Valkey 6.2.0 + */ + public Long zrangestore(String destination, String source, long start, long stop) { + return executeCommandWithGlide( + "ZRANGESTORE", + () -> { + RangeQuery range = new RangeByIndex(start, stop); + return glideClient.zrangestore(destination, source, range).get(); + }); + } + + /** + * Stores a range of elements from the sorted set at source into a new sorted set at destination + * (binary version). + * + * @param destination the key of the destination sorted set + * @param source the key of the source sorted set + * @param start the start index (inclusive) + * @param stop the stop index (inclusive) + * @return the number of elements stored in the destination sorted set + */ + public Long zrangestore(byte[] destination, byte[] source, long start, long stop) { + return executeCommandWithGlide( + "ZRANGESTORE", + () -> { + RangeQuery range = new RangeByIndex(start, stop); + return glideClient + .zrangestore(GlideString.of(destination), GlideString.of(source), range) + .get(); + }); + } + + /** + * Returns the rank of member in the sorted set with its score. + * + * @param key the key of the sorted set + * @param member the member whose rank and score to get + * @return a KeyValue containing the rank (Long) and score (Double), or null if member doesn't + * exist + * @see valkey.io + * @since Valkey 7.2.0 + */ + public redis.clients.jedis.resps.KeyValue zrankWithScore( + String key, String member) { + return executeCommandWithGlide( + "ZRANK", + () -> { + Object[] result = glideClient.zrankWithScore(key, member).get(); + if (result == null) { + return null; + } + Long rank = (Long) result[0]; + Double score = (Double) result[1]; + return new redis.clients.jedis.resps.KeyValue<>(rank, score); + }); + } + + /** + * Returns the rank of member in the sorted set with its score (binary version). + * + * @param key the key of the sorted set + * @param member the member whose rank and score to get + * @return a KeyValue containing the rank (Long) and score (Double), or null if member doesn't + * exist + */ + public redis.clients.jedis.resps.KeyValue zrankWithScore( + byte[] key, byte[] member) { + return executeCommandWithGlide( + "ZRANK", + () -> { + Object[] result = + glideClient.zrankWithScore(GlideString.of(key), GlideString.of(member)).get(); + if (result == null) { + return null; + } + Long rank = (Long) result[0]; + Double score = (Double) result[1]; + return new redis.clients.jedis.resps.KeyValue<>(rank, score); + }); + } + + /** + * Returns the rank of member in the sorted set with its score, with scores ordered from high to + * low. + * + * @param key the key of the sorted set + * @param member the member whose reverse rank and score to get + * @return a KeyValue containing the rank (Long) and score (Double), or null if member doesn't + * exist + * @see valkey.io + * @since Valkey 7.2.0 + */ + public redis.clients.jedis.resps.KeyValue zrevrankWithScore( + String key, String member) { + return executeCommandWithGlide( + "ZREVRANK", + () -> { + Object[] result = glideClient.zrevrankWithScore(key, member).get(); + if (result == null) { + return null; + } + Long rank = (Long) result[0]; + Double score = (Double) result[1]; + return new redis.clients.jedis.resps.KeyValue<>(rank, score); + }); + } + + /** + * Returns the rank of member in the sorted set with its score, with scores ordered from high to + * low (binary version). + * + * @param key the key of the sorted set + * @param member the member whose reverse rank and score to get + * @return a KeyValue containing the rank (Long) and score (Double), or null if member doesn't + * exist + */ + public redis.clients.jedis.resps.KeyValue zrevrankWithScore( + byte[] key, byte[] member) { + return executeCommandWithGlide( + "ZREVRANK", + () -> { + Object[] result = + glideClient.zrevrankWithScore(GlideString.of(key), GlideString.of(member)).get(); + if (result == null) { + return null; + } + Long rank = (Long) result[0]; + Double score = (Double) result[1]; + return new redis.clients.jedis.resps.KeyValue<>(rank, score); + }); + } + + /** + * Converts Jedis ZAddParams to GLIDE ZAddOptions. + * + * @param params the Jedis ZAddParams + * @return the corresponding GLIDE ZAddOptions + */ + private static ZAddOptions convertZAddParams(ZAddParams params) { + ZAddOptions.ZAddOptionsBuilder builder = ZAddOptions.builder(); + + if (params.getNx() != null && params.getNx()) { + builder.conditionalChange(ZAddOptions.ConditionalChange.ONLY_IF_DOES_NOT_EXIST); + } else if (params.getXx() != null && params.getXx()) { + builder.conditionalChange(ZAddOptions.ConditionalChange.ONLY_IF_EXISTS); + } + + if (params.getGt() != null && params.getGt()) { + builder.updateOptions(ZAddOptions.UpdateOptions.SCORE_GREATER_THAN_CURRENT); + } else if (params.getLt() != null && params.getLt()) { + builder.updateOptions(ZAddOptions.UpdateOptions.SCORE_LESS_THAN_CURRENT); + } + + return builder.build(); + } + + /** + * Parses a Jedis-style score boundary string into a ScoreBoundary object. + * + * @param scoreStr the score string (e.g., "5.0", "(5.0" for exclusive) + * @return the corresponding ScoreBoundary object + */ + private static RangeOptions.ScoreBoundary parseScoreBoundary(String scoreStr) { + if (scoreStr == null || scoreStr.isEmpty()) { + throw new JedisException("Score boundary cannot be null or empty"); + } + + if (scoreStr.startsWith("(")) { + // Exclusive boundary + double score = Double.parseDouble(scoreStr.substring(1)); + return new RangeOptions.ScoreBoundary(score, false); + } else { + // Inclusive boundary + double score = Double.parseDouble(scoreStr); + return new RangeOptions.ScoreBoundary(score, true); + } + } + + /** + * Converts Jedis ZIncrByParams to GLIDE ZAddOptions (ZINCRBY uses ZADD with INCR option). + * + * @param params the Jedis ZIncrByParams + * @return the corresponding GLIDE ZAddOptions + */ + private static ZAddOptions convertZIncrByParams(ZIncrByParams params) { + ZAddOptions.ZAddOptionsBuilder builder = ZAddOptions.builder(); + + if (params.getNx() != null && params.getNx()) { + builder.conditionalChange(ZAddOptions.ConditionalChange.ONLY_IF_DOES_NOT_EXIST); + } else if (params.getXx() != null && params.getXx()) { + builder.conditionalChange(ZAddOptions.ConditionalChange.ONLY_IF_EXISTS); + } + + return builder.build(); + } + + /** + * Parses a Jedis-style lex range string into a LexRange object. + * + * @param lexStr the lex range string (e.g., "[a", "(b", "+", "-") + * @return the corresponding LexRange object + * @throws IllegalArgumentException if lexStr is null or empty, or has invalid format + */ + private LexRange parseLexRange(String lexStr) { + if (lexStr == null || lexStr.isEmpty()) { + throw new IllegalArgumentException("Lex range string cannot be null or empty"); + } + + if (lexStr.equals("+")) { + return InfLexBound.POSITIVE_INFINITY; + } else if (lexStr.equals("-")) { + return InfLexBound.NEGATIVE_INFINITY; + } else if (lexStr.startsWith("[")) { + if (lexStr.length() < 2) { + throw new IllegalArgumentException( + "Invalid lex range format: '[' must be followed by a value"); + } + return new LexBoundary(lexStr.substring(1), true); + } else if (lexStr.startsWith("(")) { + if (lexStr.length() < 2) { + throw new IllegalArgumentException( + "Invalid lex range format: '(' must be followed by a value"); + } + return new LexBoundary(lexStr.substring(1), false); + } else { + // Default to inclusive if no prefix + return new LexBoundary(lexStr, true); + } + } + + /** + * Parses a Jedis-style lex range byte array into a LexRange object. + * + *

Note: This method assumes UTF-8 encoding when converting the byte array to a string. If the + * byte array contains non-UTF-8 data, the behavior is undefined and may result in incorrect lex + * range parsing. + * + * @param lexBytes the lex range byte array (assumed to be UTF-8 encoded) + * @return the corresponding LexRange object + * @throws IllegalArgumentException if lexBytes is null or empty, or has invalid format + */ + private LexRange parseLexRange(byte[] lexBytes) { + if (lexBytes == null || lexBytes.length == 0) { + throw new IllegalArgumentException("Lex range byte array cannot be null or empty"); + } + String lexStr = new String(lexBytes, StandardCharsets.UTF_8); + return parseLexRange(lexStr); + } + + /** + * Returns the number of members in the sorted set with lexicographical values between min and + * max. + * + * @param key the key of the sorted set + * @param min the minimum lexicographical value (inclusive with "[", exclusive with "(") + * @param max the maximum lexicographical value (inclusive with "[", exclusive with "(") + * @return the number of members in the specified range + * @see valkey.io + * @since Valkey 2.8.9 + */ + public Long zlexcount(String key, String min, String max) { + return executeCommandWithGlide( + "ZLEXCOUNT", + () -> { + LexRange minLex = parseLexRange(min); + LexRange maxLex = parseLexRange(max); + return glideClient.zlexcount(key, minLex, maxLex).get(); + }); + } + + /** + * Returns the number of members in the sorted set with lexicographical values between min and max + * (binary version). + * + * @param key the key of the sorted set + * @param min the minimum lexicographical value (inclusive with "[", exclusive with "(") + * @param max the maximum lexicographical value (inclusive with "[", exclusive with "(") + * @return the number of members in the specified range + */ + public Long zlexcount(byte[] key, byte[] min, byte[] max) { + return executeCommandWithGlide( + "ZLEXCOUNT", + () -> { + LexRange minLex = parseLexRange(min); + LexRange maxLex = parseLexRange(max); + return glideClient.zlexcount(GlideString.of(key), minLex, maxLex).get(); + }); + } + + /** + * Removes and returns a member with the lowest score from the first non-empty sorted set. Blocks + * until a member is available or timeout is reached. + * + * @param timeout the timeout in seconds (0 means block indefinitely) + * @param keys the keys of the sorted sets to check + * @return a KeyValue containing the key and a Tuple (member-score pair), or null if timeout is + * reached + * @see valkey.io + * @since Valkey 5.0.0 + */ + public redis.clients.jedis.resps.KeyValue bzpopmin( + double timeout, String... keys) { + return executeCommandWithGlide( + "BZPOPMIN", + () -> { + Object[] result = glideClient.bzpopmin(keys, timeout).get(); + if (result == null) { + return null; + } + // result[0] = key, result[1] = member, result[2] = score + String key = (String) result[0]; + String member = (String) result[1]; + Double score = (Double) result[2]; + return new redis.clients.jedis.resps.KeyValue<>(key, new Tuple(member, score)); + }); + } + + /** + * Removes and returns a member with the lowest score from the first non-empty sorted set. Blocks + * until a member is available or timeout is reached (binary version). + * + * @param timeout the timeout in seconds (0 means block indefinitely) + * @param keys the keys of the sorted sets to check + * @return a KeyValue containing the key and a Tuple (member-score pair), or null if timeout is + * reached + */ + public redis.clients.jedis.resps.KeyValue bzpopmin( + double timeout, byte[]... keys) { + return executeCommandWithGlide( + "BZPOPMIN", + () -> { + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + Object[] result = glideClient.bzpopmin(glideKeys, timeout).get(); + if (result == null) { + return null; + } + // result[0] = key, result[1] = member, result[2] = score + GlideString key = (GlideString) result[0]; + GlideString member = (GlideString) result[1]; + Double score = (Double) result[2]; + return new redis.clients.jedis.resps.KeyValue<>( + key.getBytes(), new Tuple(member.getBytes(), score)); + }); + } + + /** + * Removes and returns a member with the highest score from the first non-empty sorted set. Blocks + * until a member is available or timeout is reached. + * + * @param timeout the timeout in seconds (0 means block indefinitely) + * @param keys the keys of the sorted sets to check + * @return a KeyValue containing the key and a Tuple (member-score pair), or null if timeout is + * reached + * @see valkey.io + * @since Valkey 5.0.0 + */ + public redis.clients.jedis.resps.KeyValue bzpopmax( + double timeout, String... keys) { + return executeCommandWithGlide( + "BZPOPMAX", + () -> { + Object[] result = glideClient.bzpopmax(keys, timeout).get(); + if (result == null) { + return null; + } + // result[0] = key, result[1] = member, result[2] = score + String key = (String) result[0]; + String member = (String) result[1]; + Double score = (Double) result[2]; + return new redis.clients.jedis.resps.KeyValue<>(key, new Tuple(member, score)); + }); + } + + /** + * Removes and returns a member with the highest score from the first non-empty sorted set. Blocks + * until a member is available or timeout is reached (binary version). + * + * @param timeout the timeout in seconds (0 means block indefinitely) + * @param keys the keys of the sorted sets to check + * @return a KeyValue containing the key and a Tuple (member-score pair), or null if timeout is + * reached + */ + public redis.clients.jedis.resps.KeyValue bzpopmax( + double timeout, byte[]... keys) { + return executeCommandWithGlide( + "BZPOPMAX", + () -> { + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + Object[] result = glideClient.bzpopmax(glideKeys, timeout).get(); + if (result == null) { + return null; + } + // result[0] = key, result[1] = member, result[2] = score + GlideString key = (GlideString) result[0]; + GlideString member = (GlideString) result[1]; + Double score = (Double) result[2]; + return new redis.clients.jedis.resps.KeyValue<>( + key.getBytes(), new Tuple(member.getBytes(), score)); + }); + } + + /** + * Returns the difference between the first sorted set and all successive sorted sets. + * + * @param keys the keys of the sorted sets + * @return an array of members in the resulting set + * @see valkey.io + * @since Valkey 6.2.0 + */ + public List zdiff(String... keys) { + return executeCommandWithGlide("ZDIFF", () -> Arrays.asList(glideClient.zdiff(keys).get())); + } + + /** + * Returns the difference between the first sorted set and all successive sorted sets (binary + * version). + * + * @param keys the keys of the sorted sets + * @return an array of members in the resulting set + */ + public List zdiff(byte[]... keys) { return executeCommandWithGlide( - "SMISMEMBER", + "ZDIFF", () -> { - GlideString[] glideMembers = convertToGlideStringArray(members); - Boolean[] result = glideClient.smismember(GlideString.of(key), glideMembers).get(); - return result != null ? Arrays.asList(result) : Collections.emptyList(); + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + GlideString[] result = glideClient.zdiff(glideKeys).get(); + return Arrays.stream(result).map(GlideString::getBytes).collect(Collectors.toList()); + }); + } + + /** + * Returns the difference between the first sorted set and all successive sorted sets, with + * scores. + * + * @param keys the keys of the sorted sets + * @return a list of Tuples (element-score pairs) in the resulting set + * @see valkey.io + * @since Valkey 6.2.0 + */ + public List zdiffWithScores(String... keys) { + return executeCommandWithGlide( + "ZDIFF", + () -> { + Map result = glideClient.zdiffWithScores(keys).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Returns the difference between the first sorted set and all successive sorted sets, with scores + * (binary version). + * + * @param keys the keys of the sorted sets + * @return a list of Tuples (element-score pairs) in the resulting set + */ + public List zdiffWithScores(byte[]... keys) { + return executeCommandWithGlide( + "ZDIFF", + () -> { + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + Map result = glideClient.zdiffWithScores(glideKeys).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey().getBytes(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Computes the difference between the first sorted set and all successive sorted sets and stores + * the result in destination. + * + * @param destination the key where the result will be stored + * @param keys the keys of the sorted sets + * @return the number of members in the resulting sorted set + * @see valkey.io + * @since Valkey 6.2.0 + */ + public Long zdiffstore(String destination, String... keys) { + return executeCommandWithGlide( + "ZDIFFSTORE", () -> glideClient.zdiffstore(destination, keys).get()); + } + + /** + * Computes the difference between the first sorted set and all successive sorted sets and stores + * the result in destination (binary version). + * + * @param destination the key where the result will be stored + * @param keys the keys of the sorted sets + * @return the number of members in the resulting sorted set + */ + public Long zdiffstore(byte[] destination, byte[]... keys) { + return executeCommandWithGlide( + "ZDIFFSTORE", + () -> { + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + return glideClient.zdiffstore(GlideString.of(destination), glideKeys).get(); + }); + } + + /** + * Returns the union of multiple sorted sets. + * + * @param keys the keys of the sorted sets + * @return an array of members in the resulting set + * @see valkey.io + * @since Valkey 6.2.0 + */ + public List zunion(String... keys) { + return executeCommandWithGlide( + "ZUNION", () -> Arrays.asList(glideClient.zunion(new KeyArray(keys)).get())); + } + + /** + * Returns the union of multiple sorted sets (binary version). + * + * @param keys the keys of the sorted sets + * @return an array of members in the resulting set + */ + public List zunion(byte[]... keys) { + return executeCommandWithGlide( + "ZUNION", + () -> { + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + GlideString[] result = glideClient.zunion(new KeyArrayBinary(glideKeys)).get(); + return Arrays.stream(result).map(GlideString::getBytes).collect(Collectors.toList()); + }); + } + + /** + * Returns the union of multiple sorted sets with scores. + * + * @param keys the keys of the sorted sets + * @return a list of Tuples (element-score pairs) in the resulting set + * @see valkey.io + * @since Valkey 6.2.0 + */ + public List zunionWithScores(String... keys) { + return executeCommandWithGlide( + "ZUNION", + () -> { + Map result = glideClient.zunionWithScores(new KeyArray(keys)).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Returns the union of multiple sorted sets with scores (binary version). + * + * @param keys the keys of the sorted sets + * @return a list of Tuples (element-score pairs) in the resulting set + */ + public List zunionWithScores(byte[]... keys) { + return executeCommandWithGlide( + "ZUNION", + () -> { + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + Map result = + glideClient.zunionWithScores(new KeyArrayBinary(glideKeys)).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey().getBytes(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Returns the intersection of multiple sorted sets. + * + * @param keys the keys of the sorted sets + * @return an array of members in the resulting set + * @see valkey.io + * @since Valkey 6.2.0 + */ + public List zinter(String... keys) { + return executeCommandWithGlide( + "ZINTER", () -> Arrays.asList(glideClient.zinter(new KeyArray(keys)).get())); + } + + /** + * Returns the intersection of multiple sorted sets (binary version). + * + * @param keys the keys of the sorted sets + * @return an array of members in the resulting set + */ + public List zinter(byte[]... keys) { + return executeCommandWithGlide( + "ZINTER", + () -> { + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + GlideString[] result = glideClient.zinter(new KeyArrayBinary(glideKeys)).get(); + return Arrays.stream(result).map(GlideString::getBytes).collect(Collectors.toList()); + }); + } + + /** + * Returns the intersection of multiple sorted sets with scores. + * + * @param keys the keys of the sorted sets + * @return a list of Tuples (element-score pairs) in the resulting set + * @see valkey.io + * @since Valkey 6.2.0 + */ + public List zinterWithScores(String... keys) { + return executeCommandWithGlide( + "ZINTER", + () -> { + Map result = glideClient.zinterWithScores(new KeyArray(keys)).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Returns the intersection of multiple sorted sets with scores (binary version). + * + * @param keys the keys of the sorted sets + * @return a list of Tuples (element-score pairs) in the resulting set + */ + public List zinterWithScores(byte[]... keys) { + return executeCommandWithGlide( + "ZINTER", + () -> { + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + Map result = + glideClient.zinterWithScores(new KeyArrayBinary(glideKeys)).get(); + List tuples = new ArrayList<>(); + for (Map.Entry entry : result.entrySet()) { + tuples.add(new Tuple(entry.getKey().getBytes(), entry.getValue())); + } + return tuples; + }); + } + + /** + * Returns the cardinality of the intersection of multiple sorted sets. + * + * @param keys the keys of the sorted sets + * @return the number of members in the resulting intersection + * @see valkey.io + * @since Valkey 7.0.0 + */ + public long zintercard(String... keys) { + return executeCommandWithGlide("ZINTERCARD", () -> glideClient.zintercard(keys).get()); + } + + /** + * Returns the cardinality of the intersection of multiple sorted sets (binary version). + * + * @param keys the keys of the sorted sets + * @return the number of members in the resulting intersection + */ + public long zintercard(byte[]... keys) { + return executeCommandWithGlide( + "ZINTERCARD", + () -> { + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + return glideClient.zintercard(glideKeys).get(); + }); + } + + /** + * Returns the cardinality of the intersection of multiple sorted sets, stopping at the specified + * limit. + * + * @param limit if the intersection cardinality reaches limit, the algorithm will exit early + * @param keys the keys of the sorted sets + * @return the number of members in the resulting intersection (up to limit) + * @see valkey.io + * @since Valkey 7.0.0 + */ + public long zintercard(long limit, String... keys) { + return executeCommandWithGlide("ZINTERCARD", () -> glideClient.zintercard(keys, limit).get()); + } + + /** + * Returns the cardinality of the intersection of multiple sorted sets, stopping at the specified + * limit (binary version). + * + * @param limit if the intersection cardinality reaches limit, the algorithm will exit early + * @param keys the keys of the sorted sets + * @return the number of members in the resulting intersection (up to limit) + */ + public long zintercard(long limit, byte[]... keys) { + return executeCommandWithGlide( + "ZINTERCARD", + () -> { + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + return glideClient.zintercard(glideKeys, limit).get(); + }); + } + + /** + * Pops member-score pairs from the first non-empty sorted set. + * + * @param option MIN to pop members with lowest scores, MAX for highest scores + * @param keys the keys of the sorted sets to check + * @return a KeyValue containing the key and a list of Tuples, or null if all sets are empty + * @see valkey.io + * @since Valkey 7.0.0 + */ + @SuppressWarnings("unchecked") + public redis.clients.jedis.resps.KeyValue> zmpop( + SortedSetOption option, byte[]... keys) { + return executeCommandWithGlide( + "ZMPOP", + () -> { + ScoreFilter modifier = + (option == SortedSetOption.MIN) ? ScoreFilter.MIN : ScoreFilter.MAX; + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + Map result = glideClient.zmpop(glideKeys, modifier).get(); + if (result == null) { + return null; + } + // Result has single entry: key -> map of member -> score + Map.Entry entry = result.entrySet().iterator().next(); + byte[] key = entry.getKey().getBytes(); + Map membersMap = (Map) entry.getValue(); + List tuples = new ArrayList<>(); + for (Map.Entry memberScore : membersMap.entrySet()) { + tuples.add(new Tuple(memberScore.getKey().getBytes(), memberScore.getValue())); + } + return new redis.clients.jedis.resps.KeyValue<>(key, tuples); + }); + } + + /** + * Pops up to count member-score pairs from the first non-empty sorted set. + * + * @param option MIN to pop members with lowest scores, MAX for highest scores + * @param count the maximum number of members to pop + * @param keys the keys of the sorted sets to check + * @return a KeyValue containing the key and a list of Tuples, or null if all sets are empty + * @see valkey.io + * @since Valkey 7.0.0 + */ + @SuppressWarnings("unchecked") + public redis.clients.jedis.resps.KeyValue> zmpop( + SortedSetOption option, int count, byte[]... keys) { + return executeCommandWithGlide( + "ZMPOP", + () -> { + ScoreFilter modifier = + (option == SortedSetOption.MIN) ? ScoreFilter.MIN : ScoreFilter.MAX; + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + Map result = + glideClient.zmpop(glideKeys, modifier, (long) count).get(); + if (result == null) { + return null; + } + // Result has single entry: key -> map of member -> score + Map.Entry entry = result.entrySet().iterator().next(); + byte[] key = entry.getKey().getBytes(); + Map membersMap = (Map) entry.getValue(); + List tuples = new ArrayList<>(); + for (Map.Entry memberScore : membersMap.entrySet()) { + tuples.add(new Tuple(memberScore.getKey().getBytes(), memberScore.getValue())); + } + return new redis.clients.jedis.resps.KeyValue<>(key, tuples); + }); + } + + /** + * Blocks until it pops member-score pairs from the first non-empty sorted set. + * + * @param timeout the timeout in seconds (0 means block indefinitely) + * @param option MIN to pop members with lowest scores, MAX for highest scores + * @param keys the keys of the sorted sets to check + * @return a KeyValue containing the key and a list of Tuples, or null if timeout is reached + * @see valkey.io + * @since Valkey 7.0.0 + */ + @SuppressWarnings("unchecked") + public redis.clients.jedis.resps.KeyValue> bzmpop( + double timeout, SortedSetOption option, byte[]... keys) { + return executeCommandWithGlide( + "BZMPOP", + () -> { + ScoreFilter modifier = + (option == SortedSetOption.MIN) ? ScoreFilter.MIN : ScoreFilter.MAX; + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + Map result = glideClient.bzmpop(glideKeys, modifier, timeout).get(); + if (result == null) { + return null; + } + // Result has single entry: key -> map of member -> score + Map.Entry entry = result.entrySet().iterator().next(); + byte[] key = entry.getKey().getBytes(); + Map membersMap = (Map) entry.getValue(); + List tuples = new ArrayList<>(); + for (Map.Entry memberScore : membersMap.entrySet()) { + tuples.add(new Tuple(memberScore.getKey().getBytes(), memberScore.getValue())); + } + return new redis.clients.jedis.resps.KeyValue<>(key, tuples); + }); + } + + /** + * Blocks until it pops up to count member-score pairs from the first non-empty sorted set. + * + * @param timeout the timeout in seconds (0 means block indefinitely) + * @param option MIN to pop members with lowest scores, MAX for highest scores + * @param count the maximum number of members to pop + * @param keys the keys of the sorted sets to check + * @return a KeyValue containing the key and a list of Tuples, or null if timeout is reached + * @see valkey.io + * @since Valkey 7.0.0 + */ + @SuppressWarnings("unchecked") + public redis.clients.jedis.resps.KeyValue> bzmpop( + double timeout, SortedSetOption option, int count, byte[]... keys) { + return executeCommandWithGlide( + "BZMPOP", + () -> { + ScoreFilter modifier = + (option == SortedSetOption.MIN) ? ScoreFilter.MIN : ScoreFilter.MAX; + GlideString[] glideKeys = + Arrays.stream(keys).map(GlideString::of).toArray(GlideString[]::new); + Map result = + glideClient.bzmpop(glideKeys, modifier, timeout, (long) count).get(); + if (result == null) { + return null; + } + // Result has single entry: key -> map of member -> score + Map.Entry entry = result.entrySet().iterator().next(); + byte[] key = entry.getKey().getBytes(); + Map membersMap = (Map) entry.getValue(); + List tuples = new ArrayList<>(); + for (Map.Entry memberScore : membersMap.entrySet()) { + tuples.add(new Tuple(memberScore.getKey().getBytes(), memberScore.getValue())); + } + return new redis.clients.jedis.resps.KeyValue<>(key, tuples); + }); + } + + /** + * Removes all members with lexicographical values between min and max from the sorted set. + * + * @param key the key of the sorted set + * @param min the minimum lexicographical value (inclusive with "[", exclusive with "(") + * @param max the maximum lexicographical value (inclusive with "[", exclusive with "(") + * @return the number of members removed + * @see valkey.io + * @since Valkey 2.8.9 + */ + public Long zremrangebylex(String key, String min, String max) { + return executeCommandWithGlide( + "ZREMRANGEBYLEX", + () -> { + LexRange minLex = parseLexRange(min); + LexRange maxLex = parseLexRange(max); + return glideClient.zremrangebylex(key, minLex, maxLex).get(); + }); + } + + /** + * Removes all members with lexicographical values between min and max from the sorted set (binary + * version). + * + * @param key the key of the sorted set + * @param min the minimum lexicographical value (inclusive with "[", exclusive with "(") + * @param max the maximum lexicographical value (inclusive with "[", exclusive with "(") + * @return the number of members removed + */ + public Long zremrangebylex(byte[] key, byte[] min, byte[] max) { + return executeCommandWithGlide( + "ZREMRANGEBYLEX", + () -> { + LexRange minLex = parseLexRange(min); + LexRange maxLex = parseLexRange(max); + return glideClient.zremrangebylex(GlideString.of(key), minLex, maxLex).get(); + }); + } + + /** + * Returns a random member from the sorted set. + * + * @param key the key of the sorted set + * @return a random member, or null if the set is empty + * @see valkey.io + * @since Valkey 6.2.0 + */ + public String zrandmember(String key) { + return executeCommandWithGlide("ZRANDMEMBER", () -> glideClient.zrandmember(key).get()); + } + + /** + * Returns a random member from the sorted set (binary version). + * + * @param key the key of the sorted set + * @return a random member, or null if the set is empty + */ + public byte[] zrandmember(byte[] key) { + return executeCommandWithGlide( + "ZRANDMEMBER", + () -> { + GlideString result = glideClient.zrandmember(GlideString.of(key)).get(); + return result != null ? result.getBytes() : null; }); } @@ -7480,6 +9085,36 @@ public byte[] spop(final byte[] key) { }); } + /** + * Returns random members from the sorted set. + * + * @param key the key of the sorted set + * @param count the number of members to return (negative values allow duplicates) + * @return an array of random members + * @see valkey.io + * @since Valkey 6.2.0 + */ + public List zrandmemberWithCount(String key, long count) { + return executeCommandWithGlide( + "ZRANDMEMBER", () -> Arrays.asList(glideClient.zrandmemberWithCount(key, count).get())); + } + + /** + * Returns random members from the sorted set (binary version). + * + * @param key the key of the sorted set + * @param count the number of members to return (negative values allow duplicates) + * @return an array of random members + */ + public List zrandmemberWithCount(byte[] key, long count) { + return executeCommandWithGlide( + "ZRANDMEMBER", + () -> { + GlideString[] result = glideClient.zrandmemberWithCount(GlideString.of(key), count).get(); + return Arrays.stream(result).map(GlideString::getBytes).collect(Collectors.toList()); + }); + } + /** * Removes and returns up to count random members from the set stored at key. * @@ -7507,6 +9142,30 @@ public Set spop(final byte[] key, final long count) { }); } + /** + * Returns random members from the sorted set with their scores. + * + * @param key the key of the sorted set + * @param count the number of members to return (negative values allow duplicates) + * @return a list of Tuples (element-score pairs) + * @see valkey.io + * @since Valkey 6.2.0 + */ + public List zrandmemberWithCountWithScores(String key, long count) { + return executeCommandWithGlide( + "ZRANDMEMBER", + () -> { + Object[][] result = glideClient.zrandmemberWithCountWithScores(key, count).get(); + List tuples = new ArrayList<>(); + for (Object[] pair : result) { + String member = (String) pair[0]; + Double score = (Double) pair[1]; + tuples.add(new Tuple(member, score)); + } + return tuples; + }); + } + /** * Returns one or more random members from the set stored at key. * @@ -7571,6 +9230,29 @@ public List srandmember(final byte[] key, final int count) { }); } + /** + * Returns random members from the sorted set with their scores (binary version). + * + * @param key the key of the sorted set + * @param count the number of members to return (negative values allow duplicates) + * @return a list of Tuples (element-score pairs) + */ + public List zrandmemberWithCountWithScores(byte[] key, long count) { + return executeCommandWithGlide( + "ZRANDMEMBER", + () -> { + Object[][] result = + glideClient.zrandmemberWithCountWithScores(GlideString.of(key), count).get(); + List tuples = new ArrayList<>(); + for (Object[] pair : result) { + GlideString member = (GlideString) pair[0]; + Double score = (Double) pair[1]; + tuples.add(new Tuple(member.getBytes(), score)); + } + return tuples; + }); + } + /** * Moves member from the set at source to the set at destination. * diff --git a/java/jedis-compatibility/src/main/java/redis/clients/jedis/args/SortedSetOption.java b/java/jedis-compatibility/src/main/java/redis/clients/jedis/args/SortedSetOption.java new file mode 100644 index 00000000000..27f1629d5dd --- /dev/null +++ b/java/jedis-compatibility/src/main/java/redis/clients/jedis/args/SortedSetOption.java @@ -0,0 +1,26 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package redis.clients.jedis.args; + +import redis.clients.jedis.util.SafeEncoder; + +/** + * Options for sorted set operations that specify whether to select minimum or maximum scored + * elements. Used in commands like ZMPOP, BZMPOP. + * + *

This enum is compatible with Jedis SortedSetOption. + */ +public enum SortedSetOption implements Rawable { + MIN, + MAX; + + private final byte[] raw; + + private SortedSetOption() { + raw = SafeEncoder.encode(name()); + } + + @Override + public byte[] getRaw() { + return raw; + } +} diff --git a/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZAddParams.java b/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZAddParams.java new file mode 100644 index 00000000000..6b5abb04bd5 --- /dev/null +++ b/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZAddParams.java @@ -0,0 +1,112 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package redis.clients.jedis.params; + +/** + * Parameters for ZADD command. Provides options for controlling ZADD behavior such as NX (only add + * new elements), XX (only update existing elements), GT (only update if new score is greater), LT + * (only update if new score is less), and CH (return number of elements changed instead of added). + * + *

This class is compatible with Jedis ZAddParams and provides the same builder-style API. + */ +public class ZAddParams { + + private Boolean nx; + private Boolean xx; + private Boolean gt; + private Boolean lt; + private Boolean ch; + + public ZAddParams() {} + + public static ZAddParams zAddParams() { + return new ZAddParams(); + } + + /** + * Only set the key if it does not already exist. + * + * @return ZAddParams + */ + public ZAddParams nx() { + this.nx = true; + return this; + } + + /** + * Only set the key if it already exists. + * + * @return ZAddParams + */ + public ZAddParams xx() { + this.xx = true; + return this; + } + + /** + * Only update existing elements if the new score is greater than the current score. + * + * @return ZAddParams + */ + public ZAddParams gt() { + this.gt = true; + return this; + } + + /** + * Only update existing elements if the new score is less than the current score. + * + * @return ZAddParams + */ + public ZAddParams lt() { + this.lt = true; + return this; + } + + /** + * Modify the return value from the number of new elements added to the total number of elements + * changed. + * + * @return ZAddParams + */ + public ZAddParams ch() { + this.ch = true; + return this; + } + + public Boolean getNx() { + return nx; + } + + public Boolean getXx() { + return xx; + } + + public Boolean getGt() { + return gt; + } + + public Boolean getLt() { + return lt; + } + + public Boolean getCh() { + return ch; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ZAddParams that = (ZAddParams) o; + return java.util.Objects.equals(nx, that.nx) + && java.util.Objects.equals(xx, that.xx) + && java.util.Objects.equals(gt, that.gt) + && java.util.Objects.equals(lt, that.lt) + && java.util.Objects.equals(ch, that.ch); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(nx, xx, gt, lt, ch); + } +} diff --git a/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZIncrByParams.java b/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZIncrByParams.java new file mode 100644 index 00000000000..afac5446f99 --- /dev/null +++ b/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZIncrByParams.java @@ -0,0 +1,67 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package redis.clients.jedis.params; + +/** + * Parameters for ZINCRBY command. In fact, Redis doesn't have parameters for ZINCRBY. Instead Redis + * has INCR parameter for ZADD. + * + *

When users call ZADD with INCR option, its restriction (only one member) and return type is + * same to ZINCRBY. Document page for ZADD also describes INCR option to act like ZINCRBY. So we + * decided to wrap "ZADD with INCR option" to ZINCRBY. + * + *

Works with Redis 3.0.2 and onwards. + * + *

This class is compatible with Jedis ZIncrByParams and provides the same builder-style API. + */ +public class ZIncrByParams { + + private Boolean nx; + private Boolean xx; + + public ZIncrByParams() {} + + public static ZIncrByParams zIncrByParams() { + return new ZIncrByParams(); + } + + /** + * Only set the key if it does not already exist. + * + * @return ZIncrByParams + */ + public ZIncrByParams nx() { + this.nx = true; + return this; + } + + /** + * Only set the key if it already exist. + * + * @return ZIncrByParams + */ + public ZIncrByParams xx() { + this.xx = true; + return this; + } + + public Boolean getNx() { + return nx; + } + + public Boolean getXx() { + return xx; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ZIncrByParams that = (ZIncrByParams) o; + return java.util.Objects.equals(nx, that.nx) && java.util.Objects.equals(xx, that.xx); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(nx, xx); + } +} diff --git a/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZParams.java b/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZParams.java new file mode 100644 index 00000000000..0cdfe5143d5 --- /dev/null +++ b/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZParams.java @@ -0,0 +1,71 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package redis.clients.jedis.params; + +import java.util.ArrayList; +import java.util.List; + +/** + * Parameters for sorted set aggregation operations like ZUNIONSTORE, ZINTERSTORE, ZUNION, ZINTER, + * etc. Provides options for specifying weights and aggregation functions (SUM, MIN, MAX). + * + *

This class is compatible with Jedis ZParams and provides the same builder-style API. + */ +public class ZParams { + + public enum Aggregate { + SUM, + MIN, + MAX + } + + private final List weightsList = new ArrayList<>(); + private Aggregate aggregate; + + public ZParams() {} + + /** + * Set weights for the input sorted sets. + * + * @param weights the weights to apply to each input sorted set + * @return ZParams + */ + public ZParams weights(final double... weights) { + for (final double weight : weights) { + weightsList.add(weight); + } + return this; + } + + /** + * Set the aggregation function to use when combining scores. + * + * @param aggregate the aggregation function (SUM, MIN, or MAX) + * @return ZParams + */ + public ZParams aggregate(final Aggregate aggregate) { + this.aggregate = aggregate; + return this; + } + + public List getWeights() { + return weightsList; + } + + public Aggregate getAggregate() { + return aggregate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ZParams zParams = (ZParams) o; + return java.util.Objects.equals(weightsList, zParams.weightsList) + && aggregate == zParams.aggregate; + } + + @Override + public int hashCode() { + return java.util.Objects.hash(weightsList, aggregate); + } +} diff --git a/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZRangeParams.java b/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZRangeParams.java new file mode 100644 index 00000000000..710f57f98df --- /dev/null +++ b/java/jedis-compatibility/src/main/java/redis/clients/jedis/params/ZRangeParams.java @@ -0,0 +1,135 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package redis.clients.jedis.params; + +/** + * Parameters for ZRANGE command. Provides options for controlling ZRANGE behavior such as BYSCORE, + * BYLEX, REV (reverse order), and LIMIT (pagination). + * + *

This class is compatible with Jedis ZRangeParams and provides the same builder-style API. + */ +public class ZRangeParams { + + public enum ZRangeBy { + INDEX, + SCORE, + LEX + } + + private final ZRangeBy by; + private final Object min; + private final Object max; + private boolean rev = false; + private boolean limit = false; + private int offset; + private int count; + + private ZRangeParams() { + throw new InstantiationError("Empty constructor must not be called."); + } + + public ZRangeParams(int min, int max) { + this.by = ZRangeBy.INDEX; + this.min = min; + this.max = max; + } + + public static ZRangeParams zrangeParams(int min, int max) { + return new ZRangeParams(min, max); + } + + public ZRangeParams(double min, double max) { + this.by = ZRangeBy.SCORE; + this.min = min; + this.max = max; + } + + public static ZRangeParams zrangeByScoreParams(double min, double max) { + return new ZRangeParams(min, max); + } + + private ZRangeParams(ZRangeBy by, Object min, Object max) { + if (by == null || by == ZRangeBy.SCORE || by == ZRangeBy.LEX) { + // ok + } else { + throw new IllegalArgumentException(by.name() + " is not a valid ZRANGE type argument."); + } + this.by = by; + this.min = min; + this.max = max; + } + + public ZRangeParams(ZRangeBy by, String min, String max) { + this(by, (Object) min, (Object) max); + } + + public ZRangeParams(ZRangeBy by, byte[] min, byte[] max) { + this(by, (Object) min, (Object) max); + } + + public static ZRangeParams zrangeByLexParams(String min, String max) { + return new ZRangeParams(ZRangeBy.LEX, min, max); + } + + public static ZRangeParams zrangeByLexParams(byte[] min, byte[] max) { + return new ZRangeParams(ZRangeBy.LEX, min, max); + } + + public ZRangeParams rev() { + this.rev = true; + return this; + } + + public ZRangeParams limit(int offset, int count) { + this.limit = true; + this.offset = offset; + this.count = count; + return this; + } + + public ZRangeBy getBy() { + return by; + } + + public Object getMin() { + return min; + } + + public Object getMax() { + return max; + } + + public boolean isRev() { + return rev; + } + + public boolean hasLimit() { + return limit; + } + + public int getOffset() { + return offset; + } + + public int getCount() { + return count; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ZRangeParams that = (ZRangeParams) o; + return rev == that.rev + && limit == that.limit + && offset == that.offset + && count == that.count + && by == that.by + && java.util.Objects.equals(min, that.min) + && java.util.Objects.equals(max, that.max); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(by, min, max, rev, limit, offset, count); + } +} diff --git a/java/jedis-compatibility/src/main/java/redis/clients/jedis/resps/Tuple.java b/java/jedis-compatibility/src/main/java/redis/clients/jedis/resps/Tuple.java index 041309102e0..a9ab1a3d6ff 100644 --- a/java/jedis-compatibility/src/main/java/redis/clients/jedis/resps/Tuple.java +++ b/java/jedis-compatibility/src/main/java/redis/clients/jedis/resps/Tuple.java @@ -1,45 +1,99 @@ /** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ package redis.clients.jedis.resps; +import java.util.Arrays; +import java.util.Objects; +import redis.clients.jedis.util.SafeEncoder; + /** - * Tuple compatibility class for Valkey GLIDE wrapper. Represents a scored set element with element - * and score. + * Tuple compatibility class for Valkey GLIDE wrapper. Represents a sorted set element with element + * and score. Supports both String and binary (byte[]) element representations for full Jedis + * compatibility. */ -public class Tuple { +public class Tuple implements Comparable { + + private byte[] element; + private Double score; - private final String element; - private final double score; + public Tuple(String element, Double score) { + this(SafeEncoder.encode(element), score); + } - public Tuple(String element, double score) { + public Tuple(byte[] element, Double score) { + super(); this.element = element; this.score = score; } - public String getElement() { - return element; + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result; + if (null != element) { + for (final byte b : element) { + result = prime * result + b; + } + } + long temp = Double.doubleToLongBits(score); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; } - public double getScore() { - return score; + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (obj == this) return true; + if (!(obj instanceof Tuple)) return false; + + Tuple other = (Tuple) obj; + if (!Arrays.equals(element, other.element)) return false; + return Objects.equals(score, other.score); } @Override - public String toString() { - return "Tuple{element='" + element + "', score=" + score + "}"; + public int compareTo(Tuple other) { + return compare(this, other); } - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; + public static int compare(Tuple t1, Tuple t2) { + int compScore = Double.compare(t1.score, t2.score); + if (compScore != 0) return compScore; - Tuple tuple = (Tuple) obj; - return Double.compare(tuple.score, score) == 0 - && java.util.Objects.equals(element, tuple.element); + return compareByteArrays(t1.element, t2.element); + } + + private static int compareByteArrays(byte[] a, byte[] b) { + if (a == b) return 0; + if (a == null) return -1; + if (b == null) return 1; + + int minLength = Math.min(a.length, b.length); + for (int i = 0; i < minLength; i++) { + int cmp = Byte.compare(a[i], b[i]); + if (cmp != 0) return cmp; + } + return Integer.compare(a.length, b.length); + } + + public String getElement() { + if (null != element) { + return SafeEncoder.encode(element); + } else { + return null; + } + } + + public byte[] getBinaryElement() { + return element; + } + + public double getScore() { + return score; } @Override - public int hashCode() { - return java.util.Objects.hash(element, score); + public String toString() { + return '[' + SafeEncoder.encode(element) + ',' + score + ']'; } } diff --git a/java/jedis-compatibility/src/test/java/redis/clients/jedis/JedisMethodsTest.java b/java/jedis-compatibility/src/test/java/redis/clients/jedis/JedisMethodsTest.java index 07e6fda5dd7..3c3e54d9663 100644 --- a/java/jedis-compatibility/src/test/java/redis/clients/jedis/JedisMethodsTest.java +++ b/java/jedis-compatibility/src/test/java/redis/clients/jedis/JedisMethodsTest.java @@ -4,10 +4,8 @@ import static org.junit.jupiter.api.Assertions.*; import java.lang.reflect.Method; -import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; -import redis.clients.jedis.resps.AccessControlUser; /** * Unit tests for Jedis method signatures and API contracts. Tests that required methods exist with @@ -129,59 +127,524 @@ public void testJedisStateManagementMethods() throws NoSuchMethodException { } @Test - public void testAclMethodSignatures() throws NoSuchMethodException { + public void testSortedSetMethodSignatures() throws NoSuchMethodException { Class jedisClass = Jedis.class; - Method aclList = jedisClass.getMethod("aclList"); - assertEquals(List.class, aclList.getReturnType()); + // Test zadd methods + Method zaddSingle = jedisClass.getMethod("zadd", String.class, double.class, String.class); + assertEquals(long.class, zaddSingle.getReturnType()); - Method aclGetUser = jedisClass.getMethod("aclGetUser", String.class); - assertEquals(AccessControlUser.class, aclGetUser.getReturnType()); + Method zaddMap = jedisClass.getMethod("zadd", String.class, java.util.Map.class); + assertEquals(long.class, zaddMap.getReturnType()); - Method aclSetUserNoRules = jedisClass.getMethod("aclSetUser", String.class); - assertEquals(String.class, aclSetUserNoRules.getReturnType()); + Method zaddBinary = jedisClass.getMethod("zadd", byte[].class, double.class, byte[].class); + assertEquals(long.class, zaddBinary.getReturnType()); - Method aclSetUserWithRules = jedisClass.getMethod("aclSetUser", String.class, String[].class); - assertEquals(String.class, aclSetUserWithRules.getReturnType()); + // Test zaddIncr methods + Method zaddIncr = jedisClass.getMethod("zaddIncr", String.class, double.class, String.class); + assertEquals(double.class, zaddIncr.getReturnType()); - Method aclDelUser = jedisClass.getMethod("aclDelUser", String[].class); - assertEquals(long.class, aclDelUser.getReturnType()); + Method zaddIncrBinary = + jedisClass.getMethod("zaddIncr", byte[].class, double.class, byte[].class); + assertEquals(double.class, zaddIncrBinary.getReturnType()); - Method aclCatNoArg = jedisClass.getMethod("aclCat"); - assertEquals(List.class, aclCatNoArg.getReturnType()); + // Test zrem methods + Method zrem = jedisClass.getMethod("zrem", String.class, String[].class); + assertEquals(long.class, zrem.getReturnType()); - Method aclCatCategory = jedisClass.getMethod("aclCat", String.class); - assertEquals(List.class, aclCatCategory.getReturnType()); + Method zremBinary = jedisClass.getMethod("zrem", byte[].class, byte[][].class); + assertEquals(long.class, zremBinary.getReturnType()); - Method aclGenPassNoArg = jedisClass.getMethod("aclGenPass"); - assertEquals(String.class, aclGenPassNoArg.getReturnType()); + // Test zcard methods + Method zcard = jedisClass.getMethod("zcard", String.class); + assertEquals(long.class, zcard.getReturnType()); - Method aclGenPassBits = jedisClass.getMethod("aclGenPass", int.class); - assertEquals(String.class, aclGenPassBits.getReturnType()); + Method zcardBinary = jedisClass.getMethod("zcard", byte[].class); + assertEquals(long.class, zcardBinary.getReturnType()); - Method aclLogNoArg = jedisClass.getMethod("aclLog"); - assertEquals(List.class, aclLogNoArg.getReturnType()); + // Test zscore methods + Method zscore = jedisClass.getMethod("zscore", String.class, String.class); + assertEquals(Double.class, zscore.getReturnType()); - Method aclLogCount = jedisClass.getMethod("aclLog", int.class); - assertEquals(List.class, aclLogCount.getReturnType()); + Method zscoreBinary = jedisClass.getMethod("zscore", byte[].class, byte[].class); + assertEquals(Double.class, zscoreBinary.getReturnType()); - Method aclLogReset = jedisClass.getMethod("aclLogReset"); - assertEquals(String.class, aclLogReset.getReturnType()); + // Test zmscore methods + Method zmscore = jedisClass.getMethod("zmscore", String.class, String[].class); + assertEquals(java.util.List.class, zmscore.getReturnType()); - Method aclWhoAmI = jedisClass.getMethod("aclWhoAmI"); - assertEquals(String.class, aclWhoAmI.getReturnType()); + Method zmscoreBinary = jedisClass.getMethod("zmscore", byte[].class, byte[][].class); + assertEquals(java.util.List.class, zmscoreBinary.getReturnType()); - Method aclUsers = jedisClass.getMethod("aclUsers"); - assertEquals(List.class, aclUsers.getReturnType()); + // Test zrange methods + Method zrange = jedisClass.getMethod("zrange", String.class, long.class, long.class); + assertEquals(java.util.List.class, zrange.getReturnType()); - Method aclSave = jedisClass.getMethod("aclSave"); - assertEquals(String.class, aclSave.getReturnType()); + Method zrangeBinary = jedisClass.getMethod("zrange", byte[].class, long.class, long.class); + assertEquals(java.util.List.class, zrangeBinary.getReturnType()); - Method aclLoad = jedisClass.getMethod("aclLoad"); - assertEquals(String.class, aclLoad.getReturnType()); + // Test zrangeWithScores methods + Method zrangeWithScores = + jedisClass.getMethod("zrangeWithScores", String.class, long.class, long.class); + assertEquals(java.util.List.class, zrangeWithScores.getReturnType()); - Method aclDryRun = - jedisClass.getMethod("aclDryRun", String.class, String.class, String[].class); - assertEquals(String.class, aclDryRun.getReturnType()); + Method zrangeWithScoresBinary = + jedisClass.getMethod("zrangeWithScores", byte[].class, long.class, long.class); + assertEquals(java.util.List.class, zrangeWithScoresBinary.getReturnType()); + + // Test zrank methods + Method zrank = jedisClass.getMethod("zrank", String.class, String.class); + assertEquals(Long.class, zrank.getReturnType()); + + Method zrankBinary = jedisClass.getMethod("zrank", byte[].class, byte[].class); + assertEquals(Long.class, zrankBinary.getReturnType()); + + // Test zrevrank methods + Method zrevrank = jedisClass.getMethod("zrevrank", String.class, String.class); + assertEquals(Long.class, zrevrank.getReturnType()); + + Method zrevrankBinary = jedisClass.getMethod("zrevrank", byte[].class, byte[].class); + assertEquals(Long.class, zrevrankBinary.getReturnType()); + + // Test zcount methods + Method zcount = jedisClass.getMethod("zcount", String.class, double.class, double.class); + assertEquals(long.class, zcount.getReturnType()); + + Method zcountBinary = jedisClass.getMethod("zcount", byte[].class, double.class, double.class); + assertEquals(long.class, zcountBinary.getReturnType()); + + // Test zincrby methods + Method zincrby = jedisClass.getMethod("zincrby", String.class, double.class, String.class); + assertEquals(double.class, zincrby.getReturnType()); + + Method zincrbyBinary = + jedisClass.getMethod("zincrby", byte[].class, double.class, byte[].class); + assertEquals(double.class, zincrbyBinary.getReturnType()); + + // Test zpopmin methods + Method zpopmin = jedisClass.getMethod("zpopmin", String.class, int.class); + assertEquals(java.util.List.class, zpopmin.getReturnType()); + + Method zpopminBinary = jedisClass.getMethod("zpopmin", byte[].class, int.class); + assertEquals(java.util.List.class, zpopminBinary.getReturnType()); + + // Test zpopmax methods + Method zpopmax = jedisClass.getMethod("zpopmax", String.class, int.class); + assertEquals(java.util.List.class, zpopmax.getReturnType()); + + Method zpopmaxBinary = jedisClass.getMethod("zpopmax", byte[].class, int.class); + assertEquals(java.util.List.class, zpopmaxBinary.getReturnType()); + + // Test zunionstore methods + Method zunionstore = jedisClass.getMethod("zunionstore", String.class, String[].class); + assertEquals(long.class, zunionstore.getReturnType()); + + Method zunionstoreBinary = jedisClass.getMethod("zunionstore", byte[].class, byte[][].class); + assertEquals(long.class, zunionstoreBinary.getReturnType()); + + // Test zinterstore methods + Method zinterstore = jedisClass.getMethod("zinterstore", String.class, String[].class); + assertEquals(long.class, zinterstore.getReturnType()); + + Method zinterstoreBinary = jedisClass.getMethod("zinterstore", byte[].class, byte[][].class); + assertEquals(long.class, zinterstoreBinary.getReturnType()); + + // Test zremrangebyrank methods + Method zremrangebyrank = + jedisClass.getMethod("zremrangebyrank", String.class, long.class, long.class); + assertEquals(long.class, zremrangebyrank.getReturnType()); + + Method zremrangebyrankBinary = + jedisClass.getMethod("zremrangebyrank", byte[].class, long.class, long.class); + assertEquals(long.class, zremrangebyrankBinary.getReturnType()); + + // Test zremrangebyscore methods + Method zremrangebyscore = + jedisClass.getMethod("zremrangebyscore", String.class, double.class, double.class); + assertEquals(long.class, zremrangebyscore.getReturnType()); + + Method zremrangebyscoreBinary = + jedisClass.getMethod("zremrangebyscore", byte[].class, double.class, double.class); + assertEquals(long.class, zremrangebyscoreBinary.getReturnType()); + + // Test zscan methods + Method zscan = jedisClass.getMethod("zscan", String.class, String.class); + assertEquals(redis.clients.jedis.resps.ScanResult.class, zscan.getReturnType()); + + Method zscanBinary = jedisClass.getMethod("zscan", byte[].class, byte[].class); + assertEquals(redis.clients.jedis.resps.ScanResult.class, zscanBinary.getReturnType()); + + // Test zrangestore methods + Method zrangestore = + jedisClass.getMethod("zrangestore", String.class, String.class, long.class, long.class); + assertEquals(Long.class, zrangestore.getReturnType()); + + Method zrangestoreBinary = + jedisClass.getMethod("zrangestore", byte[].class, byte[].class, long.class, long.class); + assertEquals(Long.class, zrangestoreBinary.getReturnType()); + + // Test zrankWithScore methods + Method zrankWithScore = jedisClass.getMethod("zrankWithScore", String.class, String.class); + assertEquals(redis.clients.jedis.resps.KeyValue.class, zrankWithScore.getReturnType()); + + Method zrankWithScoreBinary = + jedisClass.getMethod("zrankWithScore", byte[].class, byte[].class); + assertEquals(redis.clients.jedis.resps.KeyValue.class, zrankWithScoreBinary.getReturnType()); + + // Test zrevrankWithScore methods + Method zrevrankWithScore = + jedisClass.getMethod("zrevrankWithScore", String.class, String.class); + assertEquals(redis.clients.jedis.resps.KeyValue.class, zrevrankWithScore.getReturnType()); + + Method zrevrankWithScoreBinary = + jedisClass.getMethod("zrevrankWithScore", byte[].class, byte[].class); + assertEquals(redis.clients.jedis.resps.KeyValue.class, zrevrankWithScoreBinary.getReturnType()); + + // Test zlexcount methods + Method zlexcount = jedisClass.getMethod("zlexcount", String.class, String.class, String.class); + assertEquals(Long.class, zlexcount.getReturnType()); + + Method zlexcountBinary = + jedisClass.getMethod("zlexcount", byte[].class, byte[].class, byte[].class); + assertEquals(Long.class, zlexcountBinary.getReturnType()); + + // Test bzpopmin methods + Method bzpopmin = jedisClass.getMethod("bzpopmin", double.class, String[].class); + assertEquals(redis.clients.jedis.resps.KeyValue.class, bzpopmin.getReturnType()); + + Method bzpopminBinary = jedisClass.getMethod("bzpopmin", double.class, byte[][].class); + assertEquals(redis.clients.jedis.resps.KeyValue.class, bzpopminBinary.getReturnType()); + + // Test bzpopmax methods + Method bzpopmax = jedisClass.getMethod("bzpopmax", double.class, String[].class); + assertEquals(redis.clients.jedis.resps.KeyValue.class, bzpopmax.getReturnType()); + + Method bzpopmaxBinary = jedisClass.getMethod("bzpopmax", double.class, byte[][].class); + assertEquals(redis.clients.jedis.resps.KeyValue.class, bzpopmaxBinary.getReturnType()); + + // Test zdiff methods + Method zdiff = jedisClass.getMethod("zdiff", String[].class); + assertEquals(java.util.List.class, zdiff.getReturnType()); + + Method zdiffBinary = jedisClass.getMethod("zdiff", byte[][].class); + assertEquals(java.util.List.class, zdiffBinary.getReturnType()); + + // Test zdiffWithScores methods + Method zdiffWithScores = jedisClass.getMethod("zdiffWithScores", String[].class); + assertEquals(java.util.List.class, zdiffWithScores.getReturnType()); + + Method zdiffWithScoresBinary = jedisClass.getMethod("zdiffWithScores", byte[][].class); + assertEquals(java.util.List.class, zdiffWithScoresBinary.getReturnType()); + + // Test zdiffstore methods + Method zdiffstore = jedisClass.getMethod("zdiffstore", String.class, String[].class); + assertEquals(Long.class, zdiffstore.getReturnType()); + + Method zdiffstoreBinary = jedisClass.getMethod("zdiffstore", byte[].class, byte[][].class); + assertEquals(Long.class, zdiffstoreBinary.getReturnType()); + + // Test zunion methods + Method zunion = jedisClass.getMethod("zunion", String[].class); + assertEquals(java.util.List.class, zunion.getReturnType()); + + Method zunionBinary = jedisClass.getMethod("zunion", byte[][].class); + assertEquals(java.util.List.class, zunionBinary.getReturnType()); + + // Test zunionWithScores methods + Method zunionWithScores = jedisClass.getMethod("zunionWithScores", String[].class); + assertEquals(java.util.List.class, zunionWithScores.getReturnType()); + + Method zunionWithScoresBinary = jedisClass.getMethod("zunionWithScores", byte[][].class); + assertEquals(java.util.List.class, zunionWithScoresBinary.getReturnType()); + + // Test zinter methods + Method zinter = jedisClass.getMethod("zinter", String[].class); + assertEquals(java.util.List.class, zinter.getReturnType()); + + Method zinterBinary = jedisClass.getMethod("zinter", byte[][].class); + assertEquals(java.util.List.class, zinterBinary.getReturnType()); + + // Test zinterWithScores methods + Method zinterWithScores = jedisClass.getMethod("zinterWithScores", String[].class); + assertEquals(java.util.List.class, zinterWithScores.getReturnType()); + + Method zinterWithScoresBinary = jedisClass.getMethod("zinterWithScores", byte[][].class); + assertEquals(java.util.List.class, zinterWithScoresBinary.getReturnType()); + + // Test zintercard methods + Method zintercard = jedisClass.getMethod("zintercard", String[].class); + assertEquals(long.class, zintercard.getReturnType()); + + Method zintercardBinary = jedisClass.getMethod("zintercard", byte[][].class); + assertEquals(long.class, zintercardBinary.getReturnType()); + + // Test zmpop methods (binary only, uses SortedSetOption) + Method zmpop = + jedisClass.getMethod( + "zmpop", redis.clients.jedis.args.SortedSetOption.class, byte[][].class); + assertEquals(redis.clients.jedis.resps.KeyValue.class, zmpop.getReturnType()); + + // Test bzmpop methods (binary only, uses SortedSetOption) + Method bzmpop = + jedisClass.getMethod( + "bzmpop", double.class, redis.clients.jedis.args.SortedSetOption.class, byte[][].class); + assertEquals(redis.clients.jedis.resps.KeyValue.class, bzmpop.getReturnType()); + + // Test zremrangebylex methods + Method zremrangebylex = + jedisClass.getMethod("zremrangebylex", String.class, String.class, String.class); + assertEquals(Long.class, zremrangebylex.getReturnType()); + + Method zremrangebylexBinary = + jedisClass.getMethod("zremrangebylex", byte[].class, byte[].class, byte[].class); + assertEquals(Long.class, zremrangebylexBinary.getReturnType()); + + // Test zrandmember methods + Method zrandmember = jedisClass.getMethod("zrandmember", String.class); + assertEquals(String.class, zrandmember.getReturnType()); + + Method zrandmemberBinary = jedisClass.getMethod("zrandmember", byte[].class); + assertEquals(byte[].class, zrandmemberBinary.getReturnType()); + + // Test zrandmemberWithCount methods + Method zrandmemberWithCount = + jedisClass.getMethod("zrandmemberWithCount", String.class, long.class); + assertEquals(java.util.List.class, zrandmemberWithCount.getReturnType()); + + Method zrandmemberWithCountBinary = + jedisClass.getMethod("zrandmemberWithCount", byte[].class, long.class); + assertEquals(java.util.List.class, zrandmemberWithCountBinary.getReturnType()); + + // Test zrandmemberWithCountWithScores methods + Method zrandmemberWithCountWithScores = + jedisClass.getMethod("zrandmemberWithCountWithScores", String.class, long.class); + assertEquals(java.util.List.class, zrandmemberWithCountWithScores.getReturnType()); + + Method zrandmemberWithCountWithScoresBinary = + jedisClass.getMethod("zrandmemberWithCountWithScores", byte[].class, long.class); + assertEquals(java.util.List.class, zrandmemberWithCountWithScoresBinary.getReturnType()); + } + + @Test + public void testSetMethodSignatures() throws NoSuchMethodException { + Class jedisClass = Jedis.class; + + Method saddString = jedisClass.getMethod("sadd", String.class, String[].class); + assertEquals(long.class, saddString.getReturnType()); + + Method saddBinary = jedisClass.getMethod("sadd", byte[].class, byte[][].class); + assertEquals(long.class, saddBinary.getReturnType()); + + Method sremString = jedisClass.getMethod("srem", String.class, String[].class); + assertEquals(long.class, sremString.getReturnType()); + + Method sremBinary = jedisClass.getMethod("srem", byte[].class, byte[][].class); + assertEquals(long.class, sremBinary.getReturnType()); + + Method smembersString = jedisClass.getMethod("smembers", String.class); + assertEquals(Set.class, smembersString.getReturnType()); + + Method smembersBinary = jedisClass.getMethod("smembers", byte[].class); + assertEquals(Set.class, smembersBinary.getReturnType()); + } + + @Test + public void saddStringSignatureAndReturnType() throws NoSuchMethodException { + Method m = Jedis.class.getMethod("sadd", String.class, String[].class); + assertEquals(long.class, m.getReturnType()); + assertEquals(2, m.getParameterCount()); + assertEquals(String.class, m.getParameterTypes()[0]); + assertEquals(String[].class, m.getParameterTypes()[1]); + } + + @Test + public void saddBinarySignatureAndReturnType() throws NoSuchMethodException { + Method m = Jedis.class.getMethod("sadd", byte[].class, byte[][].class); + assertEquals(long.class, m.getReturnType()); + assertEquals(2, m.getParameterCount()); + assertEquals(byte[].class, m.getParameterTypes()[0]); + assertEquals(byte[][].class, m.getParameterTypes()[1]); + } + + @Test + public void sremStringSignatureAndReturnType() throws NoSuchMethodException { + Method m = Jedis.class.getMethod("srem", String.class, String[].class); + assertEquals(long.class, m.getReturnType()); + assertEquals(2, m.getParameterCount()); + assertEquals(String.class, m.getParameterTypes()[0]); + assertEquals(String[].class, m.getParameterTypes()[1]); + } + + @Test + public void sremBinarySignatureAndReturnType() throws NoSuchMethodException { + Method m = Jedis.class.getMethod("srem", byte[].class, byte[][].class); + assertEquals(long.class, m.getReturnType()); + assertEquals(2, m.getParameterCount()); + assertEquals(byte[].class, m.getParameterTypes()[0]); + assertEquals(byte[][].class, m.getParameterTypes()[1]); + } + + @Test + public void smembersStringSignatureAndReturnType() throws NoSuchMethodException { + Method m = Jedis.class.getMethod("smembers", String.class); + assertEquals(Set.class, m.getReturnType()); + assertEquals(1, m.getParameterCount()); + assertEquals(String.class, m.getParameterTypes()[0]); + } + + @Test + public void smembersBinarySignatureAndReturnType() throws NoSuchMethodException { + Method m = Jedis.class.getMethod("smembers", byte[].class); + assertEquals(Set.class, m.getReturnType()); + assertEquals(1, m.getParameterCount()); + assertEquals(byte[].class, m.getParameterTypes()[0]); + } + + @Test + public void scardMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("scard", String.class)); + assertNotNull(Jedis.class.getMethod("scard", byte[].class)); + assertEquals(long.class, Jedis.class.getMethod("scard", String.class).getReturnType()); + assertEquals(long.class, Jedis.class.getMethod("scard", byte[].class).getReturnType()); + } + + @Test + public void sismemberMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("sismember", String.class, String.class)); + assertNotNull(Jedis.class.getMethod("sismember", byte[].class, byte[].class)); + assertEquals( + boolean.class, + Jedis.class.getMethod("sismember", String.class, String.class).getReturnType()); + assertEquals( + boolean.class, + Jedis.class.getMethod("sismember", byte[].class, byte[].class).getReturnType()); + } + + @Test + public void smismemberMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("smismember", String.class, String[].class)); + assertNotNull(Jedis.class.getMethod("smismember", byte[].class, byte[][].class)); + assertEquals( + java.util.List.class, + Jedis.class.getMethod("smismember", String.class, String[].class).getReturnType()); + assertEquals( + java.util.List.class, + Jedis.class.getMethod("smismember", byte[].class, byte[][].class).getReturnType()); + } + + @Test + public void spopMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("spop", String.class)); + assertNotNull(Jedis.class.getMethod("spop", byte[].class)); + assertNotNull(Jedis.class.getMethod("spop", String.class, long.class)); + assertNotNull(Jedis.class.getMethod("spop", byte[].class, long.class)); + } + + @Test + public void srandmemberMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("srandmember", String.class)); + assertNotNull(Jedis.class.getMethod("srandmember", byte[].class)); + assertNotNull(Jedis.class.getMethod("srandmember", String.class, int.class)); + assertNotNull(Jedis.class.getMethod("srandmember", byte[].class, int.class)); + } + + @Test + public void smoveMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("smove", String.class, String.class, String.class)); + assertNotNull(Jedis.class.getMethod("smove", byte[].class, byte[].class, byte[].class)); + assertEquals( + long.class, + Jedis.class.getMethod("smove", String.class, String.class, String.class).getReturnType()); + assertEquals( + long.class, + Jedis.class.getMethod("smove", byte[].class, byte[].class, byte[].class).getReturnType()); + } + + @Test + public void sinterMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("sinter", String[].class)); + assertNotNull(Jedis.class.getMethod("sinter", byte[][].class)); + assertEquals(Set.class, Jedis.class.getMethod("sinter", String[].class).getReturnType()); + assertEquals(Set.class, Jedis.class.getMethod("sinter", byte[][].class).getReturnType()); + } + + @Test + public void sunionMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("sunion", String[].class)); + assertNotNull(Jedis.class.getMethod("sunion", byte[][].class)); + assertEquals(Set.class, Jedis.class.getMethod("sunion", String[].class).getReturnType()); + assertEquals(Set.class, Jedis.class.getMethod("sunion", byte[][].class).getReturnType()); + } + + @Test + public void sdiffMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("sdiff", String[].class)); + assertNotNull(Jedis.class.getMethod("sdiff", byte[][].class)); + assertEquals(Set.class, Jedis.class.getMethod("sdiff", String[].class).getReturnType()); + assertEquals(Set.class, Jedis.class.getMethod("sdiff", byte[][].class).getReturnType()); + } + + @Test + public void sintercardMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("sintercard", String[].class)); + assertNotNull(Jedis.class.getMethod("sintercard", long.class, String[].class)); + assertNotNull(Jedis.class.getMethod("sintercard", byte[][].class)); + assertNotNull(Jedis.class.getMethod("sintercard", long.class, byte[][].class)); + assertEquals(long.class, Jedis.class.getMethod("sintercard", String[].class).getReturnType()); + assertEquals( + long.class, + Jedis.class.getMethod("sintercard", long.class, String[].class).getReturnType()); + assertEquals(long.class, Jedis.class.getMethod("sintercard", byte[][].class).getReturnType()); + assertEquals( + long.class, + Jedis.class.getMethod("sintercard", long.class, byte[][].class).getReturnType()); + } + + @Test + public void sinterstoreMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("sinterstore", String.class, String[].class)); + assertNotNull(Jedis.class.getMethod("sinterstore", byte[].class, byte[][].class)); + assertEquals( + long.class, + Jedis.class.getMethod("sinterstore", String.class, String[].class).getReturnType()); + assertEquals( + long.class, + Jedis.class.getMethod("sinterstore", byte[].class, byte[][].class).getReturnType()); + } + + @Test + public void sunionstoreMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("sunionstore", String.class, String[].class)); + assertNotNull(Jedis.class.getMethod("sunionstore", byte[].class, byte[][].class)); + assertEquals( + long.class, + Jedis.class.getMethod("sunionstore", String.class, String[].class).getReturnType()); + assertEquals( + long.class, + Jedis.class.getMethod("sunionstore", byte[].class, byte[][].class).getReturnType()); + } + + @Test + public void sdiffstoreMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("sdiffstore", String.class, String[].class)); + assertNotNull(Jedis.class.getMethod("sdiffstore", byte[].class, byte[][].class)); + assertEquals( + long.class, + Jedis.class.getMethod("sdiffstore", String.class, String[].class).getReturnType()); + assertEquals( + long.class, + Jedis.class.getMethod("sdiffstore", byte[].class, byte[][].class).getReturnType()); + } + + @Test + public void sscanMethodsExist() throws NoSuchMethodException { + assertNotNull(Jedis.class.getMethod("sscan", String.class, String.class)); + assertNotNull( + Jedis.class.getMethod( + "sscan", String.class, String.class, redis.clients.jedis.params.ScanParams.class)); + assertNotNull(Jedis.class.getMethod("sscan", byte[].class, byte[].class)); + assertNotNull( + Jedis.class.getMethod( + "sscan", byte[].class, byte[].class, redis.clients.jedis.params.ScanParams.class)); } }