diff --git a/src/main/java/io/lettuce/core/masterreplica/ReadOnlyCommands.java b/src/main/java/io/lettuce/core/masterreplica/ReadOnlyCommands.java deleted file mode 100644 index 67616a7ef5..0000000000 --- a/src/main/java/io/lettuce/core/masterreplica/ReadOnlyCommands.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2020-Present, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - * - * This file contains contributions from third-party contributors - * licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.lettuce.core.masterreplica; - -import java.util.Collections; -import java.util.EnumSet; -import java.util.Set; - -import io.lettuce.core.protocol.CommandType; -import io.lettuce.core.protocol.ProtocolKeyword; - -/** - * Contains all command names that are read-only commands. - * - * @author Mark Paluch - */ -class ReadOnlyCommands { - - private static final Set READ_ONLY_COMMANDS = EnumSet.noneOf(CommandType.class); - - static { - for (CommandName commandNames : CommandName.values()) { - READ_ONLY_COMMANDS.add(CommandType.valueOf(commandNames.name())); - } - } - - /** - * @param protocolKeyword must not be {@code null}. - * @return {@code true} if {@link ProtocolKeyword} is a read-only command. - */ - public static boolean isReadOnlyCommand(ProtocolKeyword protocolKeyword) { - return READ_ONLY_COMMANDS.contains(protocolKeyword); - } - - /** - * @return an unmodifiable {@link Set} of {@link CommandType read-only} commands. - */ - public static Set getReadOnlyCommands() { - return Collections.unmodifiableSet(READ_ONLY_COMMANDS); - } - - enum CommandName { - ASKING, BITCOUNT, BITPOS, CLIENT, COMMAND, DUMP, ECHO, EVAL_RO, EVALSHA_RO, EXISTS, FCALL_RO, // - GEODIST, GEOPOS, GEORADIUS, GEORADIUS_RO, GEORADIUSBYMEMBER, GEORADIUSBYMEMBER_RO, GEOSEARCH, GEOHASH, GET, GETBIT, // - GETRANGE, HEXISTS, HGET, HGETALL, HKEYS, HLEN, HMGET, HRANDFIELD, HSCAN, HSTRLEN, // - HVALS, INFO, KEYS, LINDEX, LLEN, LPOS, LRANGE, SORT_RO, MGET, PFCOUNT, PTTL, // - RANDOMKEY, READWRITE, SCAN, SCARD, SCRIPT, // - SDIFF, SINTER, SISMEMBER, SMISMEMBER, SMEMBERS, SRANDMEMBER, SSCAN, STRLEN, // - SUNION, TIME, TTL, TYPE, // - XINFO, XLEN, XPENDING, XRANGE, XREVRANGE, XREAD, // - ZCARD, ZCOUNT, ZLEXCOUNT, ZRANGE, // - ZRANDMEMBER, ZRANGEBYLEX, ZRANGEBYSCORE, ZRANK, ZREVRANGE, ZREVRANGEBYLEX, ZREVRANGEBYSCORE, ZREVRANK, ZSCAN, ZSCORE, - } - -} diff --git a/src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java b/src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java index a6bfc6333f..81671bf761 100644 --- a/src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java +++ b/src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java @@ -71,7 +71,7 @@ public static ReadOnlyPredicate asPredicate() { enum CommandName { ASKING, BITCOUNT, BITPOS, CLIENT, COMMAND, DUMP, ECHO, EVAL_RO, EVALSHA_RO, EXISTS, FCALL_RO, // - GEODIST, GEOPOS, GEORADIUS, GEORADIUS_RO, GEORADIUSBYMEMBER, GEORADIUSBYMEMBER_RO, GEOSEARCH, GEOHASH, GET, GETBIT, // + GEODIST, GEOPOS, GEORADIUS_RO, GEORADIUSBYMEMBER_RO, GEOSEARCH, GEOHASH, GET, GETBIT, // GETRANGE, HEXISTS, HGET, HGETALL, HKEYS, HLEN, HMGET, HRANDFIELD, HSCAN, HSTRLEN, // HVALS, INFO, KEYS, LINDEX, LLEN, LPOS, LRANGE, SORT_RO, MGET, PFCOUNT, PTTL, // RANDOMKEY, READWRITE, SCAN, SCARD, SCRIPT, // diff --git a/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java b/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java index b1f6cad9ff..502198b7ae 100644 --- a/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java +++ b/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java @@ -16,7 +16,7 @@ class ClusterReadOnlyCommandsUnitTests { @Test void testCount() { - assertThat(ClusterReadOnlyCommands.getReadOnlyCommands()).hasSize(86); + assertThat(ClusterReadOnlyCommands.getReadOnlyCommands()).hasSize(84); } @Test diff --git a/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java index 4984914641..2cd1608e4a 100644 --- a/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/HashCommandIntegrationTests.java @@ -561,7 +561,6 @@ void hexpire() { assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue(); assertThat(redis.hexpire(MY_KEY, 1, MY_FIELD, MY_SECOND_FIELD)).containsExactly(1L, -2L); assertThat(redis.hexpire("invalidKey", 1, MY_FIELD)).containsExactly(-2L); - await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null); } @@ -573,7 +572,6 @@ void hexpireExpireArgs() { assertThat(redis.hexpire(MY_KEY, Duration.ofSeconds(1), ExpireArgs.Builder.xx(), MY_FIELD)).containsExactly(1L); assertThat(redis.hexpire(MY_KEY, Duration.ofSeconds(10), ExpireArgs.Builder.gt(), MY_FIELD)).containsExactly(1L); assertThat(redis.hexpire(MY_KEY, Duration.ofSeconds(1), ExpireArgs.Builder.lt(), MY_FIELD)).containsExactly(1L); - await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null); } @@ -585,7 +583,6 @@ void hexpireat() { assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue(); assertThat(redis.hexpireat(MY_KEY, Instant.now().plusSeconds(1), MY_FIELD)).containsExactly(1L); assertThat(redis.hexpireat("invalidKey", Instant.now().plusSeconds(1), MY_FIELD)).containsExactly(-2L); - await().until(() -> redis.hget(MY_KEY, MY_FIELD) == null); } @@ -610,7 +607,6 @@ void hexpiretime() { @EnabledOnCommand("HPERSIST") void hpersist() { assertThat(redis.hpersist(MY_KEY, MY_FIELD)).containsExactly(-2L); - assertThat(redis.hset(MY_KEY, MY_FIELD, MY_VALUE)).isTrue(); assertThat(redis.hpersist(MY_KEY, MY_FIELD)).containsExactly(-1L); diff --git a/src/test/java/io/lettuce/core/commands/ServerCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/ServerCommandIntegrationTests.java index b9be862e79..a64414edf4 100644 --- a/src/test/java/io/lettuce/core/commands/ServerCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/ServerCommandIntegrationTests.java @@ -33,6 +33,8 @@ import javax.inject.Inject; +import io.lettuce.core.cluster.ClusterReadOnlyCommands; +import io.lettuce.core.protocol.ProtocolKeyword; import io.lettuce.test.condition.RedisConditions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -592,6 +594,13 @@ void clientSetinfo() { assertThat(redis.clientInfo().contains("lib-name=lettuce")).isTrue(); } + @Test + void testReadOnlyCommands() { + for (ProtocolKeyword readOnlyCommand : ClusterReadOnlyCommands.getReadOnlyCommands()) { + assertThat(isCommandReadOnly(readOnlyCommand.name())).isTrue(); + } + } + private boolean noSaveInProgress() { String info = redis.info(); @@ -599,4 +608,19 @@ private boolean noSaveInProgress() { return !info.contains("aof_rewrite_in_progress:1") && !info.contains("rdb_bgsave_in_progress:1"); } + private boolean isCommandReadOnly(String commandName) { + List commandInfo = redis.commandInfo(commandName); + if (commandInfo == null || commandInfo.isEmpty()) { + throw new IllegalArgumentException("Command not found: " + commandName); + } + + List details = CommandDetailParser.parse(commandInfo); + if (details.isEmpty()) { + throw new IllegalArgumentException("Command details could not be parsed: " + commandName); + } + + CommandDetail detail = details.get(0); + return !detail.getFlags().contains(CommandDetail.Flag.WRITE); + } + }