Compare commits

...

17 Commits
4.0.x ... 3.3.1

Author SHA1 Message Date
Christoph Strobl
4b92ecc337 Release version 3.3.1 (2021.1.1).
See #3877
2022-01-14 10:28:56 +01:00
Christoph Strobl
78899c757f Prepare 3.3.1 (2021.1.1).
See #3877
2022-01-14 10:28:25 +01:00
Christoph Strobl
a3861b607a Avoid schema keyId uuid representation errors.
To avoid driver configuration specific UUID representation format errors (binary subtype 3 vs. subtype 4) we now directly convert the given key into its subtype 4 format.

Resolves: #3929
Original pull request: #3931.
2022-01-13 15:26:58 +01:00
Mark Paluch
ee4160997b Polishing.
Simplify assertions, reformat code.

See #3921
Original pull request: #3930.
2022-01-13 11:05:41 +01:00
Christoph Strobl
aeeac56d19 Use index instead of iterator to map position and map keys for updates.
This commit removes usage of the iterator and replaces map key and positional parameter mappings with an index based token lookup.

Closes #3921
Original pull request: #3930.
2022-01-13 11:05:41 +01:00
Mark Paluch
e9c15eb169 Polishing.
Reformat code. Tweak documentation wording and callout syntax.

See #3914, see #3901
Original pull request: #3915.
2022-01-12 15:59:05 +01:00
Christoph Strobl
7f223d1332 Avoid creating invalid index definitions for Map-like properties.
This commit makes sure to exclude Map like structures from index inspection unless annotated with WilcardIndexed.

Closes #3914, closes #3901
Original pull request: #3915.
2022-01-12 15:57:40 +01:00
Mark Paluch
cffee123dc Polishing.
Add author tags, extend copyright license years, simplify tests.

See #3892
2022-01-12 15:35:26 +01:00
rolag-it
352376166a Fix pagination with reactive fluent Querydsl query definition.
Pageable object was not passed to Query, so fetchPage retrieved erroneously the whole dataset as Page content.

Closes #3892
2022-01-12 15:35:26 +01:00
Hett
64b0096c7b Avoid double call of fetch method in DefaultReferenceResolver.
This commit fixes an issue where the fetch method is called twice when looking up singe value references.

Resolves: #3918
Original Pull Request: #3919
2022-01-11 09:45:12 +01:00
Mark Paluch
fb905761a0 Upgrade to MongoDB driver 4.4.1.
Closes #3926
2022-01-11 09:45:12 +01:00
Mark Paluch
1e40448b70 Polishing.
Tweak Javadoc.

See #3898
Original pull request: #3904.
2021-12-14 09:36:56 +01:00
Christoph Strobl
530912d07f Fix field inclusion in aggregation project operation.
Closes #3898
Original pull request: #3904.
2021-12-14 09:36:55 +01:00
Mark Paluch
82331451ea Propagate Bean ClassLoader to MongoTypeMapper.
We now set the ClassLoader from the ApplicationContext to the type mapper to ensure the type mapper has access to entities. Previously, `SimpleTypeInformationMapper` used the contextual class loader and that failed in Fork/Join-Pool threads such as parallel streams as ForkJoinPool uses the system classloader. Running e.g. a packaged Boot application sets up an application ClassLoader that has access to packaged code while the system ClassLoader does not.

Also, consistently access the MongoTypeMapper through its getter.

Closes #3905
2021-12-09 11:34:11 +01:00
Jens Schauder
6899567c01 Update build trigger to use branch build.
See #3865
2021-11-12 14:43:55 +01:00
Jens Schauder
0d869b3c23 After release cleanups.
See #3865
2021-11-12 11:00:06 +01:00
Jens Schauder
d8ef0db1a9 Prepare next development iteration.
See #3865
2021-11-12 11:00:03 +01:00
21 changed files with 245 additions and 102 deletions

2
Jenkinsfile vendored
View File

