Skip to content

Commit

Permalink
Fix zset score parsing for infinite scores #528
Browse files Browse the repository at this point in the history
Lettuce now parses positive and negative infinite scores correctly. Infinite scores are received as inf and -inf that require appropriate transformation instead of parsing the floating point value.

Previously parsing caused NumberFormatException.
  • Loading branch information
mp911de committed Apr 29, 2017
1 parent 40b3eb1 commit 4edbaa8
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 37 deletions.
26 changes: 24 additions & 2 deletions src/main/java/io/lettuce/core/LettuceStrings.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -60,7 +60,8 @@ public static boolean isNotEmpty(final CharSequence cs) {
}

/**
* Convert double to string. If double is infinite, returns positive/negative infinity {@code +inf} and {@code -inf}.
* Convert {@code double} to {@link String}. If {@code n} is infinite, returns positive/negative infinity {@code +inf} and
* {@code -inf}.
*
* @param n the double.
* @return string representation of {@code n}
Expand All @@ -72,6 +73,27 @@ public static String string(double n) {
return Double.toString(n);
}

/**
* Convert {@link String} to {@code double}. If {@code s} is {@literal +inf}/{@literal -inf}, returns positive/negative
* infinity.
*
* @param s string representation of the number
* @return the {@code double} value.
* @since 4.3.3
*/
public static double toDouble(String s) {

if ("+inf".equals(s) || "inf".equals(s)) {
return Double.POSITIVE_INFINITY;
}

if ("-inf".equals(s)) {
return Double.NEGATIVE_INFINITY;
}

return Double.parseDouble(s);
}

