Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adc76123a7 | ||
|
|
e642199abe | ||
|
|
0be2237c4c | ||
|
|
250fe5150d | ||
|
|
50245ca37a | ||
|
|
cc234a84b9 | ||
|
|
b06e8d5f9e | ||
|
|
bb65692c0c | ||
|
|
d3b4972d75 | ||
|
|
4554b62171 | ||
|
|
9920777e55 | ||
|
|
fc26641235 | ||
|
|
c9d72337fd |
4
.mvn/wrapper/maven-wrapper.properties
vendored
4
.mvn/wrapper/maven-wrapper.properties
vendored
@@ -1,2 +1,2 @@
|
|||||||
#Mon Jul 03 09:46:32 CEST 2023
|
#Mon Aug 14 07:45:45 EDT 2023
|
||||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip
|
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Java versions
|
# Java versions
|
||||||
java.main.tag=17.0.7_7-jdk-focal
|
java.main.tag=17.0.8_7-jdk-focal
|
||||||
java.next.tag=20-jdk-jammy
|
java.next.tag=20-jdk-jammy
|
||||||
|
|
||||||
# Docker container images - standard
|
# Docker container images - standard
|
||||||
@@ -7,12 +7,12 @@ docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/ecli
|
|||||||
docker.java.next.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.next.tag}
|
docker.java.next.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.next.tag}
|
||||||
|
|
||||||
# Supported versions of MongoDB
|
# Supported versions of MongoDB
|
||||||
docker.mongodb.4.4.version=4.4.22
|
docker.mongodb.4.4.version=4.4.23
|
||||||
docker.mongodb.5.0.version=5.0.18
|
docker.mongodb.5.0.version=5.0.19
|
||||||
docker.mongodb.6.0.version=6.0.7
|
docker.mongodb.6.0.version=6.0.8
|
||||||
|
|
||||||
# Supported versions of Redis
|
# Supported versions of Redis
|
||||||
docker.redis.6.version=6.2.12
|
docker.redis.6.version=6.2.13
|
||||||
|
|
||||||
# Supported versions of Cassandra
|
# Supported versions of Cassandra
|
||||||
docker.cassandra.3.version=3.11.15
|
docker.cassandra.3.version=3.11.15
|
||||||
|
|||||||
6
pom.xml
6
pom.xml
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<groupId>org.springframework.data</groupId>
|
<groupId>org.springframework.data</groupId>
|
||||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||||
<version>4.0.8</version>
|
<version>4.0.9</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<name>Spring Data MongoDB</name>
|
<name>Spring Data MongoDB</name>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.data.build</groupId>
|
<groupId>org.springframework.data.build</groupId>
|
||||||
<artifactId>spring-data-parent</artifactId>
|
<artifactId>spring-data-parent</artifactId>
|
||||||
<version>3.0.8</version>
|
<version>3.0.9</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<project.type>multi</project.type>
|
<project.type>multi</project.type>
|
||||||
<dist.id>spring-data-mongodb</dist.id>
|
<dist.id>spring-data-mongodb</dist.id>
|
||||||
<springdata.commons>3.0.8</springdata.commons>
|
<springdata.commons>3.0.9</springdata.commons>
|
||||||
<mongo>4.8.2</mongo>
|
<mongo>4.8.2</mongo>
|
||||||
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
|
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
|
||||||
<jmh.version>1.19</jmh.version>
|
<jmh.version>1.19</jmh.version>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.data</groupId>
|
<groupId>org.springframework.data</groupId>
|
||||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||||
<version>4.0.8</version>
|
<version>4.0.9</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.data</groupId>
|
<groupId>org.springframework.data</groupId>
|
||||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||||
<version>4.0.8</version>
|
<version>4.0.9</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.data</groupId>
|
<groupId>org.springframework.data</groupId>
|
||||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||||
<version>4.0.8</version>
|
<version>4.0.9</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -203,8 +203,9 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
|||||||
target.properties(nestedProperties.toArray(new JsonSchemaProperty[0])), required));
|
target.properties(nestedProperties.toArray(new JsonSchemaProperty[0])), required));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return targetProperties.size() == 1 ? targetProperties.iterator().next()
|
JsonSchemaProperty schemaProperty = targetProperties.size() == 1 ? targetProperties.iterator().next()
|
||||||
: JsonSchemaProperty.merged(targetProperties);
|
: JsonSchemaProperty.merged(targetProperties);
|
||||||
|
return applyEncryptionDataIfNecessary(property, schemaProperty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class AggregationOperationRenderer {
|
|||||||
contextToUse = new InheritingExposedFieldsAggregationOperationContext(fields, contextToUse);
|
contextToUse = new InheritingExposedFieldsAggregationOperationContext(fields, contextToUse);
|
||||||
} else {
|
} else {
|
||||||
contextToUse = fields.exposesNoFields() ? DEFAULT_CONTEXT
|
contextToUse = fields.exposesNoFields() ? DEFAULT_CONTEXT
|
||||||
: new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), contextToUse);
|
: new ExposedFieldsAggregationOperationContext(fields, contextToUse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.mongodb.core.aggregation;
|
package org.springframework.data.mongodb.core.aggregation;
|
||||||
|
|
||||||
|
import org.bson.Document;
|
||||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,6 +23,7 @@ import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldRefe
|
|||||||
* {@link AggregationOperationContext}.
|
* {@link AggregationOperationContext}.
|
||||||
*
|
*
|
||||||
* @author Mark Paluch
|
* @author Mark Paluch
|
||||||
|
* @author Christoph Strobl
|
||||||
* @since 1.9
|
* @since 1.9
|
||||||
*/
|
*/
|
||||||
class InheritingExposedFieldsAggregationOperationContext extends ExposedFieldsAggregationOperationContext {
|
class InheritingExposedFieldsAggregationOperationContext extends ExposedFieldsAggregationOperationContext {
|
||||||
@@ -43,6 +45,11 @@ class InheritingExposedFieldsAggregationOperationContext extends ExposedFieldsAg
|
|||||||
this.previousContext = previousContext;
|
this.previousContext = previousContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Document getMappedObject(Document document) {
|
||||||
|
return previousContext.getMappedObject(document);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected FieldReference resolveExposedField(Field field, String name) {
|
protected FieldReference resolveExposedField(Field field, String name) {
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.Timest
|
|||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link JsonSchemaProperty} implementation.
|
* {@link JsonSchemaProperty} implementation.
|
||||||
@@ -1139,7 +1140,9 @@ public class IdentifiableJsonSchemaProperty<T extends JsonSchemaObject> implemen
|
|||||||
enc.append("bsonType", type.toBsonType().value()); // TODO: no samples with type -> is it bson type all the way?
|
enc.append("bsonType", type.toBsonType().value()); // TODO: no samples with type -> is it bson type all the way?
|
||||||
}
|
}
|
||||||
|
|
||||||
enc.append("algorithm", algorithm);
|
if (StringUtils.hasText(algorithm)) {
|
||||||
|
enc.append("algorithm", algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
propertySpecification.append("encrypt", enc);
|
propertySpecification.append("encrypt", enc);
|
||||||
|
|
||||||
|
|||||||
@@ -271,6 +271,17 @@ class MappingMongoJsonSchemaCreatorUnitTests {
|
|||||||
.containsEntry("properties.value", new Document("type", "string"));
|
.containsEntry("properties.value", new Document("type", "string"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-4454
|
||||||
|
void wrapEncryptedEntityTypeLikeProperty() {
|
||||||
|
|
||||||
|
MongoJsonSchema schema = MongoJsonSchemaCreator.create() //
|
||||||
|
.filter(MongoJsonSchemaCreator.encryptedOnly()) // filter non encrypted fields
|
||||||
|
.createSchemaFor(WithEncryptedEntityLikeProperty.class);
|
||||||
|
|
||||||
|
assertThat(schema.schemaDocument()) //
|
||||||
|
.containsEntry("properties.domainTypeValue", Document.parse("{'encrypt': {'bsonType': 'object' } }"));
|
||||||
|
}
|
||||||
|
|
||||||
// --> TYPES AND JSON
|
// --> TYPES AND JSON
|
||||||
|
|
||||||
// --> ENUM
|
// --> ENUM
|
||||||
@@ -676,4 +687,9 @@ class MappingMongoJsonSchemaCreatorUnitTests {
|
|||||||
static class PropertyClashWithA {
|
static class PropertyClashWithA {
|
||||||
Integer aNonEncrypted;
|
Integer aNonEncrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
|
||||||
|
static class WithEncryptedEntityLikeProperty {
|
||||||
|
@Encrypted SomeDomainType domainTypeValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
|
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
|
||||||
import org.springframework.data.mongodb.core.CollectionOptions.ValidationOptions;
|
import org.springframework.data.mongodb.core.CollectionOptions.ValidationOptions;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Encrypted;
|
||||||
import org.springframework.data.mongodb.core.mapping.Field;
|
import org.springframework.data.mongodb.core.mapping.Field;
|
||||||
import org.springframework.data.mongodb.core.query.Criteria;
|
import org.springframework.data.mongodb.core.query.Criteria;
|
||||||
|
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
|
||||||
import org.springframework.data.mongodb.test.util.Client;
|
import org.springframework.data.mongodb.test.util.Client;
|
||||||
import org.springframework.data.mongodb.test.util.MongoClientExtension;
|
import org.springframework.data.mongodb.test.util.MongoClientExtension;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
@@ -48,11 +50,13 @@ import com.mongodb.client.model.ValidationLevel;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for {@link CollectionOptions#validation(ValidationOptions)} using
|
* Integration tests for {@link CollectionOptions#validation(ValidationOptions)} using
|
||||||
* {@link org.springframework.data.mongodb.core.validation.CriteriaValidator} and
|
* {@link org.springframework.data.mongodb.core.validation.CriteriaValidator},
|
||||||
* {@link org.springframework.data.mongodb.core.validation.DocumentValidator}.
|
* {@link org.springframework.data.mongodb.core.validation.DocumentValidator} and
|
||||||
|
* {@link org.springframework.data.mongodb.core.validation.JsonSchemaValidator}.
|
||||||
*
|
*
|
||||||
* @author Andreas Zink
|
* @author Andreas Zink
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
|
* @author Julia Lee
|
||||||
*/
|
*/
|
||||||
@ExtendWith({ MongoClientExtension.class, SpringExtension.class })
|
@ExtendWith({ MongoClientExtension.class, SpringExtension.class })
|
||||||
public class MongoTemplateValidationTests {
|
public class MongoTemplateValidationTests {
|
||||||
@@ -188,6 +192,20 @@ public class MongoTemplateValidationTests {
|
|||||||
assertThat(getValidatorInfo(COLLECTION_NAME)).isEqualTo(new Document("customName", new Document("$type", "bool")));
|
assertThat(getValidatorInfo(COLLECTION_NAME)).isEqualTo(new Document("customName", new Document("$type", "bool")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-4454
|
||||||
|
public void failsJsonSchemaValidationForEncryptedDomainEntityProperty() {
|
||||||
|
|
||||||
|
MongoJsonSchema schema = MongoJsonSchemaCreator.create().createSchemaFor(BeanWithEncryptedDomainEntity.class);
|
||||||
|
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schema(schema));
|
||||||
|
|
||||||
|
BeanWithEncryptedDomainEntity person = new BeanWithEncryptedDomainEntity();
|
||||||
|
person.encryptedDomainEntity = new SimpleBean("some string", 100, null);
|
||||||
|
|
||||||
|
assertThatExceptionOfType(DataIntegrityViolationException.class)
|
||||||
|
.isThrownBy(() -> template.save(person))
|
||||||
|
.withMessageContaining("Document failed validation");
|
||||||
|
}
|
||||||
|
|
||||||
private Document getCollectionOptions(String collectionName) {
|
private Document getCollectionOptions(String collectionName) {
|
||||||
return getCollectionInfo(collectionName).get("options", Document.class);
|
return getCollectionInfo(collectionName).get("options", Document.class);
|
||||||
}
|
}
|
||||||
@@ -222,4 +240,10 @@ public class MongoTemplateValidationTests {
|
|||||||
private @Nullable Integer rangedInteger;
|
private @Nullable Integer rangedInteger;
|
||||||
private @Field("customName") Object customFieldName;
|
private @Field("customName") Object customFieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@org.springframework.data.mongodb.core.mapping.Document(collection = COLLECTION_NAME)
|
||||||
|
@Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
|
||||||
|
static class BeanWithEncryptedDomainEntity {
|
||||||
|
@Encrypted SimpleBean encryptedDomainEntity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.mongodb.core.aggregation;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christoph Strobl
|
||||||
|
*/
|
||||||
|
public class AggregationOperationRendererUnitTests {
|
||||||
|
|
||||||
|
@Test // GH-4443
|
||||||
|
void nonFieldsExposingAggregationOperationContinuesWithSameContextForNextStage() {
|
||||||
|
|
||||||
|
AggregationOperationContext rootContext = mock(AggregationOperationContext.class);
|
||||||
|
AggregationOperation stage1 = mock(AggregationOperation.class);
|
||||||
|
AggregationOperation stage2 = mock(AggregationOperation.class);
|
||||||
|
|
||||||
|
AggregationOperationRenderer.toDocument(List.of(stage1, stage2), rootContext);
|
||||||
|
|
||||||
|
verify(stage1).toPipelineStages(eq(rootContext));
|
||||||
|
verify(stage2).toPipelineStages(eq(rootContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-4443
|
||||||
|
void fieldsExposingAggregationOperationNotExposingFieldsForcesUseOfDefaultContextForNextStage() {
|
||||||
|
|
||||||
|
AggregationOperationContext rootContext = mock(AggregationOperationContext.class);
|
||||||
|
FieldsExposingAggregationOperation stage1 = mock(FieldsExposingAggregationOperation.class);
|
||||||
|
ExposedFields stage1fields = mock(ExposedFields.class);
|
||||||
|
AggregationOperation stage2 = mock(AggregationOperation.class);
|
||||||
|
|
||||||
|
when(stage1.getFields()).thenReturn(stage1fields);
|
||||||
|
when(stage1fields.exposesNoFields()).thenReturn(true);
|
||||||
|
|
||||||
|
AggregationOperationRenderer.toDocument(List.of(stage1, stage2), rootContext);
|
||||||
|
|
||||||
|
verify(stage1).toPipelineStages(eq(rootContext));
|
||||||
|
verify(stage2).toPipelineStages(eq(AggregationOperationRenderer.DEFAULT_CONTEXT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-4443
|
||||||
|
void fieldsExposingAggregationOperationForcesNewContextForNextStage() {
|
||||||
|
|
||||||
|
AggregationOperationContext rootContext = mock(AggregationOperationContext.class);
|
||||||
|
FieldsExposingAggregationOperation stage1 = mock(FieldsExposingAggregationOperation.class);
|
||||||
|
ExposedFields stage1fields = mock(ExposedFields.class);
|
||||||
|
AggregationOperation stage2 = mock(AggregationOperation.class);
|
||||||
|
|
||||||
|
when(stage1.getFields()).thenReturn(stage1fields);
|
||||||
|
when(stage1fields.exposesNoFields()).thenReturn(false);
|
||||||
|
|
||||||
|
ArgumentCaptor<AggregationOperationContext> captor = ArgumentCaptor.forClass(AggregationOperationContext.class);
|
||||||
|
|
||||||
|
AggregationOperationRenderer.toDocument(List.of(stage1, stage2), rootContext);
|
||||||
|
|
||||||
|
verify(stage1).toPipelineStages(eq(rootContext));
|
||||||
|
verify(stage2).toPipelineStages(captor.capture());
|
||||||
|
|
||||||
|
assertThat(captor.getValue()).isInstanceOf(ExposedFieldsAggregationOperationContext.class)
|
||||||
|
.isNotInstanceOf(InheritingExposedFieldsAggregationOperationContext.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-4443
|
||||||
|
void inheritingFieldsExposingAggregationOperationForcesNewContextForNextStageKeepingReferenceToPreviousContext() {
|
||||||
|
|
||||||
|
AggregationOperationContext rootContext = mock(AggregationOperationContext.class);
|
||||||
|
InheritsFieldsAggregationOperation stage1 = mock(InheritsFieldsAggregationOperation.class);
|
||||||
|
InheritsFieldsAggregationOperation stage2 = mock(InheritsFieldsAggregationOperation.class);
|
||||||
|
InheritsFieldsAggregationOperation stage3 = mock(InheritsFieldsAggregationOperation.class);
|
||||||
|
|
||||||
|
ExposedFields exposedFields = mock(ExposedFields.class);
|
||||||
|
when(exposedFields.exposesNoFields()).thenReturn(false);
|
||||||
|
when(stage1.getFields()).thenReturn(exposedFields);
|
||||||
|
when(stage2.getFields()).thenReturn(exposedFields);
|
||||||
|
when(stage3.getFields()).thenReturn(exposedFields);
|
||||||
|
|
||||||
|
ArgumentCaptor<AggregationOperationContext> captor = ArgumentCaptor.forClass(AggregationOperationContext.class);
|
||||||
|
|
||||||
|
AggregationOperationRenderer.toDocument(List.of(stage1, stage2, stage3), rootContext);
|
||||||
|
|
||||||
|
verify(stage1).toPipelineStages(captor.capture());
|
||||||
|
verify(stage2).toPipelineStages(captor.capture());
|
||||||
|
verify(stage3).toPipelineStages(captor.capture());
|
||||||
|
|
||||||
|
assertThat(captor.getAllValues().get(0)).isEqualTo(rootContext);
|
||||||
|
|
||||||
|
assertThat(captor.getAllValues().get(1))
|
||||||
|
.asInstanceOf(InstanceOfAssertFactories.type(InheritingExposedFieldsAggregationOperationContext.class))
|
||||||
|
.extracting("previousContext").isSameAs(captor.getAllValues().get(0));
|
||||||
|
|
||||||
|
assertThat(captor.getAllValues().get(2))
|
||||||
|
.asInstanceOf(InstanceOfAssertFactories.type(InheritingExposedFieldsAggregationOperationContext.class))
|
||||||
|
.extracting("previousContext").isSameAs(captor.getAllValues().get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -90,6 +90,7 @@ import com.mongodb.client.MongoCollection;
|
|||||||
* @author Maninder Singh
|
* @author Maninder Singh
|
||||||
* @author Sergey Shcherbakov
|
* @author Sergey Shcherbakov
|
||||||
* @author Minsu Kim
|
* @author Minsu Kim
|
||||||
|
* @author Julia Lee
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MongoTemplateExtension.class)
|
@ExtendWith(MongoTemplateExtension.class)
|
||||||
public class AggregationTests {
|
public class AggregationTests {
|
||||||
@@ -118,7 +119,7 @@ public class AggregationTests {
|
|||||||
|
|
||||||
mongoTemplate.flush(Product.class, UserWithLikes.class, DATAMONGO753.class, Data.class, DATAMONGO788.class,
|
mongoTemplate.flush(Product.class, UserWithLikes.class, DATAMONGO753.class, Data.class, DATAMONGO788.class,
|
||||||
User.class, Person.class, Reservation.class, Venue.class, MeterData.class, LineItem.class, InventoryItem.class,
|
User.class, Person.class, Reservation.class, Venue.class, MeterData.class, LineItem.class, InventoryItem.class,
|
||||||
Sales.class, Sales2.class, Employee.class, Art.class, Venue.class);
|
Sales.class, Sales2.class, Employee.class, Art.class, Venue.class, Item.class);
|
||||||
|
|
||||||
mongoTemplate.dropCollection(INPUT_COLLECTION);
|
mongoTemplate.dropCollection(INPUT_COLLECTION);
|
||||||
mongoTemplate.dropCollection("personQueryTemp");
|
mongoTemplate.dropCollection("personQueryTemp");
|
||||||
@@ -1952,6 +1953,42 @@ public class AggregationTests {
|
|||||||
assertThat(aggregate.getMappedResults()).contains(widget);
|
assertThat(aggregate.getMappedResults()).contains(widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-4443
|
||||||
|
void shouldHonorFieldAliasesForFieldReferencesUsingFieldExposingOperation() {
|
||||||
|
|
||||||
|
Item item1 = Item.builder().itemId("1").tags(Arrays.asList("a", "b")).build();
|
||||||
|
Item item2 = Item.builder().itemId("1").tags(Arrays.asList("a", "c")).build();
|
||||||
|
mongoTemplate.insert(Arrays.asList(item1, item2), Item.class);
|
||||||
|
|
||||||
|
TypedAggregation<Item> aggregation = newAggregation(Item.class,
|
||||||
|
match(where("itemId").is("1")),
|
||||||
|
unwind("tags"),
|
||||||
|
match(where("itemId").is("1").and("tags").is("c")));
|
||||||
|
AggregationResults<Document> results = mongoTemplate.aggregate(aggregation, Document.class);
|
||||||
|
List<Document> mappedResults = results.getMappedResults();
|
||||||
|
assertThat(mappedResults).hasSize(1);
|
||||||
|
assertThat(mappedResults.get(0)).containsEntry("item_id", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-4443
|
||||||
|
void projectShouldResetContextToAvoidMappingFieldsAgainstANoLongerExistingTarget() {
|
||||||
|
|
||||||
|
Item item1 = Item.builder().itemId("1").tags(Arrays.asList("a", "b")).build();
|
||||||
|
Item item2 = Item.builder().itemId("1").tags(Arrays.asList("a", "c")).build();
|
||||||
|
mongoTemplate.insert(Arrays.asList(item1, item2), Item.class);
|
||||||
|
|
||||||
|
TypedAggregation<Item> aggregation = newAggregation(Item.class,
|
||||||
|
match(where("itemId").is("1")),
|
||||||
|
unwind("tags"),
|
||||||
|
project().and("itemId").as("itemId").and("tags").as("tags"),
|
||||||
|
match(where("itemId").is("1").and("tags").is("c")));
|
||||||
|
|
||||||
|
AggregationResults<Document> results = mongoTemplate.aggregate(aggregation, Document.class);
|
||||||
|
List<Document> mappedResults = results.getMappedResults();
|
||||||
|
assertThat(mappedResults).hasSize(1);
|
||||||
|
assertThat(mappedResults.get(0)).containsEntry("itemId", "1");
|
||||||
|
}
|
||||||
|
|
||||||
private void createUsersWithReferencedPersons() {
|
private void createUsersWithReferencedPersons() {
|
||||||
|
|
||||||
mongoTemplate.dropCollection(User.class);
|
mongoTemplate.dropCollection(User.class);
|
||||||
@@ -2204,7 +2241,7 @@ public class AggregationTests {
|
|||||||
List<Item> items;
|
List<Item> items;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DATAMONGO-1491
|
// DATAMONGO-1491, GH-4443
|
||||||
@lombok.Data
|
@lombok.Data
|
||||||
@Builder
|
@Builder
|
||||||
static class Item {
|
static class Item {
|
||||||
@@ -2213,6 +2250,7 @@ public class AggregationTests {
|
|||||||
String itemId;
|
String itemId;
|
||||||
Integer quantity;
|
Integer quantity;
|
||||||
Long price;
|
Long price;
|
||||||
|
List<String> tags = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// DATAMONGO-1538
|
// DATAMONGO-1538
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import com.mongodb.client.model.Projections;
|
|||||||
* @author Thomas Darimont
|
* @author Thomas Darimont
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
* @author Mark Paluch
|
* @author Mark Paluch
|
||||||
|
* @author Julia Lee
|
||||||
*/
|
*/
|
||||||
public class AggregationUnitTests {
|
public class AggregationUnitTests {
|
||||||
|
|
||||||
@@ -612,7 +613,7 @@ public class AggregationUnitTests {
|
|||||||
WithRetypedIdField.class, mappingContext,
|
WithRetypedIdField.class, mappingContext,
|
||||||
new QueryMapper(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext)));
|
new QueryMapper(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext)));
|
||||||
Document document = project(WithRetypedIdField.class).toDocument(context);
|
Document document = project(WithRetypedIdField.class).toDocument(context);
|
||||||
assertThat(document).isEqualTo(new Document("$project", new Document("_id", 1).append("renamed-field", 1)));
|
assertThat(document).isEqualTo(new Document("$project", new Document("_id", 1).append("renamed-field", 1).append("entries", 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // GH-4038
|
@Test // GH-4038
|
||||||
@@ -653,6 +654,22 @@ public class AggregationUnitTests {
|
|||||||
assertThat(documents.get(2)).isEqualTo("{ $sort : { 'serial_number' : -1, 'label_name' : -1 } }");
|
assertThat(documents.get(2)).isEqualTo("{ $sort : { 'serial_number' : -1, 'label_name' : -1 } }");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-4443
|
||||||
|
void fieldsExposingContextShouldUseCustomFieldNameFromRelaxedRootContext() {
|
||||||
|
|
||||||
|
MongoMappingContext mappingContext = new MongoMappingContext();
|
||||||
|
RelaxedTypeBasedAggregationOperationContext context = new RelaxedTypeBasedAggregationOperationContext(
|
||||||
|
WithRetypedIdField.class, mappingContext,
|
||||||
|
new QueryMapper(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext)));
|
||||||
|
|
||||||
|
TypedAggregation<WithRetypedIdField> agg = newAggregation(WithRetypedIdField.class,
|
||||||
|
unwind("entries"), match(where("foo").is("value 2")));
|
||||||
|
List<Document> pipeline = agg.toPipeline(context);
|
||||||
|
|
||||||
|
Document fields = getAsDocument(pipeline.get(1), "$match");
|
||||||
|
assertThat(fields.get("renamed-field")).isEqualTo("value 2");
|
||||||
|
}
|
||||||
|
|
||||||
private Document extractPipelineElement(Document agg, int index, String operation) {
|
private Document extractPipelineElement(Document agg, int index, String operation) {
|
||||||
|
|
||||||
List<Document> pipeline = (List<Document>) agg.get("pipeline");
|
List<Document> pipeline = (List<Document>) agg.get("pipeline");
|
||||||
@@ -672,5 +689,7 @@ public class AggregationUnitTests {
|
|||||||
|
|
||||||
@org.springframework.data.mongodb.core.mapping.Field("renamed-field") private String foo;
|
@org.springframework.data.mongodb.core.mapping.Field("renamed-field") private String foo;
|
||||||
|
|
||||||
|
private List<String> entries = new ArrayList<>();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class Person {
|
|||||||
----
|
----
|
||||||
Account account = …
|
Account account = …
|
||||||
|
|
||||||
tempate.insert(account); <2>
|
template.insert(account); <2>
|
||||||
|
|
||||||
template.update(Person.class)
|
template.update(Person.class)
|
||||||
.matching(where("id").is(…))
|
.matching(where("id").is(…))
|
||||||
@@ -441,7 +441,7 @@ class Entity {
|
|||||||
"lastname" : "Long", <2>
|
"lastname" : "Long", <2>
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> Read/wirte the keys `fn` & `ln` from/to the linkage document based on the lookup query.
|
<1> Read/write the keys `fn` & `ln` from/to the linkage document based on the lookup query.
|
||||||
<2> Use non _id_ fields for the lookup of the target documents.
|
<2> Use non _id_ fields for the lookup of the target documents.
|
||||||
====
|
====
|
||||||
|
|
||||||
@@ -477,7 +477,7 @@ class ToDocumentPointerConverter implements Converter<ReferencedObject, Document
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> Read/wirte the keys `_id` from/to the reference document to use them in the lookup query.
|
<1> Read/write the keys `_id` from/to the reference document to use them in the lookup query.
|
||||||
<2> The collection name can be read from the reference document using its key.
|
<2> The collection name can be read from the reference document using its key.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
|||||||
@@ -350,6 +350,14 @@ You can add additional converters to the converter by overriding the `customConv
|
|||||||
MongoDB's native JSR-310 support can be enabled through `MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs()`.
|
MongoDB's native JSR-310 support can be enabled through `MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs()`.
|
||||||
Also shown in the preceding example is a `LoggingEventListener`, which logs `MongoMappingEvent` instances that are posted onto Spring's `ApplicationContextEvent` infrastructure.
|
Also shown in the preceding example is a `LoggingEventListener`, which logs `MongoMappingEvent` instances that are posted onto Spring's `ApplicationContextEvent` infrastructure.
|
||||||
|
|
||||||
|
[TIP]
|
||||||
|
====
|
||||||
|
.Java Time Types
|
||||||
|
|
||||||
|
We recommend using MongoDB's native JSR-310 support via `MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs()` as described above as it is using an `UTC` based approach.
|
||||||
|
The default JSR-310 support for `java.time` types inherited from Spring Data Commons uses the local machine timezone as reference and should only be used for backwards compatibility.
|
||||||
|
====
|
||||||
|
|
||||||
NOTE: `AbstractMongoClientConfiguration` creates a `MongoTemplate` instance and registers it with the container under the name `mongoTemplate`.
|
NOTE: `AbstractMongoClientConfiguration` creates a `MongoTemplate` instance and registers it with the container under the name `mongoTemplate`.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ embedded schema objects that describe properties and subdocuments.
|
|||||||
<2> `required` is a property that describes which properties are required in a document. It can be specified optionally, along with other
|
<2> `required` is a property that describes which properties are required in a document. It can be specified optionally, along with other
|
||||||
schema constraints. See MongoDB's documentation on https://docs.mongodb.com/manual/reference/operator/query/jsonSchema/#available-keywords[available keywords].
|
schema constraints. See MongoDB's documentation on https://docs.mongodb.com/manual/reference/operator/query/jsonSchema/#available-keywords[available keywords].
|
||||||
<3> `properties` is related to a schema object that describes an `object` type. It contains property-specific schema constraints.
|
<3> `properties` is related to a schema object that describes an `object` type. It contains property-specific schema constraints.
|
||||||
<4> `firstname` specifies constraints for the `firsname` field inside the document. Here, it is a string-based `properties` element declaring
|
<4> `firstname` specifies constraints for the `firstname` field inside the document. Here, it is a string-based `properties` element declaring
|
||||||
possible field values.
|
possible field values.
|
||||||
<5> `address` is a subdocument defining a schema for values in its `postCode` field.
|
<5> `address` is a subdocument defining a schema for values in its `postCode` field.
|
||||||
====
|
====
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ Therefore, the `Sort` properties are mapped against the methods return type `Per
|
|||||||
<4> `$skip`, `$limit` and `$sort` can be passed on via a `Pageable` argument. Same as in <2>, the operators are appended to the pipeline definition. Methods accepting `Pageable` can return `Slice` for easier pagination.
|
<4> `$skip`, `$limit` and `$sort` can be passed on via a `Pageable` argument. Same as in <2>, the operators are appended to the pipeline definition. Methods accepting `Pageable` can return `Slice` for easier pagination.
|
||||||
<5> Aggregation methods can return `Stream` to consume results directly from an underlying cursor. Make sure to close the stream after consuming it to release the server-side cursor by either calling `close()` or through `try-with-resources`.
|
<5> Aggregation methods can return `Stream` to consume results directly from an underlying cursor. Make sure to close the stream after consuming it to release the server-side cursor by either calling `close()` or through `try-with-resources`.
|
||||||
<6> Map the result of an aggregation returning a single `Document` to an instance of a desired `SumValue` target type.
|
<6> Map the result of an aggregation returning a single `Document` to an instance of a desired `SumValue` target type.
|
||||||
<7> Aggregations resulting in single document holding just an accumulation result like eg. `$sum` can be extracted directly from the result `Document`.
|
<7> Aggregations resulting in single document holding just an accumulation result like e.g. `$sum` can be extracted directly from the result `Document`.
|
||||||
To gain more control, you might consider `AggregationResult` as method return type as shown in <7>.
|
To gain more control, you might consider `AggregationResult` as method return type as shown in <7>.
|
||||||
<8> Obtain the raw `AggregationResults` mapped to the generic target wrapper type `SumValue` or `org.bson.Document`.
|
<8> Obtain the raw `AggregationResults` mapped to the generic target wrapper type `SumValue` or `org.bson.Document`.
|
||||||
<9> Like in <6>, a single value can be directly obtained from multiple result ``Document``s.
|
<9> Like in <6>, a single value can be directly obtained from multiple result ``Document``s.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Spring Data MongoDB 4.0.8 (2022.0.8)
|
Spring Data MongoDB 4.0.9 (2022.0.9)
|
||||||
Copyright (c) [2010-2019] Pivotal Software, Inc.
|
Copyright (c) [2010-2019] Pivotal Software, Inc.
|
||||||
|
|
||||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||||
@@ -48,5 +48,6 @@ conditions of the subcomponent's license, as noted in the LICENSE file.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user