DATAREDIS-308 - Add support for HyperLogLog.

We added methods for PFADD, PFCOUNT and PFMERGE to RedisConnection and StringRedisConnection. HyperLogLogOperations available via RedisTemplate allow more indrect usage of HLL.

Please note that currently Jedis is the only driver that can be used for HyperLogLog commands.

Original pull request: #116.
This commit is contained in:
Christoph Strobl
2014-11-26 08:16:37 +01:00
committed by Thomas Darimont
parent 83cd7a5060
commit 22aa5df73e
16 changed files with 795 additions and 15 deletions

View File

@@ -2067,6 +2067,93 @@ public abstract class AbstractConnectionIntegrationTests {
assertThat(i, is(3));
}
/**
* @see DATAREDIS-308
*/
@Test
public void pfAddShouldAddToNonExistingKeyCorrectly() {
if (!ConnectionUtils.isJedis(connectionFactory)) {
throw new AssumptionViolatedException("PFADD is only available for jedis");
}
actual.add(connection.pfAdd("hll", "a", "b", "c"));
List<Object> results = getResults();
assertThat((Long) results.get(0), is(1L));
}
/**
* @see DATAREDIS-308
*/
@Test
public void pfAddShouldReturnZeroWhenValueAlreadyExists() {
if (!ConnectionUtils.isJedis(connectionFactory)) {
throw new AssumptionViolatedException("PFADD is only available for jedis");
}
actual.add(connection.pfAdd("hll", "a", "b", "c"));
actual.add(connection.pfAdd("hll2", "c", "d", "e"));
actual.add(connection.pfAdd("hll2", "e"));
List<Object> results = getResults();
assertThat((Long) results.get(0), is(1L));
assertThat((Long) results.get(1), is(1L));
assertThat((Long) results.get(2), is(0L));
}
/**
* @see DATAREDIS-308
*/
@Test
public void pfCountShouldReturnCorrectly() {
if (!ConnectionUtils.isJedis(connectionFactory)) {
throw new AssumptionViolatedException("PFADD is only available for jedis");
}
actual.add(connection.pfAdd("hll", "a", "b", "c"));
actual.add(connection.pfCount("hll"));
List<Object> results = getResults();
assertThat((Long) results.get(0), is(1L));
assertThat((Long) results.get(1), is(3L));
}
/**
* @see DATAREDIS-308
*/
@Test
public void pfCountWithMultipleKeysShouldReturnCorrectly() {
if (!ConnectionUtils.isJedis(connectionFactory)) {
throw new AssumptionViolatedException("PFADD is only available for jedis");
}
actual.add(connection.pfAdd("hll", "a", "b", "c"));
actual.add(connection.pfAdd("hll2", "d", "e", "f"));
actual.add(connection.pfCount("hll", "hll2"));
List<Object> results = getResults();
assertThat((Long) results.get(0), is(1L));
assertThat((Long) results.get(1), is(1L));
assertThat((Long) results.get(2), is(6L));
}
/**
* @see DATAREDIS-308
*/
@Test(expected = IllegalArgumentException.class)
public void pfCountWithNullKeysShouldThrowIllegalArgumentException() {
if (!ConnectionUtils.isJedis(connectionFactory)) {
throw new AssumptionViolatedException("PFADD is only available for jedis");
}
actual.add(connection.pfCount((String[]) null));
}
protected void verifyResults(List<Object> expected) {
assertEquals(expected, getResults());
}

View File

@@ -1720,6 +1720,38 @@ public class DefaultStringRedisConnectionTests {
verify(nativeConnection, times(1)).setClientName(eq("foo".getBytes()));
}
/**
* @see DATAREDIS-308
*/
@Test
public void pfAddShouldDelegateToNativeConnectionCorrectly() {
connection.pfAdd("hll", "spring", "data", "redis");
verify(nativeConnection, times(1)).pfAdd("hll".getBytes(), "spring".getBytes(), "data".getBytes(),
"redis".getBytes());
}
/**
* @see DATAREDIS-308
*/
@Test
public void pfCountShouldDelegateToNativeConnectionCorrectly() {
connection.pfCount("hll", "hyperLogLog");
verify(nativeConnection, times(1)).pfCount("hll".getBytes(), "hyperLogLog".getBytes());
}
/**
* @see DATAREDIS-308
*/
@Test
public void pfMergeShouldDelegateToNativeConnectionCorrectly() {
connection.pfMerge("merged", "spring", "data", "redis");
verify(nativeConnection, times(1)).pfMerge("merged".getBytes(), "spring".getBytes(), "data".getBytes(),
"redis".getBytes());
}
/**
* @see DATAREDIS-270
*/

View File