/**
* Create SHA1 digest from Lua script.
*
Expand Down
10 changes: 6 additions & 4 deletions src/main/java/io/lettuce/core/output/ScoredValueListOutput.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.List;

import io.lettuce.core.LettuceStrings;
import io.lettuce.core.ScoredValue;
import io.lettuce.core.codec.RedisCodec;
import io.lettuce.core.internal.LettuceAssert;
Expand All @@ -30,8 +31,9 @@
* @param <V> Value type.
* @author Will Glozer
*/
public class ScoredValueListOutput<K, V> extends CommandOutput<K, V, List<ScoredValue<V>>>
implements StreamingOutput<ScoredValue<V>> {
public class ScoredValueListOutput<K, V> extends CommandOutput<K, V, List<ScoredValue<V>>> implements
StreamingOutput<ScoredValue<V>> {

private V value;
private Subscriber<ScoredValue<V>> subscriber;

Expand All @@ -47,7 +49,7 @@ public void set(ByteBuffer bytes) {
return;
}

double score = Double.parseDouble(decodeAscii(bytes));
double score = LettuceStrings.toDouble(decodeAscii(bytes));
subscriber.onNext(ScoredValue.fromNullable(score, value));
value = null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@

import java.nio.ByteBuffer;

import io.lettuce.core.LettuceStrings;
import io.lettuce.core.ScoredValue;
import io.lettuce.core.ScoredValueScanCursor;
import io.lettuce.core.codec.RedisCodec;
Expand Down Expand Up @@ -44,7 +45,7 @@ protected void setOutput(ByteBuffer bytes) {
return;
}

double score = Double.parseDouble(decodeAscii(bytes));
double score = LettuceStrings.toDouble(decodeAscii(bytes));
output.getValues().add(ScoredValue.fromNullable(score, value));
value = null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@

import java.nio.ByteBuffer;

import io.lettuce.core.LettuceStrings;
import io.lettuce.core.ScoredValue;
import io.lettuce.core.StreamScanCursor;
import io.lettuce.core.codec.RedisCodec;
Expand Down Expand Up @@ -45,7 +46,7 @@ protected void setOutput(ByteBuffer bytes) {
return;
}

double score = Double.parseDouble(decodeAscii(bytes));
double score = LettuceStrings.toDouble(decodeAscii(bytes));
channel.onValue(ScoredValue.fromNullable(score, value));
value = null;
output.setCount(output.getCount() + 1);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@

import java.nio.ByteBuffer;

import io.lettuce.core.LettuceStrings;
import io.lettuce.core.ScoredValue;
import io.lettuce.core.codec.RedisCodec;

Expand Down Expand Up @@ -44,7 +45,7 @@ public void set(ByteBuffer bytes) {
return;
}

double score = Double.parseDouble(decodeAscii(bytes));
double score = LettuceStrings.toDouble(decodeAscii(bytes));
channel.onValue(ScoredValue.fromNullable(score, value));
value = null;
output = output.longValue() + 1;
Expand Down
88 changes: 63 additions & 25 deletions src/test/java/io/lettuce/core/commands/SortedSetCommandTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,6 +15,7 @@
*/
package io.lettuce.core.commands;

import static io.lettuce.core.Range.Boundary.including;
import static io.lettuce.core.ZStoreArgs.Builder.max;
import static io.lettuce.core.ZStoreArgs.Builder.min;
import static io.lettuce.core.ZStoreArgs.Builder.sum;
Expand Down Expand Up @@ -146,7 +147,7 @@ public void zcount() throws Exception {
assertThat(redis.zcount(key, Range.create(1.0, 2.0))).isEqualTo(2);
assertThat(redis.zcount(key, Range.create(NEGATIVE_INFINITY, POSITIVE_INFINITY))).isEqualTo(3);

assertThat(redis.zcount(key, Range.from(Boundary.excluding(1.0), Boundary.including(3.0)))).isEqualTo(2);
assertThat(redis.zcount(key, Range.from(Boundary.excluding(1.0), including(3.0)))).isEqualTo(2);
assertThat(redis.zcount(key, Range.unbounded())).isEqualTo(3);
}

Expand Down Expand Up @@ -219,7 +220,8 @@ public void zrangebyscore() throws Exception {
assertThat(redis.zrangebyscore(key, "-inf", "+inf", 2, 2)).isEqualTo(list("c", "d"));

assertThat(redis.zrangebyscore(key, Range.create(2.0, 3.0))).isEqualTo(list("b", "c"));
assertThat(redis.zrangebyscore(key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0)))).isEqualTo(list("b", "c"));
assertThat(redis.zrangebyscore(key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0)))).isEqualTo(
list("b", "c"));
assertThat(redis.zrangebyscore(key, Range.unbounded())).isEqualTo(list("a", "b", "c", "d"));
assertThat(redis.zrangebyscore(key, Range.create(0.0, 4.0), Limit.create(1, 3))).isEqualTo(list("b", "c", "d"));
assertThat(redis.zrangebyscore(key, Range.unbounded(), Limit.create(2, 2))).isEqualTo(list("c", "d"));
Expand All @@ -239,7 +241,8 @@ public void zrangebyscoreStreaming() throws Exception {
assertThat(redis.zrangebyscore(streamingAdapter, key, "-inf", "+inf", 2, 2)).isEqualTo(2);

assertThat(redis.zrangebyscore(streamingAdapter, key, Range.create(2.0, 3.0))).isEqualTo(2);
assertThat(redis.zrangebyscore(streamingAdapter, key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0)))).isEqualTo(2);
assertThat(redis.zrangebyscore(streamingAdapter, key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0))))
.isEqualTo(2);
assertThat(redis.zrangebyscore(streamingAdapter, key, Range.unbounded())).isEqualTo(4);
assertThat(redis.zrangebyscore(streamingAdapter, key, Range.create(0.0, 4.0), Limit.create(1, 3))).isEqualTo(3);
assertThat(redis.zrangebyscore(streamingAdapter, key, Range.unbounded(), Limit.create(2, 2))).isEqualTo(2);
Expand All @@ -262,12 +265,30 @@ public void zrangebyscoreWithScores() throws Exception {
assertThat(redis.zrangebyscoreWithScores(key, "-inf", "+inf", 2, 2)).isEqualTo(svlist(sv(3.0, "c"), sv(4.0, "d")));

assertThat(redis.zrangebyscoreWithScores(key, Range.create(2.0, 3.0))).isEqualTo(svlist(sv(2.0, "b"), sv(3.0, "c")));
assertThat(redis.zrangebyscoreWithScores(key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0)))).isEqualTo(svlist(sv(2.0, "b"), sv(3.0, "c")));
assertThat(redis.zrangebyscoreWithScores(key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0)))).isEqualTo(
svlist(sv(2.0, "b"), sv(3.0, "c")));
assertThat(redis.zrangebyscoreWithScores(key, Range.unbounded())).isEqualTo(
svlist(sv(1.0, "a"), sv(2.0, "b"), sv(3.0, "c"), sv(4.0, "d")));
assertThat(redis.zrangebyscoreWithScores(key, Range.create(0.0, 4.0), Limit.create(1, 3))).isEqualTo(
svlist(sv(2.0, "b"), sv(3.0, "c"), sv(4.0, "d")));
assertThat(redis.zrangebyscoreWithScores(key, Range.unbounded(), Limit.create(2, 2))).isEqualTo(svlist(sv(3.0, "c"), sv(4.0, "d")));
assertThat(redis.zrangebyscoreWithScores(key, Range.unbounded(), Limit.create(2, 2))).isEqualTo(
svlist(sv(3.0, "c"), sv(4.0, "d")));
}

