Browse Source

Merge 4804b77e10 into 2cb6184d45

pull/5147/merge
backend-choijunhyeong 3 days ago committed by GitHub
parent
commit
6c258dbb3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 60
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java
  2. 116
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  3. 12
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java
  4. 25
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  5. 85
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java

60
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java

@ -26,6 +26,7 @@ import java.util.regex.Pattern; @@ -26,6 +26,7 @@ import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.BsonRegularExpression;
import org.bson.Document;
import org.jspecify.annotations.Nullable;
import org.springframework.data.core.PropertyPath;
@ -65,6 +66,7 @@ import org.springframework.util.ObjectUtils; @@ -65,6 +66,7 @@ import org.springframework.util.ObjectUtils;
* @author Thomas Darimont
* @author Christoph Strobl
* @author Edward Prentice
* @author Junhyeong Choi
*/
public class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
@ -218,6 +220,10 @@ public class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> { @@ -218,6 +220,10 @@ public class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
return criteria.is(true);
case FALSE:
return criteria.is(false);
case IS_EMPTY:
return createIsEmptyCriteria(property, criteria);
case IS_NOT_EMPTY:
return createIsNotEmptyCriteria(property, criteria);
case NEAR:
return createNearCriteria(property, criteria, parameters);
case WITHIN:
@ -254,6 +260,60 @@ public class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> { @@ -254,6 +260,60 @@ public class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
return criteria.exists((Boolean) param);
}
/**
* Creates a criterion for the {@literal IS_EMPTY} keyword. For {@link Collection} properties, checks if the
* collection size is 0. For {@link String} properties, checks if the value equals an empty string. For {@link Map}
* and other types, checks if the value equals an empty document.
*
* @param property the property to check.
* @param criteria the criteria to extend.
* @return the extended criteria.
* @since 5.1
*/
protected Criteria createIsEmptyCriteria(MongoPersistentProperty property, Criteria criteria) {
if (property.isCollectionLike()) {
return criteria.size(0);
}
if (property.isMap()) {
return criteria.is(new Document());
}
if (property.getType() == String.class) {
return criteria.is("");
}
return criteria.is(new Document());
}
/**
* Creates a criterion for the {@literal IS_NOT_EMPTY} keyword. For {@link Collection} properties, checks if the
* collection size is not 0. For {@link String} properties, checks if the value is not an empty string. For
* {@link Map} and other types, checks if the value is not an empty document.
*
* @param property the property to check.
* @param criteria the criteria to extend.
* @return the extended criteria.
* @since 5.1
*/
protected Criteria createIsNotEmptyCriteria(MongoPersistentProperty property, Criteria criteria) {
if (property.isCollectionLike()) {
return criteria.not().size(0);
}
if (property.isMap()) {
return criteria.ne(new Document());
}
if (property.getType() == String.class) {
return criteria.ne("");
}
return criteria.ne(new Document());
}
private Criteria createNearCriteria(MongoPersistentProperty property, Criteria criteria,
Iterator<Object> parameters) {

116
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

@ -26,8 +26,10 @@ import java.util.ArrayList; @@ -26,8 +26,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
@ -93,6 +95,7 @@ import org.springframework.test.util.ReflectionTestUtils; @@ -93,6 +95,7 @@ import org.springframework.test.util.ReflectionTestUtils;
* @author Mark Paluch
* @author Fırat KÜÇÜK
* @author Edward Prentice
* @author Junhyeong Choi
*/
@ExtendWith({ SpringExtension.class, DirtiesStateExtension.class })
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -1830,4 +1833,117 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie @@ -1830,4 +1833,117 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie
assertThat(repository.findById(dave.getId()).map(Person::getShippingAddresses))
.contains(Collections.singleton(address));
}
@Test // GH-4606
@DirtiesState
void findsByFirstnameIsEmpty() {
Person emptyFirstname = new Person("", "EmptyFirstname");
repository.save(emptyFirstname);
List<Person> result = repository.findByFirstnameIsEmpty();
assertThat(result).hasSize(1).contains(emptyFirstname);
}
@Test // GH-4606
void findsByFirstnameIsNotEmpty() {
List<Person> result = repository.findByFirstnameIsNotEmpty();
assertThat(result).hasSize(all.size()).containsAll(all);
}
@Test // GH-4606
@DirtiesState
void blankStringIsNotConsideredEmpty() {
Person blankFirstname = new Person(" ", "BlankFirstname");
repository.save(blankFirstname);
List<Person> emptyResult = repository.findByFirstnameIsEmpty();
List<Person> notEmptyResult = repository.findByFirstnameIsNotEmpty();
assertThat(emptyResult).doesNotContain(blankFirstname);
assertThat(notEmptyResult).contains(blankFirstname);
}
@Test // GH-4606
@DirtiesState
void findsBySkillsIsEmpty() {
Person emptySkills = new Person("Empty", "Skills");
emptySkills.setSkills(Collections.emptyList());
repository.save(emptySkills);
List<Person> result = repository.findBySkillsIsEmpty();
assertThat(result).contains(emptySkills).doesNotContain(carter, boyd);
}
@Test // GH-4606
void findsBySkillsIsNotEmpty() {
List<Person> result = repository.findBySkillsIsNotEmpty();
assertThat(result).contains(carter, boyd);
}
@Test // GH-4606
@DirtiesState
void findsByAddressIsEmpty() {
Person emptyAddress = new Person("Empty", "Address");
emptyAddress.setAddress(new Address());
repository.save(emptyAddress);
dave.setAddress(new Address("street", "zip", "city"));
repository.save(dave);
List<Person> result = repository.findByAddressIsEmpty();
assertThat(result).contains(emptyAddress).doesNotContain(dave);
}
@Test // GH-4606
@DirtiesState
void findsByAddressIsNotEmpty() {
dave.setAddress(new Address("street", "zip", "city"));
repository.save(dave);
List<Person> result = repository.findByAddressIsNotEmpty();
assertThat(result).contains(dave);
}
@Test // GH-4606
@DirtiesState
void findsByMetadataIsEmpty() {
dave.setMetadata(new HashMap<>());
repository.save(dave);
oliver.setMetadata(Map.of("key", "value"));
repository.save(oliver);
List<Person> result = repository.findByMetadataIsEmpty();
assertThat(result).contains(dave).doesNotContain(oliver);
}
@Test // GH-4606
@DirtiesState
void findsByMetadataIsNotEmpty() {
dave.setMetadata(new HashMap<>());
repository.save(dave);
oliver.setMetadata(Map.of("key", "value"));
repository.save(oliver);
List<Person> result = repository.findByMetadataIsNotEmpty();
assertThat(result).contains(oliver).doesNotContain(dave);
}
}

12
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java

@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository; @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -39,6 +40,7 @@ import org.springframework.data.mongodb.core.mapping.Unwrapped; @@ -39,6 +40,7 @@ import org.springframework.data.mongodb.core.mapping.Unwrapped;
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
* @author Junhyeong Choi
*/
@Document
public class Person extends Contact {
@ -81,6 +83,8 @@ public class Person extends Contact { @@ -81,6 +83,8 @@ public class Person extends Contact {
int visits;
Map<String, String> metadata;
public Person() {
this(null, null);
@ -334,6 +338,14 @@ public class Person extends Contact { @@ -334,6 +338,14 @@ public class Person extends Contact {
this.lazySpiritAnimal = lazySpiritAnimal;
}
public Map<String, String> getMetadata() {
return metadata;
}
public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}
@Override
public int hashCode() {

25
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

@ -56,6 +56,7 @@ import org.springframework.data.util.Streamable; @@ -56,6 +56,7 @@ import org.springframework.data.util.Streamable;
* @author Christoph Strobl
* @author Fırat KÜÇÜK
* @author Mark Paluch
* @author Junhyeong Choi
*/
public interface PersonRepository extends MongoRepository<Person, String>, QuerydslPredicateExecutor<Person> {
@ -510,6 +511,30 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query @@ -510,6 +511,30 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
List<Person> findBySpiritAnimal(User user);
// GH-4606
List<Person> findByFirstnameIsEmpty();
// GH-4606
List<Person> findByFirstnameIsNotEmpty();
// GH-4606
List<Person> findBySkillsIsEmpty();
// GH-4606
List<Person> findBySkillsIsNotEmpty();
// GH-4606
List<Person> findByAddressIsEmpty();
// GH-4606
List<Person> findByAddressIsNotEmpty();
// GH-4606
List<Person> findByMetadataIsEmpty();
// GH-4606
List<Person> findByMetadataIsNotEmpty();
class Persons implements Streamable<Person> {
private final Streamable<Person> streamable;

85
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java

@ -22,6 +22,7 @@ import static org.springframework.data.mongodb.test.util.Assertions.*; @@ -22,6 +22,7 @@ import static org.springframework.data.mongodb.test.util.Assertions.*;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.bson.BsonRegularExpression;
@ -65,6 +66,7 @@ import org.springframework.data.repository.query.parser.PartTree; @@ -65,6 +66,7 @@ import org.springframework.data.repository.query.parser.PartTree;
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Junhyeong Choi
*/
class MongoQueryCreatorUnitTests {
@ -670,6 +672,87 @@ class MongoQueryCreatorUnitTests { @@ -670,6 +672,87 @@ class MongoQueryCreatorUnitTests {
assertThat(creator.createQuery()).isEqualTo(query(where("location").nearSphere(point).maxDistance(1000.0D)));
}
@Test // GH-4606
void createsIsEmptyQueryForStringPropertyCorrectly() {
PartTree tree = new PartTree("findByFirstNameIsEmpty", Person.class);
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context);
Query query = creator.createQuery();
assertThat(query).isEqualTo(query(where("firstName").is("")));
}
@Test // GH-4606
void createsIsNotEmptyQueryForStringPropertyCorrectly() {
PartTree tree = new PartTree("findByFirstNameIsNotEmpty", Person.class);
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context);
Query query = creator.createQuery();
assertThat(query).isEqualTo(query(where("firstName").ne("")));
}
@Test // GH-4606
void createsIsEmptyQueryForCollectionPropertyCorrectly() {
PartTree tree = new PartTree("findByEmailAddressesIsEmpty", User.class);
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context);
Query query = creator.createQuery();
assertThat(query).isEqualTo(query(where("emailAddresses").size(0)));
}
@Test // GH-4606
void createsIsNotEmptyQueryForCollectionPropertyCorrectly() {
PartTree tree = new PartTree("findByEmailAddressesIsNotEmpty", User.class);
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context);
Query query = creator.createQuery();
// size > 0 is equivalent to exists and not empty
assertThat(query).isEqualTo(query(where("emailAddresses").not().size(0)));
}
@Test // GH-4606
void createsIsEmptyQueryForMapPropertyCorrectly() {
PartTree tree = new PartTree("findByMetadataIsEmpty", User.class);
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context);
Query query = creator.createQuery();
assertThat(query).isEqualTo(query(where("metadata").is(new Document())));
}
@Test // GH-4606
void createsIsNotEmptyQueryForMapPropertyCorrectly() {
PartTree tree = new PartTree("findByMetadataIsNotEmpty", User.class);
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context);
Query query = creator.createQuery();
assertThat(query).isEqualTo(query(where("metadata").ne(new Document())));
}
@Test // GH-4606
void createsIsEmptyQueryForDomainTypePropertyCorrectly() {
PartTree tree = new PartTree("findByAddressIsEmpty", User.class);
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context);
Query query = creator.createQuery();
assertThat(query).isEqualTo(query(where("address").is(new Document())));
}
@Test // GH-4606
void createsIsNotEmptyQueryForDomainTypePropertyCorrectly() {
PartTree tree = new PartTree("findByAddressIsNotEmpty", User.class);
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context);
Query query = creator.createQuery();
assertThat(query).isEqualTo(query(where("address").ne(new Document())));
}
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname);
@ -685,6 +768,8 @@ class MongoQueryCreatorUnitTests { @@ -685,6 +768,8 @@ class MongoQueryCreatorUnitTests {
List<String> emailAddresses;
Map<String, String> metadata;
Address address;
Address2dSphere address2dSphere;

Loading…
Cancel
Save