DATACASS-284 - Add support for TupleValue.
We now support TupleValue as simple Cassandra type within domain objects along with table schema generation. Tuple columns must be annotated with @CassandraType to derive the according Cassandra column type for schema generation.
@Table
class Person {
@Id
String id;
@CassandraType(type = Name.TUPLE, typeArguments = { Name.VARCHAR, Name.BIGINT })
TupleValue tupleValue;
}
This commit is contained in:
@@ -19,16 +19,7 @@ import static org.springframework.data.cassandra.core.cql.CqlIdentifier.*;
|
||||
import static org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification.*;
|
||||
import static org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
@@ -56,8 +47,11 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.datastax.driver.core.CodecRegistry;
|
||||
import com.datastax.driver.core.DataType;
|
||||
import com.datastax.driver.core.DataType.Name;
|
||||
import com.datastax.driver.core.ProtocolVersion;
|
||||
import com.datastax.driver.core.TupleType;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link MappingContext} for Cassandra using {@link CassandraPersistentEntity} and
|
||||
@@ -509,6 +503,14 @@ public class CassandraMappingContext
|
||||
}
|
||||
}
|
||||
|
||||
if (annotation.type() == Name.TUPLE) {
|
||||
|
||||
DataType[] dataTypes = Arrays.stream(annotation.typeArguments()) //
|
||||
.map(CassandraSimpleTypeHolder::getDataTypeFor) //
|
||||
.toArray(DataType[]::new);
|
||||
return TupleType.of(ProtocolVersion.NEWEST_SUPPORTED, CodecRegistry.DEFAULT_INSTANCE, dataTypes);
|
||||
}
|
||||
|
||||
return property.getDataType();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.datastax.driver.core.CodecRegistry;
|
||||
import com.datastax.driver.core.DataType;
|
||||
import com.datastax.driver.core.DataType.Name;
|
||||
import com.datastax.driver.core.Row;
|
||||
import com.datastax.driver.core.TupleValue;
|
||||
import com.datastax.driver.core.TypeCodec;
|
||||
import com.datastax.driver.core.UDTValue;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
@@ -73,6 +74,7 @@ public class CassandraSimpleTypeHolder extends SimpleTypeHolder {
|
||||
simpleTypes.add(Number.class);
|
||||
simpleTypes.add(Row.class);
|
||||
simpleTypes.add(UDTValue.class);
|
||||
simpleTypes.add(TupleValue.class);
|
||||
|
||||
classToDataType = Collections.unmodifiableMap(classToDataType(codecRegistry, primitiveWrappers));
|
||||
nameToDataType = Collections.unmodifiableMap(nameToDataType());
|
||||
|
||||
@@ -20,16 +20,19 @@ import static org.junit.Assume.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
@@ -45,9 +48,12 @@ import org.springframework.data.cassandra.support.CassandraVersion;
|
||||
import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTest;
|
||||
import org.springframework.data.util.Version;
|
||||
|
||||
import com.datastax.driver.core.DataType;
|
||||
import com.datastax.driver.core.Duration;
|
||||
import com.datastax.driver.core.LocalDate;
|
||||
import com.datastax.driver.core.SimpleStatement;
|
||||
import com.datastax.driver.core.TupleType;
|
||||
import com.datastax.driver.core.TupleValue;
|
||||
|
||||
/**
|
||||
* Integration tests for type mapping using {@link CassandraOperations}.
|
||||
@@ -72,8 +78,13 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
|
||||
SchemaTestUtils.potentiallyCreateTableFor(AllPossibleTypes.class, operations);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(TimeEntity.class, operations);
|
||||
|
||||
operations.getCqlOperations().execute("DROP TABLE IF EXISTS ListOfTuples;");
|
||||
operations.getCqlOperations()
|
||||
.execute("CREATE TABLE ListOfTuples (id varchar PRIMARY KEY, tuples frozen<list<tuple<varchar, bigint>>>);");
|
||||
|
||||
SchemaTestUtils.truncate(AllPossibleTypes.class, operations);
|
||||
SchemaTestUtils.truncate(TimeEntity.class, operations);
|
||||
SchemaTestUtils.truncate(ListOfTuples.class, operations);
|
||||
|
||||
if (cassandraVersion.isGreaterThanOrEqualTo(VERSION_3_10)) {
|
||||
|
||||
@@ -445,6 +456,38 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
|
||||
assertThat(loaded.getSetOfEnum()).contains(Condition.MINT);
|
||||
}
|
||||
|
||||
@Test // DATACASS-284
|
||||
public void shouldReadAndWriteTupleType() {
|
||||
|
||||
TupleType tupleType = cluster.getMetadata().newTupleType(DataType.varchar(), DataType.bigint());
|
||||
AllPossibleTypes entity = new AllPossibleTypes("1");
|
||||
|
||||
entity.setTupleValue(tupleType.newValue("foo", 23L));
|
||||
|
||||
operations.insert(entity);
|
||||
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
|
||||
|
||||
assertThat(loaded.getTupleValue().getObject(0)).isEqualTo("foo");
|
||||
assertThat(loaded.getTupleValue().getObject(1)).isEqualTo(23L);
|
||||
}
|
||||
|
||||
@Test // DATACASS-284
|
||||
public void shouldReadAndWriteListOfTuples() {
|
||||
|
||||
TupleType tupleType = cluster.getMetadata().newTupleType(DataType.varchar(), DataType.bigint());
|
||||
|
||||
ListOfTuples entity = new ListOfTuples();
|
||||
entity.setId("foo");
|
||||
|
||||
entity.setTuples(Arrays.asList(tupleType.newValue("foo", 23L), tupleType.newValue("bar", 42L)));
|
||||
|
||||
operations.insert(entity);
|
||||
ListOfTuples loaded = operations.selectOneById(entity.getId(), ListOfTuples.class);
|
||||
|
||||
assertThat(loaded.getTuples().get(0).getObject(0)).isEqualTo("foo");
|
||||
assertThat(loaded.getTuples().get(1).getObject(0)).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test // DATACASS-271
|
||||
public void shouldReadAndWriteTime() {
|
||||
|
||||
@@ -647,4 +690,12 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
|
||||
@Id String id;
|
||||
Duration duration;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
static class ListOfTuples {
|
||||
|
||||
@Id String id;
|
||||
List<TupleValue> tuples;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,13 @@ import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
|
||||
import com.datastax.driver.core.CodecRegistry;
|
||||
import com.datastax.driver.core.DataType;
|
||||
import com.datastax.driver.core.DataType.Name;
|
||||
import com.datastax.driver.core.ProtocolVersion;
|
||||
import com.datastax.driver.core.TableMetadata;
|
||||
import com.datastax.driver.core.TupleType;
|
||||
import com.datastax.driver.core.TupleValue;
|
||||
import com.datastax.driver.core.UDTValue;
|
||||
import com.datastax.driver.core.UserType;
|
||||
|
||||
@@ -336,6 +340,27 @@ public class CassandraMappingContextUnitTests {
|
||||
assertThat(entries.getColumnFunction()).isEqualTo(ColumnFunction.NONE);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class) // DATACASS-284
|
||||
public void shouldRejectUntypedTuples() {
|
||||
|
||||
mappingContext.getCreateTableSpecificationFor(mappingContext.getRequiredPersistentEntity(UntypedTupleEntity.class));
|
||||
}
|
||||
|
||||
@Test // DATACASS-284
|
||||
public void shouldCreateTableForTypedTupleType() {
|
||||
|
||||
CreateTableSpecification tableSpecification = mappingContext
|
||||
.getCreateTableSpecificationFor(mappingContext.getRequiredPersistentEntity(TypedTupleEntity.class));
|
||||
|
||||
assertThat(tableSpecification.getColumns()).hasSize(2);
|
||||
|
||||
ColumnSpecification column = tableSpecification.getColumns().get(1);
|
||||
|
||||
assertThat(column.getType()).isInstanceOf(TupleType.class);
|
||||
assertThat(column.getType()).isEqualTo(TupleType.of(ProtocolVersion.NEWEST_SUPPORTED,
|
||||
CodecRegistry.DEFAULT_INSTANCE, DataType.varchar(), DataType.bigint()));
|
||||
}
|
||||
|
||||
private static CreateIndexSpecification getSpecificationFor(String column,
|
||||
List<CreateIndexSpecification> specifications) {
|
||||
|
||||
@@ -636,4 +661,18 @@ public class CassandraMappingContextUnitTests {
|
||||
public static class AnotherNested {
|
||||
String str;
|
||||
}
|
||||
|
||||
@Table
|
||||
static class UntypedTupleEntity {
|
||||
|
||||
@Id String id;
|
||||
TupleType untyped;
|
||||
}
|
||||
|
||||
@Table
|
||||
static class TypedTupleEntity {
|
||||
|
||||
@Id String id;
|
||||
@CassandraType(type = Name.TUPLE, typeArguments = { Name.VARCHAR, Name.BIGINT }) TupleValue typed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
@@ -25,17 +30,13 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.cassandra.core.convert.CassandraTypeMappingIntegrationTest.Condition;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType;
|
||||
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
|
||||
import org.springframework.data.cassandra.core.mapping.Table;
|
||||
|
||||
import com.datastax.driver.core.DataType.Name;
|
||||
import com.datastax.driver.core.TupleValue;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
@@ -46,50 +47,52 @@ import com.datastax.driver.core.DataType.Name;
|
||||
@RequiredArgsConstructor
|
||||
public class AllPossibleTypes {
|
||||
|
||||
@PrimaryKey @NonNull private String id;
|
||||
@PrimaryKey @NonNull String id;
|
||||
|
||||
private InetAddress inet;
|
||||
InetAddress inet;
|
||||
|
||||
private UUID uuid;
|
||||
UUID uuid;
|
||||
|
||||
@CassandraType(type = Name.INT) private Number justNumber;
|
||||
@CassandraType(type = Name.INT) Number justNumber;
|
||||
|
||||
private Byte boxedByte;
|
||||
private byte primitiveByte;
|
||||
Byte boxedByte;
|
||||
byte primitiveByte;
|
||||
|
||||
private Short boxedShort;
|
||||
private short primitiveShort;
|
||||
Short boxedShort;
|
||||
short primitiveShort;
|
||||
|
||||
private Long boxedLong;
|
||||
private long primitiveLong;
|
||||
Long boxedLong;
|
||||
long primitiveLong;
|
||||
|
||||
private Integer boxedInteger;
|
||||
private int primitiveInteger;
|
||||
Integer boxedInteger;
|
||||
int primitiveInteger;
|
||||
|
||||
private Float boxedFloat;
|
||||
private float primitiveFloat;
|
||||
Float boxedFloat;
|
||||
float primitiveFloat;
|
||||
|
||||
private Double boxedDouble;
|
||||
private double primitiveDouble;
|
||||
Double boxedDouble;
|
||||
double primitiveDouble;
|
||||
|
||||
private Boolean boxedBoolean;
|
||||
private boolean primitiveBoolean;
|
||||
Boolean boxedBoolean;
|
||||
boolean primitiveBoolean;
|
||||
|
||||
private com.datastax.driver.core.LocalDate date;
|
||||
com.datastax.driver.core.LocalDate date;
|
||||
|
||||
private Date timestamp;
|
||||
Date timestamp;
|
||||
|
||||
private BigDecimal bigDecimal;
|
||||
private BigInteger bigInteger;
|
||||
private ByteBuffer blob;
|
||||
BigDecimal bigDecimal;
|
||||
BigInteger bigInteger;
|
||||
ByteBuffer blob;
|
||||
|
||||
private Set<String> setOfString;
|
||||
private List<String> listOfString;
|
||||
private Map<String, String> mapOfString;
|
||||
Set<String> setOfString;
|
||||
List<String> listOfString;
|
||||
Map<String, String> mapOfString;
|
||||
|
||||
private Condition anEnum;
|
||||
private Set<Condition> setOfEnum;
|
||||
private List<Condition> listOfEnum;
|
||||
Condition anEnum;
|
||||
Set<Condition> setOfEnum;
|
||||
List<Condition> listOfEnum;
|
||||
|
||||
@CassandraType(type = Name.TUPLE, typeArguments = { Name.VARCHAR, Name.BIGINT }) TupleValue tupleValue;
|
||||
|
||||
// supported by conversion
|
||||
java.time.Instant instant;
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.repository.isolated;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.offset;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
@@ -32,7 +31,6 @@ import java.util.Set;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.cassandra.config.SchemaAction;
|
||||
@@ -268,7 +266,7 @@ public class RepositoryReturnTypesIntegrationTests extends AbstractSpringDataEmb
|
||||
allPossibleTypesRepository.save(entity);
|
||||
|
||||
Map<String, Object> result = allPossibleTypesRepository.findEntityAsMapById(entity.getId());
|
||||
assertThat(result).hasSize(42);
|
||||
assertThat(result.size()).isGreaterThan(30);
|
||||
assertThat(result.get("primitiveinteger")).isEqualTo((Object) Integer.valueOf(123));
|
||||
assertThat(result.get("biginteger")).isEqualTo((Object) BigInteger.ONE);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user