DATAREDIS-744 - Support Redis hashes with colon in their id.
We now support Redis hashes via Repository support that contain colon in their id. We're using colons to split a composite id string into keyspace and id parts. Previously, we partially rejected processing of id's that don't exactly match the number of parts delimited by colon. This caused leftovers in secondary indexes. Original Pull Request: #298
This commit is contained in:
committed by
Christoph Strobl
parent
8e481f484c
commit
c6ad238cea
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -18,13 +18,14 @@ package org.springframework.data.redis.core;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.data.redis.util.ByteUtils;
|
||||
import org.springframework.data.redis.core.convert.MappingRedisConverter.BinaryKeyspaceIdentifier;
|
||||
|
||||
/**
|
||||
* {@link RedisKeyExpiredEvent} is Redis specific {@link ApplicationEvent} published when a specific key in Redis
|
||||
* expires. It might but must not hold the expired value itself next to the key.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
public class RedisKeyExpiredEvent<T> extends RedisKeyspaceEvent {
|
||||
@@ -34,12 +35,12 @@ public class RedisKeyExpiredEvent<T> extends RedisKeyspaceEvent {
|
||||
*/
|
||||
public static final Charset CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
private final byte[][] args;
|
||||
private final BinaryKeyspaceIdentifier objectId;
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Creates new {@link RedisKeyExpiredEvent}.
|
||||
*
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
public RedisKeyExpiredEvent(byte[] key) {
|
||||
@@ -67,36 +68,36 @@ public class RedisKeyExpiredEvent<T> extends RedisKeyspaceEvent {
|
||||
public RedisKeyExpiredEvent(String channel, byte[] key, Object value) {
|
||||
super(channel, key);
|
||||
|
||||
args = ByteUtils.split(key, ':');
|
||||
if (BinaryKeyspaceIdentifier.isValid(key)) {
|
||||
this.objectId = BinaryKeyspaceIdentifier.of(key);
|
||||
} else {
|
||||
this.objectId = null;
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the keyspace in which the expiration occured.
|
||||
*
|
||||
*
|
||||
* @return {@literal null} if it could not be determined.
|
||||
*/
|
||||
public String getKeyspace() {
|
||||
|
||||
if (args.length >= 2) {
|
||||
return new String(args[0], CHARSET);
|
||||
}
|
||||
|
||||
return null;
|
||||
return objectId != null ? new String(objectId.getKeyspace(), CHARSET) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expired objects id;
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public byte[] getId() {
|
||||
return args.length == 2 ? args[1] : args[0];
|
||||
return objectId != null ? objectId.getId() : getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expired Object
|
||||
*
|
||||
*
|
||||
* @return {@literal null} if not present.
|
||||
*/
|
||||
public Object getValue() {
|
||||
|
||||
@@ -27,8 +27,6 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -52,6 +50,8 @@ import org.springframework.data.redis.core.convert.CustomConversions;
|
||||
import org.springframework.data.redis.core.convert.GeoIndexedPropertyValue;
|
||||
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
|
||||
import org.springframework.data.redis.core.convert.MappingRedisConverter;
|
||||
import org.springframework.data.redis.core.convert.MappingRedisConverter.BinaryKeyspaceIdentifier;
|
||||
import org.springframework.data.redis.core.convert.MappingRedisConverter.KeyspaceIdentifier;
|
||||
import org.springframework.data.redis.core.convert.PathIndexResolver;
|
||||
import org.springframework.data.redis.core.convert.RedisConverter;
|
||||
import org.springframework.data.redis.core.convert.RedisData;
|
||||
@@ -64,7 +64,6 @@ import org.springframework.data.redis.util.ByteUtils;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Redis specific {@link KeyValueAdapter} implementation. Uses binary codec to read/write data from/to Redis. Objects
|
||||
@@ -228,7 +227,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
connection.expire(objectKey, rdo.getTimeToLive().longValue());
|
||||
|
||||
// add phantom key so values can be restored
|
||||
byte[] phantomKey = ByteUtils.concat(objectKey, toBytes(":phantom"));
|
||||
byte[] phantomKey = ByteUtils.concat(objectKey, BinaryKeyspaceIdentifier.PHANTOM_SUFFIX);
|
||||
connection.del(phantomKey);
|
||||
connection.hMSet(phantomKey, rdo.getBucket().rawMap());
|
||||
connection.expire(phantomKey, rdo.getTimeToLive().longValue() + 300);
|
||||
@@ -484,14 +483,14 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
connection.expire(redisKey, rdo.getTimeToLive().longValue());
|
||||
|
||||
// add phantom key so values can be restored
|
||||
byte[] phantomKey = ByteUtils.concat(redisKey, toBytes(":phantom"));
|
||||
byte[] phantomKey = ByteUtils.concat(redisKey, BinaryKeyspaceIdentifier.PHANTOM_SUFFIX);
|
||||
connection.hMSet(phantomKey, rdo.getBucket().rawMap());
|
||||
connection.expire(phantomKey, rdo.getTimeToLive().longValue() + 300);
|
||||
|
||||
} else {
|
||||
|
||||
connection.persist(redisKey);
|
||||
connection.persist(ByteUtils.concat(redisKey, toBytes(":phantom")));
|
||||
connection.persist(ByteUtils.concat(redisKey, BinaryKeyspaceIdentifier.PHANTOM_SUFFIX));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,7 +778,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
byte[] key = message.getBody();
|
||||
|
||||
final byte[] phantomKey = ByteUtils.concat(key,
|
||||
converter.getConversionService().convert(":phantom", byte[].class));
|
||||
converter.getConversionService().convert(KeyspaceIdentifier.PHANTOM_SUFFIX, byte[].class));
|
||||
|
||||
Map<byte[], byte[]> hash = ops.execute(new RedisCallback<Map<byte[], byte[]>>() {
|
||||
|
||||
@@ -822,12 +821,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[][] args = ByteUtils.split(message.getBody(), ':');
|
||||
if (args.length != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return BinaryKeyspaceIdentifier.isValid(message.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.redis.core.convert;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -58,6 +62,7 @@ import org.springframework.data.redis.core.PartialUpdate.UpdateCommand;
|
||||
import org.springframework.data.redis.core.index.Indexed;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
|
||||
import org.springframework.data.redis.util.ByteUtils;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -312,9 +317,14 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
for (Entry<String, byte[]> entry : bucket.entrySet()) {
|
||||
|
||||
String referenceKey = fromBytes(entry.getValue(), String.class);
|
||||
String[] args = referenceKey.split(":");
|
||||
|
||||
Map<byte[], byte[]> rawHash = referenceResolver.resolveReference(args[1], args[0]);
|
||||
if (!KeyspaceIdentifier.isValid(referenceKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
KeyspaceIdentifier identifier = KeyspaceIdentifier.of(referenceKey);
|
||||
Map<byte[], byte[]> rawHash = referenceResolver.resolveReference(identifier.getId(),
|
||||
identifier.getKeyspace());
|
||||
|
||||
if (!CollectionUtils.isEmpty(rawHash)) {
|
||||
target.add(read(association.getInverse().getActualType(), new RedisData(rawHash)));
|
||||
@@ -330,15 +340,18 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = fromBytes(binKey, String.class);
|
||||
String referenceKey = fromBytes(binKey, String.class);
|
||||
if (KeyspaceIdentifier.isValid(referenceKey)) {
|
||||
|
||||
String[] args = key.split(":");
|
||||
KeyspaceIdentifier identifier = KeyspaceIdentifier.of(referenceKey);
|
||||
|
||||
Map<byte[], byte[]> rawHash = referenceResolver.resolveReference(args[1], args[0]);
|
||||
Map<byte[], byte[]> rawHash = referenceResolver.resolveReference(identifier.getId(),
|
||||
identifier.getKeyspace());
|
||||
|
||||
if (!CollectionUtils.isEmpty(rawHash)) {
|
||||
accessor.setProperty(association.getInverse(),
|
||||
read(association.getInverse().getActualType(), new RedisData(rawHash)));
|
||||
if (!CollectionUtils.isEmpty(rawHash)) {
|
||||
accessor.setProperty(association.getInverse(),
|
||||
read(association.getInverse().getActualType(), new RedisData(rawHash)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1144,4 +1157,177 @@ public class MappingRedisConverter implements RedisConverter, InitializingBean {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object representing a Redis Hash/Object identifier composed from keyspace and object id in the form of
|
||||
* {@literal keyspace:id}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.8.10
|
||||
*/
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@Getter
|
||||
public static class KeyspaceIdentifier {
|
||||
|
||||
public static final String PHANTOM = "phantom";
|
||||
public static final String DELIMITTER = ":";
|
||||
public static final String PHANTOM_SUFFIX = DELIMITTER + PHANTOM;
|
||||
|
||||
private String keyspace;
|
||||
private String id;
|
||||
private boolean phantomKey;
|
||||
|
||||
/**
|
||||
* Parse a {@code key} into {@link KeyspaceIdentifier}.
|
||||
*
|
||||
* @param key the key representation.
|
||||
* @return {@link BinaryKeyspaceIdentifier} for binary key.
|
||||
*/
|
||||
public static KeyspaceIdentifier of(String key) {
|
||||
|
||||
Assert.isTrue(isValid(key), String.format("Invalid key %s", key));
|
||||
|
||||
boolean phantomKey = key.endsWith(PHANTOM_SUFFIX);
|
||||
int keyspaceEndIndex = key.indexOf(DELIMITTER);
|
||||
String keyspace = key.substring(0, keyspaceEndIndex);
|
||||
String id;
|
||||
|
||||
if (phantomKey) {
|
||||
id = key.substring(keyspaceEndIndex + 1, key.length() - PHANTOM_SUFFIX.length());
|
||||
} else {
|
||||
id = key.substring(keyspaceEndIndex + 1);
|
||||
}
|
||||
|
||||
return new KeyspaceIdentifier(keyspace, id, phantomKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the {@code key} is valid, in particular whether the key contains a keyspace and an id part in the
|
||||
* form of {@literal keyspace:id}.
|
||||
*
|
||||
* @param key the key.
|
||||
* @return {@literal true} if the key is valid.
|
||||
*/
|
||||
public static boolean isValid(String key) {
|
||||
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int keyspaceEndIndex = key.indexOf(DELIMITTER);
|
||||
|
||||
return keyspaceEndIndex > 0 && key.length() > keyspaceEndIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object representing a binary Redis Hash/Object identifier composed from keyspace and object id in the form of
|
||||
* {@literal keyspace:id}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.8.10
|
||||
*/
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@Getter
|
||||
public static class BinaryKeyspaceIdentifier {
|
||||
|
||||
public static final byte[] PHANTOM = KeyspaceIdentifier.PHANTOM.getBytes();
|
||||
public static final byte DELIMITTER = ':';
|
||||
public static final byte[] PHANTOM_SUFFIX = ByteUtils.concat(new byte[] { DELIMITTER }, PHANTOM);
|
||||
|
||||
private byte[] keyspace;
|
||||
private byte[] id;
|
||||
private boolean phantomKey;
|
||||
|
||||
/**
|
||||
* Parse a binary {@code key} into {@link BinaryKeyspaceIdentifier}.
|
||||
*
|
||||
* @param key the binary key representation.
|
||||
* @return {@link BinaryKeyspaceIdentifier} for binary key.
|
||||
*/
|
||||
public static BinaryKeyspaceIdentifier of(byte[] key) {
|
||||
|
||||
Assert.isTrue(isValid(key), String.format("Invalid key %s", new String(key)));
|
||||
|
||||
boolean phantomKey = startsWith(key, PHANTOM_SUFFIX, key.length - PHANTOM_SUFFIX.length);
|
||||
|
||||
int keyspaceEndIndex = find(key, DELIMITTER);
|
||||
byte[] keyspace = getKeyspace(key, keyspaceEndIndex);
|
||||
byte[] id = getId(key, phantomKey, keyspaceEndIndex);
|
||||
|
||||
return new BinaryKeyspaceIdentifier(keyspace, id, phantomKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the {@code key} is valid, in particular whether the key contains a keyspace and an id part in the
|
||||
* form of {@literal keyspace:id}.
|
||||
*
|
||||
* @param key the key.
|
||||
* @return {@literal true} if the key is valid.
|
||||
*/
|
||||
public static boolean isValid(byte[] key) {
|
||||
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int keyspaceEndIndex = find(key, DELIMITTER);
|
||||
|
||||
return keyspaceEndIndex > 0 && key.length > keyspaceEndIndex;
|
||||
}
|
||||
|
||||
private static byte[] getId(byte[] key, boolean phantomKey, int keyspaceEndIndex) {
|
||||
|
||||
int idSize;
|
||||
|
||||
if (phantomKey) {
|
||||
idSize = (key.length - PHANTOM_SUFFIX.length) - (keyspaceEndIndex + 1);
|
||||
} else {
|
||||
|
||||
idSize = key.length - (keyspaceEndIndex + 1);
|
||||
}
|
||||
|
||||
byte[] id = new byte[idSize];
|
||||
System.arraycopy(key, keyspaceEndIndex + 1, id, 0, idSize);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private static byte[] getKeyspace(byte[] key, int keyspaceEndIndex) {
|
||||
|
||||
byte[] keyspace = new byte[keyspaceEndIndex];
|
||||
System.arraycopy(key, 0, keyspace, 0, keyspaceEndIndex);
|
||||
|
||||
return keyspace;
|
||||
}
|
||||
|
||||
private static boolean startsWith(byte[] haystack, byte[] prefix, int offset) {
|
||||
|
||||
int to = offset;
|
||||
int prefixOffset = 0;
|
||||
int prefixLength = prefix.length;
|
||||
|
||||
if ((offset < 0) || (offset > haystack.length - prefixLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (--prefixLength >= 0) {
|
||||
if (haystack[to++] != prefix[prefixOffset++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int find(byte[] haystack, byte needle) {
|
||||
|
||||
for (int i = 0; i < haystack.length; i++) {
|
||||
if (haystack[i] == needle) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* 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.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RedisKeyExpiredEvent}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class RedisKeyExpiredEventUnitTests {
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void shouldReturnKeyspace() {
|
||||
|
||||
assertThat(new RedisKeyExpiredEvent<Object>("foo".getBytes(), "").getKeyspace(), is(nullValue()));
|
||||
assertThat(new RedisKeyExpiredEvent<Object>("foo:bar".getBytes(), "").getKeyspace(), is(equalTo("foo")));
|
||||
assertThat(new RedisKeyExpiredEvent<Object>("foo:bar:baz".getBytes(), "").getKeyspace(), is(equalTo("foo")));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void shouldReturnId() {
|
||||
|
||||
assertThat(new RedisKeyExpiredEvent<Object>("foo".getBytes(), "").getId(), is(equalTo("foo".getBytes())));
|
||||
assertThat(new RedisKeyExpiredEvent<Object>("foo:bar".getBytes(), "").getId(), is(equalTo("bar".getBytes())));
|
||||
assertThat(new RedisKeyExpiredEvent<Object>("foo:bar:baz".getBytes(), "").getId(),
|
||||
is(equalTo("bar:baz".getBytes())));
|
||||
}
|
||||
}
|
||||
@@ -151,6 +151,20 @@ public class RedisKeyValueAdapterTests {
|
||||
assertThat(template.opsForHash().entries("persons:1").size(), is(2));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void putWritesDataWithColonCorrectly() {
|
||||
|
||||
Person rand = new Person();
|
||||
rand.age = 24;
|
||||
|
||||
adapter.put("1:a", rand, "persons");
|
||||
|
||||
assertThat(template.keys("persons*"), hasItems("persons", "persons:1:a"));
|
||||
assertThat(template.opsForSet().size("persons"), is(1L));
|
||||
assertThat(template.opsForSet().members("persons"), hasItems("1:a"));
|
||||
assertThat(template.opsForHash().entries("persons:1:a").size(), is(2));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-425
|
||||
public void putWritesSimpleIndexDataCorrectly() {
|
||||
|
||||
@@ -163,6 +177,18 @@ public class RedisKeyValueAdapterTests {
|
||||
assertThat(template.opsForSet().members("persons:firstname:rand"), hasItems("1"));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void putWritesSimpleIndexDataWithColonCorrectly() {
|
||||
|
||||
Person rand = new Person();
|
||||
rand.firstname = "rand";
|
||||
|
||||
adapter.put("1:a", rand, "persons");
|
||||
|
||||
assertThat(template.keys("persons*"), hasItem("persons:firstname:rand"));
|
||||
assertThat(template.opsForSet().members("persons:firstname:rand"), hasItems("1:a"));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-425
|
||||
public void putWritesNestedDataCorrectly() {
|
||||
|
||||
@@ -189,15 +215,15 @@ public class RedisKeyValueAdapterTests {
|
||||
assertThat(template.opsForSet().members("persons:address.country:Andor"), hasItems("1"));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-425
|
||||
@Test // DATAREDIS-425, DATAREDIS-744
|
||||
public void getShouldReadSimpleObjectCorrectly() {
|
||||
|
||||
Map<String, String> map = new LinkedHashMap<String, String>();
|
||||
map.put("_class", Person.class.getName());
|
||||
map.put("age", "24");
|
||||
template.opsForHash().putAll("persons:load-1", map);
|
||||
template.opsForHash().putAll("persons:load-1:a", map);
|
||||
|
||||
Object loaded = adapter.get("load-1", "persons");
|
||||
Object loaded = adapter.get("load-1:a", "persons");
|
||||
|
||||
assertThat(loaded, instanceOf(Person.class));
|
||||
assertThat(((Person) loaded).age, is(24));
|
||||
@@ -262,7 +288,7 @@ public class RedisKeyValueAdapterTests {
|
||||
assertThat(template.opsForSet().members("persons:firstname:rand"), not(hasItem("1")));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-425
|
||||
@Test // DATAREDIS-425, DATAREDIS-744
|
||||
public void keyExpiredEventShouldRemoveHelperStructures() throws Exception {
|
||||
|
||||
assumeTrue(RedisTestProfileValueSource.matches("runLongTests", "true"));
|
||||
@@ -272,22 +298,22 @@ public class RedisKeyValueAdapterTests {
|
||||
map.put("firstname", "rand");
|
||||
map.put("address.country", "Andor");
|
||||
|
||||
template.opsForHash().putAll("persons:1", map);
|
||||
template.opsForHash().putAll("persons:1:b", map);
|
||||
|
||||
template.opsForSet().add("persons", "1");
|
||||
template.opsForSet().add("persons:firstname:rand", "1");
|
||||
template.opsForSet().add("persons:1:idx", "persons:firstname:rand");
|
||||
template.opsForSet().add("persons:firstname:rand", "1:b");
|
||||
template.opsForSet().add("persons:1:b:idx", "persons:firstname:rand");
|
||||
|
||||
template.expire("persons:1", 100, TimeUnit.MILLISECONDS);
|
||||
template.expire("persons:1:b", 100, TimeUnit.MILLISECONDS);
|
||||
|
||||
waitUntilKeyIsGone(template, "persons:1");
|
||||
waitUntilKeyIsGone(template, "persons:1:phantom");
|
||||
waitUntilKeyIsGone(template, "persons:1:b");
|
||||
waitUntilKeyIsGone(template, "persons:1:b:phantom");
|
||||
waitUntilKeyIsGone(template, "persons:firstname:rand");
|
||||
|
||||
assertThat(template.hasKey("persons:1"), is(false));
|
||||
assertThat(template.hasKey("persons:firstname:rand"), is(false));
|
||||
assertThat(template.hasKey("persons:1:idx"), is(false));
|
||||
assertThat(template.opsForSet().members("persons"), not(hasItem("1")));
|
||||
assertThat(template.hasKey("persons:1:b:idx"), is(false));
|
||||
assertThat(template.opsForSet().members("persons"), not(hasItem("1:b")));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-589
|
||||
@@ -353,17 +379,17 @@ public class RedisKeyValueAdapterTests {
|
||||
assertThat(template.opsForSet().isMember("persons:mat:idx", "persons:firstname:mat"), is(true));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-471
|
||||
@Test // DATAREDIS-471, DATAREDIS-744
|
||||
public void updateShouldAlterIndexDataCorrectly() {
|
||||
|
||||
Person rand = new Person();
|
||||
rand.firstname = "rand";
|
||||
|
||||
adapter.put("1", rand, "persons");
|
||||
adapter.put("1:a", rand, "persons");
|
||||
|
||||
assertThat(template.hasKey("persons:firstname:rand"), is(true));
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1:a", Person.class) //
|
||||
.set("firstname", "mat");
|
||||
|
||||
adapter.update(update);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* 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.convert;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.redis.core.convert.MappingRedisConverter.BinaryKeyspaceIdentifier;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link BinaryKeyspaceIdentifier}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class BinaryKeyspaceIdentifierUnitTests {
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void shouldReturnIfKeyIsValid() {
|
||||
|
||||
assertThat(BinaryKeyspaceIdentifier.isValid(null), is(false));
|
||||
assertThat(BinaryKeyspaceIdentifier.isValid("foo".getBytes()), is(false));
|
||||
assertThat(BinaryKeyspaceIdentifier.isValid("".getBytes()), is(false));
|
||||
assertThat(BinaryKeyspaceIdentifier.isValid("foo:bar".getBytes()), is(true));
|
||||
assertThat(BinaryKeyspaceIdentifier.isValid("foo:bar:baz".getBytes()), is(true));
|
||||
assertThat(BinaryKeyspaceIdentifier.isValid("foo:bar:baz:phantom".getBytes()), is(true));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void shouldReturnKeyspace() {
|
||||
|
||||
assertThat(BinaryKeyspaceIdentifier.of("foo:bar".getBytes()).getKeyspace(), is(equalTo("foo".getBytes())));
|
||||
assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz".getBytes()).getKeyspace(), is(equalTo("foo".getBytes())));
|
||||
assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz:phantom".getBytes()).getKeyspace(),
|
||||
is(equalTo("foo".getBytes())));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void shouldReturnId() {
|
||||
|
||||
assertThat(BinaryKeyspaceIdentifier.of("foo:bar".getBytes()).getId(), is(equalTo("bar".getBytes())));
|
||||
assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz".getBytes()).getId(), is(equalTo("bar:baz".getBytes())));
|
||||
assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz:phantom".getBytes()).getId(),
|
||||
is(equalTo("bar:baz".getBytes())));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void shouldReturnPhantomKey() {
|
||||
|
||||
assertThat(BinaryKeyspaceIdentifier.of("foo:bar".getBytes()).isPhantomKey(), is(false));
|
||||
assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz".getBytes()).isPhantomKey(), is(false));
|
||||
assertThat(BinaryKeyspaceIdentifier.of("foo:bar:baz:phantom".getBytes()).isPhantomKey(), is(true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* 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.convert;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.redis.core.convert.MappingRedisConverter.KeyspaceIdentifier;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link KeyspaceIdentifier}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class KeyspaceIdentifierUnitTests {
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void shouldReturnIfKeyIsValid() {
|
||||
|
||||
assertThat(KeyspaceIdentifier.isValid(null), is(false));
|
||||
assertThat(KeyspaceIdentifier.isValid("foo"), is(false));
|
||||
assertThat(KeyspaceIdentifier.isValid(""), is(false));
|
||||
assertThat(KeyspaceIdentifier.isValid("foo:bar"), is(true));
|
||||
assertThat(KeyspaceIdentifier.isValid("foo:bar:baz"), is(true));
|
||||
assertThat(KeyspaceIdentifier.isValid("foo:bar:baz:phantom"), is(true));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void shouldReturnKeyspace() {
|
||||
|
||||
assertThat(KeyspaceIdentifier.of("foo:bar").getKeyspace(), is("foo"));
|
||||
assertThat(KeyspaceIdentifier.of("foo:bar:baz").getKeyspace(), is("foo"));
|
||||
assertThat(KeyspaceIdentifier.of("foo:bar:baz:phantom").getKeyspace(), is("foo"));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void shouldReturnId() {
|
||||
|
||||
assertThat(KeyspaceIdentifier.of("foo:bar").getId(), is("bar"));
|
||||
assertThat(KeyspaceIdentifier.of("foo:bar:baz").getId(), is("bar:baz"));
|
||||
assertThat(KeyspaceIdentifier.of("foo:bar:baz:phantom").getId(), is("bar:baz"));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-744
|
||||
public void shouldReturnPhantomKey() {
|
||||
|
||||
assertThat(KeyspaceIdentifier.of("foo:bar").isPhantomKey(), is(false));
|
||||
assertThat(KeyspaceIdentifier.of("foo:bar:baz").isPhantomKey(), is(false));
|
||||
assertThat(KeyspaceIdentifier.of("foo:bar:baz:phantom").isPhantomKey(), is(true));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user