@Test
@SuppressWarnings({ "unchecked" })
public void zrangebyscoreWithScoresInfinity() throws Exception {

redis.zadd(key, Double.POSITIVE_INFINITY, "a", Double.NEGATIVE_INFINITY, "b");

assertThat(redis.zrangebyscoreWithScores(key, "-inf", "+inf")).hasSize(2);

ListStreamingAdapter<String> streamingAdapter = new ListStreamingAdapter<>();

Range<Double> range = Range.from(including(Double.NEGATIVE_INFINITY), including(Double.POSITIVE_INFINITY));
redis.zrangebyscoreWithScores(streamingAdapter, key, range);

assertThat(streamingAdapter.getList()).hasSize(2);
}

@Test
Expand All @@ -284,11 +305,14 @@ public void zrangebyscoreWithScoresStreaming() throws Exception {
assertThat(redis.zrangebyscoreWithScores(streamingAdapter, key, 0.0, 4.0, 1, 3).longValue()).isEqualTo(3);
assertThat(redis.zrangebyscoreWithScores(streamingAdapter, key, "-inf", "+inf", 2, 2).longValue()).isEqualTo(2);

assertThat(redis.zrangebyscoreWithScores(streamingAdapter,key, Range.create(2.0, 3.0))).isEqualTo(2);
assertThat(redis.zrangebyscoreWithScores(streamingAdapter,key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0)))).isEqualTo(2);
assertThat(redis.zrangebyscoreWithScores(streamingAdapter,key, Range.unbounded())).isEqualTo(4);
assertThat(redis.zrangebyscoreWithScores(streamingAdapter,key, Range.create(0.0, 4.0), Limit.create(1, 3))).isEqualTo(3);
assertThat(redis.zrangebyscoreWithScores(streamingAdapter,key, Range.unbounded(), Limit.create(2, 2))).isEqualTo(2);
assertThat(redis.zrangebyscoreWithScores(streamingAdapter, key, Range.create(2.0, 3.0))).isEqualTo(2);
assertThat(
redis.zrangebyscoreWithScores(streamingAdapter, key,
Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0)))).isEqualTo(2);
assertThat(redis.zrangebyscoreWithScores(streamingAdapter, key, Range.unbounded())).isEqualTo(4);
assertThat(redis.zrangebyscoreWithScores(streamingAdapter, key, Range.create(0.0, 4.0), Limit.create(1, 3))).isEqualTo(
3);
assertThat(redis.zrangebyscoreWithScores(streamingAdapter, key, Range.unbounded(), Limit.create(2, 2))).isEqualTo(2);

}

