Revise simple query and update value conversion.

We now defer simple value conversion in the converter to ensure we're converting all complex values first. We also apply conversion if the value isn't assignable to the requested target type.

We removed double-conversion from part-tree queries to avoid duplicate conversion.

Closes #1384
This commit is contained in:
Mark Paluch
2023-05-25 14:08:06 +02:00
parent eec39f3585
commit b399c0a069
12 changed files with 120 additions and 158 deletions

View File

@@ -500,6 +500,16 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
public DataType getDataType() {
throw new MappingException(String.format("Cannot resolve DataType for %s", getType().getName()));
}
@Override
public boolean isTupleType() {
return false;
}
@Override
public boolean isUserDefinedType() {
return false;
}
}
}

View File

@@ -36,30 +36,15 @@ import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.BasicMapId;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.core.mapping.Column;
import org.springframework.data.cassandra.core.mapping.Element;
import org.springframework.data.cassandra.core.mapping.Embedded;
import org.springframework.data.cassandra.core.mapping.*;
import org.springframework.data.cassandra.core.mapping.Embedded.OnEmpty;
import org.springframework.data.cassandra.core.mapping.EmbeddedEntityOperations;
import org.springframework.data.cassandra.core.mapping.MapId;
import org.springframework.data.cassandra.core.mapping.MapIdentifiable;
import org.springframework.data.cassandra.core.mapping.PersistentPropertyTranslator;
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mapping.AccessOptions;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
@@ -347,8 +332,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
CassandraValueProvider valueProviderToUse = new TranslatingCassandraValueProvider(propertyTranslator,
valueProvider);
InstanceCreatorMetadata<CassandraPersistentProperty> persistenceCreator = mappedEntity
.getInstanceCreatorMetadata();
InstanceCreatorMetadata<CassandraPersistentProperty> persistenceCreator = mappedEntity.getInstanceCreatorMetadata();
ParameterValueProvider<CassandraPersistentProperty> provider;
if (persistenceCreator != null && persistenceCreator.hasParameters()) {
@@ -922,10 +906,6 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
return getConversionService().convert(value, resolvedTargetType);
}
if (getCustomConversions().isSimpleType(value.getClass())) {
return getPotentiallyConvertedSimpleValue(value, requestedTargetType);
}
if (value instanceof Collection) {
return writeCollectionInternal((Collection<Object>) value, columnType);
}
@@ -938,29 +918,41 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
TypeInformation<?> actualType = type.getRequiredActualType();
BasicCassandraPersistentEntity<?> entity = getMappingContext().getPersistentEntity(actualType.getType());
if (entity != null && columnType instanceof CassandraColumnType) {
if (columnType instanceof CassandraColumnType cassandraType) {
CassandraColumnType cassandraType = (CassandraColumnType) columnType;
if (cassandraType.isTupleType()) {
if (entity.isTupleType() && cassandraType.isTupleType()) {
if (entity != null && entity.isTupleType()) {
TupleValue tupleValue = ((TupleType) cassandraType.getDataType()).newValue();
TupleValue tupleValue = ((TupleType) cassandraType.getDataType()).newValue();
write(value, tupleValue, entity);
return tupleValue;
}
write(value, tupleValue, entity);
return tupleValue;
if (value instanceof TupleValue) {
return value;
}
}
if (entity.isUserDefinedType() && cassandraType.isUserDefinedType()) {
if (cassandraType.isUserDefinedType()) {
UdtValue udtValue = ((UserDefinedType) cassandraType.getDataType()).newValue();
if (entity != null && entity.isUserDefinedType()) {
write(value, udtValue, entity);
UdtValue udtValue = ((UserDefinedType) cassandraType.getDataType()).newValue();
write(value, udtValue, entity);
return udtValue;
}
return udtValue;
if (value instanceof UdtValue) {
return value;
}
}
}
if (getCustomConversions().isSimpleType(value.getClass())) {
return getPotentiallyConvertedSimpleValue(value, requestedTargetType);
}
return value;
}
@@ -1017,6 +1009,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
return ((Enum<?>) value).name();
}
if (requestedTargetType != null && !ClassUtils.isAssignableValue(requestedTargetType, value)) {
return getConversionService().convert(value, requestedTargetType);
}
return value;
}

View File