@@ -3,7 +3,7 @@ pipeline {
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS)
upstream(upstreamProjects: "spring-data-commons/2.6.x", threshold: hudson.model.Result.SUCCESS)
}
options {

View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0</version>
<version>3.3.1</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.6.0</version>
<version>2.6.1</version>
</parent>
<modules>
@@ -26,8 +26,8 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>2.6.0</springdata.commons>
<mongo>4.4.0</mongo>
<springdata.commons>2.6.1</springdata.commons>
<mongo>4.4.1</mongo>
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>
</properties>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0</version>
<version>3.3.1</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0</version>
<version>3.3.1</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0</version>
<version>3.3.1</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -1803,8 +1803,9 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
Document projections = new Document();
Fields fields = context.getFields(type);
fields.forEach(it -> projections.append(it.getName(), 1));
return context.getMappedObject(projections, type);
fields.forEach(it -> projections.append(it.getTarget(), 1));
return projections;
}
}

View File

@@ -21,7 +21,7 @@ import java.util.ArrayList;
import java.util.List;
import org.bson.Document;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
@@ -122,13 +122,13 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
return AggregationOperationContext.super.getFields(type);
}
List<String> fields = new ArrayList<>();
List<Field> fields = new ArrayList<>();
for (MongoPersistentProperty property : entity) {
fields.add(property.getName());
fields.add(Fields.field(property.getName(), property.getFieldName()));
}
return Fields.fields(fields.toArray(new String[0]));
return Fields.from(fields.toArray(new Field[0]));
}
/*
@@ -142,12 +142,13 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
/**
* This toggle allows the {@link AggregationOperationContext context} to use any given field name without checking for
* its existence. Typically the {@link AggregationOperationContext} fails when referencing unknown fields, those that
* its existence. Typically, the {@link AggregationOperationContext} fails when referencing unknown fields, those that
* are not present in one of the previous stages or the input source, throughout the pipeline.
*
* @param type The domain type to map fields to.
* @return a more relaxed {@link AggregationOperationContext}.
* @since 3.1
* @see RelaxedTypeBasedAggregationOperationContext
*/
public AggregationOperationContext continueOnMissingFieldReference(Class<?> type) {
return new RelaxedTypeBasedAggregationOperationContext(type, mappingContext, mapper);

View File

@@ -19,6 +19,7 @@ import static org.springframework.data.mongodb.core.convert.ReferenceLookupDeleg
import java.util.Collections;
import org.bson.Document;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.DocumentReference;
@@ -32,6 +33,7 @@ import org.springframework.util.Assert;
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Anton Buzdalkin
* @since 3.3
*/
public class DefaultReferenceResolver implements ReferenceResolver {
@@ -41,8 +43,8 @@ public class DefaultReferenceResolver implements ReferenceResolver {
private final LookupFunction collectionLookupFunction = (filter, ctx) -> getReferenceLoader().fetchMany(filter, ctx);
private final LookupFunction singleValueLookupFunction = (filter, ctx) -> {
Object target = getReferenceLoader().fetchOne(filter, ctx);
return target == null ? Collections.emptyList() : Collections.singleton(getReferenceLoader().fetchOne(filter, ctx));
Document target = getReferenceLoader().fetchOne(filter, ctx);
return target == null ? Collections.emptyList() : Collections.singleton(target);
};
/**

View File

@@ -38,6 +38,7 @@ import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.CollectionFactory;
@@ -123,6 +124,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
protected @Nullable String mapKeyDotReplacement = null;
protected @Nullable CodecRegistryProvider codecRegistryProvider;
private MongoTypeMapper defaultTypeMapper;
private SpELContext spELContext;
private @Nullable EntityCallbacks entityCallbacks;
private final DocumentPointerFactory documentPointerFactory;
@@ -144,7 +146,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
this.dbRefResolver = dbRefResolver;
this.mappingContext = mappingContext;
this.typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext,
this.defaultTypeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext,
this::getWriteTarget);
this.idMapper = new QueryMapper(this);
@@ -197,9 +199,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param typeMapper the typeMapper to set. Can be {@literal null}.
*/
public void setTypeMapper(@Nullable MongoTypeMapper typeMapper) {
this.typeMapper = typeMapper == null
? new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext)
: typeMapper;
this.typeMapper = typeMapper;
}
/*
@@ -208,7 +208,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
*/
@Override
public MongoTypeMapper getTypeMapper() {
return this.typeMapper;
return this.typeMapper == null ? this.defaultTypeMapper : this.typeMapper;
}
/**
@@ -257,6 +257,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (entityCallbacks == null) {
setEntityCallbacks(EntityCallbacks.create(applicationContext));
}
ClassLoader classLoader = applicationContext.getClassLoader();
if (this.defaultTypeMapper instanceof BeanClassLoaderAware && classLoader != null) {
((BeanClassLoaderAware) this.defaultTypeMapper).setBeanClassLoader(classLoader);
}
}
/**
@@ -301,7 +306,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
TypeInformation<? extends S> typeHint) {
Document document = bson instanceof BasicDBObject ? new Document((BasicDBObject) bson) : (Document) bson;
TypeInformation<? extends S> typeToRead = typeMapper.readType(document, typeHint);
TypeInformation<? extends S> typeToRead = getTypeMapper().readType(document, typeHint);
Class<? extends S> rawType = typeToRead.getType();
if (conversions.hasCustomReadTarget(bson.getClass(), rawType)) {
@@ -657,7 +662,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
BsonUtils.removeNullId(bson);
if (requiresTypeHint(entityType)) {
typeMapper.writeType(type, bson);
getTypeMapper().writeType(type, bson);
}
}
@@ -1099,7 +1104,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
boolean notTheSameClass = !valueType.equals(reference);
if (notTheSameClass) {
typeMapper.writeType(valueType, bson);
getTypeMapper().writeType(valueType, bson);
}
}
@@ -1307,7 +1312,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Assert.notNull(bson, "Document must not be null!");
Assert.notNull(targetType, "TypeInformation must not be null!");
Class<?> mapType = typeMapper.readType(bson, targetType).getType();
Class<?> mapType = getTypeMapper().readType(bson, targetType).getType();
TypeInformation<?> keyType = targetType.getComponentType();
TypeInformation<?> valueType = targetType.getMapValueType() == null ? ClassTypeInformation.OBJECT
@@ -1326,7 +1331,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
sourceMap.forEach((k, v) -> {
if (typeMapper.isTypeKey(k)) {
if (getTypeMapper().isTypeKey(k)) {
return;
}
@@ -1489,7 +1494,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
}
if (typeMapper.isTypeKey(key)) {
if (getTypeMapper().isTypeKey(key)) {
keyToRemove = key;
@@ -1660,6 +1665,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
target.conversions = conversions;
target.spELContext = spELContext;
target.setInstantiators(instantiators);
target.defaultTypeMapper = defaultTypeMapper;
target.typeMapper = typeMapper;
target.setCodecRegistryProvider(dbFactory);
target.afterPropertiesSet();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2021 the original author or authors.
* Copyright 2011-2022 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.
@@ -1398,6 +1398,14 @@ public class QueryMapper {
this.currentIndex = 0;
}
String nextToken() {
return pathParts.get(currentIndex + 1);
}
boolean hasNexToken() {
return pathParts.size() > currentIndex + 1;
}
/**
* Maps the property name while retaining potential positional operator {@literal $}.
*
@@ -1407,31 +1415,26 @@ public class QueryMapper {
protected String mapPropertyName(MongoPersistentProperty property) {
StringBuilder mappedName = new StringBuilder(PropertyToFieldNameConverter.INSTANCE.convert(property));
boolean inspect = iterator.hasNext();
while (inspect) {
String partial = iterator.next();
currentIndex++;
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike();
if (property.isMap() && currentPropertyRoot.equals(partial) && iterator.hasNext()) {
partial = iterator.next();
currentIndex++;
}
if (isPositional || property.isMap() && !currentPropertyRoot.equals(partial)) {
mappedName.append(".").append(partial);
}
inspect = isPositional && iterator.hasNext();
if (!hasNexToken()) {
return mappedName.toString();
}
if (currentIndex + 1 < pathParts.size()) {
currentIndex++;
currentPropertyRoot = pathParts.get(currentIndex);
String nextToken = nextToken();
if (isPositionalParameter(nextToken)) {
mappedName.append(".").append(nextToken);
currentIndex += 2;
return mappedName.toString();
}
if (property.isMap()) {
mappedName.append(".").append(nextToken);
currentIndex += 2;
return mappedName.toString();
}
currentIndex++;
return mappedName.toString();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 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.
@@ -25,6 +25,16 @@ import org.springframework.util.Assert;
/**
* {@link IndexResolver} finds those {@link IndexDefinition}s to be created for a given class.
* <p>
* The {@link IndexResolver} considers index annotations like {@link Indexed}, {@link GeoSpatialIndexed},
* {@link HashIndexed}, {@link TextIndexed} and {@link WildcardIndexed} on properties as well as {@link CompoundIndex}
* and {@link WildcardIndexed} on types.
* <p>
* Unless specified otherwise the index name will be created out of the keys/path involved in the index. <br />
* {@link TextIndexed} properties are collected into a single index that covers the detected fields. <br />
* {@link java.util.Map} like structures, unless annotated with {@link WildcardIndexed}, are skipped because the
* {@link java.util.Map.Entry#getKey() map key}, which cannot be resolved from static metadata, needs to be part of the
* index.
*
* @author Christoph Strobl
* @author Thomas Darimont

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 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.
@@ -157,6 +157,10 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
List<IndexDefinitionHolder> indexes, CycleGuard guard) {
try {
if (isMapWithoutWildcardIndex(persistentProperty)) {
return;
}
if (persistentProperty.isEntity()) {
indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty),
persistentProperty.isUnwrapped() ? "" : persistentProperty.getFieldName(), Path.of(persistentProperty),
@@ -217,6 +221,10 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
Path propertyPath = path.append(persistentProperty);
guard.protect(persistentProperty, propertyPath);
if (isMapWithoutWildcardIndex(persistentProperty)) {
return;
}
if (persistentProperty.isEntity()) {
try {
indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty),
@@ -346,6 +354,10 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
indexDefinitionBuilder.withLanguageOverride(persistentProperty.getFieldName());
}
if (persistentProperty.isMap()) {
return;
}
TextIndexed indexed = persistentProperty.findAnnotation(TextIndexed.class);
if (includeOptions.isForce() || indexed != null || persistentProperty.isEntity()) {
@@ -798,6 +810,10 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
return expression.getValue(evaluationContext, Object.class);
}
private static boolean isMapWithoutWildcardIndex(MongoPersistentProperty property) {
return property.isMap() && !property.isAnnotationPresent(WildcardIndexed.class);
}
/**
* {@link CycleGuard} holds information about properties and the paths for accessing those. This information is used
* to detect potential cycles within the references.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 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.
@@ -49,13 +49,14 @@ import com.querydsl.core.types.Predicate;
import com.querydsl.mongodb.MongodbOps;
/**
* MongoDB query with utilizing {@link ReactiveMongoOperations} for command execution.
* MongoDB query utilizing {@link ReactiveMongoOperations} for command execution.
*
* @implNote This class uses {@link MongoOperations} to directly convert documents into the target entity type. Also, we
* want entites to participate in lifecycle events and entity callbacks.
* want entities to participate in lifecycle events and entity callbacks.
* @param <K> result type
* @author Mark Paluch
* @author Christoph Strobl
* @author Rocco Lagrotteria
* @since 2.2
*/
class ReactiveSpringDataMongodbQuery<K> extends SpringDataMongodbQuerySupport<ReactiveSpringDataMongodbQuery<K>> {
@@ -96,7 +97,8 @@ class ReactiveSpringDataMongodbQuery<K> extends SpringDataMongodbQuerySupport<Re
*/
Mono<Page<K>> fetchPage(Pageable pageable) {
Mono<List<K>> content = createQuery().flatMapMany(it -> find.matching(it).all()).collectList();
Mono<List<K>> content = createQuery().map(it -> it.with(pageable))
.flatMapMany(it -> find.matching(it).all()).collectList();
return content.flatMap(it -> ReactivePageableExecutionUtils.getPage(it, pageable, fetchCount()));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 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.
@@ -18,11 +18,15 @@ package org.springframework.data.mongodb.util.encryption;
import java.util.UUID;
import java.util.function.Supplier;
import org.bson.BsonBinary;
import org.bson.BsonBinarySubType;
import org.bson.types.Binary;
import org.springframework.data.mongodb.util.spel.ExpressionUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
/**
* Internal utility class for dealing with encryption related matters.
@@ -35,8 +39,8 @@ public final class EncryptionUtils {
/**
* Resolve a given plain {@link String} value into the store native {@literal keyId} format, considering potential
* {@link Expression expressions}. <br />
* The potential keyId is probed against an {@link UUID#fromString(String) UUID value} and the {@literal base64}
* encoded {@code $binary} representation.
* The potential keyId is probed against an {@link UUID#fromString(String) UUID value} or decoded from the
* {@literal base64} representation prior to conversion into its {@link Binary} format.
*
* @param value the source value to resolve the keyId for. Must not be {@literal null}.
* @param evaluationContext a {@link Supplier} used to provide the {@link EvaluationContext} in case an
@@ -57,11 +61,13 @@ public final class EncryptionUtils {
return potentialKeyId;
}
}
try {
return UUID.fromString(potentialKeyId.toString());
return new Binary(BsonBinarySubType.UUID_STANDARD,
new BsonBinary(UUID.fromString(potentialKeyId.toString())).getData());
} catch (IllegalArgumentException e) {
return org.bson.Document.parse("{ val : { $binary : { base64 : '" + potentialKeyId + "', subType : '04'} } }")
.get("val");
return new Binary(BsonBinarySubType.UUID_STANDARD, Base64Utils.decodeFromString(potentialKeyId.toString()));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 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.
@@ -23,6 +23,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.bson.BsonDocument;
import org.bson.Document;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -107,7 +108,7 @@ public class MappingMongoJsonSchemaCreatorUnitTests {
.createSchemaFor(Patient.class);
Document targetSchema = schema.schemaDocument();
assertThat(targetSchema).isEqualTo(Document.parse(PATIENT));
assertThat(targetSchema.toBsonDocument()).isEqualTo(BsonDocument.parse(PATIENT));
}
@Test // GH-3800
@@ -136,7 +137,7 @@ public class MappingMongoJsonSchemaCreatorUnitTests {
.filter(MongoJsonSchemaCreator.encryptedOnly()) //
.createSchemaFor(EncryptionMetadataFromProperty.class);
assertThat(schema.schemaDocument()).isEqualTo(Document.parse(ENC_FROM_PROPERTY_SCHEMA));
assertThat(schema.schemaDocument().toBsonDocument()).isEqualTo(BsonDocument.parse(ENC_FROM_PROPERTY_SCHEMA));
}
@Test // GH-3800
@@ -154,7 +155,7 @@ public class MappingMongoJsonSchemaCreatorUnitTests {
.filter(MongoJsonSchemaCreator.encryptedOnly()) //
.createSchemaFor(EncryptionMetadataFromMethod.class);
assertThat(schema.schemaDocument()).isEqualTo(Document.parse(ENC_FROM_METHOD_SCHEMA));
assertThat(schema.schemaDocument().toBsonDocument()).isEqualTo(BsonDocument.parse(ENC_FROM_METHOD_SCHEMA));
}
// --> TYPES AND JSON
@@ -392,7 +393,7 @@ public class MappingMongoJsonSchemaCreatorUnitTests {
}
static final String ENC_FROM_PROPERTY_ENTITY_KEY = "C5a5aMB7Ttq4wSJTFeRn8g==";
static final String ENC_FROM_PROPERTY_PROPOERTY_KEY = "Mw6mdTVPQfm4quqSCLVB3g=";
static final String ENC_FROM_PROPERTY_PROPOERTY_KEY = "Mw6mdTVPQfm4quqSCLVB3g==";
static final String ENC_FROM_PROPERTY_SCHEMA = "{" + //
" 'encryptMetadata': {" + //
" 'keyId': [" + //

View File

@@ -27,6 +27,7 @@ import java.util.List;
import org.bson.Document;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond;
import org.springframework.data.mongodb.core.aggregation.ProjectionOperationUnitTests.BookWithFieldAnnotation;
@@ -598,9 +599,31 @@ public class AggregationUnitTests {
assertThat(extractPipelineElement(target, 1, "$project")).isEqualTo(Document.parse(" { \"_id\" : \"$_id\" }"));
}
@Test // GH-3898
void shouldNotConvertIncludeExcludeValuesForProjectOperation() {
MongoMappingContext mappingContext = new MongoMappingContext();
RelaxedTypeBasedAggregationOperationContext context = new RelaxedTypeBasedAggregationOperationContext(WithRetypedIdField.class, mappingContext,
new QueryMapper(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext)));
Document document = project(WithRetypedIdField.class).toDocument(context);
assertThat(document).isEqualTo(new Document("$project", new Document("_id", 1).append("renamed-field", 1)));
}
private Document extractPipelineElement(Document agg, int index, String operation) {
List<Document> pipeline = (List<Document>) agg.get("pipeline");
return (Document) pipeline.get(index).get(operation);
}
public class WithRetypedIdField {
@Id
@org.springframework.data.mongodb.core.mapping.Field
private String id;
@org.springframework.data.mongodb.core.mapping.Field("renamed-field")
private String foo;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 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.
@@ -1208,7 +1208,7 @@ class UpdateMapperUnitTests {
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithNestedMap.class));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set",new org.bson.Document("levelOne.a.b.d","e")));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.a.b.d", "e")));
}
@Test // GH-3775
@@ -1218,7 +1218,7 @@ class UpdateMapperUnitTests {
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithNestedMap.class));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set",new org.bson.Document("levelOne.0.1.3","4")));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.0.1.3", "4")));
}
@Test // GH-3775
@@ -1228,7 +1228,7 @@ class UpdateMapperUnitTests {
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithNestedMap.class));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set",new org.bson.Document("levelOne.0.1.c","4")));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.0.1.c", "4")));
}
@Test // GH-3775
@@ -1238,7 +1238,7 @@ class UpdateMapperUnitTests {
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithNestedMap.class));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set",new org.bson.Document("levelOne.0a.1b.3c","4")));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.0a.1b.3c", "4")));
}
@Test // GH-3688
@@ -1262,7 +1262,7 @@ class UpdateMapperUnitTests {
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(WithDocumentReference.class));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set",new org.bson.Document("sample","s1")));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("sample", "s1")));
}
@Test // GH-3853
@@ -1276,7 +1276,8 @@ class UpdateMapperUnitTests {
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(WithDocumentReference.class));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set",new org.bson.Document("samples",Arrays.asList("s1"))));
assertThat(mappedUpdate)
.isEqualTo(new org.bson.Document("$set", new org.bson.Document("samples", Arrays.asList("s1"))));
}
@Test // GH-3853
@@ -1291,7 +1292,7 @@ class UpdateMapperUnitTests {
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(WithDocumentReference.class));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set",new org.bson.Document("customer","c-name")));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("customer", "c-name")));
}
@Test // GH-3853
@@ -1306,7 +1307,38 @@ class UpdateMapperUnitTests {
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(WithDocumentReference.class));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set",new org.bson.Document("customers", Arrays.asList("c-name"))));
assertThat(mappedUpdate)
.isEqualTo(new org.bson.Document("$set", new org.bson.Document("customers", Arrays.asList("c-name"))));
}
@Test // GH-3921
void mapNumericKeyInPathHavingComplexMapValyeTypes() {
Update update = new Update().set("testInnerData.testMap.1.intValue", "4");
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(TestData.class));
assertThat(mappedUpdate).isEqualTo("{ $set: { 'testInnerData.testMap.1.intValue': '4' }}");
}
@Test // GH-3921
void mapNumericKeyInPathNotMatchingExistingProperties() {
Update update = new Update().set("testInnerData.imaginaryMap.1.nonExistingProperty", "4");
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(TestData.class));
assertThat(mappedUpdate).isEqualTo("{ $set: { 'testInnerData.imaginaryMap.1.nonExistingProperty': '4' }}");
}
@Test // GH-3921
void mapNumericKeyInPathPartiallyMatchingExistingProperties() {
Update update = new Update().set("testInnerData.testMap.1.nonExistingProperty.2.someValue", "4");
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(TestData.class));
assertThat(mappedUpdate).isEqualTo("{ $set: { 'testInnerData.testMap.1.nonExistingProperty.2.someValue': '4' }}");
}
static class DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes {
@@ -1545,7 +1577,7 @@ class UpdateMapperUnitTests {
Map<Object, NestedDocument> concreteMap;
}
static class EntityWithIntKeyedMap{
static class EntityWithIntKeyedMap {
Map<Integer, EntityWithObjectMap> intKeyedMap;
}
@@ -1681,8 +1713,7 @@ class UpdateMapperUnitTests {
static class Customer {
@Id
private ObjectId id;
@Id private ObjectId id;
private String name;
}
@@ -1697,17 +1728,28 @@ class UpdateMapperUnitTests {
private String name;
@DocumentReference(lookup = "{ 'name' : ?#{#target} }")
private Customer customer;
@DocumentReference(lookup = "{ 'name' : ?#{#target} }") private Customer customer;
@DocumentReference(lookup = "{ 'name' : ?#{#target} }")
private List<Customer> customers;
@DocumentReference(lookup = "{ 'name' : ?#{#target} }") private List<Customer> customers;
@DocumentReference
private Sample sample;
@DocumentReference private Sample sample;
@DocumentReference
private List<Sample> samples;
@DocumentReference private List<Sample> samples;
}
@Data
private static class TestData {
@Id private String id;
private TestInnerData testInnerData;
}
@Data
private static class TestInnerData {
private Map<Integer, TestValue> testMap;
}
@Data
private static class TestValue {
private int intValue;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 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.
@@ -319,7 +319,8 @@ public class MongoPersistentEntityIndexResolverUnitTests {
class IndexOnLevelZeroWithExplicityNamedField {
@Indexed @Field("customFieldName") String namedProperty;
@Indexed
@Field("customFieldName") String namedProperty;
}
@Document
@@ -427,7 +428,8 @@ public class MongoPersistentEntityIndexResolverUnitTests {
@Document
class IndexOnMetaAnnotatedField {
@Field("_name") @IndexedFieldAnnotation String lastname;
@Field("_name")
@IndexedFieldAnnotation String lastname;
}
/**
@@ -1366,10 +1368,9 @@ public class MongoPersistentEntityIndexResolverUnitTests {
});
assertThat(indices.get(2)).satisfies(it -> {
assertThat(it.getIndexKeys()).containsEntry("withOptions.$**", 1);
assertThat(it.getIndexOptions()).containsEntry("name",
"withOptions.idx")
.containsEntry("collation", new org.bson.Document("locale", "en_US"))
.containsEntry("partialFilterExpression", new org.bson.Document("$eq", 1));
assertThat(it.getIndexOptions()).containsEntry("name", "withOptions.idx")
.containsEntry("collation", new org.bson.Document("locale", "en_US"))
.containsEntry("partialFilterExpression", new org.bson.Document("$eq", 1));
});
}
@@ -1393,6 +1394,15 @@ public class MongoPersistentEntityIndexResolverUnitTests {
});
}
@Test // GH-3914
public void shouldSkipMapStructuresUnlessAnnotatedWithWildcardIndex() {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(
WithMapStructures.class);
assertThat(indexDefinitions).hasSize(1);
}
@Document
class MixedIndexRoot {
@@ -1482,7 +1492,8 @@ public class MongoPersistentEntityIndexResolverUnitTests {
@Document
class SimilarityHolingBean {
@Indexed @Field("norm") String normalProperty;
@Indexed
@Field("norm") String normalProperty;
@Field("similarityL") private List<SimilaritySibling> listOfSimilarilyNamedEntities = null;
}
@@ -1626,6 +1637,17 @@ public class MongoPersistentEntityIndexResolverUnitTests {
T entity;
}
@Document
class WithMapStructures {
Map<String, ValueObject> rootMap;
NestedInMapWithStructures nested;
ValueObject plainValue;
}
class NestedInMapWithStructures {
Map<String, ValueObject> nestedMap;
}
@Document
class EntityWithGenericTypeWrapperAsElement {
List<GenericEntityWrapper<DocumentWithNamedIndex>> listWithGeneircTypeElement;
@@ -1634,7 +1656,8 @@ public class MongoPersistentEntityIndexResolverUnitTests {
@Document
class WithHashedIndexOnId {
@HashIndexed @Id String id;
@HashIndexed
@Id String id;
}
@Document

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 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.
@@ -65,6 +65,7 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
*
* @author Mark Paluch
* @author Christoph Strobl
* @author Rocco Lagrotteria
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@@ -401,7 +402,7 @@ public class ReactiveQuerydslMongoPredicateExecutorTests {
.assertNext(it -> {
assertThat(it.getTotalElements()).isEqualTo(2);
assertThat(it.getContent()).contains(dave);
assertThat(it.getContent()).containsOnly(dave);
}).verifyComplete();
repository
@@ -410,7 +411,7 @@ public class ReactiveQuerydslMongoPredicateExecutorTests {
.assertNext(it -> {
assertThat(it.getTotalElements()).isEqualTo(2);
assertThat(it.getContent()).contains(oliver);
assertThat(it.getContent()).containsOnly(oliver);
}).verifyComplete();
}

View File

@@ -402,12 +402,17 @@ Indexes are automatically created for the initial entity set on application star
We generally recommend explicit index creation for application-based control of indexes as Spring Data cannot automatically create indexes for collections that were recreated while the application was running.
`IndexResolver` provides an abstraction for programmatic index definition creation if you want to make use of `@Indexed` annotations such as `@GeoSpatialIndexed`, `@TextIndexed`, `@CompoundIndex`.
`IndexResolver` provides an abstraction for programmatic index definition creation if you want to make use of `@Indexed` annotations such as `@GeoSpatialIndexed`, `@TextIndexed`, `@CompoundIndex` and `@WildcardIndexed`.
You can use index definitions with `IndexOperations` to create indexes.
A good point in time for index creation is on application startup, specifically after the application context was refreshed, triggered by observing `ContextRefreshedEvent`.
This event guarantees that the context is fully initialized.
Note that at this time other components, especially bean factories might have access to the MongoDB database.
[WARNING]
====
``Map``-like properties are skipped by the `IndexResolver` unless annotated with `@WildcardIndexed` because the _map key_ must be part of the index definition. Since the purpose of maps is the usage of dynamic keys and values, the keys cannot be resolved from static mapping metadata.
====
.Programmatic Index Creation for a single Domain Type
====
[source,java]

View File

@@ -1,4 +1,4 @@
Spring Data MongoDB 3.3 GA (2021.1.0)
Spring Data MongoDB 3.3.1 (2021.1.1)
Copyright (c) [2010-2019] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
@@ -30,5 +30,6 @@ conditions of the subcomponent's license, as noted in the LICENSE file.