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:
Mark Paluch
2018-01-31 14:47:29 +01:00
committed by John Blum
parent 3a14cb411d
commit 649f2d7d08
6 changed files with 143 additions and 48 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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