@@ -47,18 +47,6 @@ public abstract class AbstractCassandraQuery extends CassandraRepositoryQuerySup
private final CassandraOperations operations;
private static CassandraConverter toConverter(CassandraOperations operations) {
Assert.notNull(operations, "CassandraOperations must not be null");
return operations.getConverter();
}
private static CassandraMappingContext toMappingContext(CassandraOperations operations) {
return toConverter(operations).getMappingContext();
}
/**
* Create a new {@link AbstractCassandraQuery} from the given {@link CassandraQueryMethod} and
* {@link CassandraOperations}.
@@ -87,11 +75,9 @@ public abstract class AbstractCassandraQuery extends CassandraRepositoryQuerySup
@Override
public Object execute(Object[] parameters) {
CassandraParameterAccessor parameterAccessor = new ConvertingParameterAccessor(toConverter(getOperations()),
new CassandraParametersParameterAccessor(getQueryMethod(), parameters));
CassandraParameterAccessor parameterAccessor = new CassandraParametersParameterAccessor(getQueryMethod(),
parameters);
ResultProcessor resultProcessor = getQueryMethod().getResultProcessor().withDynamicProjection(parameterAccessor);
Statement<?> statement = createQuery(parameterAccessor);
CassandraQueryExecution queryExecution = getExecution(parameterAccessor,
@@ -183,4 +169,15 @@ public abstract class AbstractCassandraQuery extends CassandraRepositoryQuerySup
* @since 2.2
*/
protected abstract boolean isModifyingQuery();
private static CassandraConverter toConverter(CassandraOperations operations) {
Assert.notNull(operations, "CassandraOperations must not be null");
return operations.getConverter();
}
private static CassandraMappingContext toMappingContext(CassandraOperations operations) {
return toConverter(operations).getMappingContext();
}
}

View File

