Skip to content

Commit d2871ec

Browse files
committed
DATAREDIS-562 - Add reactive BitField support.
Original pull request: #227.
1 parent 10f8553 commit d2871ec

File tree

6 files changed

+208
-5
lines changed

6 files changed

+208
-5
lines changed

src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,86 @@ default Mono<Long> bitCount(ByteBuffer key, long start, long end) {
902902
*/
903903
Flux<NumericResponse<BitCountCommand, Long>> bitCount(Publisher<BitCountCommand> commands);
904904

905+
/**
906+
* {@code BITFIELD} command parameters.
907+
*
908+
* @author Mark Paluch
909+
* @see <a href="http://redis.io/commands/bitfield">Redis Documentation: BITFIELD</a>
910+
* @since 2.1
911+
*/
912+
class BitFieldCommand extends KeyCommand {
913+
914+
private @Nullable BitFieldSubCommands subcommands;
915+
916+
private BitFieldCommand(ByteBuffer key, @Nullable BitFieldSubCommands subcommands) {
917+
918+
super(key);
919+
920+
this.subcommands = subcommands;
921+
}
922+
923+
/**
924+
* Creates a new {@link BitFieldCommand} given a {@literal key}.
925+
*
926+
* @param key must not be {@literal null}.
927+
* @return a new {@link BitFieldCommand} for a {@literal key}.
928+
*/
929+
public static BitFieldCommand bitField(ByteBuffer key) {
930+
931+
Assert.notNull(key, "Key must not be null!");
932+
933+
return new BitFieldCommand(key, null);
934+
}
935+
936+
/**
937+
* Applies the {@link BitFieldSubCommands}. Constructs a new command instance with all previously configured
938+
* properties.
939+
*
940+
* @param commands must not be {@literal null}.
941+
* @return a new {@link BitFieldSubCommands} with {@link BitFieldSubCommands} applied.
942+
*/
943+
public BitFieldCommand commands(BitFieldSubCommands commands) {
944+
945+
Assert.notNull(commands, "BitFieldCommands must not be null!");
946+
947+
return new BitFieldCommand(getKey(), commands);
948+
}
949+
950+
public BitFieldSubCommands getSubCommands() {
951+
return subcommands;
952+
}
953+
}
954+
955+
/**
956+
* Get / Manipulate specific integer fields of varying bit widths and arbitrary non (necessary) aligned offset stored
957+
* at a given {@code key}.
958+
*
959+
* @param key must not be {@literal null}.
960+
* @param subCommands
961+
* @return
962+
* @see <a href="http://redis.io/commands/bitfield">Redis Documentation: BITFIELD</a>
963+
* @since 2.1
964+
*/
965+
default Mono<List<Long>> bitField(ByteBuffer key, BitFieldSubCommands subCommands) {
966+
967+
Assert.notNull(key, "Key must not be null!");
968+
Assert.notNull(subCommands, "BitFieldSubCommands must not be null!");
969+
970+
return bitField(Mono.just(BitFieldCommand.bitField(key).commands(subCommands))).map(CommandResponse::getOutput)
971+
.next();
972+
}
973+
974+
/**
975+
* Get / Manipulate specific integer fields of varying bit widths and arbitrary non (necessary) aligned offset stored
976+
* at a given {@code key}.
977+
*
978+
* @param commands must not be {@literal null}.
979+
* @return
980+
* @see <a href="http://redis.io/commands/bitfield">Redis Documentation: BITFIELD</a>
981+
* @since 2.1
982+
*/
983+
Flux<MultiValueResponse<BitFieldCommand, Long>> bitField(Publisher<BitFieldCommand> commands);
984+
905985
/**
906986
* {@code BITOP} command parameters.
907987
*

src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
*/
1616
package org.springframework.data.redis.connection.lettuce;
1717

18+
import io.lettuce.core.BitFieldArgs;
1819
import io.lettuce.core.SetArgs;
1920
import reactor.core.publisher.Flux;
2021
import reactor.core.publisher.Mono;
2122

2223
import java.nio.ByteBuffer;
2324
import java.util.List;
25+
import java.util.stream.Collectors;
2426

2527
import org.reactivestreams.Publisher;
2628
import org.springframework.data.domain.Range;
@@ -328,11 +330,20 @@ public Flux<NumericResponse<BitCountCommand, Long>> bitCount(Publisher<BitCountC
328330

329331
/*
330332
* (non-Javadoc)
331-
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#bitCount(org.reactivestreams.Publisher)
333+
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#bitField(org.reactivestreams.Publisher)
332334
*/
333335
@Override
334-
public Flux<NumericResponse<BitFieldCommand, Long>> bitField(Publisher<BitFieldCommand> commands) {
335-
return null;
336+
public Flux<MultiValueResponse<BitFieldCommand, Long>> bitField(Publisher<BitFieldCommand> commands) {
337+
338+
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
339+
340+
Assert.notNull(command.getKey(), "Key must not be null!");
341+
342+
BitFieldArgs args = LettuceConverters.toBitFieldArgs(command.getSubCommands());
343+
344+
return cmd.bitfield(command.getKey(), args).collectList().map(value -> new MultiValueResponse<>(command,
345+
value.stream().map(v -> v.getValueOrElse(null)).collect(Collectors.toList())));
346+
}));
336347
}
337348

