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:
Mark Paluch
2017-12-19 10:57:50 +01:00
committed by Christoph Strobl
parent 8e481f484c
commit c6ad238cea
7 changed files with 438 additions and 53 deletions

View File

@@ -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() {

View File

@@ -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());
}
}

View File

@@ -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;
}
}
}

View File

@@ -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())));
}
}

View File

@@ -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);

View File

@@ -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));
}
}

View File

@@ -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));
}
}