Expand Down Expand Up @@ -380,8 +404,9 @@ public void zrevrangebylex() throws Exception {

assertThat(redis.zrevrangebylex(key, Range.unbounded())).hasSize(100);
assertThat(redis.zrevrangebylex(key, Range.create("value", "zzz"))).hasSize(100);
assertThat(redis.zrevrangebylex(key, Range.from(Boundary.including("value98"), Boundary.including("value99")))).containsSequence("value99", "value98");
assertThat(redis.zrevrangebylex(key, Range.from(Boundary.including("value99"), Boundary.unbounded()))).hasSize(1);
assertThat(redis.zrevrangebylex(key, Range.from(including("value98"), including("value99")))).containsSequence(
"value99", "value98");
assertThat(redis.zrevrangebylex(key, Range.from(including("value99"), Boundary.unbounded()))).hasSize(1);
assertThat(redis.zrevrangebylex(key, Range.from(Boundary.excluding("value99"), Boundary.unbounded()))).hasSize(0);
}

Expand All @@ -398,7 +423,8 @@ public void zrevrangebyscore() throws Exception {
assertThat(redis.zrevrangebyscore(key, "+inf", "-inf", 2, 2)).isEqualTo(list("b", "a"));

assertThat(redis.zrevrangebyscore(key, Range.create(2.0, 3.0))).isEqualTo(list("c", "b"));
assertThat(redis.zrevrangebyscore(key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0)))).isEqualTo(list("c", "b"));
assertThat(redis.zrevrangebyscore(key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0)))).isEqualTo(
list("c", "b"));
assertThat(redis.zrevrangebyscore(key, Range.unbounded())).isEqualTo(list("d", "c", "b", "a"));
assertThat(redis.zrevrangebyscore(key, Range.create(0.0, 4.0), Limit.create(1, 3))).isEqualTo(list("c", "b", "a"));
assertThat(redis.zrevrangebyscore(key, Range.unbounded(), Limit.create(2, 2))).isEqualTo(list("b", "a"));
Expand All @@ -420,12 +446,14 @@ public void zrevrangebyscoreWithScores() throws Exception {
assertThat(redis.zrevrangebyscoreWithScores(key, "+inf", "-inf", 2, 2)).isEqualTo(svlist(sv(2.0, "b"), sv(1.0, "a")));

assertThat(redis.zrevrangebyscoreWithScores(key, Range.create(2.0, 3.0))).isEqualTo(svlist(sv(3.0, "c"), sv(2.0, "b")));
assertThat(redis.zrevrangebyscoreWithScores(key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0)))).isEqualTo(svlist(sv(3.0, "c"), sv(2.0, "b")));
assertThat(redis.zrevrangebyscoreWithScores(key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0))))
.isEqualTo(svlist(sv(3.0, "c"), sv(2.0, "b")));
assertThat(redis.zrevrangebyscoreWithScores(key, Range.unbounded())).isEqualTo(
svlist(sv(4.0, "d"), sv(3.0, "c"), sv(2.0, "b"), sv(1.0, "a")));
assertThat(redis.zrevrangebyscoreWithScores(key, Range.create(0.0, 4.0), Limit.create(1, 3))).isEqualTo(
svlist(sv(3.0, "c"), sv(2.0, "b"), sv(1.0, "a")));
assertThat(redis.zrevrangebyscoreWithScores(key, Range.unbounded(), Limit.create(2, 2))).isEqualTo(svlist(sv(2.0, "b"), sv(1.0, "a")));
assertThat(redis.zrevrangebyscoreWithScores(key, Range.unbounded(), Limit.create(2, 2))).isEqualTo(
svlist(sv(2.0, "b"), sv(1.0, "a")));
}

@Test
Expand All @@ -442,10 +470,14 @@ public void zrevrangebyscoreStreaming() throws Exception {
assertThat(redis.zrevrangebyscore(streamingAdapter, key, "+inf", "-inf", 2, 2).longValue()).isEqualTo(2);

assertThat(redis.zrevrangebyscore(streamingAdapter, key, Range.create(2.0, 3.0)).longValue()).isEqualTo(2);
assertThat(redis.zrevrangebyscore(streamingAdapter, key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0))).longValue()).isEqualTo(2);
assertThat(
redis.zrevrangebyscore(streamingAdapter, key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0)))
.longValue()).isEqualTo(2);
assertThat(redis.zrevrangebyscore(streamingAdapter, key, Range.unbounded()).longValue()).isEqualTo(4);
assertThat(redis.zrevrangebyscore(streamingAdapter, key, Range.create(0.0, 4.0), Limit.create(1, 3)).longValue()).isEqualTo(3);
assertThat(redis.zrevrangebyscore(streamingAdapter, key, Range.unbounded(), Limit.create(2, 2)).longValue()).isEqualTo(2);
assertThat(redis.zrevrangebyscore(streamingAdapter, key, Range.create(0.0, 4.0), Limit.create(1, 3)).longValue())
.isEqualTo(3);
assertThat(redis.zrevrangebyscore(streamingAdapter, key, Range.unbounded(), Limit.create(2, 2)).longValue()).isEqualTo(
2);
}