338349
/*

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.function.Function;
3030

3131
import org.reactivestreams.Publisher;
32+
import org.springframework.data.redis.connection.BitFieldSubCommands;
3233
import org.springframework.data.redis.connection.ReactiveStringCommands;
3334
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
3435
import org.springframework.data.redis.core.types.Expiration;
@@ -318,6 +319,18 @@ public Mono<Boolean> getBit(K key, long offset) {
318319
return createMono(connection -> connection.getBit(rawKey(key), offset));
319320
}
320321

322+
/* (non-Javadoc)
323+
* @see org.springframework.data.redis.core.ReactiveValueOperations#bitField(java.lang.Object, org.springframework.data.redis.connection.BitFieldSubCommands)
324+
*/
325+
@Override
326+
public Mono<List<Long>> bitField(K key, BitFieldSubCommands subCommands) {
327+
328+
Assert.notNull(key, "Key must not be null!");
329+
Assert.notNull(subCommands, "BitFieldSubCommands must not be null!");
330+
331+
return createMono(connection -> connection.bitField(rawKey(key), subCommands));
332+
}
333+
321334
/* (non-Javadoc)
322335
* @see org.springframework.data.redis.core.ReactiveValueOperations#delete(java.lang.Object)
323336
*/

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.List;
2323
import java.util.Map;
2424

25+
import org.springframework.data.redis.connection.BitFieldSubCommands;
26+
2527
/**
2628
* Reactive Redis operations for simple (or in Redis terminology 'string') values.
2729
*
@@ -235,6 +237,17 @@ public interface ReactiveValueOperations<K, V> {
235237
*/
236238
Mono<Boolean> getBit(K key, long offset);
237239

