Skip to content

Fix NPE in QueryMapper when trying to apply target type on null value. #3643

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3633-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3633-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3633-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3633-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -751,12 +751,12 @@ protected boolean isKeyword(String candidate) {
* converted one by one.
*
* @param documentField the field and its meta data
* @param value the actual value
* @param value the actual value. Can be {@literal null}.
* @return the potentially converted target value.
*/
private Object applyFieldTargetTypeHintToValue(Field documentField, Object value) {
private Object applyFieldTargetTypeHintToValue(Field documentField, @Nullable Object value) {

if (documentField.getProperty() == null || !documentField.getProperty().hasExplicitWriteTarget()) {
if (value == null || documentField.getProperty() == null || !documentField.getProperty().hasExplicitWriteTarget()) {
return value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.stream.Collectors;

import org.bson.BsonRegularExpression;
import org.bson.BsonType;
import org.bson.Document;
import org.bson.types.Binary;
import org.springframework.data.domain.Example;
Expand Down Expand Up @@ -188,6 +189,42 @@ public Criteria is(@Nullable Object value) {
return this;
}

/**
* Creates a criterion using {@literal null} equality comparison which matches documents that either contain the item
* field whose value is {@literal null} or that do not contain the item field.
* <p />
* Use {@link #isNullValue()} to only query for documents that contain the field whose value is equal to
* {@link org.bson.BsonType#NULL}. <br />
* Use {@link #exists(boolean)} to query for documents that do (not) contain the field.
*
* @return this.
* @see <a href="https://docs.mongodb.com/manual/tutorial/query-for-null-fields/#equality-filter">Query for Null or
* Missing Fields: Equality Filter</a>
* @since 3.3
*/
public Criteria isNull() {
return is(null);
}

/**
* Creates a criterion using a {@link org.bson.BsonType} comparison which matches only documents that contain the item
* field whose value is equal to {@link org.bson.BsonType#NULL}.
* <p />
* Use {@link #isNull()} to query for documents that contain the field with a {@literal null} value or do not contain the
* field at all. <br />
* Use {@link #exists(boolean)} to query for documents that do (not) contain the field.
*
* @return this.
* @see <a href="https://docs.mongodb.com/manual/tutorial/query-for-null-fields/#type-check">Query for Null or Missing
* Fields: Type Check</a>
* @since 3.3
*/
public Criteria isNullValue() {

criteria.put("$type", BsonType.NULL.getValue());
return this;
}

private boolean lastOperatorWasNot() {
return !this.criteria.isEmpty() && "$not".equals(this.criteria.keySet().toArray()[this.criteria.size() - 1]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,28 @@ void resolvesFieldNameWithUnderscoreOnNestedMappedFieldnameWithUnderscoresCorrec
assertThat(document).isEqualTo(new org.bson.Document("double_underscore.renamed", new org.bson.Document("$exists", true)));
}

@Test // GH-3633
void mapsNullValueForFieldWithCustomTargetType() {

Query query = query(where("stringAsOid").isNull());

org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
context.getPersistentEntity(NonIdFieldWithObjectIdTargetType.class));

assertThat(document).isEqualTo(new org.bson.Document("stringAsOid", null));
}

@Test // GH-3633
void mapsNullBsonTypeForFieldWithCustomTargetType() {

Query query = query(where("stringAsOid").isNullValue());

org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
context.getPersistentEntity(NonIdFieldWithObjectIdTargetType.class));

assertThat(document).isEqualTo(new org.bson.Document("stringAsOid", new org.bson.Document("$type", 10)));
}

class WithDeepArrayNesting {

List<WithNestedArray> level0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.repository.Person.Sex;
import org.springframework.data.mongodb.repository.SampleEvaluationContextExtension.SampleSecurityContextHolder;
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
Expand Down Expand Up @@ -1422,4 +1424,13 @@ void annotatedQueryShouldAllowAggregationInProjection() {
Person target = repository.findWithAggregationInProjection(alicia.getId());
assertThat(target.getFirstname()).isEqualTo(alicia.getFirstname().toUpperCase());
}

@Test // GH-3633
void annotatedQueryWithNullEqualityCheckShouldWork() {

operations.updateFirst(Query.query(Criteria.where("id").is(dave.getId())), Update.update("age", null), Person.class);

Person byQueryWithNullEqualityCheck = repository.findByQueryWithNullEqualityCheck();
assertThat(byQueryWithNullEqualityCheck.getId()).isEqualTo(dave.getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -410,4 +410,7 @@ Person findPersonByManyArguments(String firstname, String lastname, String email
List<Person> findByUnwrappedUserUsername(String username);

List<Person> findByUnwrappedUser(User user);

@Query("{ 'age' : null }")
Person findByQueryWithNullEqualityCheck();
}
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,13 @@ void shouldParseNestedArrays() {
.parse("{ 'stores.location' : { $geoWithin: { $centerSphere: [ [ 1.948516, 48.799029 ] , 0.004 ] } } }"));
}

@Test // GH-3633
void parsesNullValue() {

Document target = parse("{ 'parent' : null }");
assertThat(target).isEqualTo(new Document("parent", null));
}

private static Document parse(String json, Object... args) {

ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, args);
Expand Down