Compare commits
70 Commits
2.0.8.RELE
...
2.0.12.REL
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b47f44531 | ||
|
|
79534fa426 | ||
|
|
b8e76ecae4 | ||
|
|
3e2b060611 | ||
|
|
4699219728 | ||
|
|
a483d95cde | ||
|
|
84f35f5655 | ||
|
|
2ec0f93325 | ||
|
|
48f9422a66 | ||
|
|
a2349405af | ||
|
|
e12ab354f7 | ||
|
|
04b20fa9c0 | ||
|
|
81c46f04d6 | ||
|
|
ce905c80fe | ||
|
|
831e4f9ef1 | ||
|
|
5ce293a871 | ||
|
|
bcd61f0dae | ||
|
|
478594c3ca | ||
|
|
bb101d5e18 | ||
|
|
a5bc7a2a08 | ||
|
|
6720967e19 | ||
|
|
99a4661e81 | ||
|
|
338bc30b96 | ||
|
|
7fa3f0068b | ||
|
|
abc74fdcc6 | ||
|
|
3a895588c8 | ||
|
|
f79d98ce23 | ||
|
|
2bcc0d8185 | ||
|
|
c8846d3d1c | ||
|
|
a4835c8fcf | ||
|
|
7875c8399f | ||
|
|
9046857721 | ||
|
|
e8bb63c9f7 | ||
|
|
b431a56a95 | ||
|
|
dc820017e0 | ||
|
|
34ce87b80c | ||
|
|
9098d509a5 | ||
|
|
861c8279a3 | ||
|
|
e545787e7e | ||
|
|
38ccdc5dfc | ||
|
|
7a34cc73d8 | ||
|
|
ba6fa834e5 | ||
|
|
7100cd17be | ||
|
|
7c65472e2d | ||
|
|
f98f586a23 | ||
|
|
19b5b6b6f0 | ||
|
|
b9ffa9b89d | ||
|
|
3ba589072f | ||
|
|
e237c5dfc4 | ||
|
|
ecb560cdbc | ||
|
|
fc4a21775a | ||
|
|
ae62e70c52 | ||
|
|
f83622709d | ||
|
|
83d218081c | ||
|
|
70fe406602 | ||
|
|
18046e9040 | ||
|
|
69310552e3 | ||
|
|
b8f093269d | ||
|
|
172db96fea | ||
|
|
c8381c734b | ||
|
|
bf82964474 | ||
|
|
2d0495874f | ||
|
|
82c91cbb71 | ||
|
|
4d309bd7f0 | ||
|
|
6f011b0fa1 | ||
|
|
1a3b9e3c42 | ||
|
|
5a37468103 | ||
|
|
d4b0963550 | ||
|
|
468c497525 | ||
|
|
4562f39d7a |
24
pom.xml
24
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.0.8.RELEASE</version>
|
||||
<version>2.0.12.RELEASE</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.0.8.RELEASE</version>
|
||||
<version>2.0.12.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
@@ -27,7 +27,7 @@
|
||||
<properties>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-mongodb</dist.id>
|
||||
<springdata.commons>2.0.8.RELEASE</springdata.commons>
|
||||
<springdata.commons>2.0.12.RELEASE</springdata.commons>
|
||||
<mongo>3.5.0</mongo>
|
||||
<mongo.reactivestreams>1.6.0</mongo.reactivestreams>
|
||||
<jmh.version>1.19</jmh.version>
|
||||
@@ -138,6 +138,24 @@
|
||||
</modules>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>distribute</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<attributes>
|
||||
<mongo-reactivestreams>${mongo.reactivestreams}</mongo-reactivestreams>
|
||||
<reactor>${reactor}</reactor>
|
||||
</attributes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.0.8.RELEASE</version>
|
||||
<version>2.0.12.RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.0.8.RELEASE</version>
|
||||
<version>2.0.12.RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.0.8.RELEASE</version>
|
||||
<version>2.0.12.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<!-- reactive -->
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.0.8.RELEASE</version>
|
||||
<version>2.0.12.RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.0.8.RELEASE</version>
|
||||
<version>2.0.12.RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCre
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -75,6 +76,7 @@ import org.w3c.dom.Element;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Zied Yaich
|
||||
*/
|
||||
public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
|
||||
@@ -159,6 +161,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanDefinition potentiallyCreateValidatingMongoEventListener(Element element, ParserContext parserContext) {
|
||||
|
||||
String disableValidation = element.getAttribute("disable-validation");
|
||||
@@ -180,6 +183,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private RuntimeBeanReference getValidator(Object source, ParserContext parserContext) {
|
||||
|
||||
if (!JSR_303_PRESENT) {
|
||||
@@ -197,7 +201,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
}
|
||||
|
||||
public static String potentiallyCreateMappingContext(Element element, ParserContext parserContext,
|
||||
BeanDefinition conversionsDefinition, String converterId) {
|
||||
@Nullable BeanDefinition conversionsDefinition, @Nullable String converterId) {
|
||||
|
||||
String ctxRef = element.getAttribute("mapping-context-ref");
|
||||
|
||||
@@ -211,7 +215,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
BeanDefinitionBuilder mappingContextBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(MongoMappingContext.class);
|
||||
|
||||
Set<String> classesToAdd = getInititalEntityClasses(element);
|
||||
Set<String> classesToAdd = getInitialEntityClasses(element);
|
||||
|
||||
if (classesToAdd != null) {
|
||||
mappingContextBuilder.addPropertyValue("initialEntitySet", classesToAdd);
|
||||
@@ -262,6 +266,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanDefinition getCustomConversions(Element element, ParserContext parserContext) {
|
||||
|
||||
List<Element> customConvertersElements = DomUtils.getChildElementsByTagName(element, "custom-converters");
|
||||
@@ -269,7 +274,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
if (customConvertersElements.size() == 1) {
|
||||
|
||||
Element customerConvertersElement = customConvertersElements.get(0);
|
||||
ManagedList<BeanMetadataElement> converterBeans = new ManagedList<BeanMetadataElement>();
|
||||
ManagedList<BeanMetadataElement> converterBeans = new ManagedList<>();
|
||||
List<Element> converterElements = DomUtils.getChildElementsByTagName(customerConvertersElement, "converter");
|
||||
|
||||
if (converterElements != null) {
|
||||
@@ -285,9 +290,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
provider.addExcludeFilter(new NegatingFilter(new AssignableTypeFilter(Converter.class),
|
||||
new AssignableTypeFilter(GenericConverter.class)));
|
||||
|
||||
for (BeanDefinition candidate : provider.findCandidateComponents(packageToScan)) {
|
||||
converterBeans.add(candidate);
|
||||
}
|
||||
converterBeans.addAll(provider.findCandidateComponents(packageToScan));
|
||||
}
|
||||
|
||||
BeanDefinitionBuilder conversionsBuilder = BeanDefinitionBuilder.rootBeanDefinition(MongoCustomConversions.class);
|
||||
@@ -304,7 +307,8 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Set<String> getInititalEntityClasses(Element element) {
|
||||
@Nullable
|
||||
private static Set<String> getInitialEntityClasses(Element element) {
|
||||
|
||||
String basePackage = element.getAttribute(BASE_PACKAGE);
|
||||
|
||||
@@ -317,7 +321,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
|
||||
|
||||
Set<String> classes = new ManagedSet<String>();
|
||||
Set<String> classes = new ManagedSet<>();
|
||||
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
|
||||
classes.add(candidate.getBeanClassName());
|
||||
}
|
||||
@@ -325,6 +329,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return classes;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BeanMetadataElement parseConverter(Element element, ParserContext parserContext) {
|
||||
|
||||
String converterRef = element.getAttribute("ref");
|
||||
@@ -375,7 +380,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
|
||||
Assert.notNull(filters, "TypeFilters must not be null");
|
||||
|
||||
this.delegates = new HashSet<TypeFilter>(Arrays.asList(filters));
|
||||
this.delegates = new HashSet<>(Arrays.asList(filters));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.config;
|
||||
|
||||
import java.beans.PropertyEditorSupport;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -26,6 +27,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.MongoCredential;
|
||||
@@ -35,6 +37,8 @@ import com.mongodb.MongoCredential;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
* @author Stephen Tyler Conrad
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
@@ -98,6 +102,20 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
verifyDatabasePresent(database);
|
||||
credentials.add(MongoCredential.createScramSha1Credential(userNameAndPassword[0], database,
|
||||
userNameAndPassword[1].toCharArray()));
|
||||
} else if ("SCRAM-SHA-256".equals(authMechanism)) {
|
||||
|
||||
Method createScramSha256Credential = ReflectionUtils.findMethod(MongoCredential.class,
|
||||
"createScramSha256Credential", String.class, String.class, char[].class);
|
||||
|
||||
if (createScramSha256Credential == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"SCRAM-SHA-256 auth mechanism is available as of MongoDB 4 and MongoDB Java Driver 3.8! Please make sure to use at least those versions.");
|
||||
}
|
||||
|
||||
verifyUsernameAndPasswordPresent(userNameAndPassword);
|
||||
verifyDatabasePresent(database);
|
||||
credentials.add(MongoCredential.class.cast(ReflectionUtils.invokeMethod(createScramSha256Credential, null,
|
||||
userNameAndPassword[0], database, userNameAndPassword[1].toCharArray())));
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Cannot create MongoCredentials for unknown auth mechanism '%s'!", authMechanism));
|
||||
@@ -164,7 +182,7 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
private static Properties extractOptions(String text) {
|
||||
|
||||
int optionsSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
|
||||
int dbSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
|
||||
int dbSeparationIndex = text.lastIndexOf(DATABASE_DELIMITER);
|
||||
|
||||
if (optionsSeparationIndex == -1 || dbSeparationIndex > optionsSeparationIndex) {
|
||||
return new Properties();
|
||||
@@ -173,7 +191,13 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
Properties properties = new Properties();
|
||||
|
||||
for (String option : text.substring(optionsSeparationIndex + 1).split(OPTION_VALUE_DELIMITER)) {
|
||||
|
||||
String[] optionArgs = option.split("=");
|
||||
|
||||
if (optionArgs.length == 1) {
|
||||
throw new IllegalArgumentException(String.format("Query parameter '%s' has no value!", optionArgs[0]));
|
||||
}
|
||||
|
||||
properties.put(optionArgs[0], optionArgs[1]);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -51,17 +52,17 @@ import com.mongodb.bulk.BulkWriteError;
|
||||
*/
|
||||
public class MongoExceptionTranslator implements PersistenceExceptionTranslator {
|
||||
|
||||
private static final Set<String> DULICATE_KEY_EXCEPTIONS = new HashSet<String>(
|
||||
private static final Set<String> DUPLICATE_KEY_EXCEPTIONS = new HashSet<>(
|
||||
Arrays.asList("MongoException.DuplicateKey", "DuplicateKeyException"));
|
||||
|
||||
private static final Set<String> RESOURCE_FAILURE_EXCEPTIONS = new HashSet<String>(
|
||||
private static final Set<String> RESOURCE_FAILURE_EXCEPTIONS = new HashSet<>(
|
||||
Arrays.asList("MongoException.Network", "MongoSocketException", "MongoException.CursorNotFound",
|
||||
"MongoCursorNotFoundException", "MongoServerSelectionException", "MongoTimeoutException"));
|
||||
|
||||
private static final Set<String> RESOURCE_USAGE_EXCEPTIONS = new HashSet<String>(
|
||||
Arrays.asList("MongoInternalException"));
|
||||
private static final Set<String> RESOURCE_USAGE_EXCEPTIONS = new HashSet<>(
|
||||
Collections.singletonList("MongoInternalException"));
|
||||
|
||||
private static final Set<String> DATA_INTEGRETY_EXCEPTIONS = new HashSet<String>(
|
||||
private static final Set<String> DATA_INTEGRITY_EXCEPTIONS = new HashSet<>(
|
||||
Arrays.asList("WriteConcernException", "MongoWriteException", "MongoBulkWriteException"));
|
||||
|
||||
/*
|
||||
@@ -79,7 +80,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
|
||||
String exception = ClassUtils.getShortName(ClassUtils.getUserClass(ex.getClass()));
|
||||
|
||||
if (DULICATE_KEY_EXCEPTIONS.contains(exception)) {
|
||||
if (DUPLICATE_KEY_EXCEPTIONS.contains(exception)) {
|
||||
return new DuplicateKeyException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
@@ -91,7 +92,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (DATA_INTEGRETY_EXCEPTIONS.contains(exception)) {
|
||||
if (DATA_INTEGRITY_EXCEPTIONS.contains(exception)) {
|
||||
|
||||
if (ex instanceof MongoServerException) {
|
||||
if (((MongoServerException) ex).getCode() == 11000) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
|
||||
|
||||
import com.mongodb.client.model.MapReduceAction;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
@@ -1715,18 +1716,32 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
if (!CollectionUtils.isEmpty(mapReduceOptions.getScopeVariables())) {
|
||||
result = result.scope(new Document(mapReduceOptions.getScopeVariables()));
|
||||
}
|
||||
|
||||
if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit().intValue() > 0) {
|
||||
result = result.limit(mapReduceOptions.getLimit());
|
||||
}
|
||||
|
||||
if (mapReduceOptions.getFinalizeFunction().filter(StringUtils::hasText).isPresent()) {
|
||||
result = result.finalizeFunction(mapReduceOptions.getFinalizeFunction().get());
|
||||
}
|
||||
|
||||
if (mapReduceOptions.getJavaScriptMode() != null) {
|
||||
result = result.jsMode(mapReduceOptions.getJavaScriptMode());
|
||||
}
|
||||
|
||||
if (mapReduceOptions.getOutputSharded().isPresent()) {
|
||||
result = result.sharded(mapReduceOptions.getOutputSharded().get());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(mapReduceOptions.getOutputCollection()) && !mapReduceOptions.usesInlineOutput()) {
|
||||
|
||||
result = result.collectionName(mapReduceOptions.getOutputCollection())
|
||||
.action(mapReduceOptions.getMapReduceAction());
|
||||
|
||||
if (mapReduceOptions.getOutputDatabase().isPresent()) {
|
||||
result = result.databaseName(mapReduceOptions.getOutputDatabase().get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = collation.map(Collation::toMongoCollation).map(result::collation).orElse(result);
|
||||
|
||||
@@ -0,0 +1,670 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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 java.util.Collections;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Gateway to {@literal convert} aggregation operations.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public class ConvertOperators {
|
||||
|
||||
/**
|
||||
* Take the field referenced by given {@literal fieldReference}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ConvertOperatorFactory valueOf(String fieldReference) {
|
||||
return new ConvertOperatorFactory(fieldReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the value resulting from the given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ConvertOperatorFactory valueOf(AggregationExpression expression) {
|
||||
return new ConvertOperatorFactory(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class ConvertOperatorFactory {
|
||||
|
||||
private final @Nullable String fieldReference;
|
||||
private final @Nullable AggregationExpression expression;
|
||||
|
||||
/**
|
||||
* Creates new {@link ConvertOperatorFactory} for given {@literal fieldReference}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
*/
|
||||
public ConvertOperatorFactory(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
|
||||
this.fieldReference = fieldReference;
|
||||
this.expression = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ConvertOperatorFactory} for given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
*/
|
||||
public ConvertOperatorFactory(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
this.fieldReference = null;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the given {@code stringTypeIdentifier}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param stringTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertTo(String stringTypeIdentifier) {
|
||||
return createConvert().to(stringTypeIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the given {@code numericTypeIdentifier}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param numericTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertTo(int numericTypeIdentifier) {
|
||||
return createConvert().to(numericTypeIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the value of the given {@link Field field reference}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertToTypeOf(String fieldReference) {
|
||||
return createConvert().toTypeOf(fieldReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the given {@link AggregationExpression expression}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertToTypeOf(AggregationExpression expression) {
|
||||
return createConvert().toTypeOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToBool aggregation expression} for {@code $toBool} that converts a value to boolean. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("bool")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToBool}.
|
||||
*/
|
||||
public ToBool convertToBoolean() {
|
||||
return ToBool.toBoolean(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDate aggregation expression} for {@code $toDate} that converts a value to a date. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("date")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToDate}.
|
||||
*/
|
||||
public ToDate convertToDate() {
|
||||
return ToDate.toDate(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDecimal aggregation expression} for {@code $toDecimal} that converts a value to a decimal.
|
||||
* Shorthand for {@link #convertTo(String) #convertTo("decimal")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToDecimal}.
|
||||
*/
|
||||
public ToDecimal convertToDecimal() {
|
||||
return ToDecimal.toDecimal(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDouble aggregation expression} for {@code $toDouble} that converts a value to a decimal.
|
||||
* Shorthand for {@link #convertTo(String) #convertTo("double")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToDouble}.
|
||||
*/
|
||||
public ToDouble convertToDouble() {
|
||||
return ToDouble.toDouble(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toInt} that converts a value to an int. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("int")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToInt convertToInt() {
|
||||
return ToInt.toInt(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toLong} that converts a value to a long. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("long")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToLong convertToLong() {
|
||||
return ToLong.toLong(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toObjectId} that converts a value to a objectId. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("objectId")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToObjectId convertToObjectId() {
|
||||
return ToObjectId.toObjectId(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toString} that converts a value to a string. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("string")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToString convertToString() {
|
||||
return ToString.toString(valueObject());
|
||||
}
|
||||
|
||||
private Convert createConvert() {
|
||||
return usesFieldRef() ? Convert.convertValueOf(fieldReference) : Convert.convertValueOf(expression);
|
||||
}
|
||||
|
||||
private Object valueObject() {
|
||||
return usesFieldRef() ? Fields.field(fieldReference) : expression;
|
||||
}
|
||||
|
||||
private boolean usesFieldRef() {
|
||||
return fieldReference != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $convert} that converts a value to a specified type. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/convert/">https://docs.mongodb.com/manual/reference/operator/aggregation/convert/</a>
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class Convert extends AbstractAggregationExpression {
|
||||
|
||||
private Convert(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert} using the given value for the {@literal input} attribute.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public static Convert convertValue(Object value) {
|
||||
return new Convert(Collections.singletonMap("input", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert} using the value of the provided {@link Field fieldReference} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public static Convert convertValueOf(String fieldReference) {
|
||||
return convertValue(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert} using the result of the provided {@link AggregationExpression expression} as
|
||||
* {@literal input} value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public static Convert convertValueOf(AggregationExpression expression) {
|
||||
return convertValue(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via its {@link String} representation.
|
||||
* <ul>
|
||||
* <li>double</li>
|
||||
* <li>string</li>
|
||||
* <li>objectId</li>
|
||||
* <li>bool</li>
|
||||
* <li>date</li>
|
||||
* <li>int</li>
|
||||
* <li>long</li>
|
||||
* <li>decimal</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param stringTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert to(String stringTypeIdentifier) {
|
||||
return new Convert(append("to", stringTypeIdentifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via its numeric representation.
|
||||
* <dl>
|
||||
* <dt>1</dt>
|
||||
* <dd>double</dd>
|
||||
* <dt>2</dt>
|
||||
* <dd>string</li>
|
||||
* <dt>7</dt>
|
||||
* <dd>objectId</li>
|
||||
* <dt>8</dt>
|
||||
* <dd>bool</dd>
|
||||
* <dt>9</dt>
|
||||
* <dd>date</dd>
|
||||
* <dt>16</dt>
|
||||
* <dd>int</dd>
|
||||
* <dt>18</dt>
|
||||
* <dd>long</dd>
|
||||
* <dt>19</dt>
|
||||
* <dd>decimal</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @param numericTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert to(int numericTypeIdentifier) {
|
||||
return new Convert(append("to", numericTypeIdentifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via the value of the given field.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert toTypeOf(String fieldReference) {
|
||||
return new Convert(append("to", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via the value of the given {@link AggregationExpression expression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert toTypeOf(AggregationExpression expression) {
|
||||
return new Convert(append("to", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the value to return on encountering an error during conversion.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onErrorReturn(Object value) {
|
||||
return new Convert(append("onError", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the field holding the value to return on encountering an error during conversion.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onErrorReturnValueOf(String fieldReference) {
|
||||
return onErrorReturn(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the expression to evaluate and return on encountering an error during conversion.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onErrorReturnValueOf(AggregationExpression expression) {
|
||||
return onErrorReturn(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the value to return when the input is {@literal null} or missing.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onNullReturn(Object value) {
|
||||
return new Convert(append("onNull", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the field holding the value to return when the input is {@literal null} or missing.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onNullReturnValueOf(String fieldReference) {
|
||||
return onNullReturn(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the expression to evaluate and return when the input is {@literal null} or missing.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onNullReturnValueOf(AggregationExpression expression) {
|
||||
return onNullReturn(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$convert";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toBool} that converts a value to {@literal boolean}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("bool")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toBool/">https://docs.mongodb.com/manual/reference/operator/aggregation/toBool/</a>
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class ToBool extends AbstractAggregationExpression {
|
||||
|
||||
private ToBool(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToBool} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToBool}.
|
||||
*/
|
||||
public static ToBool toBoolean(Object value) {
|
||||
return new ToBool(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toBool";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toDate} that converts a value to {@literal date}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("date")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDate/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDate/</a>
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class ToDate extends AbstractAggregationExpression {
|
||||
|
||||
private ToDate(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDate} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToDate}.
|
||||
*/
|
||||
public static ToDate toDate(Object value) {
|
||||
return new ToDate(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toDate";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toDecimal} that converts a value to {@literal decimal}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("decimal")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDecimal/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDecimal/</a>
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class ToDecimal extends AbstractAggregationExpression {
|
||||
|
||||
private ToDecimal(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDecimal} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToDecimal}.
|
||||
*/
|
||||
public static ToDecimal toDecimal(Object value) {
|
||||
return new ToDecimal(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toDecimal";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toDouble} that converts a value to {@literal double}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("double")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDouble/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDouble/</a>
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class ToDouble extends AbstractAggregationExpression {
|
||||
|
||||
private ToDouble(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDouble} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToDouble}.
|
||||
*/
|
||||
public static ToDouble toDouble(Object value) {
|
||||
return new ToDouble(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toDouble";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toInt} that converts a value to {@literal integer}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("int")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toInt/">https://docs.mongodb.com/manual/reference/operator/aggregation/toInt/</a>
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class ToInt extends AbstractAggregationExpression {
|
||||
|
||||
private ToInt(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public static ToInt toInt(Object value) {
|
||||
return new ToInt(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toInt";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toLong} that converts a value to {@literal long}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("long")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toLong/">https://docs.mongodb.com/manual/reference/operator/aggregation/toLong/</a>
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class ToLong extends AbstractAggregationExpression {
|
||||
|
||||
private ToLong(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToLong} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToLong}.
|
||||
*/
|
||||
public static ToLong toLong(Object value) {
|
||||
return new ToLong(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toLong";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toObjectId} that converts a value to {@literal objectId}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("objectId")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toObjectId/">https://docs.mongodb.com/manual/reference/operator/aggregation/toObjectId/</a>
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class ToObjectId extends AbstractAggregationExpression {
|
||||
|
||||
private ToObjectId(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToObjectId} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToObjectId}.
|
||||
*/
|
||||
public static ToObjectId toObjectId(Object value) {
|
||||
return new ToObjectId(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toObjectId";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toString} that converts a value to {@literal string}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("string")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toString/">https://docs.mongodb.com/manual/reference/operator/aggregation/toString/</a>
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class ToString extends AbstractAggregationExpression {
|
||||
|
||||
private ToString(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToString} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToString}.
|
||||
*/
|
||||
public static ToString toString(Object value) {
|
||||
return new ToString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toString";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import java.util.Map;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Gateway to {@literal Date} aggregation operations.
|
||||
@@ -98,7 +99,7 @@ public class DateOperators {
|
||||
* <strong>NOTE:</strong> Requires MongoDB 3.6 or later.
|
||||
*
|
||||
* @return new instance of {@link DateFromPartsOperatorFactory}.
|
||||
* @since 2.1
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static DateFromString dateFromString(String value) {
|
||||
return DateFromString.fromString(value);
|
||||
@@ -178,7 +179,7 @@ public class DateOperators {
|
||||
* Create a {@link Timezone} for the {@link AggregationExpression} resulting in the Olson Timezone Identifier or UTC
|
||||
* Offset.
|
||||
*
|
||||
* @param value the {@link AggregationExpression} resulting in the timezone.
|
||||
* @param expression the {@link AggregationExpression} resulting in the timezone.
|
||||
* @return new instance of {@link Timezone}.
|
||||
*/
|
||||
public static Timezone ofExpression(AggregationExpression expression) {
|
||||
@@ -380,6 +381,17 @@ public class DateOperators {
|
||||
return applyTimezone(DateToString.dateToString(dateReference()).toString(format), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that converts a date object to a string according to the server default
|
||||
* format.
|
||||
*
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public DateToString toStringWithDefaultFormat() {
|
||||
return applyTimezone(DateToString.dateToString(dateReference()).defaultFormat(), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that returns the weekday number in ISO 8601 format, ranging from 1
|
||||
* (for Monday) to 7 (for Sunday).
|
||||
@@ -1352,6 +1364,11 @@ public class DateOperators {
|
||||
Assert.notNull(format, "Format must not be null!");
|
||||
return new DateToString(argumentMap(value, format, Timezone.none()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateToString defaultFormat() {
|
||||
return new DateToString(argumentMap(value, null, Timezone.none()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1392,7 +1409,43 @@ public class DateOperators {
|
||||
public DateToString withTimezone(Timezone timezone) {
|
||||
|
||||
Assert.notNull(timezone, "Timezone must not be null.");
|
||||
return new DateToString(argumentMap(get("date"), get("format"), timezone));
|
||||
return new DateToString(append("timezone", timezone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the value to return when the date is {@literal null} or missing. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public DateToString onNullReturn(Object value) {
|
||||
return new DateToString(append("onNull", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the field holding the value to return when the date is {@literal null} or missing. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public DateToString onNullReturnValueOf(String fieldReference) {
|
||||
return onNullReturn(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the expression to evaluate and return when the date is {@literal null} or missing. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public DateToString onNullReturnValueOf(AggregationExpression expression) {
|
||||
return onNullReturn(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1400,10 +1453,14 @@ public class DateOperators {
|
||||
return "$dateToString";
|
||||
}
|
||||
|
||||
private static java.util.Map<String, Object> argumentMap(Object date, String format, Timezone timezone) {
|
||||
private static java.util.Map<String, Object> argumentMap(Object date, @Nullable String format, Timezone timezone) {
|
||||
|
||||
java.util.Map<String, Object> args = new LinkedHashMap<>(2);
|
||||
|
||||
if (StringUtils.hasText(format)) {
|
||||
args.put("format", format);
|
||||
}
|
||||
|
||||
java.util.Map<String, Object> args = new LinkedHashMap<String, Object>(2);
|
||||
args.put("format", format);
|
||||
args.put("date", date);
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(timezone, Timezone.none())) {
|
||||
@@ -1412,6 +1469,25 @@ public class DateOperators {
|
||||
return args;
|
||||
}
|
||||
|
||||
protected java.util.Map<String, Object> append(String key, Object value) {
|
||||
|
||||
java.util.Map<String, Object> clone = new LinkedHashMap<>(argumentMap());
|
||||
|
||||
if (value instanceof Timezone) {
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(value, Timezone.none())) {
|
||||
clone.remove("timezone");
|
||||
} else {
|
||||
clone.put("timezone", ((Timezone) value).value);
|
||||
}
|
||||
|
||||
} else {
|
||||
clone.put(key, value);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public interface FormatBuilder {
|
||||
|
||||
/**
|
||||
@@ -1421,6 +1497,16 @@ public class DateOperators {
|
||||
* @return
|
||||
*/
|
||||
DateToString toString(String format);
|
||||
|
||||
/**
|
||||
* Creates new {@link DateToString} using the server default string format ({@code %Y-%m-%dT%H:%M:%S.%LZ}) for
|
||||
* dates. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
DateToString defaultFormat();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2270,6 +2356,20 @@ public class DateOperators {
|
||||
return new DateFromString(appendTimezone(argumentMap(), timezone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally set the date format to use. If not specified {@code %Y-%m-%dT%H:%M:%S.%LZ} is used.<br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param format must not be {@literal null}.
|
||||
* @return new instance of {@link DateFromString}.
|
||||
* @throws IllegalArgumentException if given {@literal format} is {@literal null}.
|
||||
*/
|
||||
public DateFromString withFormat(String format) {
|
||||
|
||||
Assert.notNull(format, "Format must not be null!");
|
||||
return new DateFromString(append("format", format));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$dateFromString";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2018 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.
|
||||
@@ -17,40 +17,71 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Represents a {@code geoNear} aggregation operation.
|
||||
* <p>
|
||||
* We recommend to use the static factory method {@link Aggregation#geoNear(NearQuery, String)} instead of creating
|
||||
* instances of this class directly.
|
||||
*
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @since 1.3
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/">MongoDB Aggregation Framework:
|
||||
* $geoNear</a>
|
||||
*/
|
||||
public class GeoNearOperation implements AggregationOperation {
|
||||
|
||||
private final NearQuery nearQuery;
|
||||
private final String distanceField;
|
||||
private final @Nullable String indexKey;
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoNearOperation} from the given {@link NearQuery} and the given distance field. The
|
||||
* {@code distanceField} defines output field that contains the calculated distance.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
*
|
||||
* @param nearQuery must not be {@literal null}.
|
||||
* @param distanceField must not be {@literal null}.
|
||||
*/
|
||||
public GeoNearOperation(NearQuery nearQuery, String distanceField) {
|
||||
this(nearQuery, distanceField, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoNearOperation} from the given {@link NearQuery} and the given distance field. The
|
||||
* {@code distanceField} defines output field that contains the calculated distance.
|
||||
*
|
||||
* @param nearQuery must not be {@literal null}.
|
||||
* @param distanceField must not be {@literal null}.
|
||||
* @param indexKey can be {@literal null};
|
||||
* @since 2.0.10
|
||||
*/
|
||||
private GeoNearOperation(NearQuery nearQuery, String distanceField, @Nullable String indexKey) {
|
||||
|
||||
Assert.notNull(nearQuery, "NearQuery must not be null.");
|
||||
Assert.hasLength(distanceField, "Distance field must not be null or empty.");
|
||||
|
||||
this.nearQuery = nearQuery;
|
||||
this.distanceField = distanceField;
|
||||
this.indexKey = indexKey;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Optionally specify the geospatial index to use via the field to use in the calculation. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param key the geospatial index field to use when calculating the distance.
|
||||
* @return new instance of {@link GeoNearOperation}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public GeoNearOperation useIndex(String key) {
|
||||
return new GeoNearOperation(nearQuery, distanceField, key);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@@ -60,6 +91,10 @@ public class GeoNearOperation implements AggregationOperation {
|
||||
Document command = context.getMappedObject(nearQuery.toDocument());
|
||||
command.put("distanceField", distanceField);
|
||||
|
||||
if (StringUtils.hasText(indexKey)) {
|
||||
command.put("key", indexKey);
|
||||
}
|
||||
|
||||
return new Document("$geoNear", command);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
|
||||
|
||||
graphLookup.put("startWith", mappedStartWith.size() == 1 ? mappedStartWith.iterator().next() : mappedStartWith);
|
||||
|
||||
graphLookup.put("connectFromField", connectFrom.getName());
|
||||
graphLookup.put("connectToField", connectTo.getName());
|
||||
graphLookup.put("connectFromField", connectFrom.getTarget());
|
||||
graphLookup.put("connectToField", connectTo.getTarget());
|
||||
graphLookup.put("as", as.getName());
|
||||
|
||||
if (maxDepth != null) {
|
||||
@@ -112,7 +112,7 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
|
||||
}
|
||||
|
||||
if (depthField != null) {
|
||||
graphLookup.put("depthField", depthField.getName());
|
||||
graphLookup.put("depthField", depthField.getTarget());
|
||||
}
|
||||
|
||||
if (restrictSearchWithMatch != null) {
|
||||
|
||||
@@ -1204,6 +1204,18 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
return this.operation.and(DateOperators.DateToString.dateOf(getRequiredName()).toString(format));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a {@code $dateToString} expression that takes the date representation of the previously mentioned field
|
||||
* using the server default format. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public ProjectionOperationBuilder dateAsFormattedString() {
|
||||
return this.operation.and(DateOperators.DateToString.dateOf(getRequiredName()).defaultFormat());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a {@code $let} expression that binds variables for use in the specified expression, and returns the
|
||||
* result of the expression.
|
||||
|
||||
@@ -350,8 +350,7 @@ public class StringOperators {
|
||||
* @return
|
||||
*/
|
||||
public StrLenBytes length() {
|
||||
return usesFieldRef() ? StrLenBytes.stringLengthOf(fieldReference)
|
||||
: StrLenBytes.stringLengthOf(expression);
|
||||
return usesFieldRef() ? StrLenBytes.stringLengthOf(fieldReference) : StrLenBytes.stringLengthOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,6 +390,132 @@ public class StringOperators {
|
||||
return usesFieldRef() ? SubstrCP.valueOf(fieldReference) : SubstrCP.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
|
||||
* from the beginning and end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link Trim}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public Trim trim() {
|
||||
return createTrim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
|
||||
* character sequence from the beginning and end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public Trim trim(String chars) {
|
||||
return trim().chars(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
|
||||
* sequence resulting from the given {@link AggregationExpression} from the beginning and end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public Trim trim(AggregationExpression expression) {
|
||||
return trim().charsOf(expression);
|
||||
}
|
||||
|
||||
private Trim createTrim() {
|
||||
return usesFieldRef() ? Trim.valueOf(fieldReference) : Trim.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
|
||||
* from the beginning. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link LTrim}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public LTrim ltrim() {
|
||||
return createLTrim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
|
||||
* character sequence from the beginning. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public LTrim ltrim(String chars) {
|
||||
return ltrim().chars(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
|
||||
* sequence resulting from the given {@link AggregationExpression} from the beginning. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public LTrim ltrim(AggregationExpression expression) {
|
||||
return ltrim().charsOf(expression);
|
||||
}
|
||||
|
||||
private LTrim createLTrim() {
|
||||
return usesFieldRef() ? LTrim.valueOf(fieldReference) : LTrim.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
|
||||
* from the end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link RTrim}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public RTrim rtrim() {
|
||||
return createRTrim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
|
||||
* character sequence from the end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public RTrim rtrim(String chars) {
|
||||
return rtrim().chars(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
|
||||
* sequence resulting from the given {@link AggregationExpression} from the end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public RTrim rtrim(AggregationExpression expression) {
|
||||
return rtrim().charsOf(expression);
|
||||
}
|
||||
|
||||
private RTrim createRTrim() {
|
||||
return usesFieldRef() ? RTrim.valueOf(fieldReference) : RTrim.valueOf(expression);
|
||||
}
|
||||
|
||||
private boolean usesFieldRef() {
|
||||
return fieldReference != null;
|
||||
}
|
||||
@@ -1072,4 +1197,257 @@ public class StringOperators {
|
||||
return new SubstrCP(append(Arrays.asList(start, nrOfChars)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $trim} which removes whitespace or the specified characters from the
|
||||
* beginning and end of a string. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class Trim extends AbstractAggregationExpression {
|
||||
|
||||
private Trim(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Trim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public static Trim valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new Trim(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Trim} using the result of the provided {@link AggregationExpression} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public static Trim valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new Trim(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the character(s) to trim from the beginning.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public Trim chars(String chars) {
|
||||
|
||||
Assert.notNull(chars, "Chars must not be null!");
|
||||
return new Trim(append("chars", chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the character values to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public Trim charsOf(String fieldReference) {
|
||||
return new Trim(append("chars", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public Trim charsOf(AggregationExpression expression) {
|
||||
return new Trim(append("chars", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove whitespace or the specified characters from the beginning of a string.<br />
|
||||
*
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim left() {
|
||||
return new LTrim(argumentMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove whitespace or the specified characters from the end of a string.<br />
|
||||
*
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim right() {
|
||||
return new RTrim(argumentMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$trim";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $ltrim} which removes whitespace or the specified characters from the
|
||||
* beginning of a string. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class LTrim extends AbstractAggregationExpression {
|
||||
|
||||
private LTrim(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link LTrim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public static LTrim valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new LTrim(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link LTrim} using the result of the provided {@link AggregationExpression} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public static LTrim valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new LTrim(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the character(s) to trim from the beginning.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim chars(String chars) {
|
||||
|
||||
Assert.notNull(chars, "Chars must not be null!");
|
||||
return new LTrim(append("chars", chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the character values to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim charsOf(String fieldReference) {
|
||||
return new LTrim(append("chars", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim charsOf(AggregationExpression expression) {
|
||||
return new LTrim(append("chars", expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$ltrim";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $rtrim} which removes whitespace or the specified characters from the end
|
||||
* of a string. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static class RTrim extends AbstractAggregationExpression {
|
||||
|
||||
private RTrim(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RTrim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public static RTrim valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new RTrim(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RTrim} using the result of the provided {@link AggregationExpression} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public static RTrim valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new RTrim(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the character(s) to trim from the end.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim chars(String chars) {
|
||||
|
||||
Assert.notNull(chars, "Chars must not be null!");
|
||||
return new RTrim(append("chars", chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the character values to trim from the end.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim charsOf(String fieldReference) {
|
||||
return new RTrim(append("chars", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the end.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim charsOf(AggregationExpression expression) {
|
||||
return new RTrim(append("chars", expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$rtrim";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
@@ -40,7 +42,7 @@ import com.mongodb.DBObject;
|
||||
*/
|
||||
class DocumentAccessor {
|
||||
|
||||
private final Bson document;
|
||||
private final @Getter Bson document;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DocumentAccessor} for the given {@link Document}.
|
||||
@@ -137,15 +139,21 @@ class DocumentAccessor {
|
||||
|
||||
String fieldName = property.getFieldName();
|
||||
|
||||
if (this.document instanceof Document) {
|
||||
|
||||
if (((Document) this.document).containsKey(fieldName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} else if (this.document instanceof DBObject) {
|
||||
|
||||
if (((DBObject) this.document).containsField(fieldName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
|
||||
if (this.document instanceof Document) {
|
||||
return ((Document) this.document).containsKey(fieldName);
|
||||
}
|
||||
|
||||
if (this.document instanceof DBObject) {
|
||||
return ((DBObject) this.document).containsField(fieldName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] parts = fieldName.split("\\.");
|
||||
|
||||
@@ -15,17 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
@@ -42,6 +33,7 @@ import org.springframework.data.convert.TypeMapper;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
@@ -58,6 +50,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -215,6 +208,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
|
||||
if (conversions.hasCustomReadTarget(bson.getClass(), rawType)) {
|
||||
return conversionService.convert(bson, rawType);
|
||||
} else if (bson instanceof DBObject && conversions.hasCustomReadTarget(Document.class, rawType)) {
|
||||
return conversionService.convert(new Document(BsonUtils.asMap(bson)), rawType);
|
||||
}
|
||||
|
||||
if (DBObject.class.isAssignableFrom(rawType)) {
|
||||
@@ -250,11 +245,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
throw new MappingException(String.format(INVALID_TYPE_TO_READ, target, typeToUse.getType()));
|
||||
}
|
||||
|
||||
return read((MongoPersistentEntity<S>) mappingContext.getRequiredPersistentEntity(typeToUse), target, path);
|
||||
return read((MongoPersistentEntity<S>) entity, target, path);
|
||||
}
|
||||
|
||||
private ParameterValueProvider<MongoPersistentProperty> getParameterProvider(MongoPersistentEntity<?> entity,
|
||||
Bson source, DefaultSpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
DocumentAccessor source, DefaultSpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
|
||||
MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator, path);
|
||||
PersistentEntityParameterValueProvider<MongoPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
|
||||
@@ -267,8 +262,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
private <S extends Object> S read(final MongoPersistentEntity<S> entity, final Document bson, final ObjectPath path) {
|
||||
|
||||
DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
|
||||
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
|
||||
|
||||
PreferredConstructor<S, MongoPersistentProperty> constructor = entity.getPersistenceConstructor();
|
||||
|
||||
ParameterValueProvider<MongoPersistentProperty> provider = constructor != null && constructor.hasParameters() //
|
||||
? getParameterProvider(entity, documentAccessor, evaluator, path) //
|
||||
: NoOpParameterValueProvider.INSTANCE;
|
||||
|
||||
ParameterValueProvider<MongoPersistentProperty> provider = getParameterProvider(entity, bson, evaluator, path);
|
||||
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
|
||||
S instance = instantiator.createInstance(entity, provider);
|
||||
|
||||
@@ -276,7 +277,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
conversionService);
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
|
||||
|
||||
// make sure id property is set before all other properties
|
||||
Object idValue = null;
|
||||
@@ -292,9 +292,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(documentAccessor, evaluator,
|
||||
currentPath);
|
||||
|
||||
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(bson, currentPath, evaluator,
|
||||
MappingMongoConverter.this);
|
||||
readProperties(entity, accessor, idProperty, documentAccessor, valueProvider, callback);
|
||||
readProperties(entity, accessor, idProperty, documentAccessor, valueProvider, currentPath, evaluator);
|
||||
|
||||
return instance;
|
||||
}
|
||||
@@ -310,10 +308,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
|
||||
private void readProperties(MongoPersistentEntity<?> entity, PersistentPropertyAccessor accessor,
|
||||
@Nullable MongoPersistentProperty idProperty, DocumentAccessor documentAccessor,
|
||||
MongoDbPropertyValueProvider valueProvider, DbRefResolverCallback callback) {
|
||||
MongoDbPropertyValueProvider valueProvider, ObjectPath currentPath, SpELExpressionEvaluator evaluator) {
|
||||
|
||||
DbRefResolverCallback callback = null;
|
||||
|
||||
for (MongoPersistentProperty prop : entity) {
|
||||
|
||||
if (callback == null) {
|
||||
callback = getDbRefResolverCallback(documentAccessor, currentPath, evaluator);
|
||||
}
|
||||
|
||||
if (prop.isAssociation() && !entity.isConstructorArgument(prop)) {
|
||||
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
|
||||
continue;
|
||||
@@ -336,6 +340,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
}
|
||||
|
||||
private DbRefResolverCallback getDbRefResolverCallback(DocumentAccessor documentAccessor, ObjectPath currentPath,
|
||||
SpELExpressionEvaluator evaluator) {
|
||||
|
||||
return new DefaultDbRefResolverCallback(documentAccessor.getDocument(), currentPath, evaluator,
|
||||
MappingMongoConverter.this);
|
||||
}
|
||||
|
||||
private void readAssociation(Association<MongoPersistentProperty> association, PersistentPropertyAccessor accessor,
|
||||
DocumentAccessor documentAccessor, DbRefProxyHandler handler, DbRefResolverCallback callback) {
|
||||
|
||||
@@ -393,12 +404,23 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
removeFromMap(bson, "_id");
|
||||
}
|
||||
|
||||
boolean handledByCustomConverter = conversions.hasCustomWriteTarget(entityType, Document.class);
|
||||
if (!handledByCustomConverter && !(bson instanceof Collection)) {
|
||||
if (requiresTypeHint(entityType)) {
|
||||
typeMapper.writeType(type, bson);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given type requires a type hint (aka {@literal _class} attribute) when writing to the document.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return {@literal true} if not a simple type, {@link Collection} or type with custom write target.
|
||||
*/
|
||||
private boolean requiresTypeHint(Class<?> type) {
|
||||
|
||||
return !conversions.isSimpleType(type) && !ClassUtils.isAssignable(Collection.class, type)
|
||||
&& !conversions.hasCustomWriteTarget(type, Document.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal write conversion method which should be used for nested invocations.
|
||||
*
|
||||
@@ -556,7 +578,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
return;
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass())
|
||||
MongoPersistentEntity<?> entity = isSubTypeOf(obj.getClass(), prop.getType())
|
||||
? mappingContext.getRequiredPersistentEntity(obj.getClass())
|
||||
: mappingContext.getRequiredPersistentEntity(type);
|
||||
|
||||
@@ -568,10 +590,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
accessor.put(prop, document);
|
||||
}
|
||||
|
||||
private boolean isSubtype(Class<?> left, Class<?> right) {
|
||||
return left.isAssignableFrom(right) && !left.equals(right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a
|
||||
* {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element
|
||||
@@ -661,11 +679,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @param sink the {@link Collection} to write to.
|
||||
* @return
|
||||
*/
|
||||
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type, Collection<?> sink) {
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type,
|
||||
Collection<?> sink) {
|
||||
|
||||
TypeInformation<?> componentType = null;
|
||||
|
||||
List<Object> collection = sink instanceof List ? (List) sink : new ArrayList<>(sink);
|
||||
List<Object> collection = sink instanceof List ? (List<Object>) sink : new ArrayList<>(sink);
|
||||
|
||||
if (type != null) {
|
||||
componentType = type.getComponentType();
|
||||
@@ -870,7 +890,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
|
||||
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
|
||||
|
||||
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
|
||||
return value;
|
||||
@@ -946,7 +966,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
Assert.notNull(path, "Object path must not be null!");
|
||||
|
||||
Class<?> collectionType = targetType.getType();
|
||||
collectionType = Collection.class.isAssignableFrom(collectionType) //
|
||||
collectionType = isSubTypeOf(collectionType, Collection.class) //
|
||||
? collectionType //
|
||||
: List.class;
|
||||
|
||||
@@ -980,13 +1000,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
items.add(read(componentType, (BasicDBObject) element, path));
|
||||
} else {
|
||||
|
||||
if (element instanceof Collection) {
|
||||
if (!Object.class.equals(rawComponentType) && element instanceof Collection) {
|
||||
if (!rawComponentType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawComponentType)) {
|
||||
throw new MappingException(
|
||||
String.format(INCOMPATIBLE_TYPES, element, element.getClass(), rawComponentType, path));
|
||||
}
|
||||
}
|
||||
|
||||
if (element instanceof List) {
|
||||
items.add(readCollectionOrArray(componentType, (Collection<Object>) element, path));
|
||||
} else {
|
||||
@@ -1521,6 +1540,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given type is a sub type of the given reference, i.e. assignable but not the exact same type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param reference must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static boolean isSubTypeOf(Class<?> type, Class<?> reference) {
|
||||
return !type.equals(reference) && reference.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker class used to indicate we have a non root document object here that might be used within an update - so we
|
||||
* need to preserve type hints for potential nested elements but need to remove it on top level.
|
||||
@@ -1531,4 +1561,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
static class NestedDocument {
|
||||
|
||||
}
|
||||
|
||||
enum NoOpParameterValueProvider implements ParameterValueProvider<MongoPersistentProperty> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public <T> T getParameterValue(Parameter<T, MongoPersistentProperty> parameter) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 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.
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -45,26 +43,33 @@ class ObjectPath {
|
||||
|
||||
static final ObjectPath ROOT = new ObjectPath();
|
||||
|
||||
private final ObjectPathItem[] items;
|
||||
private final @Nullable ObjectPath parent;
|
||||
private final @Nullable Object object;
|
||||
private final @Nullable Object idValue;
|
||||
private final String collection;
|
||||
|
||||
private ObjectPath() {
|
||||
this.items = new ObjectPathItem[0];
|
||||
|
||||
this.parent = null;
|
||||
this.object = null;
|
||||
this.idValue = null;
|
||||
this.collection = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} by adding the provided
|
||||
* {@link ObjectPathItem} to it.
|
||||
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} and adding the provided path values.
|
||||
*
|
||||
* @param parent must not be {@literal null}.
|
||||
* @param item
|
||||
* @param collection
|
||||
* @param idValue
|
||||
* @param collection
|
||||
*/
|
||||
private ObjectPath(ObjectPath parent, ObjectPath.ObjectPathItem item) {
|
||||
private ObjectPath(ObjectPath parent, Object object, @Nullable Object idValue, String collection) {
|
||||
|
||||
ObjectPathItem[] items = new ObjectPathItem[parent.items.length + 1];
|
||||
System.arraycopy(parent.items, 0, items, 0, parent.items.length);
|
||||
items[parent.items.length] = item;
|
||||
|
||||
this.items = items;
|
||||
this.parent = parent;
|
||||
this.object = object;
|
||||
this.idValue = idValue;
|
||||
this.collection = collection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,8 +85,7 @@ class ObjectPath {
|
||||
Assert.notNull(object, "Object must not be null!");
|
||||
Assert.notNull(entity, "MongoPersistentEntity must not be null!");
|
||||
|
||||
ObjectPathItem item = new ObjectPathItem(object, id, entity.getCollection());
|
||||
return new ObjectPath(this, item);
|
||||
return new ObjectPath(this, object, id, entity.getCollection());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,15 +104,15 @@ class ObjectPath {
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
Assert.hasText(collection, "Collection name must not be null!");
|
||||
|
||||
for (ObjectPathItem item : items) {
|
||||
for (ObjectPath current = this; current != null; current = current.parent) {
|
||||
|
||||
Object object = item.getObject();
|
||||
Object object = current.getObject();
|
||||
|
||||
if (object == null || item.getIdValue() == null) {
|
||||
if (object == null || current.getIdValue() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())) {
|
||||
if (collection.equals(current.getCollection()) && id.equals(current.getIdValue())) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
@@ -133,15 +137,15 @@ class ObjectPath {
|
||||
Assert.hasText(collection, "Collection name must not be null!");
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
for (ObjectPathItem item : items) {
|
||||
for (ObjectPath current = this; current != null; current = current.parent) {
|
||||
|
||||
Object object = item.getObject();
|
||||
Object object = current.getObject();
|
||||
|
||||
if (object == null || item.getIdValue() == null) {
|
||||
if (object == null || current.getIdValue() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())
|
||||
if (collection.equals(current.getCollection()) && id.equals(current.getIdValue())
|
||||
&& ClassUtils.isAssignable(type, object.getClass())) {
|
||||
return type.cast(object);
|
||||
}
|
||||
@@ -157,7 +161,21 @@ class ObjectPath {
|
||||
*/
|
||||
@Nullable
|
||||
Object getCurrentObject() {
|
||||
return items.length == 0 ? null : items[items.length - 1].getObject();
|
||||
return getObject();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getIdValue() {
|
||||
return idValue;
|
||||
}
|
||||
|
||||
private String getCollection() {
|
||||
return collection;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -167,31 +185,16 @@ class ObjectPath {
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
if (items.length == 0) {
|
||||
if (parent == null) {
|
||||
return "[empty]";
|
||||
}
|
||||
|
||||
List<String> strings = new ArrayList<>(items.length);
|
||||
List<String> strings = new ArrayList<>();
|
||||
|
||||
for (ObjectPathItem item : items) {
|
||||
strings.add(ObjectUtils.nullSafeToString(item.object));
|
||||
for (ObjectPath current = this; current != null; current = current.parent) {
|
||||
strings.add(ObjectUtils.nullSafeToString(current.getObject()));
|
||||
}
|
||||
|
||||
return StringUtils.collectionToDelimitedString(strings, " -> ");
|
||||
}
|
||||
|
||||
/**
|
||||
* An item in an {@link ObjectPath}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
private static class ObjectPathItem {
|
||||
|
||||
Object object;
|
||||
@Nullable Object idValue;
|
||||
String collection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2017 the original author or authors.
|
||||
* Copyright 2011-2018 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.
|
||||
@@ -24,11 +24,14 @@ import org.springframework.lang.Nullable;
|
||||
* {@link MongoPersistentProperty} caching access to {@link #isIdProperty()} and {@link #getFieldName()}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty {
|
||||
|
||||
private @Nullable Boolean isIdProperty;
|
||||
private @Nullable Boolean isAssociation;
|
||||
private @Nullable boolean dbRefResolved;
|
||||
private @Nullable DBRef dbref;
|
||||
private @Nullable String fieldName;
|
||||
private @Nullable Boolean usePropertyAccess;
|
||||
private @Nullable Boolean isTransient;
|
||||
@@ -36,8 +39,7 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
|
||||
/**
|
||||
* Creates a new {@link CachingMongoPersistentProperty}.
|
||||
*
|
||||
* @param field
|
||||
* @param propertyDescriptor
|
||||
* @param property
|
||||
* @param owner
|
||||
* @param simpleTypeHolder
|
||||
* @param fieldNamingStrategy
|
||||
@@ -67,9 +69,11 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
|
||||
*/
|
||||
@Override
|
||||
public boolean isAssociation() {
|
||||
|
||||
if (this.isAssociation == null) {
|
||||
this.isAssociation = super.isAssociation();
|
||||
}
|
||||
|
||||
return this.isAssociation;
|
||||
}
|
||||
|
||||
@@ -114,4 +118,28 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
|
||||
|
||||
return this.isTransient;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#isDbReference()
|
||||
*/
|
||||
@Override
|
||||
public boolean isDbReference() {
|
||||
return getDBRef() != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#getDBRef()
|
||||
*/
|
||||
@Override
|
||||
public DBRef getDBRef() {
|
||||
|
||||
if (!dbRefResolved) {
|
||||
this.dbref = super.getDBRef();
|
||||
this.dbRefResolved = true;
|
||||
}
|
||||
|
||||
return this.dbref;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.MapReduceCommand;
|
||||
import com.mongodb.MapReduceCommand.OutputType;
|
||||
import com.mongodb.client.model.MapReduceAction;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
@@ -295,6 +297,37 @@ public class MapReduceOptions {
|
||||
return collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link MapReduceAction} derived from {@link com.mongodb.MapReduceCommand.OutputType}.
|
||||
*
|
||||
* @return the mapped action or {@literal null} if the action maps to inline output.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
@Nullable
|
||||
public MapReduceAction getMapReduceAction() {
|
||||
|
||||
switch (outputType) {
|
||||
case MERGE:
|
||||
return MapReduceAction.MERGE;
|
||||
case REDUCE:
|
||||
return MapReduceAction.REDUCE;
|
||||
case REPLACE:
|
||||
return MapReduceAction.REPLACE;
|
||||
case INLINE:
|
||||
return null;
|
||||
default:
|
||||
throw new IllegalStateException(String.format("Unknown output type %s for map reduce command.", outputType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if {@link OutputType#INLINE} is used.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public boolean usesInlineOutput() {
|
||||
return OutputType.INLINE.equals(outputType);
|
||||
}
|
||||
|
||||
public Document getOptionsObject() {
|
||||
|
||||
Document cmd = new Document();
|
||||
@@ -328,7 +361,7 @@ public class MapReduceOptions {
|
||||
|
||||
Document out = new Document();
|
||||
|
||||
switch (outputType) {
|
||||
switch (getOutputType()) {
|
||||
case INLINE:
|
||||
out.put("inline", 1);
|
||||
break;
|
||||
|
||||
@@ -890,18 +890,14 @@ public class Update {
|
||||
/**
|
||||
* Forces values to be added at the given {@literal position}.
|
||||
*
|
||||
* @param position needs to be greater than or equal to zero.
|
||||
* @param position the position offset. As of MongoDB 3.6 use a negative value to indicate starting from the end,
|
||||
* counting (but not including) the last element of the array.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.7
|
||||
*/
|
||||
public PushOperatorBuilder atPosition(int position) {
|
||||
|
||||
if (position < 0) {
|
||||
throw new IllegalArgumentException("Position must be greater than or equal to zero.");
|
||||
}
|
||||
|
||||
this.modifiers.addModifier(new PositionModifier(position));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,13 +44,14 @@ import com.mongodb.client.gridfs.model.GridFSUploadOptions;
|
||||
|
||||
/**
|
||||
* {@link GridFsOperations} implementation to store content into MongoDB GridFS.
|
||||
*
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Philipp Schneider
|
||||
* @author Thomas Darimont
|
||||
* @author Martin Baumgartner
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Niklas Helge Hanft
|
||||
*/
|
||||
public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver {
|
||||
|
||||
@@ -62,7 +63,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
|
||||
|
||||
/**
|
||||
* Creates a new {@link GridFsTemplate} using the given {@link MongoDbFactory} and {@link MongoConverter}.
|
||||
*
|
||||
*
|
||||
* @param dbFactory must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
*/
|
||||
@@ -72,7 +73,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
|
||||
|
||||
/**
|
||||
* Creates a new {@link GridFsTemplate} using the given {@link MongoDbFactory} and {@link MongoConverter}.
|
||||
*
|
||||
*
|
||||
* @param dbFactory must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param bucket
|
||||
@@ -228,7 +229,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
|
||||
public GridFsResource getResource(String location) {
|
||||
|
||||
GridFSFile file = findOne(query(whereFilename().is(location)));
|
||||
return file != null ? new GridFsResource(file, getGridFs().openDownloadStream(location)) : null;
|
||||
return file != null ? new GridFsResource(file, getGridFs().openDownloadStream(file.getObjectId())) : null;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -246,13 +247,13 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
|
||||
if (path.isPattern()) {
|
||||
|
||||
GridFSFindIterable files = find(query(whereFilename().regex(path.toRegex())));
|
||||
List<GridFsResource> resources = new ArrayList<GridFsResource>();
|
||||
List<GridFsResource> resources = new ArrayList<>();
|
||||
|
||||
for (GridFSFile file : files) {
|
||||
resources.add(new GridFsResource(file, getGridFs().openDownloadStream(file.getFilename())));
|
||||
resources.add(new GridFsResource(file, getGridFs().openDownloadStream(file.getObjectId())));
|
||||
}
|
||||
|
||||
return resources.toArray(new GridFsResource[resources.size()]);
|
||||
return resources.toArray(new GridFsResource[0]);
|
||||
}
|
||||
|
||||
return new GridFsResource[] { getResource(locationPattern) };
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.data.repository.query.QueryByExampleExecutor;
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
* @author Khaled Baklouti
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface MongoRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
|
||||
@@ -39,7 +40,7 @@ public interface MongoRepository<T, ID> extends PagingAndSortingRepository<T, ID
|
||||
* @see org.springframework.data.repository.CrudRepository#saveAll(java.lang.Iterable)
|
||||
*/
|
||||
@Override
|
||||
<S extends T> List<S> saveAll(Iterable<S> entites);
|
||||
<S extends T> List<S> saveAll(Iterable<S> entities);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecu
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -150,6 +149,8 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all();
|
||||
} else if (isCountQuery()) {
|
||||
return (q, t, c) -> operation.matching(q).count();
|
||||
} else if (isExistsQuery()) {
|
||||
return (q, t, c) -> operation.matching(q).exists();
|
||||
} else {
|
||||
return (q, t, c) -> {
|
||||
|
||||
@@ -204,6 +205,14 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
*/
|
||||
protected abstract boolean isCountQuery();
|
||||
|
||||
/**
|
||||
* Returns whether the query should get an exists projection applied.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0.9
|
||||
*/
|
||||
protected abstract boolean isExistsQuery();
|
||||
|
||||
/**
|
||||
* Return weather the query should delete matching documents.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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.repository.query;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* Utility class containing methods to interact with boolean values.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0.9
|
||||
*/
|
||||
@UtilityClass
|
||||
class BooleanUtil {
|
||||
|
||||
/**
|
||||
* Count the number of {@literal true} values.
|
||||
*
|
||||
* @param values
|
||||
* @return the number of values that are {@literal true}.
|
||||
*/
|
||||
static int countBooleanTrueValues(boolean... values) {
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (boolean value : values) {
|
||||
|
||||
if (value) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,19 @@
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -206,6 +209,7 @@ class ExpressionEvaluatingParameterBinder {
|
||||
* @param binding must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private String getParameterValueForBinding(MongoParameterAccessor accessor, MongoParameters parameters,
|
||||
ParameterBinding binding) {
|
||||
|
||||
@@ -222,43 +226,7 @@ class ExpressionEvaluatingParameterBinder {
|
||||
return binding.isExpression() ? JSON.serialize(value) : QuotedString.unquote(JSON.serialize(value));
|
||||
}
|
||||
|
||||
if (value instanceof byte[]) {
|
||||
|
||||
if (binding.isQuoted()) {
|
||||
return DatatypeConverter.printBase64Binary((byte[]) value);
|
||||
}
|
||||
|
||||
return encode(new Binary((byte[]) value), BinaryCodec::new);
|
||||
}
|
||||
|
||||
if (value instanceof UUID) {
|
||||
|
||||
if (binding.isQuoted()) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
return encode((UUID) value, UuidCodec::new);
|
||||
}
|
||||
|
||||
return JSON.serialize(value);
|
||||
}
|
||||
|
||||
private <T> String encode(T value, Supplier<Codec<T>> defaultCodec) {
|
||||
|
||||
Codec<T> codec;
|
||||
|
||||
try {
|
||||
codec = codecRegistry.get((Class<T>) value.getClass());
|
||||
} catch (CodecConfigurationException exception) {
|
||||
codec = defaultCodec.get();
|
||||
}
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
codec.encode(new JsonWriter(writer), value, null);
|
||||
writer.flush();
|
||||
|
||||
return writer.toString();
|
||||
return EncodableValue.create(value).encode(codecRegistry, binding.isQuoted());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -480,4 +448,230 @@ class ExpressionEvaluatingParameterBinder {
|
||||
return quoted.substring(1, quoted.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object encapsulating a bindable value, that can be encoded to be represented as JSON (BSON).
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
abstract static class EncodableValue {
|
||||
|
||||
/**
|
||||
* Obtain a {@link EncodableValue} given {@code value}.
|
||||
*
|
||||
* @param value the value to encode, may be {@literal null}.
|
||||
* @return the {@link EncodableValue} for {@code value}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static EncodableValue create(@Nullable Object value) {
|
||||
|
||||
if (value instanceof byte[]) {
|
||||
return new BinaryValue((byte[]) value);
|
||||
}
|
||||
|
||||
if (value instanceof UUID) {
|
||||
return new UuidValue((UUID) value);
|
||||
}
|
||||
|
||||
if (value instanceof Collection) {
|
||||
|
||||
Collection<?> collection = (Collection<?>) value;
|
||||
Class<?> commonElement = CollectionUtils.findCommonElementType(collection);
|
||||
|
||||
if (commonElement != null) {
|
||||
|
||||
if (UUID.class.isAssignableFrom(commonElement)) {
|
||||
return new UuidCollection((Collection<UUID>) value);
|
||||
}
|
||||
|
||||
if (byte[].class.isAssignableFrom(commonElement)) {
|
||||
return new BinaryCollectionValue((Collection<byte[]>) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ObjectValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the encapsulated value.
|
||||
*
|
||||
* @param provider
|
||||
* @param quoted
|
||||
* @return
|
||||
*/
|
||||
public abstract String encode(CodecRegistry codecRegistry, boolean quoted);
|
||||
|
||||
/**
|
||||
* Encode a {@code value} to JSON.
|
||||
*
|
||||
* @param provider
|
||||
* @param value
|
||||
* @param defaultCodec
|
||||
* @param <V>
|
||||
* @return
|
||||
*/
|
||||
protected <V> String encode(CodecRegistry codecRegistry, V value, Supplier<Codec<V>> defaultCodec) {
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
doEncode(codecRegistry, writer, value, defaultCodec);
|
||||
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a {@link Collection} to JSON and potentially apply a {@link Function mapping function} before encoding.
|
||||
*
|
||||
* @param provider
|
||||
* @param value
|
||||
* @param mappingFunction
|
||||
* @param defaultCodec
|
||||
* @param <I> Input value type.
|
||||
* @param <V> Target type.
|
||||
* @return
|
||||
*/
|
||||
protected <I, V> String encodeCollection(CodecRegistry codecRegistry, Iterable<I> value,
|
||||
Function<I, V> mappingFunction, Supplier<Codec<V>> defaultCodec) {
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
writer.append("[");
|
||||
value.forEach(it -> {
|
||||
|
||||
if (writer.getBuffer().length() > 1) {
|
||||
writer.append(", ");
|
||||
}
|
||||
|
||||
doEncode(codecRegistry, writer, mappingFunction.apply(it), defaultCodec);
|
||||
});
|
||||
|
||||
writer.append("]");
|
||||
writer.flush();
|
||||
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <V> void doEncode(CodecRegistry codecRegistry, StringWriter writer, V value,
|
||||
Supplier<Codec<V>> defaultCodec) {
|
||||
|
||||
Codec<V> codec = getCodec(codecRegistry, (Class<V>) value.getClass(), defaultCodec);
|
||||
|
||||
JsonWriter jsonWriter = new JsonWriter(writer);
|
||||
codec.encode(jsonWriter, value, null);
|
||||
jsonWriter.flush();
|
||||
}
|
||||
|
||||
private <T> Codec<T> getCodec(CodecRegistry codecRegistry, Class<T> type, Supplier<Codec<T>> defaultCodec) {
|
||||
|
||||
try {
|
||||
return codecRegistry.get(type);
|
||||
} catch (CodecConfigurationException exception) {
|
||||
return defaultCodec.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EncodableValue} for {@code byte[]} to render to {@literal $binary}.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class BinaryValue extends EncodableValue {
|
||||
|
||||
private final byte[] value;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
|
||||
*/
|
||||
@Override
|
||||
public String encode(CodecRegistry codecRegistry, boolean quoted) {
|
||||
|
||||
if (quoted) {
|
||||
return DatatypeConverter.printBase64Binary(this.value);
|
||||
}
|
||||
|
||||
return encode(codecRegistry, new Binary(this.value), BinaryCodec::new);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EncodableValue} for {@link Collection} containing only {@code byte[]} items to render to a BSON list
|
||||
* containing {@literal $binary}.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class BinaryCollectionValue extends EncodableValue {
|
||||
|
||||
private final Collection<byte[]> value;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
|
||||
*/
|
||||
@Override
|
||||
public String encode(CodecRegistry codecRegistry, boolean quoted) {
|
||||
return encodeCollection(codecRegistry, this.value, Binary::new, BinaryCodec::new);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EncodableValue} for {@link UUID} to render to {@literal $binary}.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class UuidValue extends EncodableValue {
|
||||
|
||||
private final UUID value;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
|
||||
*/
|
||||
@Override
|
||||
public String encode(CodecRegistry codecRegistry, boolean quoted) {
|
||||
|
||||
if (quoted) {
|
||||
return this.value.toString();
|
||||
}
|
||||
|
||||
return encode(codecRegistry, this.value, UuidCodec::new);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EncodableValue} for {@link Collection} containing only {@link UUID} items to render to a BSON list
|
||||
* containing {@literal $binary}.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class UuidCollection extends EncodableValue {
|
||||
|
||||
private final Collection<UUID> value;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
|
||||
*/
|
||||
@Override
|
||||
public String encode(CodecRegistry codecRegistry, boolean quoted) {
|
||||
return encodeCollection(codecRegistry, this.value, Function.identity(), UuidCodec::new);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback-{@link EncodableValue} for {@link Object}-typed values.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class ObjectValue extends EncodableValue {
|
||||
|
||||
private final @Nullable Object value;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.bson.codecs.configuration.CodecRegistry, boolean)
|
||||
*/
|
||||
@Override
|
||||
public String encode(CodecRegistry codecRegistry, boolean quoted) {
|
||||
return JSON.serialize(this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +134,15 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
|
||||
return tree.isCountProjection();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isExistsQuery()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return tree.isExistsProjection();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()
|
||||
|
||||
@@ -40,13 +40,14 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
|
||||
private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!";
|
||||
private static final String COUNT_EXISTS_AND_DELETE = "Manually defined query for %s cannot be a count and exists or delete query at the same time!";
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveStringBasedMongoQuery.class);
|
||||
private static final ParameterBindingParser BINDING_PARSER = ParameterBindingParser.INSTANCE;
|
||||
|
||||
private final String query;
|
||||
private final String fieldSpec;
|
||||
private final boolean isCountQuery;
|
||||
private final boolean isExistsQuery;
|
||||
private final boolean isDeleteQuery;
|
||||
private final List<ParameterBinding> queryParameterBindings;
|
||||
private final List<ParameterBinding> fieldSpecParameterBindings;
|
||||
@@ -92,11 +93,23 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
this.fieldSpec = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings(
|
||||
method.getFieldSpecification(), this.fieldSpecParameterBindings);
|
||||
|
||||
this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false;
|
||||
this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false;
|
||||
if (method.hasAnnotatedQuery()) {
|
||||
|
||||
if (isCountQuery && isDeleteQuery) {
|
||||
throw new IllegalArgumentException(String.format(COUND_AND_DELETE, method));
|
||||
org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation();
|
||||
|
||||
this.isCountQuery = queryAnnotation.count();
|
||||
this.isExistsQuery = queryAnnotation.exists();
|
||||
this.isDeleteQuery = queryAnnotation.delete();
|
||||
|
||||
if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) {
|
||||
throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
this.isCountQuery = false;
|
||||
this.isExistsQuery = false;
|
||||
this.isDeleteQuery = false;
|
||||
}
|
||||
|
||||
this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider);
|
||||
@@ -132,6 +145,15 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
return isCountQuery;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isExistsQuery()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return isExistsQuery;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()
|
||||
@@ -150,4 +172,9 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
|
||||
boolean isDeleteQuery) {
|
||||
return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import org.bson.BSON;
|
||||
import org.bson.Document;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -37,6 +39,7 @@ import org.springframework.util.StringUtils;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.DBRef;
|
||||
import com.mongodb.util.JSON;
|
||||
import com.mongodb.util.JSONCallback;
|
||||
|
||||
/**
|
||||
* Query to use a plain JSON String to create the {@link Query} to actually execute.
|
||||
@@ -169,11 +172,6 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
return this.isDeleteQuery;
|
||||
}
|
||||
|
||||
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
|
||||
boolean isDeleteQuery) {
|
||||
return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isLimiting()
|
||||
@@ -183,18 +181,9 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int countBooleanValues(boolean... values) {
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (boolean value : values) {
|
||||
|
||||
if (value) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
|
||||
boolean isDeleteQuery) {
|
||||
return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,7 +227,8 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
String transformedInput = transformQueryAndCollectExpressionParametersIntoBindings(input, bindings);
|
||||
String parseableInput = makeParameterReferencesParseable(transformedInput);
|
||||
|
||||
collectParameterReferencesIntoBindings(bindings, JSON.parse(parseableInput));
|
||||
collectParameterReferencesIntoBindings(bindings,
|
||||
JSON.parse(parseableInput, new LenientPatternDecodingCallback()));
|
||||
|
||||
return transformedInput;
|
||||
}
|
||||
@@ -373,6 +363,43 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JSONCallback} with lenient handling for {@link PatternSyntaxException} falling back to a placeholder
|
||||
* {@link Pattern} for intermediate query document rendering.
|
||||
*/
|
||||
private static class LenientPatternDecodingCallback extends JSONCallback {
|
||||
|
||||
private static final Pattern EMPTY_MARKER = Pattern.compile("__Spring_Data_MongoDB_Bind_Marker__");
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.mongodb.util.JSONCallback#objectDone()
|
||||
*/
|
||||
@Override
|
||||
public Object objectDone() {
|
||||
return exceptionSwallowingStackReducingObjectDone();
|
||||
}
|
||||
|
||||
private Object exceptionSwallowingStackReducingObjectDone/*CauseWeJustNeedTheStructureNotTheActualValue*/() {
|
||||
|
||||
Object value;
|
||||
|
||||
try {
|
||||
return super.objectDone();
|
||||
} catch (PatternSyntaxException e) {
|
||||
value = EMPTY_MARKER;
|
||||
}
|
||||
|
||||
if (!isStackEmpty()) {
|
||||
_put(curName(), value);
|
||||
} else {
|
||||
value = !BSON.hasDecodeHooks() ? value : BSON.applyDecodingHooks(value);
|
||||
setRoot(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic parameter binding with name or position information.
|
||||
*
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.springframework.util.ClassUtils;
|
||||
/**
|
||||
* {@link MongoClientVersion} holds information about the used mongo-java client and is used to distinguish between
|
||||
* different versions.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
@@ -32,6 +32,9 @@ public class MongoClientVersion {
|
||||
private static final boolean IS_MONGO_34 = ClassUtils.isPresent("org.bson.types.Decimal128",
|
||||
MongoClientVersion.class.getClassLoader());
|
||||
|
||||
private static final boolean IS_MONGO_38 = ClassUtils.isPresent("com.mongodb.TransactionOptions",
|
||||
MongoClientVersion.class.getClassLoader());
|
||||
|
||||
private static final boolean IS_ASYNC_CLIENT = ClassUtils.isPresent("com.mongodb.async.client.MongoClient",
|
||||
MongoClientVersion.class.getClassLoader());
|
||||
|
||||
@@ -51,9 +54,17 @@ public class MongoClientVersion {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {lliteral true} if MongoDB Java driver is on classpath.
|
||||
* @return {@literal true} if MongoDB Java driver is on classpath.
|
||||
*/
|
||||
public static boolean isAsyncClient() {
|
||||
return IS_ASYNC_CLIENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if MongoDB Java driver version 3.8 or later is on classpath.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public static boolean isMongo38Driver() {
|
||||
return IS_MONGO_38;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public final class MongoDbErrorCodes {
|
||||
|
||||
static {
|
||||
|
||||
dataAccessResourceFailureCodes = new HashMap<Integer, String>(10);
|
||||
dataAccessResourceFailureCodes = new HashMap<>(12, 1f);
|
||||
dataAccessResourceFailureCodes.put(6, "HostUnreachable");
|
||||
dataAccessResourceFailureCodes.put(7, "HostNotFound");
|
||||
dataAccessResourceFailureCodes.put(89, "NetworkTimeout");
|
||||
@@ -52,7 +52,7 @@ public final class MongoDbErrorCodes {
|
||||
dataAccessResourceFailureCodes.put(13441, "BadOffsetInFile");
|
||||
dataAccessResourceFailureCodes.put(13640, "DataFileHeaderCorrupt");
|
||||
|
||||
dataIntegrityViolationCodes = new HashMap<Integer, String>(6);
|
||||
dataIntegrityViolationCodes = new HashMap<>(6, 1f);
|
||||
dataIntegrityViolationCodes.put(67, "CannotCreateIndex");
|
||||
dataIntegrityViolationCodes.put(68, "IndexAlreadyExists");
|
||||
dataIntegrityViolationCodes.put(85, "IndexOptionsConflict");
|
||||
@@ -60,13 +60,13 @@ public final class MongoDbErrorCodes {
|
||||
dataIntegrityViolationCodes.put(112, "WriteConflict");
|
||||
dataIntegrityViolationCodes.put(117, "ConflictingOperationInProgress");
|
||||
|
||||
duplicateKeyCodes = new HashMap<Integer, String>(3);
|
||||
duplicateKeyCodes = new HashMap<>(4, 1f);
|
||||
duplicateKeyCodes.put(3, "OBSOLETE_DuplicateKey");
|
||||
duplicateKeyCodes.put(84, "DuplicateKeyValue");
|
||||
duplicateKeyCodes.put(11000, "DuplicateKey");
|
||||
duplicateKeyCodes.put(11001, "DuplicateKey");
|
||||
|
||||
invalidDataAccessApiUsageExeption = new HashMap<Integer, String>();
|
||||
invalidDataAccessApiUsageExeption = new HashMap<>(31, 1f);
|
||||
invalidDataAccessApiUsageExeption.put(5, "GraphContainsCycle");
|
||||
invalidDataAccessApiUsageExeption.put(9, "FailedToParse");
|
||||
invalidDataAccessApiUsageExeption.put(14, "TypeMismatch");
|
||||
@@ -80,8 +80,7 @@ public final class MongoDbErrorCodes {
|
||||
invalidDataAccessApiUsageExeption.put(30, "InvalidPath");
|
||||
invalidDataAccessApiUsageExeption.put(40, "ConflictingUpdateOperators");
|
||||
invalidDataAccessApiUsageExeption.put(45, "UserDataInconsistent");
|
||||
invalidDataAccessApiUsageExeption.put(30, "DollarPrefixedFieldName");
|
||||
invalidDataAccessApiUsageExeption.put(52, "InvalidPath");
|
||||
invalidDataAccessApiUsageExeption.put(52, "DollarPrefixedFieldName");
|
||||
invalidDataAccessApiUsageExeption.put(53, "InvalidIdField");
|
||||
invalidDataAccessApiUsageExeption.put(54, "NotSingleValueField");
|
||||
invalidDataAccessApiUsageExeption.put(55, "InvalidDBRef");
|
||||
@@ -99,17 +98,17 @@ public final class MongoDbErrorCodes {
|
||||
invalidDataAccessApiUsageExeption.put(17280, "KeyTooLong");
|
||||
invalidDataAccessApiUsageExeption.put(13334, "ShardKeyTooBig");
|
||||
|
||||
permissionDeniedCodes = new HashMap<Integer, String>();
|
||||
permissionDeniedCodes = new HashMap<>(8, 1f);
|
||||
permissionDeniedCodes.put(11, "UserNotFound");
|
||||
permissionDeniedCodes.put(18, "AuthenticationFailed");
|
||||
permissionDeniedCodes.put(31, "RoleNotFound");
|
||||
permissionDeniedCodes.put(32, "RolesNotRelated");
|
||||
permissionDeniedCodes.put(33, "PrvilegeNotFound");
|
||||
permissionDeniedCodes.put(33, "PrivilegeNotFound");
|
||||
permissionDeniedCodes.put(15847, "CannotAuthenticate");
|
||||
permissionDeniedCodes.put(16704, "CannotAuthenticateToAdminDB");
|
||||
permissionDeniedCodes.put(16705, "CannotAuthenticateToAdminDB");
|
||||
|
||||
errorCodes = new HashMap<Integer, String>();
|
||||
errorCodes = new HashMap<>();
|
||||
errorCodes.putAll(dataAccessResourceFailureCodes);
|
||||
errorCodes.putAll(dataIntegrityViolationCodes);
|
||||
errorCodes.putAll(duplicateKeyCodes);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2018 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,7 +25,7 @@ import kotlin.reflect.KClass
|
||||
* @since 2.0
|
||||
*/
|
||||
fun <T : Any> ExecutableFindOperation.query(entityClass: KClass<T>): ExecutableFindOperation.ExecutableFind<T> =
|
||||
query(entityClass.java)
|
||||
query(entityClass.java)
|
||||
|
||||
/**
|
||||
* Extension for [ExecutableFindOperation.query] leveraging reified type parameters.
|
||||
@@ -35,8 +35,7 @@ fun <T : Any> ExecutableFindOperation.query(entityClass: KClass<T>): ExecutableF
|
||||
* @since 2.0
|
||||
*/
|
||||
inline fun <reified T : Any> ExecutableFindOperation.query(): ExecutableFindOperation.ExecutableFind<T> =
|
||||
query(T::class.java)
|
||||
|
||||
query(T::class.java)
|
||||
|
||||
/**
|
||||
* Extension for [ExecutableFindOperation.FindWithProjection. as] providing a [KClass] based variant.
|
||||
@@ -45,8 +44,8 @@ inline fun <reified T : Any> ExecutableFindOperation.query(): ExecutableFindOper
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
fun <T : Any> ExecutableFindOperation.FindWithProjection<T>.asType(resultType: KClass<T>): ExecutableFindOperation.FindWithQuery<T> =
|
||||
`as`(resultType.java)
|
||||
fun <T : Any> ExecutableFindOperation.FindWithProjection<*>.asType(resultType: KClass<T>): ExecutableFindOperation.FindWithQuery<T> =
|
||||
`as`(resultType.java)
|
||||
|
||||
/**
|
||||
* Extension for [ExecutableFindOperation.FindWithProjection. as] leveraging reified type parameters.
|
||||
@@ -55,7 +54,5 @@ fun <T : Any> ExecutableFindOperation.FindWithProjection<T>.asType(resultType: K
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
inline fun <reified T : Any> ExecutableFindOperation.FindWithProjection<T>.asType(): ExecutableFindOperation.FindWithQuery<T> =
|
||||
`as`(T::class.java)
|
||||
|
||||
|
||||
inline fun <reified T : Any> ExecutableFindOperation.FindWithProjection<*>.asType(): ExecutableFindOperation.FindWithQuery<T> =
|
||||
`as`(T::class.java)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2018 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.
|
||||
@@ -41,7 +41,7 @@ inline fun <reified T : Any> ReactiveFindOperation.query(): ReactiveFindOperatio
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
fun <T : Any> ReactiveFindOperation.FindWithProjection<T>.asType(resultType: KClass<T>): ReactiveFindOperation.FindWithQuery<T> =
|
||||
fun <T : Any> ReactiveFindOperation.FindWithProjection<*>.asType(resultType: KClass<T>): ReactiveFindOperation.FindWithQuery<T> =
|
||||
`as`(resultType.java)
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ fun <T : Any> ReactiveFindOperation.FindWithProjection<T>.asType(resultType: KCl
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
inline fun <reified T : Any> ReactiveFindOperation.FindWithProjection<T>.asType(): ReactiveFindOperation.FindWithQuery<T> =
|
||||
inline fun <reified T : Any> ReactiveFindOperation.FindWithProjection<*>.asType(): ReactiveFindOperation.FindWithQuery<T> =
|
||||
`as`(T::class.java)
|
||||
|
||||
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.config;
|
||||
|
||||
import static org.hamcrest.collection.IsIterableContainingInOrder.*;
|
||||
import static org.hamcrest.core.IsNull.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.Assumptions.*;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
@@ -26,6 +25,7 @@ import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.util.MongoClientVersion;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.MongoCredential;
|
||||
@@ -34,6 +34,7 @@ import com.mongodb.MongoCredential;
|
||||
* Unit tests for {@link MongoCredentialPropertyEditor}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Stephen Tyler Conrad
|
||||
*/
|
||||
public class MongoCredentialPropertyEditorUnitTests {
|
||||
|
||||
@@ -54,6 +55,10 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
static final String USER_4_ENCODED_PWD;
|
||||
static final String USER_4_DB = "targaryen";
|
||||
|
||||
static final String USER_5_NAME = "lyanna";
|
||||
static final String USER_5_PWD = "random?password";
|
||||
static final String USER_5_DB = "mormont";
|
||||
|
||||
static final String USER_1_AUTH_STRING = USER_1_NAME + ":" + USER_1_PWD + "@" + USER_1_DB;
|
||||
static final String USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM = USER_1_AUTH_STRING + "?uri.authMechanism=PLAIN";
|
||||
|
||||
@@ -66,6 +71,13 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
|
||||
static final String USER_4_AUTH_STRING;
|
||||
|
||||
static final String USER_5_AUTH_STRING = USER_5_NAME + ":" + USER_5_PWD + "@" + USER_5_DB;
|
||||
static final String USER_5_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM = USER_5_AUTH_STRING + "?uri.authMechanism=PLAIN";
|
||||
static final String USER_5_AUTH_STRING_WITH_QUERY_ARGS = USER_5_AUTH_STRING + "?uri.authMechanism=PLAIN&foo=&bar";
|
||||
|
||||
static final String SCRAM_SHA_256_AUTH_STRING = USER_1_NAME + ":" + USER_1_PWD + "@" + USER_1_DB
|
||||
+ "?uri.authMechanism=SCRAM-SHA-256";
|
||||
|
||||
static final MongoCredential USER_1_CREDENTIALS = MongoCredential.createCredential(USER_1_NAME, USER_1_DB,
|
||||
USER_1_PWD.toCharArray());
|
||||
static final MongoCredential USER_1_CREDENTIALS_PLAIN_AUTH = MongoCredential.createPlainCredential(USER_1_NAME,
|
||||
@@ -81,6 +93,11 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
static final MongoCredential USER_4_CREDENTIALS = MongoCredential.createCredential(USER_4_PLAIN_NAME, USER_4_DB,
|
||||
USER_4_PLAIN_PWD.toCharArray());
|
||||
|
||||
static final MongoCredential USER_5_CREDENTIALS = MongoCredential.createCredential(USER_5_NAME, USER_5_DB,
|
||||
USER_5_PWD.toCharArray());
|
||||
static final MongoCredential USER_5_CREDENTIALS_PLAIN_AUTH = MongoCredential.createPlainCredential(USER_5_NAME,
|
||||
USER_5_DB, USER_5_PWD.toCharArray());
|
||||
|
||||
MongoCredentialPropertyEditor editor;
|
||||
|
||||
static {
|
||||
@@ -108,7 +125,7 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
|
||||
editor.setAsText(null);
|
||||
|
||||
assertThat(editor.getValue(), nullValue());
|
||||
assertThat(getValue()).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1158
|
||||
@@ -116,7 +133,7 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
|
||||
editor.setAsText(" ");
|
||||
|
||||
assertThat(editor.getValue(), nullValue());
|
||||
assertThat(getValue()).isNull();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1158
|
||||
@@ -135,7 +152,7 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
|
||||
editor.setAsText(USER_1_AUTH_STRING);
|
||||
|
||||
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS));
|
||||
assertThat(getValue()).contains(USER_1_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1158
|
||||
@@ -144,7 +161,7 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
|
||||
editor.setAsText(USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM);
|
||||
|
||||
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS_PLAIN_AUTH));
|
||||
assertThat(getValue()).contains(USER_1_CREDENTIALS_PLAIN_AUTH);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1158
|
||||
@@ -154,38 +171,37 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
editor
|
||||
.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList(USER_1_AUTH_STRING, USER_2_AUTH_STRING)));
|
||||
|
||||
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS));
|
||||
assertThat(getValue()).contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1158
|
||||
@SuppressWarnings("unchecked")
|
||||
public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndAuthOptions() {
|
||||
|
||||
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList(
|
||||
USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING_WITH_MONGODB_CR_AUTH_MECHANISM)));
|
||||
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays
|
||||
.asList(USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING_WITH_MONGODB_CR_AUTH_MECHANISM)));
|
||||
|
||||
assertThat((List<MongoCredential>) editor.getValue(),
|
||||
contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS_CR_AUTH));
|
||||
assertThat(getValue()).contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS_CR_AUTH);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1158
|
||||
@SuppressWarnings("unchecked")
|
||||
public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndMixedOptions() {
|
||||
|
||||
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList(
|
||||
USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING)));
|
||||
editor.setAsText(StringUtils.collectionToCommaDelimitedString(
|
||||
Arrays.asList(USER_1_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM, USER_2_AUTH_STRING)));
|
||||
|
||||
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS));
|
||||
assertThat(getValue()).contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1257
|
||||
@SuppressWarnings("unchecked")
|
||||
public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleQuotedUserNamePasswordStringWithDatabaseAndNoOptions() {
|
||||
|
||||
editor.setAsText(StringUtils.collectionToCommaDelimitedString(Arrays.asList("'" + USER_1_AUTH_STRING + "'", "'"
|
||||
+ USER_2_AUTH_STRING + "'")));
|
||||
editor.setAsText(StringUtils.collectionToCommaDelimitedString(
|
||||
Arrays.asList("'" + USER_1_AUTH_STRING + "'", "'" + USER_2_AUTH_STRING + "'")));
|
||||
|
||||
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS));
|
||||
assertThat(getValue()).contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1257
|
||||
@@ -194,7 +210,7 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
|
||||
editor.setAsText("'" + USER_1_AUTH_STRING + "'");
|
||||
|
||||
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_1_CREDENTIALS));
|
||||
assertThat(getValue()).contains(USER_1_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1257
|
||||
@@ -203,7 +219,7 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
|
||||
editor.setAsText(USER_3_AUTH_STRING_WITH_X509_AUTH_MECHANISM);
|
||||
|
||||
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_3_CREDENTIALS_X509_AUTH));
|
||||
assertThat(getValue()).contains(USER_3_CREDENTIALS_X509_AUTH);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1257
|
||||
@@ -212,7 +228,7 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
|
||||
editor.setAsText("tyrion?uri.authMechanism=MONGODB-X509");
|
||||
|
||||
assertThat((List<MongoCredential>) editor.getValue(), contains(MongoCredential.createMongoX509Credential("tyrion")));
|
||||
assertThat(getValue()).contains(MongoCredential.createMongoX509Credential("tyrion"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1257
|
||||
@@ -220,7 +236,7 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
|
||||
editor.setAsText("tyrion?uri.authMechanism=MONGODB-CR");
|
||||
|
||||
editor.getValue();
|
||||
getValue();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1257
|
||||
@@ -228,15 +244,54 @@ public class MongoCredentialPropertyEditorUnitTests {
|
||||
|
||||
editor.setAsText("tyrion@?uri.authMechanism=MONGODB-CR");
|
||||
|
||||
editor.getValue();
|
||||
getValue();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1317
|
||||
@SuppressWarnings("unchecked")
|
||||
public void encodedUserNameAndPasswrodShouldBeDecoded() throws UnsupportedEncodingException {
|
||||
public void encodedUserNameAndPasswordShouldBeDecoded() {
|
||||
|
||||
editor.setAsText(USER_4_AUTH_STRING);
|
||||
|
||||
assertThat((List<MongoCredential>) editor.getValue(), contains(USER_4_CREDENTIALS));
|
||||
assertThat(getValue()).contains(USER_4_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2016
|
||||
@SuppressWarnings("unchecked")
|
||||
public void passwordWithQuestionMarkShouldNotBeInterpretedAsOptionString() {
|
||||
|
||||
editor.setAsText(USER_5_AUTH_STRING);
|
||||
|
||||
assertThat(getValue()).contains(USER_5_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2016
|
||||
@SuppressWarnings("unchecked")
|
||||
public void passwordWithQuestionMarkShouldNotBreakParsingOfOptionString() {
|
||||
|
||||
editor.setAsText(USER_5_AUTH_STRING_WITH_PLAIN_AUTH_MECHANISM);
|
||||
|
||||
assertThat(getValue()).contains(USER_5_CREDENTIALS_PLAIN_AUTH);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2051
|
||||
public void shouldReturnScramSha256Credentials() {
|
||||
|
||||
assumeThat(MongoClientVersion.isMongo38Driver()).isTrue();
|
||||
|
||||
editor.setAsText(SCRAM_SHA_256_AUTH_STRING);
|
||||
|
||||
assertThat(getValue()).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-2016
|
||||
@SuppressWarnings("unchecked")
|
||||
public void failsGracefullyOnEmptyQueryArgument() {
|
||||
editor.setAsText(USER_5_AUTH_STRING_WITH_QUERY_ARGS);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<MongoCredential> getValue() {
|
||||
return (List<MongoCredential>) editor.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ import com.mongodb.client.model.CountOptions;
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndDeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndUpdateOptions;
|
||||
import com.mongodb.client.model.MapReduceAction;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
|
||||
@@ -139,6 +140,9 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
when(mapReduceIterable.sort(Mockito.any())).thenReturn(mapReduceIterable);
|
||||
when(mapReduceIterable.iterator()).thenReturn(cursor);
|
||||
when(mapReduceIterable.filter(any())).thenReturn(mapReduceIterable);
|
||||
when(mapReduceIterable.collectionName(any())).thenReturn(mapReduceIterable);
|
||||
when(mapReduceIterable.databaseName(any())).thenReturn(mapReduceIterable);
|
||||
when(mapReduceIterable.action(any())).thenReturn(mapReduceIterable);
|
||||
|
||||
this.mappingContext = new MongoMappingContext();
|
||||
this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext);
|
||||
@@ -774,6 +778,52 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
verify(mapReduceIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2027
|
||||
public void mapReduceShouldUseOutputCollectionWhenPresent() {
|
||||
|
||||
template.mapReduce("", "", "", MapReduceOptions.options().outputCollection("out-collection"),
|
||||
AutogenerateableId.class);
|
||||
|
||||
verify(mapReduceIterable).collectionName(eq("out-collection"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2027
|
||||
public void mapReduceShouldNotUseOutputCollectionForInline() {
|
||||
|
||||
template.mapReduce("", "", "", MapReduceOptions.options().outputCollection("out-collection").outputTypeInline(),
|
||||
AutogenerateableId.class);
|
||||
|
||||
verify(mapReduceIterable, never()).collectionName(any());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2027
|
||||
public void mapReduceShouldUseOutputActionWhenPresent() {
|
||||
|
||||
template.mapReduce("", "", "", MapReduceOptions.options().outputCollection("out-collection").outputTypeMerge(),
|
||||
AutogenerateableId.class);
|
||||
|
||||
verify(mapReduceIterable).action(eq(MapReduceAction.MERGE));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2027
|
||||
public void mapReduceShouldUseOutputDatabaseWhenPresent() {
|
||||
|
||||
template.mapReduce("", "", "",
|
||||
MapReduceOptions.options().outputDatabase("out-database").outputCollection("out-collection").outputTypeMerge(),
|
||||
AutogenerateableId.class);
|
||||
|
||||
verify(mapReduceIterable).databaseName(eq("out-database"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2027
|
||||
public void mapReduceShouldNotUseOutputDatabaseForInline() {
|
||||
|
||||
template.mapReduce("", "", "", MapReduceOptions.options().outputDatabase("out-database").outputTypeInline(),
|
||||
AutogenerateableId.class);
|
||||
|
||||
verify(mapReduceIterable, never()).databaseName(any());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void geoNearShouldUseCollationWhenPresent() {
|
||||
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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 org.bson.Document;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ConvertOperators}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @currentRead Royal Assassin - Robin Hobb
|
||||
*/
|
||||
public class ConvertOperatorsUnitTests {
|
||||
|
||||
static final String EXPRESSION_STRING = "{ \"$molly\" : \"chandler\" }";
|
||||
static final Document EXPRESSION_DOC = Document.parse(EXPRESSION_STRING);
|
||||
static final AggregationExpression EXPRESSION = context -> EXPRESSION_DOC;
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void convertToUsingStringIdentifier() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void convertToUsingIntIdentifier() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertTo(1).toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : 1 } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void convertToUsingFieldReference() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertToTypeOf("fitz").toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"$fitz\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void convertToUsingExpression() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertToTypeOf(EXPRESSION).toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : " + EXPRESSION_STRING + " } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void convertToWithOnErrorValue() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onErrorReturn("foo")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(
|
||||
Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onError\" : \"foo\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void convertToWithOnErrorValueOfField() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onErrorReturnValueOf("verity")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document
|
||||
.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onError\" : \"$verity\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void convertToWithOnErrorValueOfExpression() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onErrorReturnValueOf(EXPRESSION)
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onError\" : "
|
||||
+ EXPRESSION_STRING + " } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void convertToWithOnNullValue() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onNullReturn("foo")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(
|
||||
Document.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onNull\" : \"foo\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void convertToWithOnNullValueOfField() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onNullReturnValueOf("verity")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document
|
||||
.parse("{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onNull\" : \"$verity\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void convertToWithOnNullValueOfExpression() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertTo("double").onNullReturnValueOf(EXPRESSION)
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(Document.parse(
|
||||
"{ $convert: { \"input\" : \"$shrewd\", \"to\" : \"double\", \"onNull\" : " + EXPRESSION_STRING + " } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toBoolUsingFieldReference() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertToBoolean().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toBool: \"$shrewd\" } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toBoolUsingExpression() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToBoolean().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toBool: " + EXPRESSION_STRING + " } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toDateUsingFieldReference() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertToDate().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toDate: \"$shrewd\" } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toDateUsingExpression() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToDate().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toDate: " + EXPRESSION_STRING + " } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toDecimalUsingFieldReference() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertToDecimal().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toDecimal: \"$shrewd\" } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toDecimalUsingExpression() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToDecimal().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toDecimal: " + EXPRESSION_STRING + " } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toDoubleUsingFieldReference() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertToDouble().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toDouble: \"$shrewd\" } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toDoubleUsingExpression() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToDouble().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toDouble: " + EXPRESSION_STRING + " } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toIntUsingFieldReference() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertToInt().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toInt: \"$shrewd\" } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toIntUsingExpression() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToInt().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toInt: " + EXPRESSION_STRING + " } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toLongUsingFieldReference() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertToLong().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toLong: \"$shrewd\" } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toLongUsingExpression() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToLong().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toLong: " + EXPRESSION_STRING + " } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toObjectIdUsingFieldReference() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertToObjectId().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toObjectId: \"$shrewd\" } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toObjectIdUsingExpression() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToObjectId().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toObjectId: " + EXPRESSION_STRING + " } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toStringUsingFieldReference() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf("shrewd").convertToString().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toString: \"$shrewd\" } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2048
|
||||
public void toStringUsingExpression() {
|
||||
|
||||
assertThat(ConvertOperators.valueOf(EXPRESSION).convertToString().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $toString: " + EXPRESSION_STRING + " } "));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 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.
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Test;
|
||||
@@ -25,9 +24,10 @@ import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GeoNearOperation}.
|
||||
*
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class GeoNearOperationUnitTests {
|
||||
|
||||
@@ -41,6 +41,16 @@ public class GeoNearOperationUnitTests {
|
||||
Document nearClause = DocumentTestUtils.getAsDocument(document, "$geoNear");
|
||||
|
||||
Document expected = new Document(query.toDocument()).append("distanceField", "distance");
|
||||
assertThat(nearClause, is(expected));
|
||||
assertThat(nearClause).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2050
|
||||
public void rendersNearQueryWithKeyCorrectly() {
|
||||
|
||||
NearQuery query = NearQuery.near(10.0, 10.0);
|
||||
GeoNearOperation operation = new GeoNearOperation(query, "distance").useIndex("geo-index-1");
|
||||
Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(DocumentTestUtils.getAsDocument(document, "$geoNear")).containsEntry("key", "geo-index-1");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,19 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import static org.hamcrest.core.Is.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
|
||||
import static org.springframework.data.mongodb.test.util.Assertions.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.Person;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.util.JSON;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GraphLookupOperation}.
|
||||
*
|
||||
@@ -54,8 +50,7 @@ public class GraphLookupOperationUnitTests {
|
||||
.as("reportingHierarchy");
|
||||
|
||||
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
assertThat(document,
|
||||
isBsonObject().containing("$graphLookup.depthField", "depth").containing("$graphLookup.maxDepth", 42L));
|
||||
assertThat(document).containsEntry("$graphLookup.depthField", "depth").containsEntry("$graphLookup.maxDepth", 42L);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1551
|
||||
@@ -70,8 +65,7 @@ public class GraphLookupOperationUnitTests {
|
||||
.as("reportingHierarchy");
|
||||
|
||||
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
assertThat(document,
|
||||
isBsonObject().containing("$graphLookup.restrictSearchWithMatch", new Document("key", "value")));
|
||||
assertThat(document).containsEntry("$graphLookup.restrictSearchWithMatch", new Document("key", "value"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1551
|
||||
@@ -86,9 +80,9 @@ public class GraphLookupOperationUnitTests {
|
||||
|
||||
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(document,
|
||||
is(Document.parse("{ $graphLookup : { from: \"employees\", startWith: [\"$reportsTo\", \"$boss\"], "
|
||||
+ "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }")));
|
||||
assertThat(document)
|
||||
.isEqualTo(Document.parse("{ $graphLookup : { from: \"employees\", startWith: [\"$reportsTo\", \"$boss\"], "
|
||||
+ "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1551
|
||||
@@ -103,9 +97,8 @@ public class GraphLookupOperationUnitTests {
|
||||
|
||||
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(document,
|
||||
is(Document.parse("{ $graphLookup : { from: \"employees\", startWith: [\"$reportsTo\", { $literal: \"$boss\"}], "
|
||||
+ "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }")));
|
||||
assertThat(document).containsEntry("$graphLookup.startWith",
|
||||
Arrays.asList("$reportsTo", new Document("$literal", "$boss")));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1551
|
||||
@@ -131,7 +124,39 @@ public class GraphLookupOperationUnitTests {
|
||||
|
||||
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(document, is(Document.parse("{ $graphLookup : { from: \"employees\", startWith: { $literal: \"hello\"}, "
|
||||
+ "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }")));
|
||||
assertThat(document).containsEntry("$graphLookup.startWith", new Document("$literal", "hello"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2096
|
||||
public void connectFromShouldUseTargetFieldInsteadOfAlias() {
|
||||
|
||||
AggregationOperation graphLookupOperation = Aggregation.graphLookup("user").startWith("contacts.userId")
|
||||
.connectFrom("contacts.userId").connectTo("_id").depthField("numConnections").as("connections");
|
||||
|
||||
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(document).containsEntry("$graphLookup.startWith", "$contacts.userId");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2096
|
||||
public void connectToShouldUseTargetFieldInsteadOfAlias() {
|
||||
|
||||
AggregationOperation graphLookupOperation = Aggregation.graphLookup("user").startWith("contacts.userId")
|
||||
.connectFrom("userId").connectTo("connectto.field").depthField("numConnections").as("connections");
|
||||
|
||||
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(document).containsEntry("$graphLookup.connectToField", "connectto.field");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2096
|
||||
public void depthFieldShouldUseTargetFieldInsteadOfAlias() {
|
||||
|
||||
AggregationOperation graphLookupOperation = Aggregation.graphLookup("user").startWith("contacts.userId")
|
||||
.connectFrom("contacts.userId").connectTo("_id").depthField("foo.bar").as("connections");
|
||||
|
||||
Document document = graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(document).containsEntry("$graphLookup.depthField", "foo.bar");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1300,6 +1300,14 @@ public class ProjectionOperationUnitTests {
|
||||
Document.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2047
|
||||
public void shouldRenderDateToStringWithoutFormatOption() {
|
||||
|
||||
Document agg = project().and("date").dateAsFormattedString().as("time").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg).isEqualTo(Document.parse("{ $project: { time: { $dateToString: { date: \"$date\" } } } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1536
|
||||
public void shouldRenderDateToStringAggregationExpression() {
|
||||
|
||||
@@ -1310,7 +1318,7 @@ public class ProjectionOperationUnitTests {
|
||||
Document.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1834
|
||||
@Test // DATAMONGO-1834, DATAMONGO-2047
|
||||
public void shouldRenderDateToStringAggregationExpressionWithTimezone() {
|
||||
|
||||
Document agg = project()
|
||||
@@ -1319,6 +1327,47 @@ public class ProjectionOperationUnitTests {
|
||||
|
||||
assertThat(agg).isEqualTo(Document.parse(
|
||||
"{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\", \"timezone\" : \"America/Chicago\" } } } } } }"));
|
||||
|
||||
Document removedTimezone = project().and(DateOperators.dateOf("date")
|
||||
.withTimezone(Timezone.valueOf("America/Chicago")).toString("%H:%M:%S:%L").withTimezone(Timezone.none()))
|
||||
.as("time").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(removedTimezone).isEqualTo(
|
||||
Document.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } } } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2047
|
||||
public void shouldRenderDateToStringWithOnNull() {
|
||||
|
||||
Document agg = project()
|
||||
.and(DateOperators.dateOf("date").toStringWithDefaultFormat().onNullReturnValueOf("fallback-field")).as("time")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg).isEqualTo(Document
|
||||
.parse("{ $project: { time: { $dateToString: { date: \"$date\", \"onNull\" : \"$fallback-field\" } } } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2047
|
||||
public void shouldRenderDateToStringWithOnNullExpression() {
|
||||
|
||||
Document agg = project()
|
||||
.and(DateOperators.dateOf("date").toStringWithDefaultFormat()
|
||||
.onNullReturnValueOf(LiteralOperators.valueOf("my-literal").asLiteral()))
|
||||
.as("time").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg).isEqualTo(Document.parse(
|
||||
"{ $project: { time: { $dateToString: { date: \"$date\", \"onNull\" : { \"$literal\": \"my-literal\"} } } } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2047
|
||||
public void shouldRenderDateToStringWithOnNullAndTimezone() {
|
||||
|
||||
Document agg = project().and(DateOperators.dateOf("date").toStringWithDefaultFormat()
|
||||
.onNullReturnValueOf("fallback-field").withTimezone(Timezone.ofField("foo"))).as("time")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg).isEqualTo(Document.parse(
|
||||
"{ $project: { time: { $dateToString: { date: \"$date\", \"onNull\" : \"$fallback-field\", \"timezone\": \"$foo\" } } } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1536
|
||||
@@ -2020,6 +2069,16 @@ public class ProjectionOperationUnitTests {
|
||||
"{ $project : { newDate: { $dateFromString: { dateString : \"2017-02-08T12:10:40.787\", timezone : \"America/Chicago\" } } } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2047
|
||||
public void shouldRenderDateFromStringWithFormat() {
|
||||
|
||||
Document agg = project().and(DateOperators.dateFromString("2017-02-08T12:10:40.787").withFormat("dd/mm/yyyy"))
|
||||
.as("newDate").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg).isEqualTo(Document.parse(
|
||||
"{ $project : { newDate: { $dateFromString: { dateString : \"2017-02-08T12:10:40.787\", format : \"dd/mm/yyyy\" } } } }"));
|
||||
}
|
||||
|
||||
private static Document exctractOperation(String field, Document fromProjectClause) {
|
||||
return (Document) fromProjectClause.get(field);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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 org.bson.Document;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit test for {@link StringOperators}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @currentRead Royal Assassin - Robin Hobb
|
||||
*/
|
||||
public class StringOperatorsUnitTests {
|
||||
|
||||
static final String EXPRESSION_STRING = "{ \"$fitz\" : \"chivalry\" }";
|
||||
static final Document EXPRESSION_DOC = Document.parse(EXPRESSION_STRING);
|
||||
static final AggregationExpression EXPRESSION = context -> EXPRESSION_DOC;
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderTrim() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").trim().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $trim: { \"input\" : \"$shrewd\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderTrimForExpression() {
|
||||
|
||||
assertThat(StringOperators.valueOf(EXPRESSION).trim().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $trim: { \"input\" : " + EXPRESSION_STRING + " } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderTrimWithChars() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").trim("sh").toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $trim: { \"input\" : \"$shrewd\", \"chars\" : \"sh\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderTrimWithCharsExpression() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").trim(EXPRESSION).toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $trim: { \"input\" : \"$shrewd\", \"chars\" : " + EXPRESSION_STRING + " } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderTrimLeft() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").trim().left().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : \"$shrewd\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderTrimLeftWithChars() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").trim("sh").left().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : \"$shrewd\", \"chars\" : \"sh\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderTrimRight() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").trim().right().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : \"$shrewd\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderTrimRightWithChars() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").trim("sh").right().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : \"$shrewd\", \"chars\" : \"sh\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderLTrim() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").ltrim().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : \"$shrewd\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderLTrimForExpression() {
|
||||
|
||||
assertThat(StringOperators.valueOf(EXPRESSION).ltrim().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : " + EXPRESSION_STRING + " } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderLTrimWithChars() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").ltrim("sh").toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : \"$shrewd\", \"chars\" : \"sh\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderLTrimWithCharsExpression() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").ltrim(EXPRESSION).toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $ltrim: { \"input\" : \"$shrewd\", \"chars\" : " + EXPRESSION_STRING + " } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderRTrim() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").rtrim().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : \"$shrewd\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderRTrimForExpression() {
|
||||
|
||||
assertThat(StringOperators.valueOf(EXPRESSION).rtrim().toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : " + EXPRESSION_STRING + " } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderRTrimWithChars() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").rtrim("sh").toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : \"$shrewd\", \"chars\" : \"sh\" } } "));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2049
|
||||
public void shouldRenderRTrimWithCharsExpression() {
|
||||
|
||||
assertThat(StringOperators.valueOf("shrewd").rtrim(EXPRESSION).toDocument(Aggregation.DEFAULT_CONTEXT))
|
||||
.isEqualTo(Document.parse("{ $rtrim: { \"input\" : \"$shrewd\", \"chars\" : " + EXPRESSION_STRING + " } } "));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2017 the original author or authors.
|
||||
* Copyright 2011-2018 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.
|
||||
@@ -26,28 +26,14 @@ import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.data.mongodb.core.DocumentTestUtils.*;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.*;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
import org.hamcrest.Matcher;
|
||||
@@ -1886,6 +1872,36 @@ public class MappingMongoConverterUnitTests {
|
||||
assertThat(result.nestedFloats).isEqualTo(new float[][][] { { { 1.0f, 2.0f } } });
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2011
|
||||
public void readsNestedListsToObjectCorrectly() {
|
||||
|
||||
List<String> values = Arrays.asList("ONE", "TWO");
|
||||
org.bson.Document source = new org.bson.Document("value", Collections.singletonList(values));
|
||||
|
||||
assertThat(converter.read(Attribute.class, source).value).isInstanceOf(List.class);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2043
|
||||
public void omitsTypeHintWhenWritingSimpleTypes() {
|
||||
|
||||
org.bson.Document target = new org.bson.Document();
|
||||
converter.write(new org.bson.Document("value", "FitzChivalry"), target);
|
||||
|
||||
assertThat(target).doesNotContainKeys("_class");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2135
|
||||
public void addsEqualObjectsToCollection() {
|
||||
|
||||
org.bson.Document itemDocument = new org.bson.Document("itemKey", "123");
|
||||
org.bson.Document orderDocument = new org.bson.Document("items",
|
||||
Arrays.asList(itemDocument, itemDocument, itemDocument));
|
||||
|
||||
Order order = converter.read(Order.class, orderDocument);
|
||||
|
||||
assertThat(order.items).hasSize(3);
|
||||
}
|
||||
|
||||
static class GenericType<T> {
|
||||
T content;
|
||||
}
|
||||
@@ -2266,4 +2282,15 @@ public class MappingMongoConverterUnitTests {
|
||||
static class WithNestedLists {
|
||||
float[][][] nestedFloats;
|
||||
}
|
||||
|
||||
// DATAMONGO-2135
|
||||
|
||||
@EqualsAndHashCode // equality check by fields
|
||||
static class SomeItem {
|
||||
String itemKey;
|
||||
}
|
||||
|
||||
static class Order {
|
||||
Collection<SomeItem> items = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +277,20 @@ public class UpdateMapperUnitTests {
|
||||
assertThat(getAsDocument(push, "key")).containsKey("$each");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-943, DATAMONGO-2055
|
||||
public void updatePushEachAtNegativePositionWorksCorrectly() {
|
||||
|
||||
Update update = new Update().push("key").atPosition(-2).each(Arrays.asList("Arya", "Arry", "Weasel"));
|
||||
|
||||
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
|
||||
|
||||
Document push = getAsDocument(mappedObject, "$push");
|
||||
Document key = getAsDocument(push, "key");
|
||||
|
||||
assertThat(key.containsKey("$position")).isTrue();
|
||||
assertThat(key.get("$position")).isEqualTo(-2);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-943
|
||||
public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionFirst() {
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapreduce;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.mongodb.core.mapreduce.MapReduceOptions.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
|
||||
@@ -45,13 +45,15 @@ import com.mongodb.client.MongoCollection;
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration("classpath:infrastructure.xml")
|
||||
public class MapReduceTests {
|
||||
|
||||
private String mapFunction = "function(){ for ( var i=0; i<this.x.length; i++ ){ emit( this.x[i] , 1 ); } }";
|
||||
private String reduceFunction = "function(key,values){ var sum=0; for( var i=0; i<values.length; i++ ) sum += values[i]; return sum;}";
|
||||
private static final String MAP_FUNCTION = "function(){ for ( var i=0; i<this.x.length; i++ ){ emit( this.x[i] , 1 ); } }";
|
||||
private static final String REDUCE_FUNCTION = "function(key,values){ var sum=0; for( var i=0; i<values.length; i++ ) sum += values[i]; return sum;}";
|
||||
|
||||
@Autowired MongoTemplate template;
|
||||
@Autowired MongoTemplate mongoTemplate;
|
||||
@@ -67,73 +69,229 @@ public class MapReduceTests {
|
||||
}
|
||||
|
||||
protected void cleanDb() {
|
||||
|
||||
template.dropCollection(template.getCollectionName(ValueObject.class));
|
||||
template.dropCollection("jmr2");
|
||||
template.dropCollection("jmr2_out");
|
||||
template.dropCollection("jmr1_out");
|
||||
template.dropCollection("jmr1");
|
||||
template.dropCollection("jmrWithGeo");
|
||||
template.getMongoDbFactory().getDb("jmr1-out-db").drop();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // DATADOC-7
|
||||
@Ignore
|
||||
public void testForDocs() {
|
||||
|
||||
createMapReduceData();
|
||||
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce("jmr1", mapFunction, reduceFunction,
|
||||
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce("jmr1", MAP_FUNCTION, REDUCE_FUNCTION,
|
||||
ValueObject.class);
|
||||
|
||||
for (ValueObject valueObject : results) {
|
||||
System.out.println(valueObject);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // DATAMONGO-260
|
||||
public void testIssue260() {
|
||||
|
||||
createContentAndVersionData();
|
||||
String map = "function () { emit(this.document_id, this.version); }";
|
||||
String reduce = "function (key, values) { return Math.max.apply(Math, values); }";
|
||||
|
||||
MapReduceResults<ContentAndVersion> results = mongoTemplate.mapReduce("jmr2", map, reduce,
|
||||
new MapReduceOptions().outputCollection("jmr2_out"), ContentAndVersion.class);
|
||||
|
||||
int size = 0;
|
||||
assertThat(results).hasSize(3);
|
||||
for (ContentAndVersion cv : results) {
|
||||
|
||||
if ("Resume".equals(cv.getId())) {
|
||||
assertEquals(6, cv.getValue().longValue());
|
||||
assertThat(cv.getValue().longValue()).isEqualTo(6);
|
||||
}
|
||||
if ("Schema".equals(cv.getId())) {
|
||||
assertEquals(2, cv.getValue().longValue());
|
||||
assertThat(cv.getValue().longValue()).isEqualTo(2);
|
||||
}
|
||||
if ("mongoDB How-To".equals(cv.getId())) {
|
||||
assertEquals(2, cv.getValue().longValue());
|
||||
assertThat(cv.getValue().longValue()).isEqualTo(2);
|
||||
}
|
||||
size++;
|
||||
}
|
||||
assertEquals(3, size);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // DATAMONGO-260
|
||||
public void testIssue260Part2() {
|
||||
|
||||
createNumberAndVersionData();
|
||||
String map = "function () { emit(this.number, this.version); }";
|
||||
String reduce = "function (key, values) { return Math.max.apply(Math, values); }";
|
||||
|
||||
MapReduceResults<NumberAndVersion> results = mongoTemplate.mapReduce("jmr2", map, reduce,
|
||||
new MapReduceOptions().outputCollection("jmr2_out"), NumberAndVersion.class);
|
||||
int size = 0;
|
||||
|
||||
for (NumberAndVersion nv : results) {
|
||||
if ("1".equals(nv.getId())) {
|
||||
assertEquals(2, nv.getValue().longValue());
|
||||
assertThat(nv.getValue().longValue()).isEqualTo(2);
|
||||
}
|
||||
if ("2".equals(nv.getId())) {
|
||||
assertEquals(6, nv.getValue().longValue());
|
||||
assertThat(nv.getValue().longValue()).isEqualTo(6);
|
||||
}
|
||||
if ("3".equals(nv.getId())) {
|
||||
assertEquals(2, nv.getValue().longValue());
|
||||
assertThat(nv.getValue().longValue()).isEqualTo(2);
|
||||
}
|
||||
size++;
|
||||
}
|
||||
assertEquals(3, size);
|
||||
|
||||
assertThat(results).hasSize(3);
|
||||
}
|
||||
|
||||
@Test // DATADOC-7, DATAMONGO-2027
|
||||
public void testMapReduce() {
|
||||
|
||||
performMapReduce(false, false);
|
||||
|
||||
List<ValueObject> results = mongoTemplate.find(new Query(), ValueObject.class, "jmr1_out");
|
||||
assertMapReduceResults(copyToMap(results));
|
||||
}
|
||||
|
||||
@Test // DATADOC-7, DATAMONGO-2027
|
||||
public void testMapReduceInline() {
|
||||
|
||||
performMapReduce(true, false);
|
||||
assertThat(template.collectionExists("jmr1_out")).isFalse();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2027
|
||||
public void mapReduceWithOutputDatabaseShouldWorkCorrectly() {
|
||||
|
||||
createMapReduceData();
|
||||
|
||||
mongoTemplate.mapReduce("jmr1", MAP_FUNCTION, REDUCE_FUNCTION,
|
||||
options().outputDatabase("jmr1-out-db").outputCollection("jmr1-out"), ValueObject.class);
|
||||
|
||||
assertThat(template.getMongoDbFactory().getDb("jmr1-out-db").listCollectionNames().into(new ArrayList<>()))
|
||||
.contains("jmr1-out");
|
||||
}
|
||||
|
||||
@Test // DATADOC-7
|
||||
public void testMapReduceWithQuery() {
|
||||
performMapReduce(false, true);
|
||||
}
|
||||
|
||||
@Test // DATADOC-7
|
||||
public void testMapReduceInlineWithScope() {
|
||||
|
||||
createMapReduceData();
|
||||
|
||||
Map<String, Object> scopeVariables = new HashMap<String, Object>();
|
||||
scopeVariables.put("exclude", "a");
|
||||
|
||||
String mapWithExcludeFunction = "function(){ for ( var i=0; i<this.x.length; i++ ){ if(this.x[i] != exclude) emit( this.x[i] , 1 ); } }";
|
||||
|
||||
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce("jmr1", mapWithExcludeFunction, REDUCE_FUNCTION,
|
||||
new MapReduceOptions().scopeVariables(scopeVariables).outputTypeInline(), ValueObject.class);
|
||||
|
||||
assertThat(copyToMap(results)) //
|
||||
.hasSize(3) //
|
||||
.containsEntry("b", 2F) //
|
||||
.containsEntry("c", 2F) //
|
||||
.containsEntry("d", 1F);
|
||||
}
|
||||
|
||||
@Test // DATADOC-7
|
||||
public void testMapReduceExcludeQuery() {
|
||||
|
||||
createMapReduceData();
|
||||
|
||||
Query query = new Query(where("x").ne(new String[] { "a", "b" }));
|
||||
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce(query, "jmr1", MAP_FUNCTION, REDUCE_FUNCTION,
|
||||
ValueObject.class);
|
||||
|
||||
assertThat(copyToMap(results)) //
|
||||
.hasSize(3) //
|
||||
.containsEntry("b", 1F) //
|
||||
.containsEntry("c", 2F) //
|
||||
.containsEntry("d", 1F);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-938
|
||||
public void mapReduceShouldUseQueryMapper() {
|
||||
|
||||
MongoCollection<Document> c = mongoTemplate.getDb().getCollection("jmrWithGeo", Document.class);
|
||||
|
||||
c.insertOne(new Document("x", Arrays.asList("a", "b")).append("loc", Arrays.asList(0D, 0D)));
|
||||
c.insertOne(new Document("x", Arrays.asList("b", "c")).append("loc", Arrays.asList(0D, 0D)));
|
||||
c.insertOne(new Document("x", Arrays.asList("c", "d")).append("loc", Arrays.asList(0D, 0D)));
|
||||
|
||||
Query query = new Query(where("x").ne(new String[] { "a", "b" }).and("loc")
|
||||
.within(new Box(new double[] { 0, 0 }, new double[] { 1, 1 })));
|
||||
|
||||
MapReduceResults<ValueObject> results = template.mapReduce(query, "jmrWithGeo", MAP_FUNCTION, REDUCE_FUNCTION,
|
||||
ValueObject.class);
|
||||
|
||||
assertThat(copyToMap(results)) //
|
||||
.hasSize(3) //
|
||||
.containsEntry("b", 1F) //
|
||||
.containsEntry("c", 2F) //
|
||||
.containsEntry("d", 1F);
|
||||
}
|
||||
|
||||
private void performMapReduce(boolean inline, boolean withQuery) {
|
||||
|
||||
createMapReduceData();
|
||||
MapReduceResults<ValueObject> results;
|
||||
if (inline) {
|
||||
if (withQuery) {
|
||||
results = mongoTemplate.mapReduce(new Query(), "jmr1", "classpath:map.js", "classpath:reduce.js",
|
||||
ValueObject.class);
|
||||
} else {
|
||||
results = mongoTemplate.mapReduce("jmr1", MAP_FUNCTION, REDUCE_FUNCTION, ValueObject.class);
|
||||
}
|
||||
} else {
|
||||
if (withQuery) {
|
||||
results = mongoTemplate.mapReduce(new Query(), "jmr1", MAP_FUNCTION, REDUCE_FUNCTION,
|
||||
options().outputCollection("jmr1_out"), ValueObject.class);
|
||||
} else {
|
||||
results = mongoTemplate.mapReduce("jmr1", MAP_FUNCTION, REDUCE_FUNCTION,
|
||||
new MapReduceOptions().outputCollection("jmr1_out"), ValueObject.class);
|
||||
}
|
||||
}
|
||||
|
||||
assertMapReduceResults(copyToMap(results));
|
||||
}
|
||||
|
||||
private void createMapReduceData() {
|
||||
|
||||
MongoCollection<Document> c = mongoTemplate.getDb().getCollection("jmr1", Document.class);
|
||||
c.insertOne(new Document("x", Arrays.asList("a", "b")));
|
||||
c.insertOne(new Document("x", Arrays.asList("b", "c")));
|
||||
c.insertOne(new Document("x", Arrays.asList("c", "d")));
|
||||
}
|
||||
|
||||
private Map<String, Float> copyToMap(Iterable<ValueObject> results) {
|
||||
|
||||
List<ValueObject> valueObjects = new ArrayList<>();
|
||||
for (ValueObject valueObject : results) {
|
||||
valueObjects.add(valueObject);
|
||||
}
|
||||
|
||||
Map<String, Float> m = new HashMap<>();
|
||||
for (ValueObject vo : valueObjects) {
|
||||
m.put(vo.getId(), vo.getValue());
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
private void assertMapReduceResults(Map<String, Float> map) {
|
||||
|
||||
assertThat(map) //
|
||||
.hasSize(4) //
|
||||
.containsEntry("a", 1F) //
|
||||
.containsEntry("b", 2F) //
|
||||
.containsEntry("c", 2F) //
|
||||
.containsEntry("d", 1F);
|
||||
}
|
||||
|
||||
private void createNumberAndVersionData() {
|
||||
|
||||
NumberAndVersion nv1 = new NumberAndVersion();
|
||||
nv1.setNumber(1L);
|
||||
nv1.setVersion(1L);
|
||||
@@ -168,7 +326,7 @@ public class MapReduceTests {
|
||||
{ "_id" : 3, "document_id" : "Resume", "author" : "Author", "content" : "...", "version" : 6 }
|
||||
{ "_id" : 4, "document_id" : "Schema", "author" : "Someone Else", "content" : "...", "version" : 0.9 }
|
||||
{ "_id" : 5, "document_id" : "Schema", "author" : "Someone Else", "content" : "...", "version" : 1 }
|
||||
|
||||
|
||||
*/
|
||||
ContentAndVersion cv1 = new ContentAndVersion();
|
||||
cv1.setDocumentId("mongoDB How-To");
|
||||
@@ -204,129 +362,5 @@ public class MapReduceTests {
|
||||
cv5.setContent("...");
|
||||
cv5.setVersion(2L);
|
||||
template.save(cv5, "jmr2");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapReduce() {
|
||||
performMapReduce(false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapReduceInline() {
|
||||
performMapReduce(true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapReduceWithQuery() {
|
||||
performMapReduce(false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapReduceInlineWithScope() {
|
||||
createMapReduceData();
|
||||
|
||||
Map<String, Object> scopeVariables = new HashMap<String, Object>();
|
||||
scopeVariables.put("exclude", "a");
|
||||
|
||||
String mapWithExcludeFunction = "function(){ for ( var i=0; i<this.x.length; i++ ){ if(this.x[i] != exclude) emit( this.x[i] , 1 ); } }";
|
||||
|
||||
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce("jmr1", mapWithExcludeFunction, reduceFunction,
|
||||
new MapReduceOptions().scopeVariables(scopeVariables).outputTypeInline(), ValueObject.class);
|
||||
Map<String, Float> m = copyToMap(results);
|
||||
assertEquals(3, m.size());
|
||||
assertEquals(2, m.get("b").intValue());
|
||||
assertEquals(2, m.get("c").intValue());
|
||||
assertEquals(1, m.get("d").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapReduceExcludeQuery() {
|
||||
createMapReduceData();
|
||||
|
||||
Query query = new Query(where("x").ne(new String[] { "a", "b" }));
|
||||
MapReduceResults<ValueObject> results = mongoTemplate.mapReduce(query, "jmr1", mapFunction, reduceFunction,
|
||||
ValueObject.class);
|
||||
|
||||
Map<String, Float> m = copyToMap(results);
|
||||
assertEquals(3, m.size());
|
||||
assertEquals(1, m.get("b").intValue());
|
||||
assertEquals(2, m.get("c").intValue());
|
||||
assertEquals(1, m.get("d").intValue());
|
||||
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-938
|
||||
public void mapReduceShouldUseQueryMapper() {
|
||||
|
||||
MongoCollection<Document> c = mongoTemplate.getDb().getCollection("jmrWithGeo", Document.class);
|
||||
|
||||
c.insertOne(new Document("x", Arrays.asList("a", "b")).append("loc", Arrays.<Double> asList(0D, 0D)));
|
||||
c.insertOne(new Document("x", Arrays.asList("b", "c")).append("loc", Arrays.<Double> asList(0D, 0D)));
|
||||
c.insertOne(new Document("x", Arrays.asList("c", "d")).append("loc", Arrays.<Double> asList(0D, 0D)));
|
||||
|
||||
Query query = new Query(where("x").ne(new String[] { "a", "b" }).and("loc")
|
||||
.within(new Box(new double[] { 0, 0 }, new double[] { 1, 1 })));
|
||||
|
||||
MapReduceResults<ValueObject> results = template.mapReduce(query, "jmrWithGeo", mapFunction, reduceFunction,
|
||||
ValueObject.class);
|
||||
|
||||
Map<String, Float> m = copyToMap(results);
|
||||
assertEquals(3, m.size());
|
||||
assertEquals(1, m.get("b").intValue());
|
||||
assertEquals(2, m.get("c").intValue());
|
||||
assertEquals(1, m.get("d").intValue());
|
||||
}
|
||||
|
||||
private void performMapReduce(boolean inline, boolean withQuery) {
|
||||
createMapReduceData();
|
||||
MapReduceResults<ValueObject> results;
|
||||
if (inline) {
|
||||
if (withQuery) {
|
||||
results = mongoTemplate.mapReduce(new Query(), "jmr1", "classpath:map.js", "classpath:reduce.js",
|
||||
ValueObject.class);
|
||||
} else {
|
||||
results = mongoTemplate.mapReduce("jmr1", mapFunction, reduceFunction, ValueObject.class);
|
||||
}
|
||||
} else {
|
||||
if (withQuery) {
|
||||
results = mongoTemplate.mapReduce(new Query(), "jmr1", mapFunction, reduceFunction,
|
||||
options().outputCollection("jmr1_out"), ValueObject.class);
|
||||
} else {
|
||||
results = mongoTemplate.mapReduce("jmr1", mapFunction, reduceFunction,
|
||||
new MapReduceOptions().outputCollection("jmr1_out"), ValueObject.class);
|
||||
}
|
||||
}
|
||||
Map<String, Float> m = copyToMap(results);
|
||||
assertMapReduceResults(m);
|
||||
}
|
||||
|
||||
private void createMapReduceData() {
|
||||
MongoCollection<Document> c = mongoTemplate.getDb().getCollection("jmr1", Document.class);
|
||||
c.insertOne(new Document("x", Arrays.asList("a", "b")));
|
||||
c.insertOne(new Document("x", Arrays.asList("b", "c")));
|
||||
c.insertOne(new Document("x", Arrays.asList("c", "d")));
|
||||
}
|
||||
|
||||
private Map<String, Float> copyToMap(MapReduceResults<ValueObject> results) {
|
||||
List<ValueObject> valueObjects = new ArrayList<ValueObject>();
|
||||
for (ValueObject valueObject : results) {
|
||||
valueObjects.add(valueObject);
|
||||
}
|
||||
|
||||
Map<String, Float> m = new HashMap<String, Float>();
|
||||
for (ValueObject vo : valueObjects) {
|
||||
m.put(vo.getId(), vo.getValue());
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
private void assertMapReduceResults(Map<String, Float> m) {
|
||||
assertEquals(4, m.size());
|
||||
assertEquals(1, m.get("a").intValue());
|
||||
assertEquals(2, m.get("b").intValue());
|
||||
assertEquals(2, m.get("c").intValue());
|
||||
assertEquals(1, m.get("d").intValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -390,11 +390,6 @@ public class UpdateTests {
|
||||
.isEqualTo(new Document().append("$bit", new Document("key", new Document("xor", 10L))));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-943
|
||||
public void pushShouldThrowExceptionWhenGivenNegativePosition() {
|
||||
new Update().push("foo").atPosition(-1).each("booh");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1346
|
||||
public void registersMultiplePullAllClauses() {
|
||||
|
||||
|
||||
@@ -15,18 +15,28 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import org.springframework.data.mongodb.core.geo.GeoJson;
|
||||
|
||||
import com.querydsl.core.annotations.QueryEmbeddable;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@QueryEmbeddable
|
||||
@Getter
|
||||
@Setter
|
||||
public class Address {
|
||||
|
||||
private String street;
|
||||
private String zipCode;
|
||||
private String city;
|
||||
|
||||
private GeoJson location;
|
||||
|
||||
protected Address() {
|
||||
|
||||
}
|
||||
@@ -41,46 +51,4 @@ public class Address {
|
||||
this.zipCode = zipcode;
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the street
|
||||
*/
|
||||
public String getStreet() {
|
||||
return street;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param street the street to set
|
||||
*/
|
||||
public void setStreet(String street) {
|
||||
this.street = street;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the zipCode
|
||||
*/
|
||||
public String getZipCode() {
|
||||
return zipCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zipCode the zipCode to set
|
||||
*/
|
||||
public void setZipCode(String zipCode) {
|
||||
this.zipCode = zipCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the city
|
||||
*/
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param city the city to set
|
||||
*/
|
||||
public void setCity(String city) {
|
||||
this.city = city;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,6 +313,11 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF
|
||||
StepVerifier.create(repository.findFirstByLastname(dave.getLastname())).expectNextCount(1).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2030
|
||||
public void shouldReturnExistsBy() {
|
||||
StepVerifier.create(repository.existsByLastname(dave.getLastname())).expectNext(true).verifyComplete();
|
||||
}
|
||||
|
||||
interface ReactivePersonRepository extends ReactiveMongoRepository<Person, String> {
|
||||
|
||||
Flux<Person> findByLastname(String lastname);
|
||||
@@ -340,6 +345,8 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF
|
||||
|
||||
Flux<Person> findPersonByLocationNear(Point point, Distance maxDistance);
|
||||
|
||||
Mono<Boolean> existsByLastname(String lastname);
|
||||
|
||||
Mono<Person> findFirstByLastname(String lastname);
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,14 @@ public class ReactiveStringBasedMongoQueryUnitTests {
|
||||
createQueryForMethod("invalidMethod", String.class);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2030
|
||||
public void shouldSupportExistsProjection() throws Exception {
|
||||
|
||||
ReactiveStringBasedMongoQuery mongoQuery = createQueryForMethod("existsByLastname", String.class);
|
||||
|
||||
assertThat(mongoQuery.isExistsQuery(), is(true));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception {
|
||||
|
||||
@@ -260,5 +268,8 @@ public class ReactiveStringBasedMongoQueryUnitTests {
|
||||
|
||||
@Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}")
|
||||
Flux<Person> findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2);
|
||||
|
||||
@Query(value = "{ 'lastname' : ?0 }", exists = true)
|
||||
Mono<Boolean> existsByLastname(String lastname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.*;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -322,6 +323,21 @@ public class StringBasedMongoQueryUnitTests {
|
||||
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2029
|
||||
public void shouldSupportNonQuotedBinaryCollectionDataReplacement() {
|
||||
|
||||
byte[] binaryData = "Matthews".getBytes(StandardCharsets.UTF_8);
|
||||
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter,
|
||||
(Object) Arrays.asList(binaryData));
|
||||
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsBinaryIn", List.class);
|
||||
|
||||
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
|
||||
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : { $in: [{'$binary' : '"
|
||||
+ DatatypeConverter.printBase64Binary(binaryData) + "', '$type' : '" + BSON.B_GENERAL + "'}] }}");
|
||||
|
||||
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1911
|
||||
public void shouldSupportNonQuotedUUIDReplacement() {
|
||||
|
||||
@@ -336,6 +352,23 @@ public class StringBasedMongoQueryUnitTests {
|
||||
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2029
|
||||
public void shouldSupportNonQuotedUUIDCollectionReplacement() {
|
||||
|
||||
UUID uuid1 = UUID.fromString("864de43b-e3ea-f1e4-3663-fb8240b659b9");
|
||||
UUID uuid2 = UUID.fromString("864de43b-cafe-f1e4-3663-fb8240b659b9");
|
||||
|
||||
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter,
|
||||
(Object) Arrays.asList(uuid1, uuid2));
|
||||
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsUUIDIn", List.class);
|
||||
|
||||
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
|
||||
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(
|
||||
"{'lastname' : { $in: [{ $binary : \"5PHq4zvkTYa5WbZAgvtjNg==\", $type : \"03\" }, { $binary : \"5PH+yjvkTYa5WbZAgvtjNg==\", $type : \"03\" }]}}");
|
||||
|
||||
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1911
|
||||
public void shouldSupportQuotedUUIDReplacement() {
|
||||
|
||||
@@ -557,6 +590,19 @@ public class StringBasedMongoQueryUnitTests {
|
||||
assertThat(query.getQueryObject(), is(new Document("arg0", null)));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2119
|
||||
public void spelShouldIgnoreJsonParseErrorsForRegex() {
|
||||
|
||||
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByPersonLastnameRegex", Person.class);
|
||||
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter,
|
||||
new Person("Molly", "Chandler"));
|
||||
|
||||
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
|
||||
|
||||
assertThat(query.getQueryObject().toJson(),
|
||||
is(new BasicQuery("{lastname: {$regex: 'Chandler'}}").getQueryObject().toJson()));
|
||||
}
|
||||
|
||||
private StringBasedMongoQuery createQueryForMethod(String name, Class<?>... parameters) {
|
||||
|
||||
try {
|
||||
@@ -580,9 +626,15 @@ public class StringBasedMongoQueryUnitTests {
|
||||
@Query("{ 'lastname' : ?0 }")
|
||||
Person findByLastnameAsBinary(byte[] lastname);
|
||||
|
||||
@Query("{ 'lastname' : { $in: ?0} }")
|
||||
Person findByLastnameAsBinaryIn(List<byte[]> lastname);
|
||||
|
||||
@Query("{ 'lastname' : ?0 }")
|
||||
Person findByLastnameAsUUID(UUID lastname);
|
||||
|
||||
@Query("{ 'lastname' : { $in : ?0} }")
|
||||
Person findByLastnameAsUUIDIn(List<UUID> lastname);
|
||||
|
||||
@Query("{ 'lastname' : '?0' }")
|
||||
Person findByLastnameAsStringUUID(UUID lastname);
|
||||
|
||||
@@ -658,10 +710,14 @@ public class StringBasedMongoQueryUnitTests {
|
||||
@Query("{ 'arg0' : '?0', 'arg1' : '?1s' }")
|
||||
List<Person> findByWhenQuotedAndSomeStuffAppended(String arg0, String arg1);
|
||||
|
||||
@Query("{ 'lastname' : { '$regex' : '^(?0|John ?1|?1)'} }") // use spel or some regex string this is fucking bad
|
||||
@Query("{ 'lastname' : { '$regex' : '^(?0|John ?1|?1)'} }") // use spel or some regex string this is bad
|
||||
Person findByLastnameRegex(String lastname, String alternative);
|
||||
|
||||
@Query("{ arg0 : ?#{[0]} }")
|
||||
List<Person> findByUsingSpel(Object arg0);
|
||||
|
||||
@Query("{ 'lastname' : { '$regex' : ?#{[0].lastname} } }")
|
||||
Person findByPersonLastnameRegex(Person key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
|
||||
import org.springframework.data.mongodb.repository.Address;
|
||||
import org.springframework.data.mongodb.repository.Person;
|
||||
import org.springframework.data.mongodb.repository.QPerson;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
@@ -99,4 +101,21 @@ public class QuerydslMongoPredicateExecutorIntegrationTests {
|
||||
public void findOneWithPredicateThrowsExceptionForNonUniqueResults() {
|
||||
repository.findOne(person.firstname.contains("e"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2101
|
||||
public void readEntityWithGeoJsonValue() {
|
||||
|
||||
Address adr1 = new Address("Hauptplatz", "4020", "Linz");
|
||||
adr1.setLocation(new GeoJsonPoint(48.3063548, 14.2851337));
|
||||
|
||||
Person person1 = new Person("Max", "The Mighty");
|
||||
person1.setAddress(adr1);
|
||||
|
||||
operations.save(person1);
|
||||
|
||||
List<Person> result = new SpringDataMongodbQuery<>(operations, Person.class).where(person.firstname.eq("Max"))
|
||||
.fetch();
|
||||
|
||||
assertThat(result).containsExactly(person1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2018 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.
|
||||
@@ -50,18 +50,17 @@ class ExecutableFindOperationExtensionsTests {
|
||||
verify(operation).query(First::class.java)
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1689
|
||||
@Test // DATAMONGO-1689, DATAMONGO-2086
|
||||
fun `ExecutableFindOperation#FindOperationWithProjection#asType(KClass) extension should call its Java counterpart`() {
|
||||
|
||||
operationWithProjection.asType(First::class)
|
||||
verify(operationWithProjection).`as`(First::class.java)
|
||||
operationWithProjection.asType(User::class)
|
||||
verify(operationWithProjection).`as`(User::class.java)
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1689
|
||||
@Test // DATAMONGO-1689, DATAMONGO-2086
|
||||
fun `ExecutableFindOperation#FindOperationWithProjection#asType() with reified type parameter extension should call its Java counterpart`() {
|
||||
|
||||
operationWithProjection.asType()
|
||||
verify(operationWithProjection).`as`(First::class.java)
|
||||
operationWithProjection.asType<User>()
|
||||
verify(operationWithProjection).`as`(User::class.java)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2018 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,17 +49,17 @@ class ReactiveFindOperationExtensionsTests {
|
||||
verify(operation).query(First::class.java)
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1719
|
||||
@Test // DATAMONGO-1719, DATAMONGO-2086
|
||||
fun `ReactiveFind#FindOperatorWithProjection#asType(KClass) extension should call its Java counterpart`() {
|
||||
|
||||
operationWithProjection.asType(First::class)
|
||||
verify(operationWithProjection).`as`(First::class.java)
|
||||
operationWithProjection.asType(User::class)
|
||||
verify(operationWithProjection).`as`(User::class.java)
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1719
|
||||
@Test // DATAMONGO-1719, DATAMONGO-2086
|
||||
fun `ReactiveFind#FindOperatorWithProjection#asType() with reified type parameter extension should call its Java counterpart`() {
|
||||
|
||||
operationWithProjection.asType()
|
||||
verify(operationWithProjection).`as`(First::class.java)
|
||||
operationWithProjection.asType<User>()
|
||||
verify(operationWithProjection).`as`(User::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ Note that the domain type shown in the preceding example has a property named `i
|
||||
====
|
||||
[source]
|
||||
----
|
||||
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
|
||||
public interface PersonRepository extends PagingAndSortingRepository<Person, String> {
|
||||
|
||||
// additional custom query methods go here
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ To create a Spring project in STS:
|
||||
. Go to File -> New -> Spring Template Project -> Simple Spring Utility Project, and press Yes when prompted. Then enter a project and a package name, such as `org.spring.mongodb.example`.
|
||||
.Add the following to the pom.xml files `dependencies` element:
|
||||
+
|
||||
[source,xml]
|
||||
[source,xml,subs="+attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
|
||||
@@ -45,7 +45,7 @@ To create a Spring project in STS:
|
||||
----
|
||||
. Change the version of Spring in the pom.xml to be
|
||||
+
|
||||
[source,xml]
|
||||
[source,xml,subs="+attributes"]
|
||||
----
|
||||
<spring.framework.version>{springVersion}</spring.framework.version>
|
||||
----
|
||||
@@ -1794,6 +1794,7 @@ The MongoDB Aggregation Framework provides the following types of aggregation op
|
||||
* Array Aggregation Operators
|
||||
* Conditional Aggregation Operators
|
||||
* Lookup Aggregation Operators
|
||||
* Convert Aggregation Operators
|
||||
|
||||
At the time of this writing, we provide support for the following Aggregation Operations in Spring Data MongoDB:
|
||||
|
||||
@@ -1813,7 +1814,7 @@ At the time of this writing, we provide support for the following Aggregation Op
|
||||
| `abs`, `add` (*via `plus`), `ceil`, `divide`, `exp`, `floor`, `ln`, `log`, `log10`, `mod`, `multiply`, `pow`, `sqrt`, `subtract` (*via `minus`), `trunc`
|
||||
|
||||
| String Aggregation Operators
|
||||
| `concat`, `substr`, `toLower`, `toUpper`, `stcasecmp`, `indexOfBytes`, `indexOfCP`, `split`, `strLenBytes`, `strLenCP`, `substrCP`
|
||||
| `concat`, `substr`, `toLower`, `toUpper`, `stcasecmp`, `indexOfBytes`, `indexOfCP`, `split`, `strLenBytes`, `strLenCP`, `substrCP`, `trim`, `ltrim`, `rtim`
|
||||
|
||||
| Comparison Aggregation Operators
|
||||
| `eq` (*via: `is`), `gt`, `gte`, `lt`, `lte`, `ne`
|
||||
@@ -1836,6 +1837,8 @@ At the time of this writing, we provide support for the following Aggregation Op
|
||||
| Type Aggregation Operators
|
||||
| `type`
|
||||
|
||||
| Convert Aggregation Operators
|
||||
| `convert`, `toBool`, `toDate`, `toDecimal`, `toDouble`, `toInt`, `toLong`, `toObjectId`, `toString`
|
||||
|===
|
||||
|
||||
* The operation is mapped or added by Spring Data MongoDB.
|
||||
|
||||
@@ -47,7 +47,7 @@ Note that the entity defined in the preceding example has a property named `id`
|
||||
====
|
||||
[source]
|
||||
----
|
||||
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {
|
||||
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {
|
||||
|
||||
Flux<Person> findByFirstname(String firstname); <1>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ To create a Spring project in STS, go to File -> New -> Spring Template Project
|
||||
|
||||
Then add the following to the pom.xml dependencies section.
|
||||
|
||||
[source,xml]
|
||||
[source,xml,subs="+attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
|
||||
@@ -40,7 +40,7 @@ Then add the following to the pom.xml dependencies section.
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongodb-driver-reactivestreams</artifactId>
|
||||
<version>{mongo.reactivestreams}</version>
|
||||
<version>{mongo-reactivestreams}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -1,6 +1,172 @@
|
||||
Spring Data MongoDB Changelog
|
||||
=============================
|
||||
|
||||
Changes in version 2.0.12.RELEASE (2018-11-27)
|
||||
----------------------------------------------
|
||||
* DATAMONGO-2135 - Use List instead of Collection when reading data with MappingMongoConverter.
|
||||
* DATAMONGO-2119 - Combining a SpEL expression and a $regex filter triggers a PatternSyntaxException.
|
||||
* DATAMONGO-2118 - Fix typo in repositories reference documentation.
|
||||
* DATAMONGO-2109 - Release 2.0.12 (Kay SR12).
|
||||
* DATAMONGO-2098 - Typo in MappingMongoConverterParser.
|
||||
|
||||
|
||||
Changes in version 1.10.17.RELEASE (2018-11-27)
|
||||
-----------------------------------------------
|
||||
* DATAMONGO-2135 - Use List instead of Collection when reading data with MappingMongoConverter.
|
||||
* DATAMONGO-2118 - Fix typo in repositories reference documentation.
|
||||
* DATAMONGO-2110 - Release 1.10.17 (Ingalls SR17).
|
||||
|
||||
|
||||
Changes in version 2.1.2.RELEASE (2018-10-29)
|
||||
---------------------------------------------
|
||||
* DATAMONGO-2118 - Fix typo in repositories reference documentation.
|
||||
* DATAMONGO-2113 - ChangeStreamTasks incorrectly converts resumeAt Instants into BsonTimestamp.
|
||||
* DATAMONGO-2107 - Release 2.1.2 (Lovelace SR2).
|
||||
* DATAMONGO-2098 - Typo in MappingMongoConverterParser.
|
||||
|
||||
|
||||
Changes in version 1.10.16.RELEASE (2018-10-15)
|
||||
-----------------------------------------------
|
||||
* DATAMONGO-2096 - Aggregation.graphLookup.**.connectFrom(String) does not handle nested field.
|
||||
* DATAMONGO-2083 - Release 1.10.16 (Ingalls SR16).
|
||||
|
||||
|
||||
Changes in version 2.0.11.RELEASE (2018-10-15)
|
||||
----------------------------------------------
|
||||
* DATAMONGO-2101 - NoSuchMethodException: org.springframework.data.mongodb.core.geo.GeoJsonPoint.<init>().
|
||||
* DATAMONGO-2096 - Aggregation.graphLookup.**.connectFrom(String) does not handle nested field.
|
||||
* DATAMONGO-2087 - Typo in MongoRepository.
|
||||
* DATAMONGO-2086 - Kotlin Fluent API extensions do not allow projections with find queries.
|
||||
* DATAMONGO-2084 - Release 2.0.11 (Kay SR11).
|
||||
|
||||
|
||||
Changes in version 2.1.1.RELEASE (2018-10-15)
|
||||
---------------------------------------------
|
||||
* DATAMONGO-2096 - Aggregation.graphLookup.**.connectFrom(String) does not handle nested field.
|
||||
* DATAMONGO-2094 - Release 2.1.1 (Lovelace SR1).
|
||||
|
||||
|
||||
Changes in version 2.1.0.RELEASE (2018-09-21)
|
||||
---------------------------------------------
|
||||
* DATAMONGO-2091 - Upgrade to MongoDB Java Driver 3.8.2 and Reactive Streams Driver 1.9.2.
|
||||
* DATAMONGO-2090 - Include documentation about Object Mapping Fundamentals.
|
||||
* DATAMONGO-2087 - Typo in MongoRepository.
|
||||
* DATAMONGO-2086 - Kotlin Fluent API extensions do not allow projections with find queries.
|
||||
* DATAMONGO-2080 - DTO projections with reactive @Tailable query methods fail with IllegalArgumentException: Property must not be null.
|
||||
* DATAMONGO-2078 - Update reference documentation on tailable cursors with the sync MongoDB Java driver.
|
||||
* DATAMONGO-2076 - Fix property substitution in getting started section of reference docs.
|
||||
* DATAMONGO-2075 - Open up MongoTransaction manager to allow transaction commit retry..
|
||||
* DATAMONGO-2069 - Required dependency 'mysema-commons-lang'.
|
||||
* DATAMONGO-2065 - Make sure that MongoTemplate.doSave(…) calls local populateIdIfNecessary(…) to allow customization.
|
||||
* DATAMONGO-2064 - Upgrade to MongoDB Java Driver 3.8.1.
|
||||
* DATAMONGO-2061 - Release 2.1 GA (Lovelace).
|
||||
|
||||
|
||||
Changes in version 2.0.10.RELEASE (2018-09-10)
|
||||
----------------------------------------------
|
||||
* DATAMONGO-2076 - Fix property substitution in getting started section of reference docs.
|
||||
* DATAMONGO-2055 - Allow position modifier to be negative using push at position on Update.
|
||||
* DATAMONGO-2051 - Add support for SCRAM-SHA-256 authentication mechanism to MongoCredentialPropertyEditor.
|
||||
* DATAMONGO-2050 - Add support for index selection via key attribute for $geoNear aggregation.
|
||||
* DATAMONGO-2049 - Add support for MongoDB 4.0 string aggregation operators.
|
||||
* DATAMONGO-2048 - Add support for MongoDB 4.0 type conversion aggregation operators.
|
||||
* DATAMONGO-2047 - Update $dateToString and $dateFromString aggregation operators to match MongoDB 4.0 changes.
|
||||
* DATAMONGO-2046 - Investigate performance regressions between 2.0 GA and 2.1 RC2.
|
||||
* DATAMONGO-2043 - MappingMongoConverter.write(…) does not consider Document a native type and adds _class attribute.
|
||||
* DATAMONGO-2034 - Release 2.0.10 (Kay SR10).
|
||||
* DATAMONGO-2027 - outputCollection and outputType of MapReduceOptions not work.
|
||||
|
||||
|
||||
Changes in version 1.10.15.RELEASE (2018-09-10)
|
||||
-----------------------------------------------
|
||||
* DATAMONGO-2076 - Fix property substitution in getting started section of reference docs.
|
||||
* DATAMONGO-2057 - Guard MongoDbUtils integration tests against MongoDB 4.0 changes.
|
||||
* DATAMONGO-2055 - Allow position modifier to be negative using push at position on Update.
|
||||
* DATAMONGO-2051 - Add support for SCRAM-SHA-256 authentication mechanism to MongoCredentialPropertyEditor.
|
||||
* DATAMONGO-2050 - Add support for index selection via key attribute for $geoNear aggregation.
|
||||
* DATAMONGO-2049 - Add support for MongoDB 4.0 string aggregation operators.
|
||||
* DATAMONGO-2048 - Add support for MongoDB 4.0 type conversion aggregation operators.
|
||||
* DATAMONGO-2047 - Update $dateToString and $dateFromString aggregation operators to match MongoDB 4.0 changes.
|
||||
* DATAMONGO-2043 - MappingMongoConverter.write(…) does not consider Document a native type and adds _class attribute.
|
||||
* DATAMONGO-2035 - Release 1.10.15 (Ingalls SR15).
|
||||
|
||||
|
||||
Changes in version 2.1.0.RC2 (2018-08-20)
|
||||
-----------------------------------------
|
||||
* DATAMONGO-2055 - Allow position modifier to be negative using push at position on Update.
|
||||
* DATAMONGO-2053 - Add support for $mergeObjects aggregation operator.
|
||||
* DATAMONGO-2052 - Add support for MongoDB 3.6 array aggregation operators.
|
||||
* DATAMONGO-2051 - Add support for SCRAM-SHA-256 authentication mechanism to MongoCredentialPropertyEditor.
|
||||
* DATAMONGO-2050 - Add support for index selection via key attribute for $geoNear aggregation.
|
||||
* DATAMONGO-2049 - Add support for MongoDB 4.0 string aggregation operators.
|
||||
* DATAMONGO-2048 - Add support for MongoDB 4.0 type conversion aggregation operators.
|
||||
* DATAMONGO-2047 - Update $dateToString and $dateFromString aggregation operators to match MongoDB 4.0 changes.
|
||||
* DATAMONGO-2046 - Investigate performance regressions between 2.0 GA and 2.1 RC2.
|
||||
* DATAMONGO-2045 - Add transaction specific error codes for exception translation.
|
||||
* DATAMONGO-2043 - MappingMongoConverter.write(…) does not consider Document a native type and adds _class attribute.
|
||||
* DATAMONGO-2041 - Automatically derive the fields to be read from the expected result type.
|
||||
* DATAMONGO-2040 - Update mongodb docs to reflect Duplicates.DROP is no longer supported.
|
||||
* DATAMONGO-2033 - Release 2.1 RC2 (Lovelace).
|
||||
* DATAMONGO-2027 - outputCollection and outputType of MapReduceOptions not work.
|
||||
|
||||
|
||||
Changes in version 1.10.14.RELEASE (2018-07-27)
|
||||
-----------------------------------------------
|
||||
* DATAMONGO-2023 - ClassCastException for $sample aggregation operation.
|
||||
* DATAMONGO-2021 - Use correct document identifier on GridFsOperations getResource method.
|
||||
* DATAMONGO-2016 - MongoCredentialPropertyEditor throws ArrayIndexOutOfBoundsException when password contains a questionmark.
|
||||
* DATAMONGO-2006 - Release 1.10.14 (Ingalls SR14).
|
||||
|
||||
|
||||
Changes in version 2.0.9.RELEASE (2018-07-26)
|
||||
---------------------------------------------
|
||||
* DATAMONGO-2030 - Reactive repositories don't handle existsByProperty properly.
|
||||
* DATAMONGO-2029 - String query methods do not convert List<UUID> and List<byte[]> to their correct representation.
|
||||
* DATAMONGO-2016 - MongoCredentialPropertyEditor throws ArrayIndexOutOfBoundsException when password contains a questionmark.
|
||||
* DATAMONGO-2011 - Getting exception in MappingMongoConverter while trying to map array of arrays into Object.
|
||||
* DATAMONGO-2007 - Release 2.0.9 (Kay SR9).
|
||||
|
||||
|
||||
Changes in version 2.1.0.RC1 (2018-07-26)
|
||||
-----------------------------------------
|
||||
* DATAMONGO-2030 - Reactive repositories don't handle existsByProperty properly.
|
||||
* DATAMONGO-2029 - String query methods do not convert List<UUID> and List<byte[]> to their correct representation.
|
||||
* DATAMONGO-2028 - EntityOperations does not apply conversion to Mongo types.
|
||||
* DATAMONGO-2026 - Final id field causes UnsupportedOperationException when reading object.
|
||||
* DATAMONGO-2021 - Use correct document identifier on GridFsOperations getResource method.
|
||||
* DATAMONGO-2016 - MongoCredentialPropertyEditor throws ArrayIndexOutOfBoundsException when password contains a questionmark.
|
||||
* DATAMONGO-2012 - Upgrade to MongoDB java driver 3.8 and reactive streams 1.9.
|
||||
* DATAMONGO-2011 - Getting exception in MappingMongoConverter while trying to map array of arrays into Object.
|
||||
* DATAMONGO-2010 - SpringDataMongodbSerializer does not convert 'in' predicate for nested String id properties mapping to ObjectId.
|
||||
* DATAMONGO-2005 - Switch reactive transactions API over to Flux.usingWhen.
|
||||
* DATAMONGO-2004 - Lazily resolve DBRefs during constructor creation of the enclosing entity.
|
||||
* DATAMONGO-2003 - MongoQueryCreator will not create query correctly when passing Pattern Object with options.
|
||||
* DATAMONGO-2002 - Criteria instances using different regex Patterns are considered equal.
|
||||
* DATAMONGO-2001 - Count within session should return only the total count of documents visible to the specific session.
|
||||
* DATAMONGO-1998 - SpringDataMongodbSerializer cannot convert query on nested id.
|
||||
* DATAMONGO-1996 - ChangeStreamEvent Aggregator filter doesn't work for nested fields.
|
||||
* DATAMONGO-1993 - Change default fullDocumentLookup in ReactiveMongoTemplate.changeStream.
|
||||
* DATAMONGO-1992 - Add support for immutable objects.
|
||||
* DATAMONGO-1990 - Adapt build to changes in is-new-handling.
|
||||
* DATAMONGO-1988 - String ID not working for embeded objects.
|
||||
* DATAMONGO-1987 - Upgarde test infrastructure to MongoDB 4.0-rc0.
|
||||
* DATAMONGO-1986 - Aggregation MatchOperation fails to convert query.
|
||||
* DATAMONGO-1983 - Reference documentation does not include Transaction and sessions.
|
||||
* DATAMONGO-1982 - Release 2.1 RC1 (Lovelace).
|
||||
* DATAMONGO-1979 - Adding Support For Sorting in @Query Annotation and Creating an alias for value.
|
||||
* DATAMONGO-1919 - Decouple reactive mongo bits from blocking MongoClient.
|
||||
* DATAMONGO-1848 - Migrate to Document API-based Querydsl implementation.
|
||||
* DATAMONGO-1842 - Optimistic locking allows ReactiveMongoTemplate to modify immutable objects.
|
||||
* DATAMONGO-1827 - Allow document replacements via MongoCollection.findOneAndReplace(…).
|
||||
* DATAMONGO-1810 - Querydsl predicate using IN operator fails for DBRef.
|
||||
* DATAMONGO-1434 - Convert Exceptions into Spring Exception hierarchy when going through Querydsl.
|
||||
* DATAMONGO-1311 - Add an option to specify the cursor.batchSize() for repository methods returning streams.
|
||||
* DATAMONGO-1185 - Provide lifecycle events when using QueryDSL.
|
||||
* DATAMONGO-700 - Events not triggered during Querydsl repository usage.
|
||||
* DATAMONGO-595 - Expose new QueryDSL elemMatch feature.
|
||||
* DATAMONGO-362 - QueryDSL does not work with DBRef.
|
||||
|
||||
|
||||
Changes in version 2.0.8.RELEASE (2018-06-13)
|
||||
---------------------------------------------
|
||||
* DATAMONGO-2003 - MongoQueryCreator will not create query correctly when passing Pattern Object with options.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Spring Data MongoDB 2.0.8
|
||||
Spring Data MongoDB 2.0.12
|
||||
Copyright (c) [2010-2015] Pivotal Software, Inc.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
|
||||
Reference in New Issue
Block a user