@@ -81,14 +81,8 @@ public abstract class AbstractReactiveCassandraQuery extends CassandraRepository
private Publisher<Object> executeLater(ReactiveCassandraParameterAccessor parameterAccessor) {
CassandraParameterAccessor convertingParameterAccessor = new ConvertingParameterAccessor(
getRequiredConverter(getReactiveCassandraOperations()), parameterAccessor);
Mono<SimpleStatement> statement = createQuery(convertingParameterAccessor);
ResultProcessor resultProcessor = getQueryMethod().getResultProcessor()
.withDynamicProjection(convertingParameterAccessor);
Mono<SimpleStatement> statement = createQuery(parameterAccessor);
ResultProcessor resultProcessor = getQueryMethod().getResultProcessor().withDynamicProjection(parameterAccessor);
ReactiveCassandraQueryExecution queryExecution = getExecution(parameterAccessor, new ResultProcessingConverter(
resultProcessor, getRequiredMappingContext(getReactiveCassandraOperations()), getEntityInstantiators()));

View File

@@ -30,7 +30,6 @@ import org.springframework.data.cassandra.core.query.Criteria;
import org.springframework.data.cassandra.core.query.CriteriaDefinition;
import org.springframework.data.cassandra.core.query.Filter;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.repository.query.ConvertingParameterAccessor.PotentiallyConvertingIterator;
import org.springframework.data.domain.Range;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PersistentPropertyPath;
@@ -104,8 +103,7 @@ class CassandraQueryCreator extends AbstractQueryCreator<Query, Filter> {
Assert.state(property != null && path.toDotPath() != null, "Leaf property must not be null");
Object filterOrCriteria = from(part, property, Criteria.where(path.toDotPath()),
(PotentiallyConvertingIterator) iterator);
Object filterOrCriteria = from(part, property, Criteria.where(path.toDotPath()), iterator);
if (filterOrCriteria instanceof CriteriaDefinition) {
return Filter.from((CriteriaDefinition) filterOrCriteria);
@@ -151,38 +149,37 @@ class CassandraQueryCreator extends AbstractQueryCreator<Query, Filter> {
/**
* Returns a {@link Filter} or {@link CriteriaDefinition} object representing the criterion for a {@link Part}.
*/
private Object from(Part part, CassandraPersistentProperty property, Criteria where,
PotentiallyConvertingIterator parameters) {
private Object from(Part part, CassandraPersistentProperty property, Criteria where, Iterator<Object> parameters) {
Type type = part.getType();
switch (type) {
case AFTER:
case GREATER_THAN:
return where.gt(parameters.nextConverted(property));
return where.gt(parameters.next());
case GREATER_THAN_EQUAL:
return where.gte(parameters.nextConverted(property));
return where.gte(parameters.next());
case BEFORE:
case LESS_THAN:
return where.lt(parameters.nextConverted(property));
return where.lt(parameters.next());
case LESS_THAN_EQUAL:
return where.lte(parameters.nextConverted(property));
return where.lte(parameters.next());
case BETWEEN:
return computeBetweenPart(where, parameters);
case IN:
return where.in(nextAsArray(property, parameters));
return where.in(nextAsArray(parameters));
case LIKE:
case STARTING_WITH:
case ENDING_WITH:
return where.like(like(type, parameters.nextConverted(property)));
return where.like(like(type, parameters.next()));
case CONTAINING:
return containing(where, property, parameters.nextConverted(property));
return containing(where, property, parameters.next());
case TRUE:
return where.is(true);
case FALSE:
return where.is(false);
case SIMPLE_PROPERTY:
return where.is(parameters.nextConverted(property));
return where.is(parameters.next());
default:
throw new InvalidDataAccessApiUsageException(
String.format("Unsupported keyword [%s] in part [%s]", type, part));
@@ -193,7 +190,7 @@ class CassandraQueryCreator extends AbstractQueryCreator<Query, Filter> {
* Compute a {@link Type#BETWEEN} {@link Part}.
* <p>
* In case the first {@literal value} is actually a {@link Range} the lower and upper bounds of the {@link Range} are
* used according to their {@link Range.Bound#isInclusive() inclusion} definition. Otherwise the {@literal value} is
* used according to their {@link Range.Bound#isInclusive() inclusion} definition. Otherwise, the {@literal value} is
* used for greater than and {@link Iterator#next() parameters.next()} as less than criterions.
*
* @param where must not be {@literal null}.
@@ -260,9 +257,9 @@ class CassandraQueryCreator extends AbstractQueryCreator<Query, Filter> {
throw new IllegalArgumentException(String.format("Part Type [%s] not supported with like queries", type));
}
private Object[] nextAsArray(CassandraPersistentProperty property, PotentiallyConvertingIterator iterator) {
private Object[] nextAsArray(Iterator<Object> iterator) {
Object next = iterator.nextConverted(property);
Object next = iterator.next();
if (next instanceof Collection) {
return ((Collection<?>) next).toArray();

View File

@@ -15,12 +15,10 @@
*/
package org.springframework.data.cassandra.repository.query;
import java.util.Collection;
import java.util.Iterator;
import org.springframework.data.cassandra.core.convert.CassandraConverter;
import org.springframework.data.cassandra.core.cql.QueryOptions;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.core.mapping.CassandraType;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Range;
@@ -74,7 +72,7 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
@Override
public Object getBindableValue(int index) {
return potentiallyConvert(index, this.delegate.getBindableValue(index), null);
return potentiallyConvert(index, this.delegate.getBindableValue(index));
}
@Override
@@ -114,7 +112,7 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
@SuppressWarnings("unchecked")
@Nullable
Object potentiallyConvert(int index, @Nullable Object bindableValue, @Nullable CassandraPersistentProperty property) {
Object potentiallyConvert(int index, @Nullable Object bindableValue) {
if (bindableValue == null) {
return null;
@@ -130,11 +128,6 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
this.converter.convertToColumnType(bindableValue, converter.getColumnTypeResolver().resolve(cassandraType));
}
if (property != null && ((property.isCollectionLike() && bindableValue instanceof Collection)
|| (!property.isCollectionLike() && !(bindableValue instanceof Collection)))) {
return this.converter.convertToColumnType(bindableValue, converter.getColumnTypeResolver().resolve(property));
}
return this.converter.convertToColumnType(bindableValue, converter.getColumnTypeResolver().resolve(bindableValue));
}
@@ -143,7 +136,7 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
*
* @author Mark Paluch
*/
private class ConvertingIterator implements PotentiallyConvertingIterator {
private class ConvertingIterator implements Iterator<Object> {
private final Iterator<Object> delegate;
@@ -164,34 +157,12 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
@Nullable
public Object next() {
return potentiallyConvert(this.index++, this.delegate.next(), null);
return potentiallyConvert(this.index++, this.delegate.next());
}
public void remove() {
this.delegate.remove();
}
@Nullable
@Override
public Object nextConverted(CassandraPersistentProperty property) {
return potentiallyConvert(this.index++, this.delegate.next(), property);
}
}
/**
* Custom {@link Iterator} that adds a method to access elements in a converted manner.
*
* @author Mark Paluch
*/
interface PotentiallyConvertingIterator extends Iterator<Object> {
/**
* Returns the next element and pass in type information for potential conversion.
*
* @return the converted object, may be {@literal null}.
*/
@Nullable
Object nextConverted(CassandraPersistentProperty property);
}
}

View File

@@ -124,11 +124,12 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
public Mono<SimpleStatement> createQuery(CassandraParameterAccessor parameterAccessor) {
StringBasedQuery query = getStringBasedQuery();
ConvertingParameterAccessor parameterAccessorToUse = new ConvertingParameterAccessor(
getReactiveCassandraOperations().getConverter(), parameterAccessor);
Mono<SpELExpressionEvaluator> spelEvaluator = getSpelEvaluatorFor(query.getExpressionDependencies(),
parameterAccessor);
parameterAccessorToUse);
return spelEvaluator.map(it -> getQueryStatementCreator().select(query, parameterAccessor, it));
return spelEvaluator.map(it -> getQueryStatementCreator().select(query, parameterAccessorToUse, it));
}
@Override

View File

@@ -88,8 +88,7 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
this.expressionParser = expressionParser;
this.evaluationContextProvider = evaluationContextProvider;
this.stringBasedQuery = new StringBasedQuery(query,
method.getParameters(), expressionParser);
this.stringBasedQuery = new StringBasedQuery(query, method.getParameters(), expressionParser);
if (method.hasAnnotatedQuery()) {
@@ -115,11 +114,12 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
public SimpleStatement createQuery(CassandraParameterAccessor parameterAccessor) {
StringBasedQuery query = getStringBasedQuery();
ConvertingParameterAccessor parameterAccessorToUse = new ConvertingParameterAccessor(getOperations().getConverter(),
parameterAccessor);
EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(
getQueryMethod().getParameters(), parameterAccessor.getValues(), query.getExpressionDependencies());
getQueryMethod().getParameters(), parameterAccessorToUse.getValues(), query.getExpressionDependencies());
return getQueryStatementCreator().select(query, parameterAccessor,
return getQueryStatementCreator().select(query, parameterAccessorToUse,
new DefaultSpELExpressionEvaluator(expressionParser, evaluationContext));
}

View File

@@ -44,6 +44,7 @@ import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity
import org.springframework.data.cassandra.core.mapping.Column;
import org.springframework.data.cassandra.core.mapping.Element;
import org.springframework.data.cassandra.core.mapping.Embedded;
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.core.mapping.Tuple;
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
@@ -378,14 +379,6 @@ public class QueryMapperUnitTests {
Filter mappedFilter = this.queryMapper. getMappedObject(filter, this.personPersistentEntity);
/*
mappedFilter.get().findFirst()
.map(CriteriaDefinition::getPredicate)
.map(CriteriaDefinition.Predicate::getValue)
.map(Object::getClass)
.ifPresent(System.out::println);
*/
assertThat(mappedFilter).contains(Criteria.where("localtime").gt(time));
}
@@ -418,6 +411,18 @@ public class QueryMapperUnitTests {
assertThat(mappedObject.iterator().next().getColumnName()).isEqualTo(ColumnName.from("prefixfirstname"));
}
@Test // GH-1383
void shouldConvertPrimaryKeyValues() {
Filter filter = Filter.from(Criteria.where("id.foo").is(42));
Filter mappedObject = this.queryMapper.getMappedObject(filter,
this.mappingContext.getRequiredPersistentEntity(WithPrimaryKeyClass.class));
assertThat(mappedObject.iterator().next().getColumnName()).isEqualTo(ColumnName.from("foo"));
assertThat(mappedObject.iterator().next().getPredicate().getValue()).isEqualTo(42L);
}
@SuppressWarnings("unused")
static class Person {
@@ -440,6 +445,20 @@ public class QueryMapperUnitTests {
}
static class WithPrimaryKeyClass {
@Id MyPrimaryKeyClass id;
}
static class MyPrimaryKeyClass {
@PrimaryKeyColumn long foo;
@PrimaryKeyColumn long bar;
}
@Tuple
@AllArgsConstructor
static class MappedTuple {

View File

@@ -140,18 +140,18 @@ class CassandraQueryCreatorUnitTests {
@Test // DATACASS-627
void createsBetweenQueryCorrectly() {
String query = createQuery("findByFirstnameBetween", Person.class, 1, 2);
String query = createQuery("findByNumberOfChildrenBetween", Person.class, 1, 2);
assertThat(query).isEqualTo("SELECT * FROM person WHERE firstname>1 AND firstname<2");
assertThat(query).isEqualTo("SELECT * FROM person WHERE numberofchildren>1 AND numberofchildren<2");
}
@Test // DATACASS-627
void createsBetweenQueryWithRangeCorrectly() {
String query = createQuery("findByFirstnameBetween", Person.class,
String query = createQuery("findByNumberOfChildrenBetween", Person.class,
Range.from(Range.Bound.inclusive(1)).to(Range.Bound.exclusive(2)));
assertThat(query).isEqualTo("SELECT * FROM person WHERE firstname>=1 AND firstname<2");
assertThat(query).isEqualTo("SELECT * FROM person WHERE numberofchildren>=1 AND numberofchildren<2");
}
@Test // DATACASS-7
@@ -239,25 +239,25 @@ class CassandraQueryCreatorUnitTests {
@Test // DATACASS-7
void createsIsTrueQueryCorrectly() {
String query = createQuery("findByFirstnameIsTrue", Person.class, "Walter");
String query = createQuery("findByCoolIsTrue", Person.class, "Walter");
assertThat(query).isEqualTo("SELECT * FROM person WHERE firstname=true");
assertThat(query).isEqualTo("SELECT * FROM person WHERE cool=true");
}
@Test // DATACASS-7
void createsIsFalseQueryCorrectly() {
String query = createQuery("findByFirstnameIsFalse", Person.class, "Walter");
String query = createQuery("findByCoolIsFalse", Person.class, "Walter");
assertThat(query).isEqualTo("SELECT * FROM person WHERE firstname=false");
assertThat(query).isEqualTo("SELECT * FROM person WHERE cool=false");
}
@Test // DATACASS-7
void createsQueryUsingQuotingCorrectly() {
String query = createQuery("findByIdAndSet", QuotedType.class, "Walter", "White");
String query = createQuery("findByIdAndSet", QuotedType.class, "Walter", Arrays.asList("White"));
assertThat(query).isEqualTo("SELECT * FROM \"myTable\" WHERE my_id='Walter' AND \"set\"='White'");
assertThat(query).isEqualTo("SELECT * FROM \"myTable\" WHERE my_id='Walter' AND \"set\"={'White'}");
}
@Test // DATACASS-7

View File

@@ -19,9 +19,6 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.time.LocalDate;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -30,7 +27,6 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.repository.query.ConvertingParameterAccessor.PotentiallyConvertingIterator;
import com.datastax.oss.driver.api.core.type.DataTypes;
@@ -83,8 +79,7 @@ class ConvertingParameterAccessorUnitTests {
when(mockParameterAccessor.getBindableValue(0)).thenReturn(localDate);
assertThat(convertingParameterAccessor.getBindableValue(0))
.isEqualTo(LocalDate.of(2010, 7, 4));
assertThat(convertingParameterAccessor.getBindableValue(0)).isEqualTo(LocalDate.of(2010, 7, 4));
}
@Test // DATACASS-296, DATACASS-7
@@ -95,22 +90,4 @@ class ConvertingParameterAccessorUnitTests {
assertThat(convertingParameterAccessor.getDataType(0)).isEqualTo(DataTypes.TEXT);
}
@Test // DATACASS-296, DATACASS-7
@SuppressWarnings({ "rawtypes", "unchecked" })
void shouldConvertCollections() {
LocalDate localDate = LocalDate.of(2010, 7, 4);
when(mockParameterAccessor.iterator())
.thenReturn((Iterator) Collections.singletonList(Collections.singletonList(localDate)).iterator());
PotentiallyConvertingIterator iterator = (PotentiallyConvertingIterator) convertingParameterAccessor.iterator();
Object converted = iterator.nextConverted(mockProperty);
assertThat(converted).isInstanceOf(List.class);
List<?> list = (List<?>) converted;
assertThat(list.get(0)).isInstanceOf(LocalDate.class);
}
}

View File

@@ -335,7 +335,7 @@ class StringBasedCassandraQueryUnitTests {
}
@Test // DATACASS-172
void bindsMappedUdtPropertyCorrectly() throws Exception {
void bindsMappedUdtPropertyCorrectly() {
UserDefinedType addressType = UserDefinedTypeBuilder.forName("address").withField("city", DataTypes.TEXT)
.withField("country", DataTypes.TEXT).build();
@@ -353,7 +353,7 @@ class StringBasedCassandraQueryUnitTests {
}
@Test // DATACASS-172
void bindsUdtValuePropertyCorrectly() throws Exception {
void bindsUdtValuePropertyCorrectly() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByMainAddress", UdtValue.class);