240+
/**
241+
* Get / Manipulate specific integer fields of varying bit widths and arbitrary non (necessary) aligned offset stored
242+
* at a given {@code key}.
243+
*
244+
* @param key must not be {@literal null}.
245+
* @param command must not be {@literal null}.
246+
* @return
247+
* @since 2.1
248+
*/
249+
Mono<List<Long>> bitField(K key, BitFieldSubCommands command);
250+
238251
/**
239252
* Removes the given {@literal key}.
240253
*

src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsTests.java

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@
1616
package org.springframework.data.redis.connection.lettuce;
1717

1818
import static org.hamcrest.Matchers.*;
19+
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
20+
import static org.hamcrest.core.Is.is;
1921
import static org.junit.Assert.*;
2022
import static org.junit.Assume.*;
23+
import static org.springframework.data.redis.connection.BitFieldSubCommands.*;
24+
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldIncrBy.Overflow.*;
25+
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.*;
26+
import static org.springframework.data.redis.connection.BitFieldSubCommands.Offset.*;
2127

22-
import org.springframework.data.redis.util.ByteUtils;
2328
import reactor.core.publisher.Flux;
2429
import reactor.core.publisher.Mono;
25-
import reactor.core.publisher.MonoOperator;
2630
import reactor.test.StepVerifier;
2731

2832
import java.nio.ByteBuffer;
@@ -46,6 +50,7 @@
4650
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
4751
import org.springframework.data.redis.core.types.Expiration;
4852
import org.springframework.data.redis.test.util.HexStringUtils;
53+
import org.springframework.data.redis.util.ByteUtils;
4954

5055
/**
5156
* @author Christoph Strobl
@@ -346,6 +351,63 @@ public void bitCountShouldCountInRangeCorrectly() {
346351
.verifyComplete();
347352
}
348353

354+
@Test // DATAREDIS-562
355+
public void bitFieldSetShouldWorkCorrectly() {
356+
357+
StepVerifier
358+
.create(connection.stringCommands().bitField(KEY_1_BBUFFER, create().set(INT_8).valueAt(offset(0L)).to(10L)))
359+
.expectNext(Collections.singletonList(0L)).verifyComplete();
360+
361+
StepVerifier
362+
.create(connection.stringCommands().bitField(KEY_1_BBUFFER, create().set(INT_8).valueAt(offset(0L)).to(20L)))
363+
.expectNext(Collections.singletonList(10L)).verifyComplete();
364+
}
365+
366+
@Test // DATAREDIS-562
367+
public void bitFieldGetShouldWorkCorrectly() {
368+
369+
StepVerifier.create(connection.stringCommands().bitField(KEY_1_BBUFFER, create().get(INT_8).valueAt(offset(0L))))
370+
.expectNext(Collections.singletonList(0L)).verifyComplete();
371+
}
372+
373+
@Test // DATAREDIS-562
374+
public void bitFieldIncrByShouldWorkCorrectly() {
375+
376+
StepVerifier
377+
.create(connection.stringCommands().bitField(KEY_1_BBUFFER, create().incr(INT_8).valueAt(offset(100L)).by(1L)))
378+
.expectNext(Collections.singletonList(1L)).verifyComplete();
379+
}
380+
381+
@Test // DATAREDIS-562
382+
public void bitFieldIncrByWithOverflowShouldWorkCorrectly() {
383+
384+
StepVerifier
385+
.create(connection.stringCommands().bitField(KEY_1_BBUFFER,
386+
create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)))
387+
.expectNext(Collections.singletonList(1L)).verifyComplete();
388+
StepVerifier
389+
.create(connection.stringCommands().bitField(KEY_1_BBUFFER,
390+
create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)))
391+
.expectNext(Collections.singletonList(2L)).verifyComplete();
392+
StepVerifier
393+
.create(connection.stringCommands().bitField(KEY_1_BBUFFER,
394+
create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)))
395+
.expectNext(Collections.singletonList(3L)).verifyComplete();
396+
StepVerifier
397+
.create(connection.stringCommands().bitField(KEY_1_BBUFFER,
398+
create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)))
399+
.expectNext(Collections.singletonList(null)).verifyComplete();
400+
}
401+
402+
@Test // DATAREDIS-562
403+
public void bitfieldShouldAllowMultipleSubcommands() {
404+
405+
StepVerifier
406+
.create(connection.stringCommands().bitField(KEY_1_BBUFFER,
407+
create().incr(signed(5)).valueAt(offset(100L)).by(1L).get(unsigned(4)).valueAt(0L)))
408+
.expectNext(Arrays.asList(1L, 0L)).verifyComplete();
409+
}
410+
349411
@Test // DATAREDIS-525
350412
public void bitOpAndShouldWorkAsExpected() {
351413

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@
1717

1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.junit.Assume.*;
20+
import static org.springframework.data.redis.connection.BitFieldSubCommands.*;
21+
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldIncrBy.Overflow.*;
22+
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.*;
23+
import static org.springframework.data.redis.connection.BitFieldSubCommands.Offset.offset;
2024

2125
import reactor.test.StepVerifier;
2226

2327
import java.nio.ByteBuffer;
2428
import java.time.Duration;
2529
import java.util.Arrays;
2630
import java.util.Collection;
31+
import java.util.Collections;
2732
import java.util.LinkedHashMap;
2833
import java.util.Map;
2934

@@ -372,6 +377,25 @@ public void getBit() {
372377
StepVerifier.create(valueOperations.getBit(key, 1)).expectNext(false).expectComplete();
373378
}
374379

380+
@Test // DATAREDIS-562
381+
public void bitField() {
382+
383+
K key = keyFactory.instance();
384+
385+
StepVerifier
386+
.create(valueOperations.bitField(key, create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)))
387+
.expectNext(Collections.singletonList(1L)).verifyComplete();
388+
StepVerifier
389+
.create(valueOperations.bitField(key, create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)))
390+
.expectNext(Collections.singletonList(2L)).verifyComplete();
391+
StepVerifier
392+
.create(valueOperations.bitField(key, create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)))
393+
.expectNext(Collections.singletonList(3L)).verifyComplete();
394+
StepVerifier
395+
.create(valueOperations.bitField(key, create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)))
396+
.expectNext(Collections.singletonList(null)).verifyComplete();
397+
}
398+
375399
@Test // DATAREDIS-602
376400
public void delete() {
377401

0 commit comments

Comments
 (0)