@@ -791,7 +791,7 @@ public class RedisConnectionUnitTests {
public <T> T evalSha(byte[] scriptSha, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {
return delegate.evalSha(scriptSha, returnType, numKeys, keysAndArgs);
}
@Override
public Set<byte[]> zRangeByScore(byte[] key, String min, String max) {
return delegate.zRangeByScore(key, min, max);
@@ -801,5 +801,20 @@ public class RedisConnectionUnitTests {
public Set<byte[]> zRangeByScore(byte[] key, String min, String max, long offset, long count) {
return delegate.zRangeByScore(key, min, max, offset, count);
}
@Override
public Long pfAdd(byte[] key, byte[]... values) {
return delegate.pfAdd(key, values);
}
@Override
public Long pfCount(byte[]... keys) {
return delegate.pfCount(keys);
}
@Override
public void pfMerge(byte[] destinationKey, byte[]... sourceKeys) {
delegate.pfMerge(destinationKey, sourceKeys);
}
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright 2014 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.
* You may obtain a copy of the License at
*
* http://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 org.springframework.data.redis.core;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Collection;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.data.redis.ObjectFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
import org.springframework.test.annotation.IfProfileValue;
/**
* @author Christoph Strobl
*/
@RunWith(Parameterized.class)
public class DefaultHyperLogLogOperationsTests<K, V> {
private RedisTemplate<K, V> redisTemplate;
private ObjectFactory<K> keyFactory;
private ObjectFactory<V> valueFactory;
private HyperLogLogOperations<K, V> hyperLogLogOps;
public @Rule MinimumRedisVersionRule versionRule = new MinimumRedisVersionRule();
public DefaultHyperLogLogOperationsTests(RedisTemplate<K, V> redisTemplate, ObjectFactory<K> keyFactory,
ObjectFactory<V> valueFactory) {
this.redisTemplate = redisTemplate;
this.keyFactory = keyFactory;
this.valueFactory = valueFactory;
}
@Parameters
public static Collection<Object[]> testParams() {
return AbstractOperationsTestParams.testParams();
}
@Before
public void setUp() {
hyperLogLogOps = redisTemplate.opsForHyperLogLog();
}
@After
public void tearDown() {
redisTemplate.execute(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) {
connection.flushDb();
return null;
}
});
}
/**
* @see DATAREDIS
*/
@Test
@SuppressWarnings("unchecked")
@IfProfileValue(name = "redisVersion", value = "2.8+")
public void addShouldAddDistinctValuesCorrectly() {
K key = keyFactory.instance();
V v1 = valueFactory.instance();
V v2 = valueFactory.instance();
V v3 = valueFactory.instance();
assertThat(hyperLogLogOps.add(key, v1, v2, v3), equalTo(1L));
}
/**
* @see DATAREDIS-308
*/
@Test
@SuppressWarnings("unchecked")
@IfProfileValue(name = "redisVersion", value = "2.8+")
public void addShouldNotAddExistingValuesCorrectly() {
K key = keyFactory.instance();
V v1 = valueFactory.instance();
V v2 = valueFactory.instance();
V v3 = valueFactory.instance();
hyperLogLogOps.add(key, v1, v2, v3);
assertThat(hyperLogLogOps.add(key, v2), equalTo(0L));
}
/**
* @see DATAREDIS-308
*/
@Test
@SuppressWarnings("unchecked")
@IfProfileValue(name = "redisVersion", value = "2.8+")
public void sizeShouldCountValuesCorrectly() {
K key = keyFactory.instance();
V v1 = valueFactory.instance();
V v2 = valueFactory.instance();
V v3 = valueFactory.instance();
hyperLogLogOps.add(key, v1, v2, v3);
assertThat(hyperLogLogOps.size(key), equalTo(3L));
}
/**
* @see DATAREDIS-308
*/
@Test
@SuppressWarnings("unchecked")
@IfProfileValue(name = "redisVersion", value = "2.8+")
public void sizeShouldCountValuesOfMultipleKeysCorrectly() {
K key = keyFactory.instance();
V v1 = valueFactory.instance();
V v2 = valueFactory.instance();
V v3 = valueFactory.instance();
K key2 = keyFactory.instance();
V v4 = valueFactory.instance();
hyperLogLogOps.add(key, v1, v2, v3);
hyperLogLogOps.add(key2, v4);
assertThat(hyperLogLogOps.size(key, key2), equalTo(4L));
}
/**
* @throws InterruptedException
* @see DATAREDIS-308
*/
@Test
@SuppressWarnings("unchecked")
@IfProfileValue(name = "redisVersion", value = "2.8+")
public void unionShouldMergeValuesOfMultipleKeysCorrectly() throws InterruptedException {
K sourceKey_1 = keyFactory.instance();
V v1 = valueFactory.instance();
V v2 = valueFactory.instance();
V v3 = valueFactory.instance();
K sourceKey_2 = keyFactory.instance();
V v4 = valueFactory.instance();
K desinationKey = keyFactory.instance();
hyperLogLogOps.add(sourceKey_1, v1, v2, v3);
hyperLogLogOps.add(sourceKey_2, v4);
Thread.sleep(10); // give redis a little time to catch up
hyperLogLogOps.union(desinationKey, sourceKey_1, sourceKey_2);
Thread.sleep(10); // give redis a little time to catch up
assertThat(hyperLogLogOps.size(desinationKey), equalTo(4L));
}
}