diff --git a/.github/wordlist.txt b/.github/wordlist.txt index ee74a49a51..3c522108fd 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -1,8 +1,8 @@ !!!Spelling check failed!!! APM ARGV -BaseObjectPoolConfig BFCommands +BaseObjectPoolConfig BitOP BitPosParams BuilderFactory @@ -20,10 +20,10 @@ CoreCommands Dockerfile EVAL EVALSHA -Failback -Failover FTCreateParams FTSearchParams +Failback +Failover GSON GenericObjectPool GenericObjectPoolConfig @@ -54,6 +54,7 @@ ListPosition Ludovico Magnocavallo McCurdy +MkDocs NOSCRIPT NUMPAT NUMPT @@ -65,8 +66,10 @@ OpenTracing Otel POJO POJOs +PoolConfig PubSub Queable +README READONLY RediSearch RediSearchCommands @@ -79,9 +82,11 @@ RedisGraph RedisInstrumentor RedisJSON RedisTimeSeries +Redislabs SHA SSLParameters SSLSocketFactory +SafeEncoder SearchCommands SentinelCommands SentinelConnectionPool @@ -126,6 +131,7 @@ bool boolean booleans bysource +cd charset clientId clientKill @@ -135,6 +141,7 @@ clusterKeySlot configs consumerName consumername +csc cumbersome dbIndex dbSize @@ -159,6 +166,7 @@ geoadd georadiusByMemberStore georadiusStore getbit +github gmail groupname hdel @@ -171,6 +179,7 @@ hset hsetnx hstrlen http +https idx iff incr @@ -178,13 +187,17 @@ incrBy incrByFloat ini io +jedis json +kb keyslot keyspace keysvalues +kllayn kwarg lastName lastsave +learningpath linsert linters llen @@ -197,14 +210,13 @@ makeapullrequest maxLen maxdepth maya +md memberCoordinateMap mget microservice microservices millisecondsTimestamp -MkDocs mkdocs -md mset msetnx multikey @@ -222,16 +234,18 @@ pexpire pexpireAt pfadd pfcount +pipelining pmessage png pre +produsage psubscribe pttl pubsub punsubscribe py pypi -PoolConfig +qsrk quickstart readonly readwrite @@ -242,12 +256,10 @@ reinitialization renamenx replicaof repo -README rpush rpushx runtime sadd -SafeEncoder scard scoreMembers sdiffstore @@ -270,7 +282,6 @@ strlen stunnel subcommands sunionstore -pipelining thevalueofmykey timeseries toctree @@ -289,13 +300,16 @@ url virtualenv waitReplicas whenver +wtd www xack xdel xgroupDelConsumer xgroupDestroy xlen +xpx xtrim +yml zadd zcard zcount @@ -322,4 +336,3 @@ zrevrangeByScore zrevrangeByScoreWithScores zrevrangeWithScores zunionstore -yml diff --git a/docs/tutorials_examples.md b/docs/tutorials_examples.md new file mode 100644 index 0000000000..16b90842ba --- /dev/null +++ b/docs/tutorials_examples.md @@ -0,0 +1,21 @@ +# Tutorials and Examples + +## General + +* Redis for Java Developers: +* Jedis Guide: +* Connecting to a Redis server: +* Using Jedis in production: + +## Client-side Caching + +* Client-side Caching: + +## JSON + +* Store, Read and Search JSON: + +## Search + +* Vector Search: +* Spring Boot Search: diff --git a/mkdocs.yml b/mkdocs.yml index 83000d59c1..679393261b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -30,6 +30,7 @@ nav: - Failover: failover.md - FAQ: faq.md - API Reference: https://www.javadoc.io/doc/redis.clients/jedis/latest/index.html + - Tutorials and Examples: tutorials_examples.md - Jedis Guide: https://redis.io/docs/latest/develop/connect/clients/java/jedis/ - Redis Command Reference: https://redis.io/docs/latest/commands/ diff --git a/pom.xml b/pom.xml index 4060d175be..5b9cf55500 100644 --- a/pom.xml +++ b/pom.xml @@ -49,8 +49,8 @@ redis.clients.jedis 1.7.36 1.7.1 - 2.18.1 - 3.5.1 + 2.18.2 + 3.5.2 @@ -247,7 +247,7 @@ maven-javadoc-plugin - 3.10.1 + 3.11.2 8 false diff --git a/src/main/java/redis/clients/jedis/BuilderFactory.java b/src/main/java/redis/clients/jedis/BuilderFactory.java index adce27f1f4..b1ca7455b9 100644 --- a/src/main/java/redis/clients/jedis/BuilderFactory.java +++ b/src/main/java/redis/clients/jedis/BuilderFactory.java @@ -975,30 +975,8 @@ public Map build(Object data) { } }; - public static final Builder> COMMAND_INFO_RESPONSE = new Builder>() { - @Override - public Map build(Object data) { - if (data == null) { - return null; - } - - List rawList = (List) data; - Map map = new HashMap<>(rawList.size()); - - for (Object rawCommandInfo : rawList) { - if (rawCommandInfo == null) { - continue; - } - - List commandInfo = (List) rawCommandInfo; - String name = STRING.build(commandInfo.get(0)); - CommandInfo info = CommandInfo.COMMAND_INFO_BUILDER.build(commandInfo); - map.put(name, info); - } - - return map; - } - }; + @Deprecated + public static final Builder> COMMAND_INFO_RESPONSE = CommandInfo.COMMAND_INFO_RESPONSE; public static final Builder> LATENCY_LATEST_RESPONSE = new Builder>() { @Override diff --git a/src/main/java/redis/clients/jedis/Jedis.java b/src/main/java/redis/clients/jedis/Jedis.java index b6148189a8..04c8945993 100644 --- a/src/main/java/redis/clients/jedis/Jedis.java +++ b/src/main/java/redis/clients/jedis/Jedis.java @@ -8250,7 +8250,13 @@ public List>> commandGetKeysAndFlags(String... com public Map commandInfo(String... commands) { checkIsInMultiOrPipeline(); connection.sendCommand(COMMAND, joinParameters(Keyword.INFO.name(), commands)); - return BuilderFactory.COMMAND_INFO_RESPONSE.build(connection.getOne()); + return CommandInfo.COMMAND_INFO_RESPONSE.build(connection.getOne()); + } + + public Map command() { + checkIsInMultiOrPipeline(); + connection.sendCommand(COMMAND); + return CommandInfo.COMMAND_INFO_RESPONSE.build(connection.getOne()); } public List commandList() { diff --git a/src/main/java/redis/clients/jedis/resps/AccessControlLogEntry.java b/src/main/java/redis/clients/jedis/resps/AccessControlLogEntry.java index 930c9b064d..922d37fd6a 100644 --- a/src/main/java/redis/clients/jedis/resps/AccessControlLogEntry.java +++ b/src/main/java/redis/clients/jedis/resps/AccessControlLogEntry.java @@ -47,9 +47,10 @@ public AccessControlLogEntry(Map map) { ageSeconds = (Double) map.get(AGE_SECONDS); clientInfo = getMapFromRawClientInfo((String) map.get(CLIENT_INFO)); logEntry = map; - entryId = (long) map.get(ENTRY_ID); - timestampCreated = (long) map.get(TIMESTAMP_CREATED); - timestampLastUpdated = (long) map.get(TIMESTAMP_LAST_UPDATED); + // Redis 7.2 + entryId = map.containsKey(ENTRY_ID) ? (long) map.get(ENTRY_ID) : -1L; + timestampCreated = map.containsKey(TIMESTAMP_CREATED) ? (long) map.get(TIMESTAMP_CREATED) : -1L; + timestampLastUpdated = map.containsKey(TIMESTAMP_LAST_UPDATED) ? (long) map.get(TIMESTAMP_LAST_UPDATED) : -1L; } public long getCount() { diff --git a/src/main/java/redis/clients/jedis/resps/CommandInfo.java b/src/main/java/redis/clients/jedis/resps/CommandInfo.java index 9f3481b5a1..7dd15c8684 100644 --- a/src/main/java/redis/clients/jedis/resps/CommandInfo.java +++ b/src/main/java/redis/clients/jedis/resps/CommandInfo.java @@ -2,12 +2,17 @@ import redis.clients.jedis.Builder; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import static redis.clients.jedis.BuilderFactory.STRING_LIST; import static redis.clients.jedis.BuilderFactory.LONG; +import static redis.clients.jedis.BuilderFactory.STRING; +import static redis.clients.jedis.BuilderFactory.STRING_LIST; public class CommandInfo { + + private final String name; private final long arity; private final List flags; private final long firstKey; @@ -15,10 +20,22 @@ public class CommandInfo { private final long step; private final List aclCategories; private final List tips; - private final List subcommands; + private final Map subcommands; + /** + * THIS IGNORES 'subcommands' parameter. + * @param subcommands WILL BE IGNORED + * @deprecated + */ + @Deprecated public CommandInfo(long arity, List flags, long firstKey, long lastKey, long step, List aclCategories, List tips, List subcommands) { + this((String) null, arity, flags, firstKey, lastKey, step, aclCategories, tips, (Map) null); + } + + private CommandInfo(String name, long arity, List flags, long firstKey, long lastKey, long step, + List aclCategories, List tips, Map subcommands) { + this.name = name; this.arity = arity; this.flags = flags; this.firstKey = firstKey; @@ -29,6 +46,13 @@ public CommandInfo(long arity, List flags, long firstKey, long lastKey, this.subcommands = subcommands; } + /** + * Command name + */ + public String getName() { + return name; + } + /** * Arity is the number of arguments a command expects. It follows a simple pattern: * A positive integer means a fixed number of arguments. @@ -89,25 +113,58 @@ public List getTips() { /** * All the command's subcommands, if any */ - public List getSubcommands() { + public Map getSubcommands() { return subcommands; } public static final Builder COMMAND_INFO_BUILDER = new Builder() { @Override public CommandInfo build(Object data) { + if (data == null) { + return null; + } + List commandData = (List) data; + if (commandData.isEmpty()) { + return null; + } + String name = STRING.build(commandData.get(0)); long arity = LONG.build(commandData.get(1)); List flags = STRING_LIST.build(commandData.get(2)); long firstKey = LONG.build(commandData.get(3)); long lastKey = LONG.build(commandData.get(4)); long step = LONG.build(commandData.get(5)); - List aclCategories = STRING_LIST.build(commandData.get(6)); - List tips = STRING_LIST.build(commandData.get(7)); - List subcommands = STRING_LIST.build(commandData.get(9)); + // Redis 6.0 + List aclCategories = commandData.size() >= 7 ? STRING_LIST.build(commandData.get(6)) : null; + // Redis 7.0 + List tips = commandData.size() >= 8 ? STRING_LIST.build(commandData.get(7)) : null; + Map subcommands = commandData.size() >= 10 + ? COMMAND_INFO_RESPONSE.build(commandData.get(9)) : null; + + return new CommandInfo(name, arity, flags, firstKey, lastKey, step, aclCategories, tips, subcommands); + } + }; - return new CommandInfo(arity, flags, firstKey, lastKey, step, aclCategories, tips, subcommands); + public static final Builder> COMMAND_INFO_RESPONSE = new Builder>() { + @Override + public Map build(Object data) { + if (data == null) { + return null; + } + + List rawList = (List) data; + Map map = new HashMap<>(rawList.size()); + + for (Object rawCommandInfo : rawList) { + CommandInfo info = CommandInfo.COMMAND_INFO_BUILDER.build(rawCommandInfo); + if (info != null) { + map.put(info.getName(), info); + } + } + + return map; } }; + } diff --git a/src/test/java/io/redis/examples/PipeTransExample.java b/src/test/java/io/redis/examples/PipeTransExample.java new file mode 100644 index 0000000000..c4d75fa0e2 --- /dev/null +++ b/src/test/java/io/redis/examples/PipeTransExample.java @@ -0,0 +1,118 @@ +// EXAMPLE: pipe_trans_tutorial +// REMOVE_START +package io.redis.examples; + +import org.junit.Assert; +import org.junit.Test; +// REMOVE_END +import java.util.List; + +import redis.clients.jedis.UnifiedJedis; +import redis.clients.jedis.AbstractPipeline; +import redis.clients.jedis.AbstractTransaction; +import redis.clients.jedis.Response; + +public class PipeTransExample { + @Test + public void run() { + UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379"); + // REMOVE_START + for (int i = 0; i < 5; i++) { + jedis.del(String.format("seat:%d", i)); + } + + jedis.del("counter:1", "counter:2", "counter:3", "shellpath"); + // REMOVE_END + + // STEP_START basic_pipe + AbstractPipeline pipe = jedis.pipelined(); + + for (int i = 0; i < 5; i++) { + pipe.set(String.format("seat:%d", i), String.format("#%d", i)); + } + + pipe.sync(); + + pipe = jedis.pipelined(); + + Response resp0 = pipe.get("seat:0"); + Response resp3 = pipe.get("seat:3"); + Response resp4 = pipe.get("seat:4"); + + pipe.sync(); + + // Responses are available after the pipeline has executed. + System.out.println(resp0.get()); // >>> #0 + System.out.println(resp3.get()); // >>> #3 + System.out.println(resp4.get()); // >>> #4 + // STEP_END + // REMOVE_START + Assert.assertEquals("#0", resp0.get()); + Assert.assertEquals("#3", resp3.get()); + Assert.assertEquals("#4", resp4.get()); + // REMOVE_END + + // STEP_START basic_trans + AbstractTransaction trans = jedis.multi(); + + trans.incrBy("counter:1", 1); + trans.incrBy("counter:2", 2); + trans.incrBy("counter:3", 3); + + trans.exec(); + + System.out.println(jedis.get("counter:1")); // >>> 1 + System.out.println(jedis.get("counter:2")); // >>> 2 + System.out.println(jedis.get("counter:3")); // >>> 3 + // STEP_END + // REMOVE_START + Assert.assertEquals("1", jedis.get("counter:1")); + Assert.assertEquals("2", jedis.get("counter:2")); + Assert.assertEquals("3", jedis.get("counter:3")); + // REMOVE_END + + // STEP_START trans_watch + // Set initial value of `shellpath`. + jedis.set("shellpath", "/usr/syscmds/"); + + // Start the transaction and watch the key we are about to update. + trans = jedis.transaction(false); // create a Transaction object without sending MULTI command + trans.watch("shellpath"); // send WATCH command(s) + trans.multi(); // send MULTI command + + String currentPath = jedis.get("shellpath"); + String newPath = currentPath + ":/usr/mycmds/"; + + // Commands added to the `trans` object + // will be buffered until `trans.exec()` is called. + Response setResult = trans.set("shellpath", newPath); + List transResults = trans.exec(); + + // The `exec()` call returns null if the transaction failed. + if (transResults != null) { + // Responses are available if the transaction succeeded. + System.out.println(setResult.get()); // >>> OK + + // You can also get the results from the list returned by + // `trans.exec()`. + for (Object item: transResults) { + System.out.println(item); + } + // >>> OK + + System.out.println(jedis.get("shellpath")); + // >>> /usr/syscmds/:/usr/mycmds/ + } + // STEP_END + // REMOVE_START + Assert.assertEquals("/usr/syscmds/:/usr/mycmds/", jedis.get("shellpath")); + Assert.assertEquals("OK", setResult.get()); + Assert.assertEquals(1, transResults.size()); + Assert.assertEquals("OK", transResults.get(0).toString()); + // REMOVE_END + + // HIDE_START + jedis.close(); + } +} +// HIDE_END \ No newline at end of file diff --git a/src/test/java/redis/clients/jedis/commands/jedis/ControlCommandsTest.java b/src/test/java/redis/clients/jedis/commands/jedis/ControlCommandsTest.java index 4c9059f8d0..d79eb2086f 100644 --- a/src/test/java/redis/clients/jedis/commands/jedis/ControlCommandsTest.java +++ b/src/test/java/redis/clients/jedis/commands/jedis/ControlCommandsTest.java @@ -501,6 +501,28 @@ public void commandGetKeys() { assertEquals(2, keySandFlags.get(0).getValue().size()); } + + @Test + public void commandNoArgs() { + Map infos = jedis.command(); + + assertThat(infos.size(), greaterThan(0)); + + CommandInfo getInfo = infos.get("get"); + assertEquals(2, getInfo.getArity()); + assertEquals(2, getInfo.getFlags().size()); + assertEquals(1, getInfo.getFirstKey()); + assertEquals(1, getInfo.getLastKey()); + assertEquals(1, getInfo.getStep()); + + assertNull(infos.get("foo")); // non-existing command + + CommandInfo setInfo = infos.get("set"); + assertEquals(3, setInfo.getAclCategories().size()); + assertEquals(0, setInfo.getTips().size()); + assertEquals(0, setInfo.getSubcommands().size()); + } + @Test public void commandInfo() { Map infos = jedis.commandInfo("GET", "foo", "SET"); @@ -520,6 +542,27 @@ public void commandInfo() { assertEquals(0, setInfo.getSubcommands().size()); } + @Test // GitHub Issue #4020 + public void commandInfoAcl() { + Map infos = jedis.commandInfo("ACL"); + assertThat(infos, Matchers.aMapWithSize(1)); + + CommandInfo aclInfo = infos.get("acl"); + assertEquals(-2, aclInfo.getArity()); + assertEquals(0, aclInfo.getFlags().size()); + assertEquals(0, aclInfo.getFirstKey()); + assertEquals(0, aclInfo.getLastKey()); + assertEquals(0, aclInfo.getStep()); + assertEquals(1, aclInfo.getAclCategories().size()); + assertEquals(0, aclInfo.getTips().size()); + assertThat(aclInfo.getSubcommands().size(), Matchers.greaterThanOrEqualTo(13)); + aclInfo.getSubcommands().forEach((name, subcommand) -> { + assertThat(name, Matchers.startsWith("acl|")); + assertNotNull(subcommand); + assertEquals(name, subcommand.getName()); + }); + } + @Test public void commandList() { List commands = jedis.commandList();