Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81271a4f2f | ||
|
|
c36f9988c7 | ||
|
|
b49beb08b6 | ||
|
|
18b0946879 | ||
|
|
cf3681f7c2 | ||
|
|
49ef3fbc74 | ||
|
|
01141502a0 | ||
|
|
5d8f3d5c8b | ||
|
|
5cf801ff8e | ||
|
|
2f3fb4aea9 | ||
|
|
82e05e7e8e | ||
|
|
5be2e3eea2 | ||
|
|
54f55e04de |
6
pom.xml
6
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.1.9</version>
|
||||
<version>3.1.10</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Spring Data MongoDB</name>
|
||||
@@ -15,7 +15,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>2.4.9</version>
|
||||
<version>2.4.10</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
@@ -26,7 +26,7 @@
|
||||
<properties>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-mongodb</dist.id>
|
||||
<springdata.commons>2.4.9</springdata.commons>
|
||||
<springdata.commons>2.4.10</springdata.commons>
|
||||
<mongo>4.1.2</mongo>
|
||||
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
|
||||
<jmh.version>1.19</jmh.version>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.1.9</version>
|
||||
<version>3.1.10</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.1.9</version>
|
||||
<version>3.1.10</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.1.9</version>
|
||||
<version>3.1.10</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -19,11 +19,14 @@ import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.domain.Example;
|
||||
@@ -66,6 +69,8 @@ import com.mongodb.DBRef;
|
||||
*/
|
||||
public class QueryMapper {
|
||||
|
||||
protected static final Logger LOGGER = LoggerFactory.getLogger(QueryMapper.class);
|
||||
|
||||
private static final List<String> DEFAULT_ID_NAMES = Arrays.asList("id", "_id");
|
||||
private static final Document META_TEXT_SCORE = new Document("$meta", "textScore");
|
||||
static final ClassTypeInformation<?> NESTED_DOCUMENT = ClassTypeInformation.from(NestedDocument.class);
|
||||
@@ -378,6 +383,10 @@ public class QueryMapper {
|
||||
}
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isNestedKeyword(value)) {
|
||||
return getMappedKeyword(new Keyword((Bson) value), documentField.getPropertyEntity());
|
||||
}
|
||||
@@ -635,7 +644,7 @@ public class QueryMapper {
|
||||
* @param candidate
|
||||
* @return
|
||||
*/
|
||||
protected boolean isNestedKeyword(Object candidate) {
|
||||
protected boolean isNestedKeyword(@Nullable Object candidate) {
|
||||
|
||||
if (!(candidate instanceof Document)) {
|
||||
return false;
|
||||
@@ -680,12 +689,13 @@ public class QueryMapper {
|
||||
* 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) {
|
||||
@Nullable
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -716,7 +726,6 @@ public class QueryMapper {
|
||||
*/
|
||||
static class Keyword {
|
||||
|
||||
private static final String N_OR_PATTERN = "\\$.*or";
|
||||
private static final Set<String> NON_DBREF_CONVERTING_KEYWORDS = new HashSet<>(
|
||||
Arrays.asList("$", "$size", "$slice", "$gt", "$lt"));
|
||||
|
||||
@@ -747,7 +756,7 @@ public class QueryMapper {
|
||||
}
|
||||
|
||||
public boolean isOrOrNor() {
|
||||
return key.matches(N_OR_PATTERN);
|
||||
return key.equalsIgnoreCase("$or") || key.equalsIgnoreCase("$nor");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1095,29 +1104,47 @@ public class QueryMapper {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
PersistentPropertyPath<MongoPersistentProperty> propertyPath = tryToResolvePersistentPropertyPath(path);
|
||||
|
||||
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path);
|
||||
if (propertyPath == null) {
|
||||
|
||||
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
|
||||
boolean associationDetected = false;
|
||||
if (QueryMapper.LOGGER.isInfoEnabled()) {
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
String types = StringUtils.collectionToDelimitedString(
|
||||
path.stream().map(it -> it.getType().getSimpleName()).collect(Collectors.toList()), " -> ");
|
||||
QueryMapper.LOGGER.info(
|
||||
"Could not map '{}'. Maybe a fragment in '{}' is considered a simple type. Mapper continues with {}.",
|
||||
path, types, pathExpression);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
MongoPersistentProperty property = iterator.next();
|
||||
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
|
||||
boolean associationDetected = false;
|
||||
|
||||
if (property.isAssociation()) {
|
||||
associationDetected = true;
|
||||
continue;
|
||||
}
|
||||
while (iterator.hasNext()) {
|
||||
|
||||
if (associationDetected && !property.isIdProperty()) {
|
||||
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
|
||||
}
|
||||
MongoPersistentProperty property = iterator.next();
|
||||
|
||||
if (property.isAssociation()) {
|
||||
associationDetected = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
return propertyPath;
|
||||
} catch (InvalidPersistentPropertyPath e) {
|
||||
if (associationDetected && !property.isIdProperty()) {
|
||||
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
|
||||
}
|
||||
}
|
||||
|
||||
return propertyPath;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PersistentPropertyPath<MongoPersistentProperty> tryToResolvePersistentPropertyPath(PropertyPath path) {
|
||||
|
||||
try {
|
||||
return mappingContext.getPersistentPropertyPath(path);
|
||||
} catch (MappingException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package org.springframework.data.mongodb.core.geo;
|
||||
|
||||
/**
|
||||
* Interface definition for structures defined in <a href="https://geojson.org/>GeoJSON</a> format.
|
||||
* Interface definition for structures defined in <a href="https://geojson.org/">GeoJSON</a> format.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
|
||||
@@ -36,7 +36,12 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.geo.Point;
|
||||
@@ -72,6 +77,7 @@ import com.mongodb.client.model.Filters;
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class QueryMapperUnitTests {
|
||||
|
||||
private QueryMapper mapper;
|
||||
@@ -1082,6 +1088,47 @@ public class QueryMapperUnitTests {
|
||||
.isEqualTo(new org.bson.Document("double_underscore.renamed", new org.bson.Document("$exists", true)));
|
||||
}
|
||||
|
||||
@Test // GH-3633
|
||||
void mapsNullValueForFieldWithCustomTargetType() {
|
||||
|
||||
Query query = query(where("stringAsOid").is(null));
|
||||
|
||||
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
|
||||
context.getPersistentEntity(NonIdFieldWithObjectIdTargetType.class));
|
||||
|
||||
assertThat(document).isEqualTo(new org.bson.Document("stringAsOid", null));
|
||||
}
|
||||
|
||||
@Test // GH-3635
|
||||
void $floorKeywordDoesNotMatch$or$norPattern() {
|
||||
|
||||
Query query = new BasicQuery(" { $expr: { $gt: [ \"$spent\" , { $floor : \"$budget\" } ] } }");
|
||||
assertThatNoException()
|
||||
.isThrownBy(() -> mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(Foo.class)));
|
||||
}
|
||||
|
||||
@Test // GH-3659
|
||||
void allowsUsingFieldPathsForPropertiesHavingCustomConversionRegistered() {
|
||||
|
||||
Query query = query(where("address.street").is("1007 Mountain Drive"));
|
||||
|
||||
MongoCustomConversions mongoCustomConversions = new MongoCustomConversions(
|
||||
Collections.singletonList(new MyAddressToDocumentConverter()));
|
||||
|
||||
this.context = new MongoMappingContext();
|
||||
this.context.setSimpleTypeHolder(mongoCustomConversions.getSimpleTypeHolder());
|
||||
this.context.afterPropertiesSet();
|
||||
|
||||
this.converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context);
|
||||
this.converter.setCustomConversions(mongoCustomConversions);
|
||||
this.converter.afterPropertiesSet();
|
||||
|
||||
this.mapper = new QueryMapper(converter);
|
||||
|
||||
assertThat(mapper.getMappedSort(query.getQueryObject(), context.getPersistentEntity(Customer.class)))
|
||||
.isEqualTo(new org.bson.Document("address.street", "1007 Mountain Drive"));
|
||||
}
|
||||
|
||||
class WithDeepArrayNesting {
|
||||
|
||||
List<WithNestedArray> level0;
|
||||
@@ -1221,18 +1268,18 @@ public class QueryMapperUnitTests {
|
||||
@Field("geoJsonPointWithNameViaFieldAnnotation") GeoJsonPoint namedGeoJsonPoint;
|
||||
}
|
||||
|
||||
static class SimpeEntityWithoutId {
|
||||
static class SimpleEntityWithoutId {
|
||||
|
||||
String stringProperty;
|
||||
Integer integerProperty;
|
||||
}
|
||||
|
||||
static class EntityWithComplexValueTypeMap {
|
||||
Map<Integer, SimpeEntityWithoutId> map;
|
||||
Map<Integer, SimpleEntityWithoutId> map;
|
||||
}
|
||||
|
||||
static class EntityWithComplexValueTypeList {
|
||||
List<SimpeEntityWithoutId> list;
|
||||
List<SimpleEntityWithoutId> list;
|
||||
}
|
||||
|
||||
static class WithExplicitTargetTypes {
|
||||
@@ -1278,4 +1325,27 @@ public class QueryMapperUnitTests {
|
||||
|
||||
@Field("renamed") String renamed_fieldname_with_underscores;
|
||||
}
|
||||
|
||||
@Document
|
||||
static class Customer {
|
||||
|
||||
@Id private ObjectId id;
|
||||
private String name;
|
||||
private MyAddress address;
|
||||
}
|
||||
|
||||
static class MyAddress {
|
||||
private String street;
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public static class MyAddressToDocumentConverter implements Converter<MyAddress, org.bson.Document> {
|
||||
|
||||
@Override
|
||||
public org.bson.Document convert(MyAddress address) {
|
||||
org.bson.Document doc = new org.bson.Document();
|
||||
doc.put("street", address.street);
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,9 @@ import org.springframework.data.mongodb.core.MongoOperations;
|
||||
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.querydsl.QSort;
|
||||
@@ -1378,4 +1380,14 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
|
||||
void caseSensitiveInClauseIgnoresExpressions() {
|
||||
assertThat(repository.findByFirstnameIn(".*")).isEmpty();
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -403,4 +403,7 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
|
||||
Person findPersonByManyArguments(String firstname, String lastname, String email, Integer age, Sex sex,
|
||||
Date createdAt, List<String> skills, String street, String zipCode, //
|
||||
String city, UUID uniqueId, String username, String password);
|
||||
|
||||
@Query("{ 'age' : null }")
|
||||
Person findByQueryWithNullEqualityCheck();
|
||||
}
|
||||
|
||||
@@ -119,6 +119,9 @@ public class MongoTestTemplateConfiguration {
|
||||
mappingContext = new MongoMappingContext();
|
||||
mappingContext.setInitialEntitySet(mappingContextConfigurer.initialEntitySet());
|
||||
mappingContext.setAutoIndexCreation(mappingContextConfigurer.autocreateIndex);
|
||||
if(mongoConverterConfigurer.customConversions != null) {
|
||||
mappingContext.setSimpleTypeHolder(mongoConverterConfigurer.customConversions.getSimpleTypeHolder());
|
||||
}
|
||||
mappingContext.afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
@@ -383,6 +383,13 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
.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);
|
||||
|
||||
@@ -214,7 +214,7 @@ public class AppConfig {
|
||||
----
|
||||
====
|
||||
|
||||
To access the `com.mongodb.client.MongoClient` object created by the `MongoClientFactoryBean` in other `@Configuration` classes or your own classes, use a `private @Autowired Mongo mongo;` field.
|
||||
To access the `com.mongodb.client.MongoClient` object created by the `MongoClientFactoryBean` in other `@Configuration` classes or your own classes, use a `private @Autowired MongoClient mongoClient;` field.
|
||||
|
||||
[[mongo.mongo-xml-config]]
|
||||
=== Registering a Mongo Instance by Using XML-based Metadata
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
Spring Data MongoDB Changelog
|
||||
=============================
|
||||
|
||||
Changes in version 3.1.10 (2021-06-22)
|
||||
--------------------------------------
|
||||
* #3677 - Add missing double quote to GeoJson.java JSDoc header.
|
||||
* #3666 - Documentation references outdated `Mongo` client.
|
||||
* #3659 - [3.2.1] Indexing Class with Custom Converter -> Couldn't find PersistentEntity for property private [...].
|
||||
* #3635 - $floor isOrOrNor() return true.
|
||||
* #3633 - NPE in QueryMapper when use Query with `null` as value.
|
||||
|
||||
|
||||
Changes in version 3.2.1 (2021-05-14)
|
||||
-------------------------------------
|
||||
* #3638 - Introduce template method for easier customization of fragments.
|
||||
* #3632 - Fix bullet points in aggregations framework asciidoc.
|
||||
|
||||
|
||||
Changes in version 3.1.9 (2021-05-14)
|
||||
-------------------------------------
|
||||
|
||||
@@ -3434,6 +3449,8 @@ Repository
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Spring Data MongoDB 3.1.9 (2020.0.9)
|
||||
Spring Data MongoDB 3.1.10 (2020.0.10)
|
||||
Copyright (c) [2010-2019] Pivotal Software, Inc.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
@@ -27,5 +27,6 @@ conditions of the subcomponent's license, as noted in the LICENSE file.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user