diff --git a/pom.xml b/pom.xml index f08114766..aa7d360c9 100644 --- a/pom.xml +++ b/pom.xml @@ -107,7 +107,7 @@ 4.19.0 spring-data-cassandra 1.01 - 4.1.0-SNAPSHOT + 4.1.0-GH-3400-SNAPSHOT diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/QueryMapper.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/QueryMapper.java index f6ac33679..e93d23d19 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/QueryMapper.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/QueryMapper.java @@ -19,7 +19,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -46,6 +45,7 @@ import org.springframework.data.core.PropertyReferenceException; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; +import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyHandler; @@ -125,15 +125,15 @@ public Filter getMappedObject(Filter filter, CassandraPersistentEntity entity Field field = createPropertyField(entity, criteriaDefinition.getColumnName()); - field.getProperty().filter(CassandraPersistentProperty::isCompositePrimaryKey).ifPresent(it -> { + if (field.hasProperty(CassandraPersistentProperty::isCompositePrimaryKey)) { throw new IllegalArgumentException( "Cannot use composite primary key directly; Reference a property of the composite primary key"); - }); + } - field.getProperty().filter(CassandraPersistentProperty::hasOrdinal).ifPresent(it -> { + if (field.hasProperty(CassandraPersistentProperty::hasOrdinal)) { throw new IllegalArgumentException( String.format("Cannot reference tuple value elements, property [%s]", field.getMappedKey())); - }); + } Predicate predicate = criteriaDefinition.getPredicate(); Object value = predicate.getValue(); @@ -148,10 +148,9 @@ public Filter getMappedObject(Filter filter, CassandraPersistentEntity entity protected @Nullable Object getMappedValue(Field field, CriteriaDefinition.Operator operator, Object value) { - if (field.getProperty().isPresent() - && field.getProperty().filter(it -> converter.getCustomConversions().hasValueConverter(it)).isPresent()) { + if (field.hasProperty() && converter.getCustomConversions().hasValueConverter(field.getProperty())) { - CassandraPersistentProperty property = field.getProperty().get(); + CassandraPersistentProperty property = field.getProperty(); CassandraConversionContext conversionContext = new CassandraConversionContext(new PropertyValueProvider<>() { @Override public T getPropertyValue(CassandraPersistentProperty property) { @@ -202,11 +201,23 @@ public List getMappedSelectors(Columns columns, CassandraPersistentEnt columns.getSelector(column).forEach(selector -> { - List mappedColumnNames = getCqlIdentifier(column, field); + List mappedColumnNames = getCqlIdentifiers(field); - for (CqlIdentifier mappedColumnName : mappedColumnNames) { - selectors.add(getMappedSelector(selector, mappedColumnName, field)); + if (selector instanceof ColumnSelector cs && cs.getAlias().isEmpty()) { + + for (CqlIdentifier mappedColumnName : mappedColumnNames) { + selectors.add(ColumnSelector.from(mappedColumnName)); + } + + return; + } + + if (mappedColumnNames.size() != 1) { + throw new IllegalArgumentException( + "Cannot map selector %s to multiple columns %s".formatted(selector, mappedColumnNames)); } + + selectors.add(getMappedSelector(selector, entity, field)); }); } @@ -231,13 +242,10 @@ private void addColumns(CassandraPersistentEntity entity, List sele }); } - private Selector getMappedSelector(Selector selector, CqlIdentifier cqlIdentifier, Field field) { + private Selector getMappedSelector(Selector selector, CassandraPersistentEntity entity, Field field) { if (selector instanceof ColumnSelector columnSelector) { - - ColumnSelector mappedColumnSelector = ColumnSelector.from(cqlIdentifier); - - return columnSelector.getAlias().map(mappedColumnSelector::as).orElse(mappedColumnSelector); + return getMappedSelector(columnSelector, entity); } if (selector instanceof FunctionCall functionCall) { @@ -246,22 +254,18 @@ private Selector getMappedSelector(Selector selector, CqlIdentifier cqlIdentifie functionCall.getParameters().stream().map(obj -> { if (obj instanceof Selector sel) { - return getMappedSelector(sel, cqlIdentifier, field); + return getMappedSelector(sel, entity, field); } if (obj instanceof ColumnName cn) { - - CqlIdentifier identifier = cn.getCqlIdentifier().or(() -> cn.getColumnName().map(CqlIdentifier::fromCql)) - .orElseGet(() -> CqlIdentifier.fromCql(cn.toCql())); - - return getMappedSelector(ColumnSelector.from(cn), identifier, field); + return getMappedSelector(cn, entity); } if (obj instanceof CqlIdentifier identifier) { - return getMappedSelector(ColumnSelector.from(identifier), identifier, field); + return getMappedSelector(ColumnName.from(identifier), entity); } - if (field.getProperty().isPresent()) { + if (field.hasProperty()) { return getMappedValue(field, CriteriaDefinition.Operators.EQ, obj); } @@ -275,6 +279,17 @@ private Selector getMappedSelector(Selector selector, CqlIdentifier cqlIdentifie throw new IllegalArgumentException(String.format("Selector [%s] not supported", selector)); } + private ColumnSelector getMappedSelector(ColumnSelector columnSelector, CassandraPersistentEntity entity) { + + ColumnSelector mappedSelector = getMappedSelector(columnSelector.getColumnName(), entity); + return columnSelector.getAlias().map(mappedSelector::as).orElse(mappedSelector); + } + + private ColumnSelector getMappedSelector(ColumnName columnName, CassandraPersistentEntity entity) { + Field mappedField = createPropertyField(entity, columnName); + return ColumnSelector.from(mappedField.getMappedKey()); + } + /** * Map {@link Columns} with a {@link CassandraPersistentEntity type hint} to column names for included columns. * Function call selectors or other {@link org.springframework.data.cassandra.core.query.Columns.Selector} types are @@ -300,12 +315,14 @@ public List getMappedColumnNames(Columns columns, CassandraPersis for (ColumnName column : columns) { Field field = createPropertyField(entity, column); - field.getProperty().ifPresent(seen::add); + if (field.hasProperty()) { + seen.add(field.getProperty()); + } columns.getSelector(column).forEach(columnSelector -> { if (columnSelector instanceof ColumnSelector) { - columnNames.addAll(getCqlIdentifier(column, field)); + columnNames.addAll(getCqlIdentifiers(field)); } }); } @@ -342,15 +359,13 @@ public Sort getMappedSort(Sort sort, CassandraPersistentEntity entity) { for (Order order : sort) { - ColumnName columnName = ColumnName.from(order.getProperty()); - - Field field = createPropertyField(entity, columnName); + Field field = createPropertyField(entity, order.getProperty()); if (vector != null) { vector = getMappedValue(field, CriteriaDefinition.Operators.EQ, vector); } - List mappedColumnNames = getCqlIdentifier(columnName, field); + List mappedColumnNames = getCqlIdentifiers(field); if (mappedColumnNames.isEmpty()) { mappedOrders.add(order); @@ -364,14 +379,14 @@ public Sort getMappedSort(Sort sort, CassandraPersistentEntity entity) { return vector != null ? new VectorSort(mappedOrders, vector) : Sort.by(mappedOrders); } - private List getCqlIdentifier(ColumnName column, Field field) { + private List getCqlIdentifiers(Field field) { List identifiers = new ArrayList<>(1); try { - if (field.getProperty().isPresent()) { + if (field.hasProperty()) { - CassandraPersistentProperty property = field.getProperty().get(); + CassandraPersistentProperty property = field.getProperty(); if (property.isCompositePrimaryKey()) { @@ -383,10 +398,8 @@ private List getCqlIdentifier(ColumnName column, Field field) { } else { identifiers.add(property.getRequiredColumnName()); } - } else if (column.getColumnName().isPresent()) { - identifiers.add(CqlIdentifier.fromCql(column.getColumnName().get())); } else { - column.getCqlIdentifier().ifPresent(identifiers::add); + identifiers.add(field.getMappedKey().getRequiredCqlIdentifier()); } } catch (IllegalStateException cause) { @@ -396,18 +409,28 @@ private List getCqlIdentifier(ColumnName column, Field field) { return identifiers; } + Field createPropertyField(@Nullable CassandraPersistentEntity entity, String key) { + return createPropertyField(entity, ColumnName.from(key)); + } + Field createPropertyField(@Nullable CassandraPersistentEntity entity, ColumnName key) { - return Optional.ofNullable(entity). map(e -> new MetadataBackedField(key, e, getMappingContext())) - .orElseGet(() -> new Field(key)); + if (entity == null) { + return new Field(key); + } + + return MetadataBackedField.of(key, entity, getMappingContext()); } ColumnType getColumnType(Field field, @Nullable Object value, ColumnTypeTransformer operator) { ColumnTypeResolver resolver = converter.getColumnTypeResolver(); - return field.getProperty().map(it -> operator.transform(resolver.resolve(it), it)) - .orElseGet(() -> resolver.resolve(value)); + if (field.hasProperty()) { + return operator.transform(resolver.resolve(field.getProperty()), field.getProperty()); + } + + return resolver.resolve(value); } /** @@ -513,8 +536,11 @@ ColumnType transform(ColumnType typeDescriptor, CassandraPersistentProperty prop static ColumnTypeTransformer of(Field field, CriteriaDefinition.Operator operator) { if (operator == CriteriaDefinition.Operators.CONTAINS) { - return field.getProperty().filter(CassandraPersistentProperty::isMapLike).map(it -> MAP_VALUE_TYPE) - .orElse(COLLECTION_COMPONENT_TYPE); + + if (field.hasProperty(CassandraPersistentProperty::isMapLike)) { + return MAP_VALUE_TYPE; + } + return COLLECTION_COMPONENT_TYPE; } if (operator == CriteriaDefinition.Operators.CONTAINS_KEY) { @@ -558,13 +584,37 @@ public Field with(ColumnName name) { return new Field(name); } + /** + * Return {@literal true} if the field is backed by a {@link CassandraPersistentProperty}. + * + * @return {@literal true} if the field is backed by a property; {@literal false} otherwise. + * @since 5.1 + */ + public boolean hasProperty() { + return false; + } + + /** + * Return {@literal true} if the field is backed by a {@link CassandraPersistentProperty} and matches the given + * {@link Predicate}. + * + * @return {@literal true} if the field is backed by a property and matches the given predicate; {@literal false} + * otherwise. + * @since 5.1 + */ + public boolean hasProperty(java.util.function.Predicate predicate) { + return hasProperty() && predicate.test(getProperty()); + } + /** * Returns the underlying {@link CassandraPersistentProperty} backing the field. For path traversals this will be * the property that represents the value to handle. This means it'll be the leaf property for plain paths or the * association property in case we refer to an association somewhere in the path. + * + * @throws IllegalStateException if the field is not backed by a property. */ - public Optional getProperty() { - return Optional.empty(); + public CassandraPersistentProperty getProperty() { + throw new IllegalStateException("No property for this field: " + getMappedKey()); } /** @@ -586,93 +636,96 @@ protected static class MetadataBackedField extends Field { private final MappingContext, CassandraPersistentProperty> mappingContext; - private final Optional> path; + private final @Nullable PropertyPath propertyPath; + + private final @Nullable PersistentPropertyPath path; private final @Nullable CassandraPersistentProperty property; - private final Optional optionalProperty; + private MetadataBackedField(ColumnName name, CassandraPersistentEntity entity, + MappingContext, CassandraPersistentProperty> mappingContext, + @Nullable PropertyPath propertyPath) { - /** - * Creates a new {@link MetadataBackedField} with the given name, {@link CassandraPersistentEntity} and - * {@link MappingContext}. - * - * @param name must not be {@literal null} or empty. - * @param entity must not be {@literal null}. - * @param mappingContext must not be {@literal null}. - */ - public MetadataBackedField(ColumnName name, CassandraPersistentEntity entity, - MappingContext, CassandraPersistentProperty> mappingContext) { + super(name); + this.entity = entity; + this.mappingContext = mappingContext; + this.propertyPath = propertyPath; + + if (propertyPath != null) { - this(name, entity, mappingContext, null); + PersistentPropertyPath ppp = mappingContext + .getPersistentPropertyPath(propertyPath); + this.path = ppp; + this.property = ppp.getLeafProperty(); + } else { + this.path = null; + this.property = null; + } } /** - * Creates a new {@link MetadataBackedField} with the given name, {@link CassandraPersistentProperty} and - * {@link MappingContext} with the given {@link CassandraPersistentProperty}. + * Create a new {@link MetadataBackedField} with the given name, {@link CassandraPersistentEntity} and + * {@link MappingContext}. * - * @param name must not be {@literal null} or empty. + * @param columnName must not be {@literal null} or empty. * @param entity must not be {@literal null}. * @param mappingContext must not be {@literal null}. - * @param property may be {@literal null}. */ - public MetadataBackedField(ColumnName name, CassandraPersistentEntity entity, - MappingContext, CassandraPersistentProperty> mappingContext, - @Nullable CassandraPersistentProperty property) { + public static MetadataBackedField of(ColumnName columnName, CassandraPersistentEntity entity, + CassandraMappingContext mappingContext) { - super(name); + PropertyPath propertyPath; + if (columnName.hasPropertyPath()) { + propertyPath = columnName.getRequiredPropertyPath(); - Assert.notNull(entity, "CassandraPersistentEntity must not be null"); + } else { + propertyPath = getPath(entity, columnName.toCql()); + } - this.entity = entity; - this.mappingContext = mappingContext; - this.path = getPath(name.toCql()); - this.property = path.map(PersistentPropertyPath::getLeafProperty).orElse(property); - this.optionalProperty = Optional.ofNullable(this.property); + return new MetadataBackedField(columnName, entity, mappingContext, propertyPath); } - /** - * Returns the {@link PersistentPropertyPath} for the given {@code pathExpression}. - * - * @param pathExpression {@link String} containing the path expression to evaluate - * @return the {@link PersistentPropertyPath} for the given {@code pathExpression}. - */ - private Optional> getPath(String pathExpression) { + private static @Nullable PropertyPath getPath(PersistentEntity entity, String pathExpression) { try { - PropertyPath propertyPath = PropertyPath.from(pathExpression.replaceAll("\\.\\d", ""), - this.entity.getTypeInformation()); - - PersistentPropertyPath persistentPropertyPath = this.mappingContext - .getPersistentPropertyPath(propertyPath); - - return Optional.of(persistentPropertyPath); + return PropertyPath.from(pathExpression.replaceAll("\\.\\d", ""), entity.getTypeInformation()); } catch (PropertyReferenceException e) { - return Optional.empty(); + return null; } } @Override public MetadataBackedField with(ColumnName name) { - return new MetadataBackedField(name, entity, mappingContext, property); + return new MetadataBackedField(name, entity, mappingContext, propertyPath); } @Override - public Optional getProperty() { - return this.optionalProperty; + public boolean hasProperty() { + return this.property != null; + } + + @Override + public CassandraPersistentProperty getProperty() { + + if (this.property == null) { + return super.getProperty(); + } + + return this.property; } @Override @SuppressWarnings("NullAway") public ColumnName getMappedKey() { - if (path.isEmpty()) { + if (path == null || path.isEmpty()) { return name; } boolean embedded = false; CassandraPersistentEntity parentEntity = null; CassandraPersistentProperty leafProperty = null; - for (CassandraPersistentProperty p : path.get()) { + for (CassandraPersistentProperty p : path) { leafProperty = p; diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UpdateMapper.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UpdateMapper.java index ac3fbccad..f8238edb3 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UpdateMapper.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UpdateMapper.java @@ -28,10 +28,10 @@ import java.util.Set; import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty; import org.springframework.data.cassandra.core.query.Filter; import org.springframework.data.cassandra.core.query.Update; import org.springframework.data.core.TypeInformation; -import org.springframework.data.mapping.PersistentProperty; import org.springframework.util.Assert; import com.datastax.oss.driver.api.core.type.DataType; @@ -81,10 +81,10 @@ public Update getMappedObject(Update update, CassandraPersistentEntity entity Field field = createPropertyField(entity, assignmentOp.getColumnName()); - field.getProperty().filter(it -> it.getOrdinal() != null).ifPresent(it -> { + if (field.hasProperty(CassandraPersistentProperty::hasOrdinal)) { throw new IllegalArgumentException( String.format("Cannot reference tuple value elements, property [%s]", field.getMappedKey())); - }); + } mapped.add(getMappedUpdateOperation(assignmentOp, field)); } @@ -128,8 +128,9 @@ private AssignmentOp getMappedUpdateOperation(Field field, SetOp updateOp) { Assert.state(op.getValue() != null, () -> String.format("SetAtKeyOp for %s attempts to set null", field.getProperty())); - Optional> typeInformation = field.getProperty() - .map(PersistentProperty::getTypeInformation); + Optional> typeInformation = field.hasProperty() + ? Optional.of(field.getProperty().getTypeInformation()) + : Optional.empty(); Optional> keyType = typeInformation.map(TypeInformation::getComponentType); Optional> valueType = typeInformation.map(TypeInformation::getMapValueType); @@ -161,9 +162,9 @@ private AssignmentOp getMappedUpdateOperation(Field field, SetOp updateOp) { if (collection.isEmpty()) { - int protocolCode = field.getProperty() - .map(property -> getConverter().getColumnTypeResolver().resolve(property).getDataType()) - .map(DataType::getProtocolCode).orElse(ProtocolConstants.DataType.LIST); + int protocolCode = field.hasProperty() + ? getConverter().getColumnTypeResolver().resolve(field.getProperty()).getDataType().getProtocolCode() + : ProtocolConstants.DataType.LIST; if (protocolCode == ProtocolConstants.DataType.SET) { return new SetOp(field.getMappedKey(), Collections.emptySet()); @@ -184,7 +185,7 @@ private AssignmentOp getMappedUpdateOperation(Field field, RemoveOp updateOp) { ColumnType descriptor = getColumnType(field, value, ColumnTypeTransformer.AS_IS); boolean mapLike = false; - if (field.getProperty().isPresent() && field.getProperty().get().isMapLike()) { + if (field.hasProperty(CassandraPersistentProperty::isMapLike)) { descriptor = getColumnType(field, value, value instanceof Collection ? ColumnTypeTransformer.ENCLOSING_MAP_KEY_SET : ColumnTypeTransformer.MAP_KEY_TYPE); @@ -207,9 +208,9 @@ private AssignmentOp getMappedUpdateOperation(Field field, AddToOp updateOp) { ColumnType descriptor = getColumnType(field, value, ColumnTypeTransformer.AS_IS); Collection mappedValue = (Collection) getConverter().convertToColumnType(value, descriptor); - if (field.getProperty().isPresent()) { + if (field.hasProperty()) { - DataType dataType = getConverter().getColumnTypeResolver().resolve(field.getProperty().get()).getDataType(); + DataType dataType = getConverter().getColumnTypeResolver().resolve(field.getProperty()).getDataType(); if (dataType instanceof SetType && !(mappedValue instanceof Set)) { Collection collection = new HashSet<>(); @@ -229,8 +230,9 @@ private AssignmentOp getMappedUpdateOperation(Field field, AddToOp updateOp) { private AssignmentOp getMappedUpdateOperation(Field field, AddToMapOp updateOp) { - Optional> typeInformation = field.getProperty() - .map(PersistentProperty::getTypeInformation); + Optional> typeInformation = field.hasProperty() + ? Optional.of(field.getProperty().getTypeInformation()) + : Optional.empty(); Optional> keyType = typeInformation.map(TypeInformation::getComponentType); Optional> valueType = typeInformation.map(TypeInformation::getMapValueType); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentProperty.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentProperty.java index 83a2e11ae..68cc510f5 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentProperty.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentProperty.java @@ -154,7 +154,6 @@ private CqlIdentifier determineColumnName() { } String overriddenName = null; - boolean forceQuote = false; Column column = findAnnotation(Column.class); @@ -209,6 +208,7 @@ protected Association createAssociation() { @Override public boolean isMapLike() { + // TODO: Super isMap? return ClassUtils.isAssignable(Map.class, getType()); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/ColumnName.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/ColumnName.java index 6fccd44c7..d127e27b1 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/ColumnName.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/ColumnName.java @@ -18,6 +18,9 @@ import java.util.Optional; import org.jspecify.annotations.Nullable; + +import org.springframework.data.core.PropertyPath; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.util.Assert; import com.datastax.oss.driver.api.core.CqlIdentifier; @@ -36,6 +39,28 @@ */ public abstract class ColumnName { + /** + * Create a {@link ColumnName} given a {@link TypedPropertyPath property}. + * + * @param property must not be {@literal null}. + * @return the {@link ColumnName} for {@link PropertyPath} + * @since 5.1 + */ + public static ColumnName from(TypedPropertyPath property) { + return from((PropertyPath) TypedPropertyPath.of(property)); + } + + /** + * Create a {@link ColumnName} given a {@link PropertyPath property}. + * + * @param propertyPath must not be {@literal null}. + * @return the {@link ColumnName} for {@link PropertyPath} + * @since 5.1 + */ + public static ColumnName from(PropertyPath propertyPath) { + return new PropertyPathColumnName(propertyPath); + } + /** * Create a {@link ColumnName} given {@link CqlIdentifier}. The resulting instance uses CQL identifier rules to * identify column names (quoting, case-sensitivity). @@ -45,9 +70,6 @@ public abstract class ColumnName { * @see CqlIdentifier */ public static ColumnName from(CqlIdentifier cqlIdentifier) { - - Assert.notNull(cqlIdentifier, "Column name must not be null"); - return new CqlIdentifierColumnName(cqlIdentifier); } @@ -56,26 +78,79 @@ public static ColumnName from(CqlIdentifier cqlIdentifier) { * column names (case-sensitivity). * * @param columnName must not be {@literal null} or empty. - * @return the {@link ColumnName} for {@link CqlIdentifier} + * @return the {@link ColumnName} for {@code columnName} */ public static ColumnName from(String columnName) { - - Assert.hasText(columnName, "Column name must not be null or empty"); - return new StringColumnName(columnName); } /** * @return the optional column name. */ - public abstract Optional getColumnName(); + public Optional getColumnName() { + return Optional.empty(); + } + + /** + * Indicates whether a column name is available. + * + * @return {@literal true} if a (string or {@link CqlIdentifier}) column name is available; {@literal false} + * otherwise. + * @since 5.1 + */ + public boolean hasColumnName() { + return getColumnName().isPresent(); + } + + /** + * @return the optional {@link PropertyPath}. + */ + public @Nullable PropertyPath getPropertyPath() { + return null; + } + + /** + * Indicates whether a {@link PropertyPath} is available. + * + * @return {@literal true} if a {@link PropertyPath} is available; {@literal false} otherwise. + * @since 5.1 + */ + public boolean hasPropertyPath() { + return getPropertyPath() != null; + } + + /** + * Returns the required {@link PropertyPath} or throws an {@link IllegalStateException} if not available. + * + * @return the required {@link PropertyPath}. + * @throws IllegalStateException if no {@link PropertyPath} is available. + * @since 5.1 + */ + public PropertyPath getRequiredPropertyPath() { + + PropertyPath propertyPath = getPropertyPath(); + + if (propertyPath == null) { + throw new IllegalStateException("No PropertyPath available"); + } + + return propertyPath; + } /** * @return the optional {@link CqlIdentifier}. */ - public abstract Optional getCqlIdentifier(); + public Optional getCqlIdentifier() { + return Optional.empty(); + } - CqlIdentifier getRequiredCqlIdentifier() { + /** + * Returns the required {@link CqlIdentifier} or constructs one from available information. + * + * @return the required {@link CqlIdentifier}. + * @since 5.1 + */ + public CqlIdentifier getRequiredCqlIdentifier() { return getCqlIdentifier().or(() -> getColumnName().map(CqlIdentifier::fromCql)) .orElseGet(() -> CqlIdentifier.fromCql(toCql())); } @@ -110,6 +185,54 @@ public int hashCode() { return hashValue; } + /** + * {@link PropertyPath}-based column name representation. + * + * @author Mark Paluch + */ + static class PropertyPathColumnName extends ColumnName { + + private final PropertyPath propertyPath; + + PropertyPathColumnName(PropertyPath propertyPath) { + + Assert.notNull(propertyPath, "Property path must not be null"); + + this.propertyPath = propertyPath; + } + + @Override + public Optional getColumnName() { + return Optional.of(toCql()); + } + + @Override + public PropertyPath getPropertyPath() { + return propertyPath; + } + + @Override + public boolean equals(@Nullable Object obj) { + return super.equals(obj) + && (obj instanceof PropertyPathColumnName that && this.propertyPath.equals(that.propertyPath)); + } + + @Override + public String toCql() { + return this.propertyPath.toDotPath(); + } + + @Override + public String toString() { + + if (this.propertyPath.hasNext()) { + return this.propertyPath.toString(); + } + + return this.propertyPath.toDotPath(); + } + } + /** * {@link String}-based column name representation. Preserves letter casing. * @@ -120,6 +243,9 @@ static class StringColumnName extends ColumnName { private final String columnName; StringColumnName(String columnName) { + + Assert.hasText(columnName, "Column name must not be null or empty"); + this.columnName = columnName; } @@ -128,11 +254,6 @@ public Optional getColumnName() { return Optional.of(columnName); } - @Override - public Optional getCqlIdentifier() { - return Optional.empty(); - } - @Override public String toCql() { return this.columnName; @@ -154,12 +275,10 @@ static class CqlIdentifierColumnName extends ColumnName { private final CqlIdentifier cqlIdentifier; CqlIdentifierColumnName(CqlIdentifier cqlIdentifier) { - this.cqlIdentifier = cqlIdentifier; - } - @Override - public Optional getColumnName() { - return Optional.empty(); + Assert.notNull(cqlIdentifier, "Column name must not be null"); + + this.cqlIdentifier = cqlIdentifier; } @Override diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Columns.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Columns.java index a400a5eba..fd1f8fe97 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Columns.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Columns.java @@ -31,6 +31,7 @@ import org.springframework.data.cassandra.core.convert.CassandraVector; import org.springframework.data.cassandra.core.mapping.SimilarityFunction; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.domain.Vector; import org.springframework.lang.CheckReturnValue; import org.springframework.util.Assert; @@ -69,8 +70,31 @@ public static Columns empty() { return new Columns(Collections.emptyMap()); } + /** + * Create a {@link Columns} given {@code properties} as property path. Property paths are mapped against the + * underlying domain model. + * + * @param properties must not be {@literal null}. + * @return the {@link Columns} object for {@code properties}. + * @since 5.1 + */ + public static Columns from(TypedPropertyPath... properties) { + + Assert.notNull(properties, "Column names must not be null"); + + Map> columns = new LinkedHashMap<>(properties.length, 1); + + for (TypedPropertyPath columnName : properties) { + TypedPropertyPath path = TypedPropertyPath.of(columnName); + add(columns, ColumnName.from(path)); + } + + return new Columns(columns); + } + /** * Create a {@link Columns} given {@code columnNames}. Individual column names can be either quoted or unquoted. + * Property paths are mapped against the underlying domain model. * * @param columnNames must not be {@literal null}. * @return the {@link Columns} object for {@code columnNames}. @@ -82,7 +106,7 @@ public static Columns from(String... columnNames) { Map> columns = new LinkedHashMap<>(columnNames.length, 1); for (String columnName : columnNames) { - columns.put(ColumnName.from(columnName), new ArrayList<>(List.of(ColumnSelector.from(columnName)))); + add(columns, ColumnName.from(columnName)); } return new Columns(columns); @@ -101,12 +125,29 @@ public static Columns from(CqlIdentifier... columnNames) { Map> columns = new LinkedHashMap<>(columnNames.length, 1); for (CqlIdentifier cqlId : columnNames) { - columns.put(ColumnName.from(cqlId), new ArrayList<>(List.of(ColumnSelector.from(cqlId)))); + add(columns, ColumnName.from(cqlId)); } return new Columns(columns); } + private static void add(Map> columns, ColumnName name) { + columns.put(name, new ArrayList<>(List.of(ColumnSelector.from(name)))); + } + + /** + * Include column {@code propertyPath} to the selection. Column inclusion overrides an existing selection for the + * column name. + * + * @param propertyPath must not be {@literal null}. + * @return a new {@link Columns} object containing all column definitions and the included {@code propertyPath}. + * @since 5.1 + */ + @CheckReturnValue + public Columns include(TypedPropertyPath propertyPath) { + return select(ColumnName.from(propertyPath)); + } + /** * Include column {@code columnName} to the selection. Column inclusion overrides an existing selection for the column * name. @@ -116,7 +157,7 @@ public static Columns from(CqlIdentifier... columnNames) { */ @CheckReturnValue public Columns include(String columnName) { - return select(columnName, ColumnSelector.from(columnName)); + return select(ColumnName.from(columnName)); } /** @@ -128,7 +169,7 @@ public Columns include(String columnName) { */ @CheckReturnValue public Columns include(CqlIdentifier columnName) { - return select(columnName, ColumnSelector.from(columnName)); + return select(ColumnName.from(columnName)); } /** @@ -155,6 +196,30 @@ public Columns ttl(CqlIdentifier columnName) { return select(columnName, FunctionCall.from("TTL", ColumnSelector.from(columnName))); } + /** + * Include column {@code columnName}. This column selection overrides an existing selection for the column name. + * + * @param columnName must not be {@literal null}. + * @return a new {@link Columns} object containing all column definitions and the selected {@code columnName}. + * @since 5.1 + */ + private Columns select(ColumnName columnName) { + return select(columnName, ColumnSelector.from(columnName)); + } + + /** + * Include column {@code propertyPath} with {@link Selector}. This column selection overrides an existing selection + * for the column name. + * + * @param propertyPath must not be {@literal null}. + * @return a new {@link Columns} object containing all column definitions and the selected {@code propertyPath}. + * @since 5.1 + */ + @CheckReturnValue + public Columns select(TypedPropertyPath propertyPath, Selector selector) { + return select(ColumnName.from(propertyPath), selector); + } + /** * Include column {@code columnName} with {@link Selector}. This column selection overrides an existing selection for * the column name. @@ -178,9 +243,21 @@ public Columns select(String columnName, Selector selector) { */ @CheckReturnValue public Columns select(CqlIdentifier columnName, Function builder) { + return select(ColumnName.from(columnName), builder); + } - ColumnName from = ColumnName.from(columnName); - return select(from, builder.apply(new DefaultSelectorBuilder(from))); + /** + * Include column {@code propertyPath} with a built {@link Selector}. This column selection overrides an existing + * selection for the column name. {@link SelectorBuilder} uses the given {@code propertyPath} as column alias to + * represent the selection in the result. + * + * @param propertyPath must not be {@literal null}. + * @return a new {@link Columns} object containing all column definitions and the selected {@code propertyPath}. + * @since 5.1 + */ + @CheckReturnValue + public Columns select(TypedPropertyPath propertyPath, Function builder) { + return select(ColumnName.from(propertyPath), builder); } /** @@ -194,9 +271,7 @@ public Columns select(CqlIdentifier columnName, Function builder) { - - ColumnName from = ColumnName.from(columnName); - return select(from, builder.apply(new DefaultSelectorBuilder(from))); + return select(ColumnName.from(columnName), builder); } /** @@ -211,6 +286,10 @@ public Columns select(CqlIdentifier columnName, Selector selector) { return select(ColumnName.from(columnName), selector); } + private Columns select(ColumnName columnName, Function builder) { + return select(columnName, builder.apply(new DefaultSelectorBuilder(columnName))); + } + /** * Include column {@code columnName} with {@link Selector}. This column selection overrides an existing selection for * the column name. @@ -424,6 +503,10 @@ public ColumnSelector as(CqlIdentifier alias) { return new ColumnSelector(columnName, alias); } + public ColumnName getColumnName() { + return columnName; + } + @Override public Optional getAlias() { return alias; @@ -465,6 +548,7 @@ public String toString() { return getAlias().map(cqlIdentifier -> String.format("%s AS %s", getExpression(), cqlIdentifier)) .orElseGet(this::getExpression); } + } /** diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Criteria.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Criteria.java index 6d05d076e..a8d23580c 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Criteria.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Criteria.java @@ -20,6 +20,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -55,6 +56,17 @@ protected Criteria(ColumnName columnName) { this.columnName = columnName; } + /** + * Static factory method to create a {@link Criteria} using the provided {@code property}. + * + * @param property must not be {@literal null}. + * @return a new {@link Criteria} for {@code property}. + * @since 5.1 + */ + public static Criteria where(TypedPropertyPath property) { + return where(ColumnName.from(property)); + } + /** * Static factory method to create a {@link Criteria} using the provided {@code columnName}. * diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Query.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Query.java index 1db4da5af..84c820313 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Query.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Query.java @@ -30,6 +30,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.cassandra.core.cql.QueryOptions; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.domain.Limit; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -91,6 +92,26 @@ public static Query empty() { return EMPTY; } + /** + * Static factory method to create a {@link Query} for the given column selection. + * + * @return a new {@link Query} selecting {@link Columns}. + * @since 5.1 + */ + public static Query select(TypedPropertyPath... properties) { + return select(Columns.from(properties)); + } + + /** + * Static factory method to create a {@link Query} for the given column selection. + * + * @return a new {@link Query} selecting {@link Columns}. + * @since 5.1 + */ + public static Query select(String... columnNames) { + return select(Columns.from(columnNames)); + } + /** * Static factory method to create a {@link Query} for the given column selection. * diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Update.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Update.java index 1dd38533d..9819afb3d 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Update.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/query/Update.java @@ -27,6 +27,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.data.cassandra.core.query.Update.AddToOp.Mode; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.lang.CheckReturnValue; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -78,6 +79,15 @@ public static Update of(Iterable assignmentOps) { return new Update(updateOperations); } + /** + * Set the {@code property} to {@code value}. + * + * @return a new {@link Update}. + */ + public static Update update(TypedPropertyPath property, @Nullable Object value) { + return empty().set(property, value); + } + /** * Set the {@code columnName} to {@code value}. * @@ -91,6 +101,19 @@ private Update(List updateOperations) { this.updateOperations = updateOperations; } + /** + * Set the {@code property} to {@code value}. + * + * @param property must not be {@literal null}. + * @param value value to set on column with name, may be {@literal null}. + * @return a new {@link Update} object containing the merge result of the existing assignments and the current + * assignment. + */ + @CheckReturnValue + public Update set(TypedPropertyPath property, @Nullable Object value) { + return set(ColumnName.from(property), value); + } + /** * Set the {@code columnName} to {@code value}. * @@ -101,7 +124,23 @@ private Update(List updateOperations) { */ @CheckReturnValue public Update set(String columnName, @Nullable Object value) { - return add(new SetOp(ColumnName.from(columnName), value)); + return set(ColumnName.from(columnName), value); + } + + private Update set(ColumnName columnName, @Nullable Object value) { + return add(new SetOp(columnName, value)); + } + + /** + * Create a new {@link SetBuilder} to set a collection item for {@code property} in a fluent style. + * + * @param property must not be {@literal null}. + * @return a new {@link AddToBuilder} to build a set assignment. + * @since 5.1 + */ + @CheckReturnValue + public SetBuilder set(TypedPropertyPath property) { + return set(ColumnName.from(property)); } /** @@ -112,7 +151,22 @@ public Update set(String columnName, @Nullable Object value) { */ @CheckReturnValue public SetBuilder set(String columnName) { - return new DefaultSetBuilder(ColumnName.from(columnName)); + return set(ColumnName.from(columnName)); + } + + private SetBuilder set(ColumnName columnName) { + return new DefaultSetBuilder(columnName); + } + + /** + * Create a new {@link AddToBuilder} to add items to a collection for {@code property} in a fluent style. + * + * @param property must not be {@literal null}. + * @return a new {@link AddToBuilder} to build an add-to assignment. + */ + @CheckReturnValue + public AddToBuilder addTo(TypedPropertyPath property) { + return new DefaultAddToBuilder(ColumnName.from(property)); } /** @@ -126,11 +180,23 @@ public AddToBuilder addTo(String columnName) { return new DefaultAddToBuilder(ColumnName.from(columnName)); } + /** + * Create a new {@link RemoveFromBuilder} to remove items from a collection for {@code property} in a fluent style. + * + * @param property must not be {@literal null}. + * @return a new {@link RemoveFromBuilder} to build a remove-from assignment. + * @since 5.1 + */ + @CheckReturnValue + public RemoveFromBuilder removeFrom(TypedPropertyPath property) { + return new DefaultRemoveFromBuilder(ColumnName.from(property)); + } + /** * Create a new {@link RemoveFromBuilder} to remove items from a collection for {@code columnName} in a fluent style. * * @param columnName must not be {@literal null}. - * @return a new {@link RemoveFromBuilder} to build an remove-from assignment. + * @return a new {@link RemoveFromBuilder} to build a remove-from assignment. * @since 3.1.4 */ @CheckReturnValue @@ -138,6 +204,20 @@ public RemoveFromBuilder removeFrom(String columnName) { return new DefaultRemoveFromBuilder(ColumnName.from(columnName)); } + /** + * Remove {@code value} from the collection at {@code property}. + * + * @param property must not be {@literal null}. + * @param value must not be {@literal null}. + * @return a new {@link Update} object containing the merge result of the existing assignments and the current + * assignment. + * @since 5.1s + */ + @CheckReturnValue + public Update remove(TypedPropertyPath property, Object value) { + return remove(ColumnName.from(property), value); + } + /** * Remove {@code value} from the collection at {@code columnName}. * @@ -148,7 +228,25 @@ public RemoveFromBuilder removeFrom(String columnName) { */ @CheckReturnValue public Update remove(String columnName, Object value) { - return add(new RemoveOp(ColumnName.from(columnName), Collections.singletonList(value))); + return remove(ColumnName.from(columnName), value); + } + + @CheckReturnValue + private Update remove(ColumnName columnName, Object value) { + return add(new RemoveOp(columnName, Collections.singletonList(value))); + } + + /** + * Cleat the collection at {@code property}. + * + * @param property must not be {@literal null}. + * @return a new {@link Update} object containing the merge result of the existing assignments and the current + * assignment. + * @since 5.1 + */ + @CheckReturnValue + public Update clear(TypedPropertyPath property) { + return set(property, Collections.emptyList()); } /** @@ -160,7 +258,34 @@ public Update remove(String columnName, Object value) { */ @CheckReturnValue public Update clear(String columnName) { - return add(new SetOp(ColumnName.from(columnName), Collections.emptyList())); + return set(columnName, Collections.emptyList()); + } + + /** + * Increment the value at {@code property} by {@literal 1}. + * + * @param property must not be {@literal null}. + * @return a new {@link Update} object containing the merge result of the existing assignments and the current + * assignment. + * @since 5.1 + */ + @CheckReturnValue + public Update increment(TypedPropertyPath property) { + return increment(property, 1); + } + + /** + * Increment the value at {@code property} by {@code delta}. + * + * @param property must not be {@literal null}. + * @param delta increment value. + * @return a new {@link Update} object containing the merge result of the existing assignments and the current + * assignment. + * @since 5.1 + */ + @CheckReturnValue + public Update increment(TypedPropertyPath property, Number delta) { + return increment(ColumnName.from(property), delta); } /** @@ -185,7 +310,39 @@ public Update increment(String columnName) { */ @CheckReturnValue public Update increment(String columnName, Number delta) { - return add(new IncrOp(ColumnName.from(columnName), delta)); + return increment(ColumnName.from(columnName), delta); + } + + @CheckReturnValue + private Update increment(ColumnName columnName, Number delta) { + return add(new IncrOp(columnName, delta)); + } + + /** + * Decrement the value at {@code property} by {@literal 1}. + * + * @param property must not be {@literal null}. + * @return a new {@link Update} object containing the merge result of the existing assignments and the current + * assignment. + * @since 5.1 + */ + @CheckReturnValue + public Update decrement(TypedPropertyPath property) { + return decrement(property, 1); + } + + /** + * Decrement the value at {@code property} by {@code delta}. + * + * @param property must not be {@literal null}. + * @param delta decrement value. + * @return a new {@link Update} object containing the merge result of the existing assignments and the current + * assignment. + * @since 5.1 + */ + @CheckReturnValue + public Update decrement(TypedPropertyPath property, Number delta) { + return decrement(ColumnName.from(property), delta); } /** @@ -210,18 +367,22 @@ public Update decrement(String columnName) { */ @CheckReturnValue public Update decrement(String columnName, Number delta) { + return decrement(ColumnName.from(columnName), delta); + } + + private Update decrement(ColumnName columnName, Number delta) { if (delta instanceof Integer || delta instanceof Long) { long deltaValue = delta.longValue() > 0 ? -Math.abs(delta.longValue()) : delta.longValue(); - return add(new IncrOp(ColumnName.from(columnName), deltaValue)); + return add(new IncrOp(columnName, deltaValue)); } double deltaValue = delta.doubleValue(); deltaValue = deltaValue > 0 ? -Math.abs(deltaValue) : deltaValue; - return add(new IncrOp(ColumnName.from(columnName), deltaValue)); + return add(new IncrOp(columnName, deltaValue)); } /** diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/QueryMapperUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/QueryMapperUnitTests.java index 284f64582..8a959dbbb 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/QueryMapperUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/QueryMapperUnitTests.java @@ -58,10 +58,12 @@ import org.springframework.data.cassandra.core.query.CriteriaDefinition.Operators; import org.springframework.data.cassandra.core.query.Filter; import org.springframework.data.cassandra.core.query.Query; +import org.springframework.data.cassandra.domain.CompositeKey; import org.springframework.data.cassandra.domain.TypeWithKeyClass; import org.springframework.data.cassandra.support.UserDefinedTypeBuilder; import org.springframework.data.convert.PropertyValueConverter; import org.springframework.data.convert.ValueConverter; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; @@ -87,7 +89,7 @@ public class QueryMapperUnitTests { private MappingCassandraConverter cassandraConverter; - private CassandraPersistentEntity personPersistentEntity; + private CassandraPersistentEntity entity; private QueryMapper queryMapper; @@ -112,7 +114,7 @@ void before() { cassandraConverter.afterPropertiesSet(); queryMapper = new QueryMapper(cassandraConverter); - personPersistentEntity = mappingContext.getRequiredPersistentEntity(Person.class); + entity = mappingContext.getRequiredPersistentEntity(Person.class); } @Test // DATACASS-343 @@ -120,7 +122,7 @@ void shouldMapSimpleQuery() { Query query = Query.query(Criteria.where("foo_name").is("bar")); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); @@ -133,31 +135,31 @@ void shouldMapEnumToString() { Query query = Query.query(Criteria.where("foo_name").is(State.Active)); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); assertThat(mappedCriteriaDefinition.getPredicate().getValue()).isInstanceOf(String.class).isEqualTo("Active"); } - @Test // DATACASS-343 + @Test // DATACASS-343, GH-1625 void shouldMapEnumToNumber() { - Query query = Query.query(Criteria.where("number").is(State.Inactive)); + Query query = Query.query(Criteria.where(Person::getNumber).is(State.Inactive)); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); assertThat(mappedCriteriaDefinition.getPredicate().getValue()).isInstanceOf(Integer.class).isEqualTo(1); } - @Test // DATACASS-343 + @Test // DATACASS-343, GH-1625 void shouldMapEnumToNumberIn() { - Query query = Query.query(Criteria.where("number").in(State.Inactive)); + Query query = Query.query(Criteria.where(Person::getNumber).in(State.Inactive)); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); @@ -170,7 +172,7 @@ void shouldMapApplyingCustomConversion() { Query query = Query.query(Criteria.where("foo_name").is(Currency.getInstance("EUR"))); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); @@ -183,7 +185,7 @@ void shouldMapApplyingCustomConversionInCollection() { Query query = Query.query(Criteria.where("foo_name").in(Currency.getInstance("EUR"))); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); @@ -191,12 +193,12 @@ void shouldMapApplyingCustomConversionInCollection() { assertThat(mappedCriteriaDefinition.getPredicate().getValue()).isEqualTo(Collections.singletonList("Euro")); } - @Test // DATACASS-343 + @Test // DATACASS-343, GH-1625 void shouldMapApplyingUdtValueConversion() { - Query query = Query.query(Criteria.where("address").is(new Address("21 Jump-Street"))); + Query query = Query.query(Criteria.where(Person::getAddress).is(new Address("21 Jump-Street"))); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); CriteriaDefinition.Predicate predicate = mappedCriteriaDefinition.getPredicate(); @@ -206,13 +208,13 @@ void shouldMapApplyingUdtValueConversion() { assertThat(predicate.as(UdtValue.class::cast).getFormattedContents()).isEqualTo("{street:'21 Jump-Street'}"); } - @Test // DATACASS-343 + @Test // DATACASS-343, GH-1625 @SuppressWarnings("unchecked") void shouldMapApplyingUdtValueCollectionConversion() { - Query query = Query.query(Criteria.where("address").in(new Address("21 Jump-Street"))); + Query query = Query.query(Criteria.where(Person::getAddress).in(new Address("21 Jump-Street"))); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); @@ -224,13 +226,13 @@ void shouldMapApplyingUdtValueCollectionConversion() { .contains("{street:'21 Jump-Street'}"); } - @Test // DATACASS-343 + @Test // DATACASS-343, GH-1625 @SuppressWarnings("unchecked") void shouldMapCollectionApplyingUdtValueCollectionConversion() { - Query query = Query.query(Criteria.where("address").in(new Address("21 Jump-Street"))); + Query query = Query.query(Criteria.where(Person::getAddress).in(new Address("21 Jump-Street"))); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); CriteriaDefinition.Predicate predicate = mappedCriteriaDefinition.getPredicate(); @@ -241,12 +243,12 @@ void shouldMapCollectionApplyingUdtValueCollectionConversion() { .contains("{street:'21 Jump-Street'}"); } - @Test // DATACASS-487 + @Test // DATACASS-487, GH-1625 void shouldMapUdtMapContainsKey() { - Query query = Query.query(Criteria.where("relocations").containsKey(new Address("21 Jump-Street"))); + Query query = Query.query(Criteria.where(Person::getRelocations).containsKey(new Address("21 Jump-Street"))); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); @@ -256,12 +258,12 @@ void shouldMapUdtMapContainsKey() { .isEqualTo("{street:'21 Jump-Street'}"); } - @Test // DATACASS-487 + @Test // DATACASS-487, GH-1625 void shouldMapUdtMapContains() { - Query query = Query.query(Criteria.where("relocations").contains(new Address("21 Jump-Street"))); + Query query = Query.query(Criteria.where(Person::getRelocations).contains(new Address("21 Jump-Street"))); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); @@ -276,7 +278,7 @@ void shouldMapPropertyToColumnName() { Query query = Query.query(Criteria.where("firstName").is("bar")); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); @@ -290,7 +292,7 @@ void shouldConsiderPropertyValueConverter() { Query query = Query.query(Criteria.where("reverseName").is("Heisenberg")); - Filter mappedObject = queryMapper.getMappedObject(query, personPersistentEntity); + Filter mappedObject = queryMapper.getMappedObject(query, entity); CriteriaDefinition mappedCriteriaDefinition = mappedObject.iterator().next(); assertThat(mappedCriteriaDefinition.getPredicate().getValue()).isEqualTo("grebnesieH"); @@ -299,7 +301,7 @@ void shouldConsiderPropertyValueConverter() { @Test // DATACASS-343 void shouldCreateSelectExpression() { - List selectors = queryMapper.getMappedSelectors(Columns.empty(), personPersistentEntity); + List selectors = queryMapper.getMappedSelectors(Columns.empty(), entity); assertThat(selectors).isEmpty(); } @@ -308,7 +310,7 @@ void shouldCreateSelectExpression() { void shouldCreateSelectExpressionWithTTL() { List selectors = queryMapper - .getMappedSelectors(Columns.from("number", "foo").ttl("firstName"), personPersistentEntity).stream() + .getMappedSelectors(Columns.from("number", "foo").ttl("firstName"), entity).stream() .map(Selector::toString).collect(Collectors.toList()); assertThat(selectors).contains("number").contains("foo").contains("TTL(first_name)"); @@ -318,7 +320,7 @@ void shouldCreateSelectExpressionWithTTL() { void shouldIncludeColumnsSelectExpressionWithTTL() { List selectors = queryMapper.getMappedColumnNames(Columns.from("number", "foo").ttl("firstName"), - personPersistentEntity); + entity); assertThat(selectors).contains(CqlIdentifier.fromCql("number"), CqlIdentifier.fromCql("foo")).hasSize(2); } @@ -345,21 +347,20 @@ void shouldMapSortWithCompositePrimaryKeyClass() { assertThat(mappedObject).contains(new Order(Direction.ASC, "first_name")); } - @Test // DATACASS-828 + @Test // DATACASS-828, GH-1625 void allowSortByCompositeKey() { - Sort sort = Sort.by("key"); - Query.empty().columns(Columns.from("key")); + Sort sort = Sort.by(TypeWithKeyClass::getKey); Sort mappedSort = queryMapper.getMappedSort(sort, mappingContext.getRequiredPersistentEntity(TypeWithKeyClass.class)); assertThat(mappedSort).isEqualTo(Sort.by(asc("first_name"), asc("lastname"))); } - @Test // DATACASS-343 + @Test // DATACASS-343, GH-1625 void shouldMapColumnWithCompositePrimaryKeyClass() { - Columns columnNames = Columns.from("key.firstname"); + Columns columnNames = Columns.from(TypedPropertyPath.of(TypeWithKeyClass::getKey).then(CompositeKey::getFirstname)); List mappedObject = queryMapper.getMappedColumnNames(columnNames, mappingContext.getRequiredPersistentEntity(TypeWithKeyClass.class)); @@ -379,7 +380,7 @@ void shouldMapMultipleColumnNames() { assertThat(mappedObject).contains(CqlIdentifier.fromCql("array")); } - @Test // + @Test void shouldMapMultipleSelectorsNames() { Columns columnNames = Columns.from("array").select("array", @@ -400,7 +401,7 @@ void shouldMapTuple() { Filter filter = Filter.from(Criteria.where("tuple").is(tuple)); - Filter mappedObject = this.queryMapper.getMappedObject(filter, this.personPersistentEntity); + Filter mappedObject = this.queryMapper.getMappedObject(filter, this.entity); TupleValue tupleValue = DataTypes.tupleOf(DataTypes.TEXT).newValue(); @@ -419,7 +420,7 @@ void shouldMapTime() { Filter filter = Filter.from(Criteria.where("localTime").gt(time)); - Filter mappedFilter = this.queryMapper.getMappedObject(filter, this.personPersistentEntity); + Filter mappedFilter = this.queryMapper.getMappedObject(filter, this.entity); assertThat(mappedFilter).contains(Criteria.where("localtime").gt(time)); } @@ -427,7 +428,7 @@ void shouldMapTime() { @Test // DATACASS-523 void referencingTupleElementsInQueryShouldFail() { assertThatIllegalArgumentException().isThrownBy(() -> this.queryMapper - .getMappedObject(Filter.from(Criteria.where("tuple.zip").is("123")), this.personPersistentEntity)); + .getMappedObject(Filter.from(Criteria.where("tuple.zip").is("123")), this.entity)); } @Test // DATACASS-167 @@ -476,7 +477,6 @@ void shouldConvertVectorValues() { assertThat(mappedObject.iterator().next().getPredicate().getValue()).isEqualTo(new float[] { 1.1f, 2.2f }); } - @Test // GH-1504 void shouldConvertVectorSelectorFunction() { @@ -506,7 +506,7 @@ static class Person { Currency currency; State state; - Integer number; + Integer number; LocalDate localDate; LocalTime localTime; @@ -517,6 +517,101 @@ static class Person { @ValueConverter(ReversingValueConverter.class) String reverseName; + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public List
getAddresses() { + return addresses; + } + + public void setAddresses(List
addresses) { + this.addresses = addresses; + } + + public Map getRelocations() { + return relocations; + } + + public void setRelocations(Map relocations) { + this.relocations = relocations; + } + + public Currency getCurrency() { + return currency; + } + + public void setCurrency(Currency currency) { + this.currency = currency; + } + + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + public Integer getNumber() { + return number; + } + + public void setNumber(Integer number) { + this.number = number; + } + + public LocalDate getLocalDate() { + return localDate; + } + + public void setLocalDate(LocalDate localDate) { + this.localDate = localDate; + } + + public LocalTime getLocalTime() { + return localTime; + } + + public void setLocalTime(LocalTime localTime) { + this.localTime = localTime; + } + + public MappedTuple getTuple() { + return tuple; + } + + public void setTuple(MappedTuple tuple) { + this.tuple = tuple; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getReverseName() { + return reverseName; + } + + public void setReverseName(String reverseName) { + this.reverseName = reverseName; + } } static class ReversingValueConverter implements PropertyValueConverter { diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/UpdateMapperUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/UpdateMapperUnitTests.java index 025e882fe..52d9007ac 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/UpdateMapperUnitTests.java @@ -63,7 +63,7 @@ class UpdateMapperUnitTests { private CassandraMappingContext mappingContext = new CassandraMappingContext(); - private CassandraPersistentEntity persistentEntity; + private CassandraPersistentEntity entity; private Currency currencyEUR = Currency.getInstance("EUR"); private Currency currencyUSD = Currency.getInstance("USD"); @@ -93,7 +93,7 @@ void before() { updateMapper = new UpdateMapper(cassandraConverter); - persistentEntity = mappingContext.getRequiredPersistentEntity(Person.class); + entity = mappingContext.getRequiredPersistentEntity(Person.class); when(userTypeResolver.resolveType(CqlIdentifier.fromCql("manufacturer"))).thenReturn(manufacturer); } @@ -101,7 +101,7 @@ void before() { @Test // DATACASS-343 void shouldCreateSimpleUpdate() { - Update update = updateMapper.getMappedObject(Update.empty().set("firstName", "foo"), persistentEntity); + Update update = updateMapper.getMappedObject(Update.empty().set("firstName", "foo"), entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("first_name = 'foo'"); @@ -114,9 +114,9 @@ void shouldReplaceUdtMap() { Map map = Collections.singletonMap(manufacturer, currencyEUR); - Update update = Update.empty().set("manufacturers", map); + Update update = Update.empty().set(Person::getManufacturers, map); - Update mappedUpdate = updateMapper.getMappedObject(update, persistentEntity); + Update mappedUpdate = updateMapper.getMappedObject(update, entity); assertThat(mappedUpdate.getUpdateOperations()).hasSize(1); assertThat(mappedUpdate).hasToString("manufacturers = {{name:'foobar'}:'Euro'}"); @@ -125,8 +125,8 @@ void shouldReplaceUdtMap() { @Test // DATACASS-343 void shouldCreateSetAtIndexUpdate() { - Update update = updateMapper.getMappedObject(Update.empty().set("list").atIndex(10).to(currencyEUR), - persistentEntity); + Update update = updateMapper.getMappedObject(Update.empty().set(Person::getList).atIndex(10).to(currencyEUR), + entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("list[10] = 'Euro'"); @@ -136,7 +136,7 @@ void shouldCreateSetAtIndexUpdate() { void shouldCreateSetAtUdtIndexUpdate() { Update update = updateMapper.getMappedObject( - Update.empty().set("manufacturerList").atIndex(10).to(new Manufacturer("foo")), persistentEntity); + Update.empty().set(Person::getManufacturerList).atIndex(10).to(new Manufacturer("foo")), entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("manufacturerlist[10] = {name:'foo'}"); @@ -146,7 +146,7 @@ void shouldCreateSetAtUdtIndexUpdate() { void shouldCreateSetAtKeyUpdate() { Update update = updateMapper.getMappedObject(Update.empty().set("map").atKey("baz").to(currencyEUR), - persistentEntity); + entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("map['baz'] = 'Euro'"); @@ -159,7 +159,7 @@ void shouldCreateSetAtUdtKeyUpdate() { Update update = updateMapper .getMappedObject(Update.empty().set("manufacturers").atKey(manufacturer).to(currencyEUR), - persistentEntity); + entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("manufacturers[{name:'foobar'}] = 'Euro'"); @@ -169,7 +169,7 @@ void shouldCreateSetAtUdtKeyUpdate() { void shouldAddToMap() { Update update = updateMapper.getMappedObject(Update.empty().addTo("map").entry("foo", currencyEUR), - persistentEntity); + entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("map = map + {'foo':'Euro'}"); @@ -178,7 +178,7 @@ void shouldAddToMap() { @Test // #1007 void shouldRemoveFromMap() { - Update update = updateMapper.getMappedObject(Update.empty().removeFrom("map").value("foo"), persistentEntity); + Update update = updateMapper.getMappedObject(Update.empty().removeFrom(Person::getMap).value("foo"), entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("map = map - {'foo'}"); @@ -189,9 +189,9 @@ void shouldAddUdtToMap() { Manufacturer manufacturer = new Manufacturer("foobar"); - Update update = Update.empty().addTo("manufacturers").entry(manufacturer, currencyEUR); + Update update = Update.empty().addTo(Person::getManufacturers).entry(manufacturer, currencyEUR); - Update mappedUpdate = updateMapper.getMappedObject(update, persistentEntity); + Update mappedUpdate = updateMapper.getMappedObject(update, entity); assertThat(mappedUpdate.getUpdateOperations()).hasSize(1); assertThat(mappedUpdate).hasToString("manufacturers = manufacturers + {{name:'foobar'}:'Euro'}"); @@ -201,7 +201,7 @@ void shouldAddUdtToMap() { void shouldPrependAllToList() { Update update = updateMapper.getMappedObject(Update.empty().addTo("list").prependAll("foo", currencyEUR), - persistentEntity); + entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("list = ['foo','Euro'] + list"); @@ -211,7 +211,7 @@ void shouldPrependAllToList() { void shouldAppendAllToList() { Update update = updateMapper.getMappedObject(Update.empty().addTo("list").appendAll("foo", currencyEUR), - persistentEntity); + entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("list = list + ['foo','Euro']"); @@ -220,7 +220,7 @@ void shouldAppendAllToList() { @Test // DATACASS-343 void shouldRemoveFromList() { - Update update = updateMapper.getMappedObject(Update.empty().remove("list", currencyEUR), persistentEntity); + Update update = updateMapper.getMappedObject(Update.empty().remove(Person::getList, currencyEUR), entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("list = list - ['Euro']"); @@ -229,7 +229,7 @@ void shouldRemoveFromList() { @Test // DATACASS-343 void shouldClearList() { - Update update = updateMapper.getMappedObject(Update.empty().clear("list"), persistentEntity); + Update update = updateMapper.getMappedObject(Update.empty().clear("list"), entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("list = []"); @@ -239,7 +239,7 @@ void shouldClearList() { void shouldPrependAllToSet() { Update update = updateMapper.getMappedObject(Update.empty().addTo("set").prependAll(currencyUSD, currencyEUR), - persistentEntity); + entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("set_col = {'US Dollar','Euro'} + set_col"); @@ -248,8 +248,8 @@ void shouldPrependAllToSet() { @Test // DATACASS-770 void shouldAppendAllToSet() { - Update update = updateMapper.getMappedObject(Update.empty().addTo("set").appendAll(currencyUSD, currencyEUR), - persistentEntity); + Update update = updateMapper + .getMappedObject(Update.empty().addTo(Person::getSet).appendAll(currencyUSD, currencyEUR), entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("set_col = set_col + {'US Dollar','Euro'}"); @@ -260,7 +260,7 @@ void shouldPrependAllToSetViaColumnNameCollectionOfElements() { Update update = updateMapper.getMappedObject( Update.empty().addTo("set_col").prependAll(new LinkedHashSet<>(Arrays.asList(currencyUSD, currencyEUR))), - persistentEntity); + entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("set_col = {'US Dollar','Euro'} + set_col"); @@ -271,7 +271,7 @@ void shouldAppendAllToSetViaColumnNameCollectionOfElements() { Update update = updateMapper.getMappedObject( Update.empty().addTo("set_col").appendAll(new LinkedHashSet<>(Arrays.asList(currencyUSD, currencyEUR))), - persistentEntity); + entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("set_col = set_col + {'US Dollar','Euro'}"); @@ -281,7 +281,7 @@ void shouldAppendAllToSetViaColumnNameCollectionOfElements() { void shouldAppendToSet() { Update update = updateMapper.getMappedObject(Update.empty().addTo("set").append(currencyEUR), - persistentEntity); + entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("set_col = set_col + {'Euro'}"); @@ -291,7 +291,7 @@ void shouldAppendToSet() { void shouldPrependToSet() { Update update = updateMapper.getMappedObject(Update.empty().addTo("set").prepend(currencyEUR), - persistentEntity); + entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("set_col = {'Euro'} + set_col"); @@ -300,7 +300,7 @@ void shouldPrependToSet() { @Test // DATACASS-770 void shouldRemoveFromSet() { - Update update = updateMapper.getMappedObject(Update.empty().remove("set", currencyEUR), persistentEntity); + Update update = updateMapper.getMappedObject(Update.empty().remove("set", currencyEUR), entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("set_col = set_col - {'Euro'}"); @@ -309,7 +309,7 @@ void shouldRemoveFromSet() { @Test // DATACASS-343 void shouldClearSet() { - Update update = updateMapper.getMappedObject(Update.empty().clear("set"), persistentEntity); + Update update = updateMapper.getMappedObject(Update.empty().clear("set"), entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("set_col = {}"); @@ -318,7 +318,7 @@ void shouldClearSet() { @Test // DATACASS-343 void shouldCreateIncrementUpdate() { - Update update = updateMapper.getMappedObject(Update.empty().increment("number"), persistentEntity); + Update update = updateMapper.getMappedObject(Update.empty().increment(Person::getNumber), entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("number = number + 1"); @@ -327,7 +327,7 @@ void shouldCreateIncrementUpdate() { @Test // DATACASS-343 void shouldCreateDecrementUpdate() { - Update update = updateMapper.getMappedObject(Update.empty().decrement("number"), persistentEntity); + Update update = updateMapper.getMappedObject(Update.empty().decrement(Person::getNumber), entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("number = number - 1"); @@ -337,7 +337,7 @@ void shouldCreateDecrementUpdate() { void shouldMapTuple() { Update update = this.updateMapper.getMappedObject(Update.empty().set("tuple", new MappedTuple("foo")), - this.persistentEntity); + this.entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("tuple = ('foo')"); @@ -347,7 +347,7 @@ void shouldMapTuple() { void shouldMapTime() { Update update = this.updateMapper.getMappedObject(Update.empty().set("localTime", LocalTime.of(1, 2, 3)), - this.persistentEntity); + this.entity); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update.toString()).startsWith("localtime = '01:02:03."); @@ -356,7 +356,7 @@ void shouldMapTime() { @Test // DATACASS-523 void referencingTupleElementsInQueryShouldFail() { assertThatIllegalArgumentException().isThrownBy( - () -> this.updateMapper.getMappedObject(Update.empty().set("tuple.zip", "bar"), this.persistentEntity)); + () -> this.updateMapper.getMappedObject(Update.empty().set("tuple.zip", "bar"), this.entity)); } @Test // DATACASS-167 @@ -403,6 +403,93 @@ static class Person { @Column("first_name") String firstName; + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Currency getCurrency() { + return currency; + } + + public void setCurrency(Currency currency) { + this.currency = currency; + } + + public Integer getNumber() { + return number; + } + + public void setNumber(Integer number) { + this.number = number; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public LocalTime getLocalTime() { + return localTime; + } + + public void setLocalTime(LocalTime localTime) { + this.localTime = localTime; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + + public Map getManufacturers() { + return manufacturers; + } + + public void setManufacturers(Map manufacturers) { + this.manufacturers = manufacturers; + } + + public List getManufacturerList() { + return manufacturerList; + } + + public void setManufacturerList(List manufacturerList) { + this.manufacturerList = manufacturerList; + } + + public MappedTuple getTuple() { + return tuple; + } + + public void setTuple(MappedTuple tuple) { + this.tuple = tuple; + } + + public Set getSet() { + return set; + } + + public void setSet(Set set) { + this.set = set; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } } @Tuple diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/query/UpdateUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/query/UpdateUnitTests.java index 9d6227dfd..99ff9dff4 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/query/UpdateUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/query/UpdateUnitTests.java @@ -29,10 +29,10 @@ */ class UpdateUnitTests { - @Test // DATACASS-343 + @Test // DATACASS-343, GH-1625 void shouldCreateSimpleUpdate() { - Update update = Update.update("foo", "bar"); + Update update = Update.update(Person::getFoo, "bar"); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("foo = 'bar'"); @@ -146,10 +146,10 @@ void shouldCreateSingleUpdateForTheSameColumn() { assertThat(update.getUpdateOperations().iterator().next()).isInstanceOf(IncrOp.class); } - @Test // DATACASS-718 + @Test // DATACASS-718, GH-1625 void shouldCreateIncrementLongUpdate() { - Update update = Update.empty().increment("foo", 2400000000L); + Update update = Update.empty().increment(Person::getFoo, 2400000000L); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("foo = foo + 2400000000"); @@ -182,10 +182,10 @@ void shouldUseLastWinsForDuplicateSetAtKeyOnSameKey() { assertThat(update).hasToString("foo['k1'] = 'v2'"); } - @Test // GH-1525 + @Test // GH-1525, GH-1625 void shouldAllowMultipleSetAtIndexOperationsOnSameColumn() { - Update update = Update.empty().set("foo").atIndex(1).to("A").set("foo").atIndex(2).to("B"); + Update update = Update.empty().set(Person::getFoo).atIndex(1).to("A").set(Person::getFoo).atIndex(2).to("B"); assertThat(update.getUpdateOperations()).hasSize(2); assertThat(update).hasToString("foo[1] = 'A', foo[2] = 'B'"); @@ -194,17 +194,18 @@ void shouldAllowMultipleSetAtIndexOperationsOnSameColumn() { @Test // GH-1525 void shouldUseLastWinsForDuplicateSetAtIndexOnSameIndex() { - Update update = Update.empty().set("foo").atIndex(1).to("A").set("bar").atIndex(1).to("B").set("foo").atIndex(1) + Update update = Update.empty().set(Person::getFoo).atIndex(1).to("A").set("bar").atIndex(1).to("B") + .set(Person::getFoo).atIndex(1) .to("B"); assertThat(update.getUpdateOperations()).hasSize(2); assertThat(update).hasToString("bar[1] = 'B', foo[1] = 'B'"); } - @Test // GH-1525 + @Test // GH-1525, GH-1625 void wholeColumnAndElementLevelShouldConflict_LastWinsWholeColumn() { - Update update = Update.empty().set("foo").atKey("k1").to("v1").set("foo", "ALL"); + Update update = Update.empty().set(Person::getFoo).atKey("k1").to("v1").set(Person::getFoo, "ALL"); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("foo = 'ALL'"); @@ -213,9 +214,24 @@ void wholeColumnAndElementLevelShouldConflict_LastWinsWholeColumn() { @Test // GH-1525 void wholeColumnAndElementLevelShouldConflict_LastWinsElementLevel() { - Update update = Update.empty().set("foo", "ALL").set("foo").atKey("k1").to("v1"); + Update update = Update.empty().set(Person::getFoo, "ALL").set(Person::getFoo).atKey("k1").to("v1"); assertThat(update.getUpdateOperations()).hasSize(1); assertThat(update).hasToString("foo['k1'] = 'v1'"); } + + static class Person { + + String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + + } + }