Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -65,6 +66,7 @@
* @author Thomas Darimont
* @author Christoph Strobl
* @author Edward Prentice
* @author Junhyeong Choi
*/
public class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {

Expand Down Expand Up @@ -218,6 +220,10 @@ private Criteria from(Part part, MongoPersistentProperty property, Criteria crit
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:
Expand Down Expand Up @@ -254,6 +260,60 @@ protected Criteria exists(Criteria criteria, Object param) {
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) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
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;
Expand Down Expand Up @@ -93,6 +95,7 @@
* @author Mark Paluch
* @author Fırat KÜÇÜK
* @author Edward Prentice
* @author Junhyeong Choi
*/
@ExtendWith({ SpringExtension.class, DirtiesStateExtension.class })
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
Expand Down Expand Up @@ -1830,4 +1833,117 @@ void allowsToUseComplexTypesInUpdate() {
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

Expand All @@ -39,6 +40,7 @@
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
* @author Junhyeong Choi
*/
@Document
public class Person extends Contact {
Expand Down Expand Up @@ -81,6 +83,8 @@ public enum Sex {

int visits;

Map<String, String> metadata;

public Person() {

this(null, null);
Expand Down Expand Up @@ -334,6 +338,14 @@ public void setLazySpiritAnimal(User lazySpiritAnimal) {
this.lazySpiritAnimal = lazySpiritAnimal;
}

public Map<String, String> getMetadata() {
return metadata;
}

public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}

@Override
public int hashCode() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
* @author Christoph Strobl
* @author Fırat KÜÇÜK
* @author Mark Paluch
* @author Junhyeong Choi
*/
public interface PersonRepository extends MongoRepository<Person, String>, QuerydslPredicateExecutor<Person> {

Expand Down Expand Up @@ -510,6 +511,30 @@ Person findPersonByManyArguments(String firstname, String lastname, String email

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;
Expand Down
Loading