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:
committed by
Thomas Darimont
parent
83cd7a5060
commit
22aa5df73e
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user