Skip to content

Commit 96f250d

Browse files
LeeHyungGeolonobc
authored andcommitted
Add missing RedisCommand enum entries for ZSet WithScores methods.
The rangeWithScores family of methods (zRangeWithScores, zRangeByScoreWithScores, zRevRangeWithScores, zRevRangeByScoreWithScores) were missing from the RedisCommand enumeration, causing ConnectionSplittingInterceptor to resolve them as UNKNOWN commands. This resulted in read-only methods being incorrectly queued in MULTI/EXEC blocks instead of executing immediately on separate connections, causing them to return null in transactional contexts. This commit adds the ZRANGEWITHSCORES, ZRANGEBYSCOREWITHSCORES, ZREVRANGEWITHSCORES, ZREVRANGEBYSCOREWITHSCORES enum entries to RedisCommand.java as well as adds a regression test in TransactionalStringRedisTemplateTests.java. The fix ensures these read-only methods are properly recognized and execute immediately in transactional contexts. Original Pull Request: #3225 Fixes: #3187 Signed-off-by: LeeHyungGeol <[email protected]>
1 parent 028a84f commit 96f250d

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

src/main/java/org/springframework/data/redis/core/RedisCommand.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
* @author Oscar Cai
3636
* @author Sébastien Volle
3737
* @author John Blum
38+
* @author LeeHyungGeol
3839
* @since 1.3
3940
* @link <a href=
4041
* "https://github.com/antirez/redis/blob/843de8b786562d8d77c78d83a971060adc61f77a/src/server.c#L180">Redis
@@ -292,12 +293,16 @@ public enum RedisCommand {
292293
ZINTERSTORE("rw", 3), //
293294
ZRANGE("r", 3), //
294295
ZRANGEBYSCORE("r", 3), //
296+
ZRANGEWITHSCORES("r", 3), //
297+
ZRANGEBYSCOREWITHSCORES("r", 2), //
295298
ZRANK("r", 2, 2), //
296299
ZREM("rw", 2), //
297300
ZREMRANGEBYRANK("rw", 3, 3), //
298301
ZREMRANGEBYSCORE("rw", 3, 3), //
299302
ZREVRANGE("r", 3), //
300303
ZREVRANGEBYSCORE("r", 3), //
304+
ZREVRANGEWITHSCORES("r", 3), //
305+
ZREVRANGEBYSCOREWITHSCORES("r", 2), //
301306
ZREVRANK("r", 2, 2), //
302307
ZSCORE("r", 2, 2), //
303308
ZUNIONSTORE("rw", 3), //

src/test/java/org/springframework/data/redis/core/TransactionalStringRedisTemplateTests.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.sql.SQLException;
2323
import java.util.LinkedHashMap;
2424
import java.util.Map;
25+
import java.util.Set;
2526
import java.util.stream.Stream;
2627

2728
import javax.sql.DataSource;
@@ -42,13 +43,15 @@
4243
import org.springframework.data.redis.connection.RedisConnectionFactory;
4344
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
4445
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
46+
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
4547
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
4648
import org.springframework.transaction.support.TransactionTemplate;
4749

4850
/**
4951
* Transactional integration tests for {@link StringRedisTemplate}.
5052
*
5153
* @author Christoph Strobl
54+
* @author LeeHyungGeol
5255
*/
5356
@ParameterizedClass
5457
@MethodSource("argumentsStream")
@@ -116,6 +119,96 @@ void visibilityDuringManagedTransaction() throws SQLException {
116119
.containsEntry("isMember(inside)", false);
117120
}
118121

122+
@SuppressWarnings("unchecked")
123+
@Test // GH-3187
124+
void allRangeWithScoresMethodsShouldExecuteImmediatelyInTransaction() throws SQLException {
125+
126+
DataSource ds = mock(DataSource.class);
127+
when(ds.getConnection()).thenReturn(mock(Connection.class));
128+
129+
DataSourceTransactionManager txMgr = new DataSourceTransactionManager(ds);
130+
TransactionTemplate txTemplate = new TransactionTemplate(txMgr);
131+
txTemplate.afterPropertiesSet();
132+
133+
// Add data outside transaction
134+
stringTemplate.opsForZSet().add("testzset", "outside1", 1.0);
135+
stringTemplate.opsForZSet().add("testzset", "outside2", 2.0);
136+
137+
Map<String, Object> result = txTemplate.execute(x -> {
138+
Map<String, Object> ops = new LinkedHashMap<>();
139+
140+
// Query data added outside transaction (should execute immediately)
141+
ops.put("rangeWithScores_before",
142+
stringTemplate.opsForZSet().rangeWithScores("testzset", 0, -1));
143+
ops.put("reverseRangeWithScores_before",
144+
stringTemplate.opsForZSet().reverseRangeWithScores("testzset", 0, -1));
145+
ops.put("rangeByScoreWithScores_before",
146+
stringTemplate.opsForZSet().rangeByScoreWithScores("testzset", 1.0, 2.0));
147+
ops.put("reverseRangeByScoreWithScores_before",
148+
stringTemplate.opsForZSet().reverseRangeByScoreWithScores("testzset", 1.0, 2.0));
149+
150+
// Add inside transaction (goes into multi/exec queue)
151+
ops.put("add_result", stringTemplate.opsForZSet().add("testzset", "inside", 3.0));
152+
153+
// Changes made inside transaction should not be visible yet (read executes immediately)
154+
ops.put("rangeWithScores_after",
155+
stringTemplate.opsForZSet().rangeWithScores("testzset", 0, -1));
156+
ops.put("reverseRangeWithScores_after",
157+
stringTemplate.opsForZSet().reverseRangeWithScores("testzset", 0, -1));
158+
ops.put("rangeByScoreWithScores_after",
159+
stringTemplate.opsForZSet().rangeByScoreWithScores("testzset", 1.0, 3.0));
160+
ops.put("reverseRangeByScoreWithScores_after",
161+
stringTemplate.opsForZSet().reverseRangeByScoreWithScores("testzset", 1.0, 3.0));
162+
163+
return ops;
164+
});
165+
166+
// add result is null (no result until exec)
167+
assertThat(result).containsEntry("add_result", null);
168+
169+
// before: only data added outside transaction is visible
170+
assertThat((Set<TypedTuple<String>>) result.get("rangeWithScores_before"))
171+
.hasSize(2)
172+
.extracting(TypedTuple::getValue)
173+
.containsExactly("outside1", "outside2");
174+
175+
assertThat((Set<TypedTuple<String>>) result.get("reverseRangeWithScores_before"))
176+
.hasSize(2)
177+
.extracting(TypedTuple::getValue)
178+
.containsExactly("outside2", "outside1");
179+
180+
assertThat((Set<TypedTuple<String>>) result.get("rangeByScoreWithScores_before"))
181+
.hasSize(2)
182+
.extracting(TypedTuple::getValue)
183+
.containsExactly("outside1", "outside2");
184+
185+
assertThat((Set<TypedTuple<String>>) result.get("reverseRangeByScoreWithScores_before"))
186+
.hasSize(2)
187+
.extracting(TypedTuple::getValue)
188+
.containsExactly("outside2", "outside1");
189+
190+
// after: changes made inside transaction are still not visible
191+
assertThat((Set<TypedTuple<String>>) result.get("rangeWithScores_after"))
192+
.hasSize(2)
193+
.extracting(TypedTuple::getValue)
194+
.containsExactly("outside1", "outside2");
195+
196+
assertThat((Set<TypedTuple<String>>) result.get("reverseRangeWithScores_after"))
197+
.hasSize(2)
198+
.extracting(TypedTuple::getValue)
199+
.containsExactly("outside2", "outside1");
200+
201+
assertThat((Set<TypedTuple<String>>) result.get("rangeByScoreWithScores_after"))
202+
.hasSize(2)
203+
.extracting(TypedTuple::getValue)
204+
.containsExactly("outside1", "outside2");
205+
206+
assertThat((Set<TypedTuple<String>>) result.get("reverseRangeByScoreWithScores_after"))
207+
.hasSize(2)
208+
.extracting(TypedTuple::getValue)
209+
.containsExactly("outside2", "outside1");
210+
}
211+
119212
static Stream<Arguments> argumentsStream() {
120213

121214
LettuceConnectionFactory lcf = new LettuceConnectionFactory(SettingsUtils.standaloneConfiguration());

0 commit comments

Comments
 (0)