@Test
Expand All @@ -463,10 +495,15 @@ public void zrevrangebyscoreWithScoresStreaming() throws Exception {
assertThat(redis.zrevrangebyscoreWithScores(streamingAdapter, key, "+inf", "-inf", 2, 2)).isEqualTo(2);

assertThat(redis.zrevrangebyscoreWithScores(streamingAdapter, key, Range.create(2.0, 3.0)).longValue()).isEqualTo(2);
assertThat(redis.zrevrangebyscoreWithScores(streamingAdapter, key, Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0))).longValue()).isEqualTo(2);
assertThat(
redis.zrevrangebyscoreWithScores(streamingAdapter, key,
Range.from(Boundary.excluding(1.0), Boundary.excluding(4.0))).longValue()).isEqualTo(2);
assertThat(redis.zrevrangebyscoreWithScores(streamingAdapter, key, Range.unbounded()).longValue()).isEqualTo(4);
assertThat(redis.zrevrangebyscoreWithScores(streamingAdapter, key, Range.create(0.0, 4.0), Limit.create(1, 3)).longValue()).isEqualTo(3);
assertThat(redis.zrevrangebyscoreWithScores(streamingAdapter, key, Range.unbounded(), Limit.create(2, 2)).longValue()).isEqualTo(2);
assertThat(
redis.zrevrangebyscoreWithScores(streamingAdapter, key, Range.create(0.0, 4.0), Limit.create(1, 3)).longValue())
.isEqualTo(3);
assertThat(redis.zrevrangebyscoreWithScores(streamingAdapter, key, Range.unbounded(), Limit.create(2, 2)).longValue())
.isEqualTo(2);
}

@Test
Expand Down Expand Up @@ -656,7 +693,7 @@ public void zlexcount() throws Exception {

assertThat(redis.zlexcount(key, Range.unbounded())).isEqualTo(100);
assertThat(redis.zlexcount(key, Range.create("value", "zzz"))).isEqualTo(100);
assertThat(redis.zlexcount(key, Range.from(Boundary.including("value99"), Boundary.unbounded()))).isEqualTo(1);
assertThat(redis.zlexcount(key, Range.from(including("value99"), Boundary.unbounded()))).isEqualTo(1);
assertThat(redis.zlexcount(key, Range.from(Boundary.excluding("value99"), Boundary.unbounded()))).isEqualTo(0);
}

Expand All @@ -669,8 +706,9 @@ public void zrangebylex() throws Exception {

assertThat(redis.zrangebylex(key, Range.unbounded())).hasSize(100);
assertThat(redis.zrangebylex(key, Range.create("value", "zzz"))).hasSize(100);
assertThat(redis.zrangebylex(key, Range.from(Boundary.including("value98"), Boundary.including("value99")))).containsSequence("value98", "value99");
assertThat(redis.zrangebylex(key, Range.from(Boundary.including("value99"), Boundary.unbounded()))).hasSize(1);
assertThat(redis.zrangebylex(key, Range.from(including("value98"), including("value99")))).containsSequence("value98",
"value99");
assertThat(redis.zrangebylex(key, Range.from(including("value99"), Boundary.unbounded()))).hasSize(1);
assertThat(redis.zrangebylex(key, Range.from(Boundary.excluding("value99"), Boundary.unbounded()))).hasSize(0);
}

Expand Down

0 comments on commit 4edbaa8

Please sign in to comment.