diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java index 6a492df13..df2df7e86 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java @@ -26,6 +26,7 @@ import java.util.stream.Stream; import org.bson.Document; import org.jspecify.annotations.Nullable; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Window; @@ -930,10 +931,14 @@ public interface MongoOperations extends FluentMongoOperations { * @return never {@literal null}. * @since 2.1 */ - default List findDistinct(String field, Class entityClass, Class resultClass) { + default List findDistinct(String field, Class entityClass, Class resultClass) { return findDistinct(new Query(), field, entityClass, resultClass); } + default List findDistinct(TypedPropertyPath path) { + return findDistinct(new Query(), path.toDotPath(), path.getOwningType().getType(), (Class) path.getType()); + } + /** * Finds the distinct values for a specified {@literal field} across a single {@link MongoCollection} or view and * returns the results in a {@link List}. @@ -946,7 +951,11 @@ public interface MongoOperations extends FluentMongoOperations { * @return never {@literal null}. * @since 2.1 */ - List findDistinct(Query query, String field, Class entityClass, Class resultClass); + List findDistinct(Query query, String field, Class entityClass, Class resultClass); + + default List findDistinct(Query query, TypedPropertyPath path){ + return findDistinct(query, path.toDotPath(), path.getOwningType().getType(), (Class) path.getType()); + } /** * Finds the distinct values for a specified {@literal field} across a single {@link MongoCollection} or view and @@ -960,8 +969,12 @@ public interface MongoOperations extends FluentMongoOperations { * @return never {@literal null}. * @since 2.1 */ - List findDistinct(Query query, String field, String collectionName, Class entityClass, - Class resultClass); + List findDistinct(Query query, String field, String collectionName, Class entityClass, + Class resultClass); + + default List findDistinct(Query query, TypedPropertyPath path, String collectionName){ + return findDistinct(query, path.toDotPath(), collectionName, path.getOwningType().getType(), (Class)path.getType()); + } /** * Finds the distinct values for a specified {@literal field} across a single {@link MongoCollection} or view and diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java index 07db7a5ce..cfcff3dc2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java @@ -35,6 +35,8 @@ import org.bson.Document; import org.bson.types.Binary; import org.jspecify.annotations.Nullable; +import org.springframework.data.core.PropertyReference; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.domain.Example; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Point; @@ -91,8 +93,13 @@ public class Criteria implements CriteriaDefinition { this.key = key; } + public Criteria(TypedPropertyPath propertyReference) { + this(propertyReference.toDotPath()); + } + protected Criteria(List criteriaChain, String key) { - this.criteriaChain = criteriaChain; + + this.criteriaChain = new ArrayList<>(criteriaChain); this.criteriaChain.add(this); this.key = key; } @@ -107,6 +114,10 @@ public class Criteria implements CriteriaDefinition { return new Criteria(key); } + public static Criteria where(TypedPropertyPath propertyReference) { + return where(propertyReference.toDotPath()); + } + /** * Static factory method to create a {@link Criteria} matching an example object. * @@ -191,6 +202,16 @@ public class Criteria implements CriteriaDefinition { return new Criteria(this.criteriaChain, key); } + /** + * Static factory method to create a Criteria using the provided key + * + * @return new instance of {@link Criteria}. + */ + @Contract("_ -> new") + public Criteria and(PropertyReference propertyReference) { + return and(propertyReference.getName()); + } + /** * Creates a criterion using equality * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaUnitTests.java index 91c8ef37a..2e4a24fc4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaUnitTests.java @@ -105,12 +105,14 @@ class CriteriaUnitTests { @Test void testChainedCriteria() { + Criteria c = new Criteria("name").is("Bubba").and("age").lt(21); assertThat(c.getCriteriaObject()).isEqualTo("{ \"name\" : \"Bubba\" , \"age\" : { \"$lt\" : 21}}"); } @Test void testCriteriaWithMultipleConditionsForSameKey() { + Criteria c = new Criteria("name").gte("M").and("name").ne("A"); assertThatExceptionOfType(InvalidMongoDbApiUsageException.class).isThrownBy(c::getCriteriaObject); @@ -458,12 +460,17 @@ class CriteriaUnitTests { } @Test // GH-3414 - void shouldEqualForSamePatternAndFlags() { + void shouldEqualOnlyForSamePatternAndFlags() { Criteria left = new Criteria("field").regex("foo", "iu"); - Criteria right = new Criteria("field").regex("foo"); + Criteria same = new Criteria("field").regex("foo", "iu"); - assertThat(left).isNotEqualTo(right); + Criteria noOption = new Criteria("field").regex("foo"); + Criteria differentRegex = new Criteria("field").regex("fox", "iu"); + + assertThat(left).isNotEqualTo(noOption); + assertThat(left).isNotEqualTo(differentRegex); + assertThat(left).isEqualTo(same); } @Test // GH-3414 @@ -476,4 +483,31 @@ class CriteriaUnitTests { assertThat(left).isEqualTo(right); } + + @Test // GH-5135 + void equalsConsidersPartialCriteria() { + + Criteria criteria = new Criteria("alpha").is("a"); + Criteria partialCriteria = new Criteria("alpha"); + + assertThat(criteria).isNotEqualTo(partialCriteria); + } + + @Test // GH-5135 + void equalsConsidersPartialCriteriaChain() { + + Criteria criteria = new Criteria("alpha").is("a").and("beta").is("b"); + Criteria partialCriteria = new Criteria("alpha").is("a").and("beta"); + + assertThat(criteria).isNotEqualTo(partialCriteria); + } + + @Test // GH-5135 + void criteriaIsImmutable() { + + Criteria base = new Criteria("alpha").is("a"); + Criteria mutated = base.and("beta"); + + assertThat(base).isNotEqualTo(mutated); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaWithPropertyReferenceUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaWithPropertyReferenceUnitTests.java new file mode 100644 index 000000000..0401a6f1f --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaWithPropertyReferenceUnitTests.java @@ -0,0 +1,50 @@ +package org.springframework.data.mongodb.core.query; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.FieldSource; +import org.springframework.data.core.TypedPropertyPath; + +class CriteriaWithPropertyReferenceUnitTests { + + static Criteria base = new Criteria("name").is("Bubba"); + + static List compare = List.of( // + new Fixture( // + "constructor", // + Criteria.where((TestEntity e) -> e.name), // + new Criteria("name") // + ), // + new Fixture( // + "path", // + Criteria.where(TypedPropertyPath.ofReference((TestEntity e) -> e.referenced).then(r -> r.value)), // + new Criteria("referenced.value") // + ), // + new Fixture( // + "where", // + Criteria.where((TestEntity e) -> e.name), // + new Criteria("name") // + ), // + new Fixture( // + "and", // + base.and((TestEntity e) -> e.age), // + base.and("age") // + ) // + ); + + @ParameterizedTest + @FieldSource + void compare(Fixture fixture) { + assertThat(fixture.underTest).describedAs(fixture.description).isEqualTo(fixture.expected); + } + + record Fixture(String description, Criteria underTest, Criteria expected) { + } + + record TestEntity(String name, Long age, Referenced referenced) { + } + record Referenced(String value) {} +}