Skip to content

Commit e9a3a5b

Browse files
christophstroblmp911de
authored andcommitted
DATAREDIS-697 - Add support for BITPOS.
We now support the BITPOS command throughout the sync and reactive api. Original pull request: #335.
1 parent 8215f68 commit e9a3a5b

16 files changed

+472
-9
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,16 @@ public Long bitOp(BitOperation op, byte[] destination, byte[]... keys) {
11601160
return convertAndReturn(delegate.bitOp(op, destination, keys), identityConverter);
11611161
}
11621162

1163+
/*
1164+
* (non-Javadoc)
1165+
* @see org.springframework.data.redis.connection.StringRedisConnection#bitPos(byte[], boolean, org.springframework.data.domain.Range)
1166+
*/
1167+
@Nullable
1168+
@Override
1169+
public Long bitPos(byte[] key, boolean bit, org.springframework.data.domain.Range<Long> range) {
1170+
return convertAndReturn(delegate.bitPos(key, bit, range), identityConverter);
1171+
}
1172+
11631173
/*
11641174
* (non-Javadoc)
11651175
* @see org.springframework.data.redis.connection.RedisPubSubCommands#subscribe(org.springframework.data.redis.connection.MessageListener, byte[][])
@@ -2470,6 +2480,16 @@ public Long bitOp(BitOperation op, String destination, String... keys) {
24702480
return bitOp(op, serialize(destination), serializeMulti(keys));
24712481
}
24722482

2483+
/*
2484+
* (non-Javadoc)
2485+
* @see org.springframework.data.redis.connection.StringRedisConnection#bitPos(java.lang.String, boolean, org.springframework.data.domain.Range)
2486+
*/
2487+
@Nullable
2488+
@Override
2489+
public Long bitPos(String key, boolean bit, org.springframework.data.domain.Range<Long> range) {
2490+
return bitPos(serialize(key), bit, range);
2491+
}
2492+
24732493
/*
24742494
* (non-Javadoc)
24752495
* @see org.springframework.data.redis.connection.StringRedisConnection#subscribe(org.springframework.data.redis.connection.MessageListener, java.lang.String[])

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.Set;
2323
import java.util.concurrent.TimeUnit;
2424

25+
import org.springframework.data.domain.Range;
2526
import org.springframework.data.geo.Circle;
2627
import org.springframework.data.geo.Distance;
2728
import org.springframework.data.geo.GeoResults;
@@ -385,6 +386,13 @@ default Long bitOp(BitOperation op, byte[] destination, byte[]... keys) {
385386
return stringCommands().bitOp(op, destination, keys);
386387
}
387388

389+
/** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */
390+
@Override
391+
@Deprecated
392+
default Long bitPos(byte[] key, boolean bit, org.springframework.data.domain.Range<Long> range) {
393+
return stringCommands().bitPos(key, bit, range);
394+
}
395+
388396
/** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */
389397
@Override
390398
@Deprecated

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,80 @@ default Mono<Long> bitOp(Collection<ByteBuffer> keys, BitOperation bitOp, ByteBu
10141014
*/
10151015
Flux<NumericResponse<BitOpCommand, Long>> bitOp(Publisher<BitOpCommand> commands);
10161016

1017+
/**
1018+
* @author Christoph Strobl
1019+
* @since 2.1
1020+
*/
1021+
class BitPosCommand extends KeyCommand {
1022+
1023+
private boolean bit;
1024+
private Range<Long> range;
1025+
1026+
private BitPosCommand(@Nullable ByteBuffer key, boolean bit, Range<Long> range) {
1027+
super(key);
1028+
this.bit = bit;
1029+
this.range = range;
1030+
}
1031+
1032+
static BitPosCommand positionOf(boolean bit) {
1033+
return new BitPosCommand(null, bit, Range.unbounded());
1034+
}
1035+
1036+
public BitPosCommand in(ByteBuffer key) {
1037+
return new BitPosCommand(key, bit, range);
1038+
}
1039+
1040+
public BitPosCommand within(Range<Long> range) {
1041+
return new BitPosCommand(getKey(), bit, range);
1042+
}
1043+
1044+
public boolean getBit() {
1045+
return bit;
1046+
}
1047+
1048+
public Range<Long> getRange() {
1049+
return range;
1050+
}
1051+
}
1052+
1053+
/**
1054+
* Return the position of the first bit set to given {@code bit} in a string.
1055+
*
1056+
* @param key the key holding the actual String.
1057+
* @param bit the bit value to look for.
1058+
* @return {@link Mono} emitting result when ready.
1059+
* @since 2.1
1060+
*/
1061+
default Mono<Long> bitPos(ByteBuffer key, boolean bit) {
1062+
return bitPos(key, bit, Range.unbounded());
1063+
}
1064+
1065+
/**
1066+
* Return the position of the first bit set to given {@code bit} in a string. {@link Range} start and end can contain
1067+
* negative values in order to index <strong>bytes</strong> starting from the end of the string, where {@literal -1}
1068+
* is the last byte, {@literal -2} is the penultimate.
1069+
*
1070+
* @param key the key holding the actual String.
1071+
* @param bit the bit value to look for.
1072+
* @param range must not be {@literal null}. Use {@link Range#unbounded()} to not limit search.
1073+
* @return {@link Mono} emitting result when ready.
1074+
* @since 2.1
1075+
*/
1076+
default Mono<Long> bitPos(ByteBuffer key, boolean bit, Range<Long> range) {
1077+
return bitPos(Mono.just(BitPosCommand.positionOf(bit).in(key).within(range))).next()
1078+
.map(NumericResponse::getOutput);
1079+
}
1080+
1081+
/**
1082+
* Emmit the the position of the first bit set to given {@code bit} in a string. Get the length of the value stored at
1083+
* {@literal key}.
1084+
*
1085+
* @param commands must not be {@literal null}.
1086+
* @return {@link Flux} emitting results when ready.
1087+
* @since 2.1
1088+
*/
1089+
Flux<NumericResponse<BitPosCommand, Long>> bitPos(Publisher<BitPosCommand> commands);
1090+
10171091
/**
10181092
* Get the length of the value stored at {@literal key}.
10191093
*

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.List;
1919
import java.util.Map;
2020

21+
import org.springframework.data.domain.Range;
2122
import org.springframework.data.redis.core.types.Expiration;
2223
import org.springframework.lang.Nullable;
2324

@@ -292,6 +293,37 @@ enum BitOperation {
292293
@Nullable
293294
Long bitOp(BitOperation op, byte[] destination, byte[]... keys);
294295

296+
/**
297+
* Return the position of the first bit set to given {@code bit} in a string.
298+
*
299+
* @param key the key holding the actual String.
300+
* @param bit the bit value to look for.
301+
* @return {@literal null} when used in pipeline / transaction. The position of the first bit set to 1 or 0 according
302+
* to the request.
303+
* @see <a href="http://redis.io/commands/bitpos">Redis Documentation: BITPOS</a>
304+
* @since 2.1
305+
*/
306+
@Nullable
307+
default Long bitPos(byte[] key, boolean bit) {
308+
return bitPos(key, bit, Range.unbounded());
309+
}
310+
311+
/**
312+
* Return the position of the first bit set to given {@code bit} in a string. {@link Range} start and end can contain
313+
* negative values in order to index <strong>bytes</strong> starting from the end of the string, where {@literal -1}
314+
* is the last byte, {@literal -2} is the penultimate.
315+
*
316+
* @param key the key holding the actual String.
317+
* @param bit the bit value to look for.
318+
* @param range must not be {@literal null}. Use {@link Range#unbounded()} to not limit search.
319+
* @return {@literal null} when used in pipeline / transaction. The position of the first bit set to 1 or 0 according
320+
* to the request.
321+
* @see <a href="http://redis.io/commands/bitpos">Redis Documentation: BITPOS</a>
322+
* @since 2.1
323+
*/
324+
@Nullable
325+
Long bitPos(byte[] key, boolean bit, Range<Long> range);
326+
295327
/**
296328
* Get the length of the value stored at {@code key}.
297329
*

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,37 @@ interface StringTuple extends Tuple {
580580
*/
581581
Long bitOp(BitOperation op, String destination, String... keys);
582582

583+
/**
584+
* Return the position of the first bit set to given {@code bit} in a string.
585+
*
586+
* @param key the key holding the actual String.
587+
* @param bit the bit value to look for.
588+
* @return {@literal null} when used in pipeline / transaction. The position of the first bit set to 1 or 0 according
589+
* to the request.
590+
* @see <a href="http://redis.io/commands/bitpos">Redis Documentation: BITPOS</a>
591+
* @since 2.1
592+
*/
593+
default Long bitPos(String key, boolean bit) {
594+
return bitPos(key, bit, org.springframework.data.domain.Range.unbounded());
595+
}
596+
597+
/**
598+
* Return the position of the first bit set to given {@code bit} in a string.
599+
* {@link org.springframework.data.domain.Range} start and end can contain negative values in order to index
600+
* <strong>bytes</strong> starting from the end of the string, where {@literal -1} is the last byte, {@literal -2} is
601+
* the penultimate.
602+
*
603+
* @param key the key holding the actual String.
604+
* @param bit the bit value to look for.
605+
* @param range must not be {@literal null}. Use {@link Range#unbounded()} to not limit search.
606+
* @return {@literal null} when used in pipeline / transaction. The position of the first bit set to 1 or 0 according
607+
* to the request.
608+
* @see <a href="http://redis.io/commands/bitpos">Redis Documentation: BITPOS</a>
609+
* @since 2.1
610+
*/
611+
@Nullable
612+
Long bitPos(String key, boolean bit, org.springframework.data.domain.Range<Long> range);
613+
583614
/**
584615
* Get the length of the value stored at {@code key}.
585616
*

src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterStringCommands.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,21 @@
1919
import lombok.RequiredArgsConstructor;
2020
import redis.clients.jedis.BinaryJedis;
2121

22+
import java.util.ArrayList;
2223
import java.util.Arrays;
2324
import java.util.List;
2425
import java.util.Map;
2526
import java.util.concurrent.TimeUnit;
2627

2728
import org.springframework.dao.DataAccessException;
2829
import org.springframework.dao.InvalidDataAccessApiUsageException;
30+
import org.springframework.data.domain.Range;
2931
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
3032
import org.springframework.data.redis.connection.RedisStringCommands;
3133
import org.springframework.data.redis.connection.convert.Converters;
3234
import org.springframework.data.redis.connection.jedis.JedisClusterConnection.JedisClusterCommandCallback;
3335
import org.springframework.data.redis.connection.jedis.JedisClusterConnection.JedisMultiKeyClusterCommandCallback;
36+
import org.springframework.data.redis.connection.lettuce.LettuceConverters;
3437
import org.springframework.data.redis.core.types.Expiration;
3538
import org.springframework.data.redis.util.ByteUtils;
3639
import org.springframework.util.Assert;
@@ -489,6 +492,29 @@ public Long bitOp(BitOperation op, byte[] destination, byte[]... keys) {
489492
throw new InvalidDataAccessApiUsageException("BITOP is only supported for same slot keys in cluster mode.");
490493
}
491494

495+
/*
496+
* (non-Javadoc)
497+
* @see org.springframework.data.redis.connection.RedisStringCommands#bitPos(byte[], boolean, org.springframework.data.Range)
498+
*/
499+
@Override
500+
public Long bitPos(byte[] key, boolean bit, Range<Long> range) {
501+
502+
Assert.notNull(key, "Key must not be null!");
503+
Assert.notNull(range, "Range must not be null! Use Range.unbounded() instead.");
504+
505+
List<byte[]> args = new ArrayList<>(3);
506+
args.add(LettuceConverters.toBit(bit));
507+
508+
if (range.getLowerBound().isBounded()) {
509+
args.add(range.getLowerBound().getValue().map(LettuceConverters::toBytes).get());
510+
}
511+
if (range.getUpperBound().isBounded()) {
512+
args.add(range.getUpperBound().getValue().map(LettuceConverters::toBytes).get());
513+
}
514+
515+
return Long.class.cast(connection.execute("BITPOS", key, args));
516+
}
517+
492518
/*
493519
* (non-Javadoc)
494520
* @see org.springframework.data.redis.connection.RedisStringCommands#strLen(byte[])

src/main/java/org/springframework/data/redis/connection/jedis/JedisStringCommands.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,17 @@
1717

1818
import lombok.NonNull;
1919
import lombok.RequiredArgsConstructor;
20+
import redis.clients.jedis.BitPosParams;
2021

2122
import java.util.List;
2223
import java.util.Map;
2324
import java.util.concurrent.TimeUnit;
2425

26+
import org.springframework.data.domain.Range;
2527
import org.springframework.data.redis.connection.RedisStringCommands;
2628
import org.springframework.data.redis.connection.convert.Converters;
2729
import org.springframework.data.redis.core.types.Expiration;
30+
import org.springframework.lang.Nullable;
2831
import org.springframework.util.Assert;
2932
import org.springframework.util.ObjectUtils;
3033

@@ -713,6 +716,43 @@ public Long bitOp(BitOperation op, byte[] destination, byte[]... keys) {
713716
}
714717
}
715718

719+
/*
720+
* (non-Javadoc)
721+
* @see org.springframework.data.redis.connection.RedisStringCommands#bitOp(byte[], boolean, org.springframework.data.domain.Range)
722+
*/
723+
@Nullable
724+
@Override
725+
public Long bitPos(byte[] key, boolean bit, Range<Long> range) {
726+
727+
Assert.notNull(key, "Key must not be null!");
728+
Assert.notNull(range, "Range must not be null! Use Range.unbounded() instead.");
729+
730+
BitPosParams params = null;
731+
if (range.getLowerBound().isBounded()) {
732+
params = range.getUpperBound().isBounded()
733+
? new BitPosParams(range.getLowerBound().getValue().get(), range.getUpperBound().getValue().get())
734+
: new BitPosParams(range.getLowerBound().getValue().get());
735+
}
736+
737+
try {
738+
if (isPipelined()) {
739+
740+
pipeline(connection.newJedisResult(params != null ? connection.getRequiredPipeline().bitpos(key, bit, params)
741+
: connection.getRequiredPipeline().bitpos(key, bit)));
742+
return null;
743+
}
744+
if (isQueueing()) {
745+
transaction(
746+
connection.newJedisResult(params != null ? connection.getRequiredTransaction().bitpos(key, bit, params)
747+
: connection.getRequiredTransaction().bitpos(key, bit)));
748+
return null;
749+
}
750+
return params != null ? connection.getJedis().bitpos(key, bit, params) : connection.getJedis().bitpos(key, bit);
751+
} catch (Exception ex) {
752+
throw convertJedisAccessException(ex);
753+
}
754+
}
755+
716756
/*
717757
* (non-Javadoc)
718758
* @see org.springframework.data.redis.connection.RedisStringCommands#strLen(byte[])

0 commit comments

Comments
 (0)