DATAMONGO-1518 - Add support for collations.
We now support collations for collections, indexes, queries, findAndModify, delete, geo, bulk and aggregation operations in both the imperative and reactive implementations on template level.
Collation collation = Collation.of("fr")
.strength(ComparisonLevel.secondary()
.includeCase())
.numericOrderingEnabled()
.alternate(Alternate.shifted().punct())
.forwardDiacriticSort()
.normalizationEnabled();
template.createCollection(Person.class, CollectionOptions.just(collation));
Query query = new Query(Criteria.where("firstName").is("Amél")).collation(collation);
Original pull request: #459.
This commit is contained in:
committed by
Mark Paluch
parent
b9d301e525
commit
0b169d5341
@@ -0,0 +1,754 @@
|
||||
/*
|
||||
* Copyright 2017 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;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.client.model.Collation.Builder;
|
||||
import com.mongodb.client.model.CollationAlternate;
|
||||
import com.mongodb.client.model.CollationCaseFirst;
|
||||
import com.mongodb.client.model.CollationMaxVariable;
|
||||
import com.mongodb.client.model.CollationStrength;
|
||||
|
||||
/**
|
||||
* Central abstraction for MongoDB collation support. <br />
|
||||
* Allows fluent creation of a collation {@link Document} that can be used for creating collections & indexes as well as
|
||||
* querying data.
|
||||
* <p />
|
||||
* <strong>NOTE:</strong> Please keep in mind that queries will only make use of an index with collation settings if the
|
||||
* query itself specifies the same collation.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/collation/">MongoDB Reference - Collation</a>
|
||||
*/
|
||||
public class Collation {
|
||||
|
||||
private static final Collation DEFAULT = of("simple");
|
||||
|
||||
private final ICULocale locale;
|
||||
|
||||
private Optional<ICUComparisonLevel> strength = Optional.empty();
|
||||
private Optional<Boolean> numericOrdering = Optional.empty();
|
||||
private Optional<Alternate> alternate = Optional.empty();
|
||||
private Optional<Boolean> backwards = Optional.empty();
|
||||
private Optional<Boolean> normalization = Optional.empty();
|
||||
private Optional<String> version = Optional.empty();
|
||||
|
||||
private Collation(ICULocale locale) {
|
||||
|
||||
Assert.notNull(locale, "ICULocale must not be null!");
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link Collation} using simple binary comparison.
|
||||
*
|
||||
* @return
|
||||
* @see #binary()
|
||||
*/
|
||||
public static Collation simple() {
|
||||
return binary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link Collation} using simple binary comparison.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Collation binary() {
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link Collation} with locale set to {{@link java.util.Locale#getLanguage()}} and
|
||||
* {@link java.util.Locale#getVariant()}.
|
||||
*
|
||||
* @param locale must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Collation of(Locale locale) {
|
||||
|
||||
Assert.notNull(locale, "Locale must not be null!");
|
||||
return of(ICULocale.of(locale.getLanguage()).variant(locale.getVariant()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link Collation} with locale set to the given ICU language.
|
||||
*
|
||||
* @param language must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Collation of(String language) {
|
||||
return of(ICULocale.of(language));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link Collation} with locale set to the given {@link ICULocale}.
|
||||
*
|
||||
* @param locale must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Collation of(ICULocale locale) {
|
||||
return new Collation(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link Collation} from values in {@link Document}.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/collation/#collation-document">MongoDB Reference -
|
||||
* Collation Document</a>
|
||||
*/
|
||||
public static Collation from(Document source) {
|
||||
|
||||
Assert.notNull(source, "Source must not be null!");
|
||||
|
||||
Collation collation = Collation.of(source.getString("locale"));
|
||||
if (source.containsKey("strength")) {
|
||||
collation = collation.strength(source.getInteger("strength"));
|
||||
}
|
||||
if (source.containsKey("caseLevel")) {
|
||||
collation = collation.caseLevel(source.getBoolean("caseLevel"));
|
||||
}
|
||||
if (source.containsKey("caseFirst")) {
|
||||
collation = collation.caseFirst(source.getString("caseFirst"));
|
||||
}
|
||||
if (source.containsKey("numericOrdering")) {
|
||||
collation = collation.numericOrdering(source.getBoolean("numericOrdering"));
|
||||
}
|
||||
if (source.containsKey("alternate")) {
|
||||
collation = collation.alternate(source.getString("alternate"));
|
||||
}
|
||||
if (source.containsKey("maxVariable")) {
|
||||
collation = collation.maxVariable(source.getString("maxVariable"));
|
||||
}
|
||||
if (source.containsKey("backwards")) {
|
||||
collation = collation.backwards(source.getBoolean("backwards"));
|
||||
}
|
||||
if (source.containsKey("normalization")) {
|
||||
collation = collation.normalization(source.getBoolean("normalization"));
|
||||
}
|
||||
if (source.containsKey("version")) {
|
||||
collation.version = Optional.of(source.get("version").toString());
|
||||
}
|
||||
return collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the level of comparison to perform.
|
||||
*
|
||||
* @param strength must not be {@literal null}.
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation strength(Integer strength) {
|
||||
|
||||
ICUComparisonLevel current = this.strength.orElseGet(() -> new ICUComparisonLevel(strength, null, null));
|
||||
return strength(new ICUComparisonLevel(strength, current.caseFirst.orElse(null), current.caseLevel.orElse(null)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the level of comparison to perform.
|
||||
*
|
||||
* @param comparisonLevel must not be {@literal null}.
|
||||
* @return new {@link Collation}
|
||||
*/
|
||||
public Collation strength(ICUComparisonLevel comparisonLevel) {
|
||||
|
||||
Collation newInstance = copy();
|
||||
newInstance.strength = Optional.ofNullable(comparisonLevel);
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set {@code caseLevel} comarison. <br />
|
||||
*
|
||||
* @param caseLevel must not be {@literal null}.
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation caseLevel(Boolean caseLevel) {
|
||||
|
||||
ICUComparisonLevel strengthValue = strength.orElseGet(() -> ICUComparisonLevel.primary());
|
||||
return strength(new ICUComparisonLevel(strengthValue.level, strengthValue.caseFirst.orElse(null), caseLevel));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flag that determines sort order of case differences during tertiary level comparisons.
|
||||
*
|
||||
* @param caseFirst must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Collation caseFirst(String caseFirst) {
|
||||
return caseFirst(new ICUCaseFirst(caseFirst));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flag that determines sort order of case differences during tertiary level comparisons.
|
||||
*
|
||||
* @param caseFirst must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Collation caseFirst(ICUCaseFirst sort) {
|
||||
|
||||
ICUComparisonLevel strengthValue = strength.orElseGet(() -> ICUComparisonLevel.tertiary());
|
||||
return strength(new ICUComparisonLevel(strengthValue.level, sort, strengthValue.caseLevel.orElse(null)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat numeric strings as numbers for comparison.
|
||||
*
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation numericOrderingEnabled() {
|
||||
return numericOrdering(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat numeric strings as string for comparison.
|
||||
*
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation numericOrderingDisabled() {
|
||||
return numericOrdering(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flag that determines whether to compare numeric strings as numbers or as strings.
|
||||
*
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation numericOrdering(Boolean flag) {
|
||||
|
||||
Collation newInstance = copy();
|
||||
newInstance.numericOrdering = Optional.ofNullable(flag);
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Field that determines whether collation should consider whitespace and punctuation as base characters for
|
||||
* purposes of comparison.
|
||||
*
|
||||
* @param alternate must not be {@literal null}.
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation alternate(String alternate) {
|
||||
|
||||
Alternate instance = this.alternate.orElseGet(() -> new Alternate(alternate, null));
|
||||
return alternate(new Alternate(alternate, instance.maxVariable.orElse(null)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Field that determines whether collation should consider whitespace and punctuation as base characters for
|
||||
* purposes of comparison.
|
||||
*
|
||||
* @param alternate must not be {@literal null}.
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation alternate(Alternate alternate) {
|
||||
|
||||
Collation newInstance = copy();
|
||||
newInstance.alternate = Optional.ofNullable(alternate);
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort string with diacritics sort from back of the string.
|
||||
*
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation backwardDiacriticSort() {
|
||||
return backwards(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not sort string with diacritics sort from back of the string.
|
||||
*
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation forwardDiacriticSort() {
|
||||
return backwards(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flag that determines whether strings with diacritics sort from back of the string.
|
||||
*
|
||||
* @param backwards must not be {@literal null}.
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation backwards(Boolean backwards) {
|
||||
|
||||
Collation newInstance = copy();
|
||||
newInstance.backwards = Optional.ofNullable(backwards);
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable text normalization.
|
||||
*
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation normalizationEnabled() {
|
||||
return normalization(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable text normalization.
|
||||
*
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation normalizationDisabled() {
|
||||
return normalization(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flag that determines whether to check if text require normalization and to perform normalization.
|
||||
*
|
||||
* @param normalization must not be {@literal null}.
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation normalization(Boolean normalization) {
|
||||
|
||||
Collation newInstance = copy();
|
||||
newInstance.normalization = Optional.ofNullable(normalization);
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the field that determines up to which characters are considered ignorable when alternate is {@code shifted}.
|
||||
*
|
||||
* @param maxVariable must not be {@literal null}.
|
||||
* @return new {@link Collation}.
|
||||
*/
|
||||
public Collation maxVariable(String maxVariable) {
|
||||
|
||||
Alternate alternateValue = alternate.orElseGet(() -> Alternate.shifted());
|
||||
return alternate(new AlternateWithMaxVariable(alternateValue.alternate, maxVariable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Document} representation of the {@link Collation}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Document toDocument() {
|
||||
return map(toMongoDocumentConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link com.mongodb.client.model.Collation} representation of the {@link Collation}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public com.mongodb.client.model.Collation toMongoCollation() {
|
||||
return map(toMongoCollationConverter());
|
||||
}
|
||||
|
||||
public <R> R map(Converter<? super Collation, ? extends R> mapper) {
|
||||
return mapper.convert(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toDocument().toJson();
|
||||
}
|
||||
|
||||
private Collation copy() {
|
||||
|
||||
Collation collation = new Collation(locale);
|
||||
collation.strength = this.strength;
|
||||
collation.normalization = this.normalization;
|
||||
collation.numericOrdering = this.numericOrdering;
|
||||
collation.alternate = this.alternate;
|
||||
collation.backwards = this.backwards;
|
||||
return collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstraction for the ICU Comparison Levels.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public static class ICUComparisonLevel {
|
||||
|
||||
protected final Integer level;
|
||||
private final Optional<ICUCaseFirst> caseFirst;
|
||||
private final Optional<Boolean> caseLevel;
|
||||
|
||||
private ICUComparisonLevel(Integer level, ICUCaseFirst caseFirst, Boolean caseLevel) {
|
||||
|
||||
this.level = level;
|
||||
this.caseFirst = Optional.ofNullable(caseFirst);
|
||||
this.caseLevel = Optional.ofNullable(caseLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary level of comparison. Collation performs comparisons of the base characters only, ignoring other
|
||||
* differences such as diacritics and case. <br />
|
||||
* The {@code caseLevel} can be set via {@link ComparisonLevelWithCase#caseLevel(Boolean)}.
|
||||
*
|
||||
* @return new {@link ComparisonLevelWithCase}.
|
||||
*/
|
||||
public static PrimaryICUComparisonLevel primary() {
|
||||
return new PrimaryICUComparisonLevel(1, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scondary level of comparison. Collation performs comparisons up to secondary differences, such as
|
||||
* diacritics.<br />
|
||||
* The {@code caseLevel} can be set via {@link ComparisonLevelWithCase#caseLevel(Boolean)}.
|
||||
*
|
||||
* @return new {@link ComparisonLevelWithCase}.
|
||||
*/
|
||||
public static SecondaryICUComparisonLevel secondary() {
|
||||
return new SecondaryICUComparisonLevel(2, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tertiary level of comparison. Collation performs comparisons up to tertiary differences, such as case and letter
|
||||
* variants. <br />
|
||||
* The {@code caseLevel} cannot be set for {@link ICUComparisonLevel} above {@code secondary}.
|
||||
*
|
||||
* @return new {@link ICUComparisonLevel}.
|
||||
*/
|
||||
public static TertiaryICUComparisonLevel tertiary() {
|
||||
return new TertiaryICUComparisonLevel(3, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quaternary Level. Limited for specific use case to consider punctuation. <br />
|
||||
* The {@code caseLevel} cannot be set for {@link ICUComparisonLevel} above {@code secondary}.
|
||||
*
|
||||
* @return new {@link ICUComparisonLevel}.
|
||||
*/
|
||||
public static ICUComparisonLevel quaternary() {
|
||||
return new ICUComparisonLevel(4, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical Level. Limited for specific use case of tie breaker. <br />
|
||||
* The {@code caseLevel} cannot be set for {@link ICUComparisonLevel} above {@code secondary}.
|
||||
*
|
||||
* @return new {@link ICUComparisonLevel}.
|
||||
*/
|
||||
public static ICUComparisonLevel identical() {
|
||||
return new ICUComparisonLevel(5, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TertiaryICUComparisonLevel extends ICUComparisonLevel {
|
||||
|
||||
private TertiaryICUComparisonLevel(Integer level, ICUCaseFirst caseFirst) {
|
||||
super(level, caseFirst, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flag that determines sort order of case differences.
|
||||
*
|
||||
* @param caseFirstSort must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public TertiaryICUComparisonLevel caseFirst(ICUCaseFirst caseFirst) {
|
||||
|
||||
Assert.notNull(caseFirst, "CaseFirst must not be null!");
|
||||
return new TertiaryICUComparisonLevel(level, caseFirst);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PrimaryICUComparisonLevel extends ICUComparisonLevel {
|
||||
|
||||
private PrimaryICUComparisonLevel(Integer level, Boolean caseLevel) {
|
||||
super(level, null, caseLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Include case comparison.
|
||||
*
|
||||
* @return new {@link ComparisonLevelWithCase}
|
||||
*/
|
||||
public PrimaryICUComparisonLevel includeCase() {
|
||||
return caseLevel(Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude case comparison.
|
||||
*
|
||||
* @return new {@link ComparisonLevelWithCase}
|
||||
*/
|
||||
public PrimaryICUComparisonLevel excludeCase() {
|
||||
return caseLevel(Boolean.FALSE);
|
||||
}
|
||||
|
||||
PrimaryICUComparisonLevel caseLevel(Boolean caseLevel) {
|
||||
return new PrimaryICUComparisonLevel(level, caseLevel);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecondaryICUComparisonLevel extends ICUComparisonLevel {
|
||||
|
||||
private SecondaryICUComparisonLevel(Integer level, Boolean caseLevel) {
|
||||
super(level, null, caseLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Include case comparison.
|
||||
*
|
||||
* @return new {@link ComparisonLevelWithCase}
|
||||
*/
|
||||
public SecondaryICUComparisonLevel includeCase() {
|
||||
return caseLevel(Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude case comparison.
|
||||
*
|
||||
* @return new {@link ComparisonLevelWithCase}
|
||||
*/
|
||||
public SecondaryICUComparisonLevel excludeCase() {
|
||||
return caseLevel(Boolean.FALSE);
|
||||
}
|
||||
|
||||
SecondaryICUComparisonLevel caseLevel(Boolean caseLevel) {
|
||||
return new SecondaryICUComparisonLevel(level, caseLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
*/
|
||||
public static class ICUCaseFirst {
|
||||
|
||||
private final String state;
|
||||
|
||||
private ICUCaseFirst(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort uppercase before lowercase.
|
||||
*
|
||||
* @return new {@link ICUCaseFirst}.
|
||||
*/
|
||||
public static ICUCaseFirst upper() {
|
||||
return new ICUCaseFirst("upper");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort lowercase before uppercase.
|
||||
*
|
||||
* @return new {@link ICUCaseFirst}.
|
||||
*/
|
||||
public static ICUCaseFirst lower() {
|
||||
return new ICUCaseFirst("lower");
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the default.
|
||||
*
|
||||
* @return new {@link ICUCaseFirst}.
|
||||
*/
|
||||
public static ICUCaseFirst off() {
|
||||
return new ICUCaseFirst("off");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
*/
|
||||
public static class Alternate {
|
||||
|
||||
protected final String alternate;
|
||||
protected Optional<String> maxVariable;
|
||||
|
||||
private Alternate(String alternate, String maxVariable) {
|
||||
this.alternate = alternate;
|
||||
this.maxVariable = Optional.ofNullable(maxVariable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consider Whitespace and punctuation as base characters.
|
||||
*
|
||||
* @return new {@link Alternate}.
|
||||
*/
|
||||
public static Alternate nonIgnorable() {
|
||||
return new Alternate("non-ignorable", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whitespace and punctuation are <strong>not</strong> considered base characters and are only distinguished at
|
||||
* strength. <br />
|
||||
* <strong>NOTE:</strong> Only works for {@link ICUComparisonLevel} above {@link ICUComparisonLevel#tertiary()}.
|
||||
*
|
||||
* @return new {@link AlternateWithMaxVariable}.
|
||||
*/
|
||||
public static AlternateWithMaxVariable shifted() {
|
||||
return new AlternateWithMaxVariable("shifted", null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
*/
|
||||
public static class AlternateWithMaxVariable extends Alternate {
|
||||
|
||||
private AlternateWithMaxVariable(String alternate, String maxVariable) {
|
||||
super(alternate, maxVariable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consider both whitespaces and punctuation as ignorable.
|
||||
*
|
||||
* @return new {@link AlternateWithMaxVariable}.
|
||||
*/
|
||||
public AlternateWithMaxVariable punct() {
|
||||
return new AlternateWithMaxVariable(alternate, "punct");
|
||||
}
|
||||
|
||||
/**
|
||||
* Only consider whitespaces as ignorable.
|
||||
*
|
||||
* @return new {@link AlternateWithMaxVariable}.
|
||||
*/
|
||||
public AlternateWithMaxVariable space() {
|
||||
return new AlternateWithMaxVariable(alternate, "space");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* ICU locale abstraction for usage with MongoDB {@link Collation}.
|
||||
*
|
||||
* @since 2.0
|
||||
* @see <a href="http://site.icu-project.org">ICU - International Components for Unicode</a>
|
||||
*/
|
||||
public static class ICULocale {
|
||||
|
||||
private final String language;
|
||||
private final Optional<String> variant;
|
||||
|
||||
private ICULocale(String language, String variant) {
|
||||
this.language = language;
|
||||
this.variant = Optional.ofNullable(variant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link ICULocale} for given language.
|
||||
*
|
||||
* @param language must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ICULocale of(String language) {
|
||||
|
||||
Assert.notNull(language, "Code must not be null!");
|
||||
return new ICULocale(language, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define language variant.
|
||||
*
|
||||
* @param variant must not be {@literal null}.
|
||||
* @return new {@link ICULocale}.
|
||||
*/
|
||||
public ICULocale variant(String variant) {
|
||||
|
||||
Assert.notNull(variant, "Variant must not be null!");
|
||||
return new ICULocale(language, variant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string representation.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String asString() {
|
||||
|
||||
StringBuilder sb = new StringBuilder(language);
|
||||
variant.ifPresent(val -> {
|
||||
|
||||
if (!val.isEmpty()) {
|
||||
sb.append("@collation=").append(val);
|
||||
}
|
||||
});
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static Converter<Collation, Document> toMongoDocumentConverter() {
|
||||
|
||||
return source -> {
|
||||
|
||||
Document document = new Document();
|
||||
document.append("locale", source.locale.asString());
|
||||
|
||||
source.strength.ifPresent(val -> {
|
||||
|
||||
document.append("strength", val.level);
|
||||
|
||||
val.caseLevel.ifPresent(cl -> document.append("caseLevel", cl));
|
||||
val.caseFirst.ifPresent(cl -> document.append("caseFirst", cl.state));
|
||||
});
|
||||
|
||||
source.numericOrdering.ifPresent(val -> document.append("numericOrdering", val));
|
||||
source.alternate.ifPresent(val -> {
|
||||
|
||||
document.append("alternate", val.alternate);
|
||||
val.maxVariable.ifPresent(maxVariable -> document.append("maxVariable", maxVariable));
|
||||
});
|
||||
|
||||
source.backwards.ifPresent(val -> document.append("backwards", val));
|
||||
source.normalization.ifPresent(val -> document.append("normalization", val));
|
||||
source.version.ifPresent(val -> document.append("version", val));
|
||||
|
||||
return document;
|
||||
};
|
||||
}
|
||||
|
||||
private static Converter<Collation, com.mongodb.client.model.Collation> toMongoCollationConverter() {
|
||||
|
||||
return source -> {
|
||||
|
||||
Builder builder = com.mongodb.client.model.Collation.builder();
|
||||
|
||||
builder.locale(source.locale.asString());
|
||||
|
||||
source.strength.ifPresent(val -> {
|
||||
|
||||
builder.collationStrength(CollationStrength.fromInt(val.level));
|
||||
|
||||
val.caseLevel.ifPresent(cl -> builder.caseLevel(cl));
|
||||
val.caseFirst.ifPresent(cl -> builder.collationCaseFirst(CollationCaseFirst.fromString(cl.state)));
|
||||
});
|
||||
|
||||
source.numericOrdering.ifPresent(val -> builder.numericOrdering(val));
|
||||
source.alternate.ifPresent(val -> {
|
||||
|
||||
builder.collationAlternate(CollationAlternate.fromString(val.alternate));
|
||||
val.maxVariable
|
||||
.ifPresent(maxVariable -> builder.collationMaxVariable(CollationMaxVariable.fromString(maxVariable)));
|
||||
});
|
||||
|
||||
source.backwards.ifPresent(val -> builder.backwards(val));
|
||||
source.normalization.ifPresent(val -> builder.normalization(val));
|
||||
|
||||
return builder.build();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2011 the original author or authors.
|
||||
* Copyright 2010-2017 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,18 +15,22 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Provides a simple wrapper to encapsulate the variety of settings you can use when creating a collection.
|
||||
*
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class CollectionOptions {
|
||||
|
||||
private Integer maxDocuments;
|
||||
|
||||
private Integer size;
|
||||
|
||||
private Boolean capped;
|
||||
private Collation collation;
|
||||
|
||||
/**
|
||||
* Constructs a new <code>CollectionOptions</code> instance.
|
||||
@@ -37,12 +41,85 @@ public class CollectionOptions {
|
||||
* false otherwise.
|
||||
*/
|
||||
public CollectionOptions(Integer size, Integer maxDocuments, Boolean capped) {
|
||||
super();
|
||||
|
||||
this.maxDocuments = maxDocuments;
|
||||
this.size = size;
|
||||
this.capped = capped;
|
||||
}
|
||||
|
||||
private CollectionOptions() {}
|
||||
|
||||
/**
|
||||
* Create new {@link CollectionOptions} by just providing the {@link Collation} to use.
|
||||
*
|
||||
* @param collation must not be {@literal null}.
|
||||
* @return new {@link CollectionOptions}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public static CollectionOptions just(Collation collation) {
|
||||
|
||||
Assert.notNull(collation, "Collation must not be null!");
|
||||
|
||||
CollectionOptions options = new CollectionOptions();
|
||||
options.setCollation(collation);
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link CollectionOptions} with already given settings and capped set to {@literal true}.
|
||||
*
|
||||
* @return new {@link CollectionOptions}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions capped() {
|
||||
|
||||
CollectionOptions options = new CollectionOptions(size, maxDocuments, true);
|
||||
options.setCollation(collation);
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link CollectionOptions} with already given settings and {@code maxDocuments} set to given value.
|
||||
*
|
||||
* @param maxDocuments can be {@literal null}.
|
||||
* @return new {@link CollectionOptions}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions maxDocuments(Integer maxDocuments) {
|
||||
|
||||
CollectionOptions options = new CollectionOptions(size, maxDocuments, capped);
|
||||
options.setCollation(collation);
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link CollectionOptions} with already given settings and {@code size} set to given value.
|
||||
*
|
||||
* @param size can be {@literal null}.
|
||||
* @return new {@link CollectionOptions}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions size(Integer size) {
|
||||
|
||||
CollectionOptions options = new CollectionOptions(size, maxDocuments, capped);
|
||||
options.setCollation(collation);
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link CollectionOptions} with already given settings and {@code collation} set to given value.
|
||||
*
|
||||
* @param collation can be {@literal null}.
|
||||
* @return new {@link CollectionOptions}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions collation(Collation collation) {
|
||||
|
||||
CollectionOptions options = new CollectionOptions(size, maxDocuments, capped);
|
||||
options.setCollation(collation);
|
||||
return options;
|
||||
}
|
||||
|
||||
public Integer getMaxDocuments() {
|
||||
return maxDocuments;
|
||||
}
|
||||
@@ -67,4 +144,23 @@ public class CollectionOptions {
|
||||
this.capped = capped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set {@link Collation} options.
|
||||
*
|
||||
* @param collation
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setCollation(Collation collation) {
|
||||
this.collation = collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Collation} settings.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public Optional<Collation> getCollation() {
|
||||
return Optional.ofNullable(collation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2017 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.
|
||||
@@ -19,6 +19,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
import org.bson.Document;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
@@ -58,12 +59,12 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
|
||||
private BulkWriteOptions bulkOptions;
|
||||
|
||||
List<WriteModel<Document>> models = new ArrayList<WriteModel<Document>>();
|
||||
List<WriteModel<Document>> models = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultBulkOperations} for the given {@link MongoOperations}, {@link BulkMode}, collection
|
||||
* name and {@link WriteConcern}.
|
||||
*
|
||||
*
|
||||
* @param mongoOperations The underlying {@link MongoOperations}, must not be {@literal null}.
|
||||
* @param bulkMode must not be {@literal null}.
|
||||
* @param collectionName Name of the collection to work on, must not be {@literal null} or empty.
|
||||
@@ -88,7 +89,7 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
|
||||
/**
|
||||
* Configures the {@link PersistenceExceptionTranslator} to be used. Defaults to {@link MongoExceptionTranslator}.
|
||||
*
|
||||
*
|
||||
* @param exceptionTranslator can be {@literal null}.
|
||||
*/
|
||||
public void setExceptionTranslator(PersistenceExceptionTranslator exceptionTranslator) {
|
||||
@@ -97,7 +98,7 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
|
||||
/**
|
||||
* Configures the {@link WriteConcernResolver} to be used. Defaults to {@link DefaultWriteConcernResolver}.
|
||||
*
|
||||
*
|
||||
* @param writeConcernResolver can be {@literal null}.
|
||||
*/
|
||||
public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) {
|
||||
@@ -107,7 +108,7 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
|
||||
/**
|
||||
* Configures the default {@link WriteConcern} to be used. Defaults to {@literal null}.
|
||||
*
|
||||
*
|
||||
* @param defaultWriteConcern can be {@literal null}.
|
||||
*/
|
||||
public void setDefaultWriteConcern(WriteConcern defaultWriteConcern) {
|
||||
@@ -244,7 +245,10 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
models.add(new DeleteManyModel<Document>(query.getQueryObject()));
|
||||
DeleteOptions deleteOptions = new DeleteOptions();
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(deleteOptions::collation);
|
||||
|
||||
models.add(new DeleteManyModel(query.getQueryObject(), deleteOptions));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -306,11 +310,12 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
|
||||
UpdateOptions options = new UpdateOptions();
|
||||
options.upsert(upsert);
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
|
||||
|
||||
if (multi) {
|
||||
models.add(new UpdateManyModel<Document>(query.getQueryObject(), update.getUpdateObject(), options));
|
||||
models.add(new UpdateManyModel<>(query.getQueryObject(), update.getUpdateObject(), options));
|
||||
} else {
|
||||
models.add(new UpdateOneModel<Document>(query.getQueryObject(), update.getUpdateObject(), options));
|
||||
models.add(new UpdateOneModel<>(query.getQueryObject(), update.getUpdateObject(), options));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2011 the original author or authors.
|
||||
* Copyright 2010-2017 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,14 +15,21 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author Mark Pollak
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class FindAndModifyOptions {
|
||||
|
||||
boolean returnNew;
|
||||
|
||||
boolean upsert;
|
||||
|
||||
boolean remove;
|
||||
|
||||
private Collation collation;
|
||||
|
||||
/**
|
||||
* Static factory method to create a FindAndModifyOptions instance
|
||||
*
|
||||
@@ -32,6 +39,27 @@ public class FindAndModifyOptions {
|
||||
return new FindAndModifyOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public static FindAndModifyOptions of(FindAndModifyOptions source) {
|
||||
|
||||
|
||||
FindAndModifyOptions options = new FindAndModifyOptions();
|
||||
if(source == null) {
|
||||
return options;
|
||||
}
|
||||
|
||||
options.returnNew = source.returnNew;
|
||||
options.upsert = source.upsert;
|
||||
options.remove = source.remove;
|
||||
options.collation = source.collation;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public FindAndModifyOptions returnNew(boolean returnNew) {
|
||||
this.returnNew = returnNew;
|
||||
return this;
|
||||
@@ -47,6 +75,19 @@ public class FindAndModifyOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the {@link Collation} specifying language-specific rules for string comparison.
|
||||
*
|
||||
* @param collation
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public FindAndModifyOptions collation(Collation collation) {
|
||||
|
||||
this.collation = collation;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isReturnNew() {
|
||||
return returnNew;
|
||||
}
|
||||
@@ -59,4 +100,14 @@ public class FindAndModifyOptions {
|
||||
return remove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Collation} specifying language-specific rules for string comparison.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public Optional<Collation> getCollation() {
|
||||
return Optional.ofNullable(collation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,21 +16,19 @@
|
||||
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.mongodb.core.index.IndexDefinition;
|
||||
import org.springframework.data.mongodb.core.index.IndexField;
|
||||
import org.springframework.data.mongodb.core.index.IndexInfo;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.mongodb.client.model.Collation;
|
||||
import com.mongodb.client.model.CollationAlternate;
|
||||
import com.mongodb.client.model.CollationCaseFirst;
|
||||
import com.mongodb.client.model.CollationMaxVariable;
|
||||
import com.mongodb.client.model.CollationStrength;
|
||||
import com.mongodb.client.model.IndexOptions;
|
||||
|
||||
/**
|
||||
@@ -45,10 +43,6 @@ abstract class IndexConverters {
|
||||
private static final Converter<IndexDefinition, IndexOptions> DEFINITION_TO_MONGO_INDEX_OPTIONS;
|
||||
private static final Converter<Document, IndexInfo> DOCUMENT_INDEX_INFO;
|
||||
|
||||
private static final Double ONE = Double.valueOf(1);
|
||||
private static final Double MINUS_ONE = Double.valueOf(-1);
|
||||
private static final Collection<String> TWO_D_IDENTIFIERS = Arrays.asList("2d", "2dsphere");
|
||||
|
||||
static {
|
||||
|
||||
DEFINITION_TO_MONGO_INDEX_OPTIONS = getIndexDefinitionIndexOptionsConverter();
|
||||
@@ -117,18 +111,57 @@ abstract class IndexConverters {
|
||||
}
|
||||
}
|
||||
|
||||
if(indexOptions.containsKey("partialFilterExpression")) {
|
||||
ops = ops.partialFilterExpression((org.bson.Document)indexOptions.get("partialFilterExpression"));
|
||||
if (indexOptions.containsKey("partialFilterExpression")) {
|
||||
ops = ops.partialFilterExpression((org.bson.Document) indexOptions.get("partialFilterExpression"));
|
||||
}
|
||||
|
||||
if (indexOptions.containsKey("collation")) {
|
||||
ops = ops.collation(fromDocument(indexOptions.get("collation", Document.class)));
|
||||
}
|
||||
|
||||
return ops;
|
||||
};
|
||||
}
|
||||
|
||||
private static Converter<Document, IndexInfo> getDocumentIndexInfoConverter() {
|
||||
public static Collation fromDocument(Document source) {
|
||||
|
||||
return ix -> {
|
||||
return IndexInfo.indexInfoOf(ix);
|
||||
};
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
com.mongodb.client.model.Collation.Builder collationBuilder = Collation.builder();
|
||||
|
||||
collationBuilder.locale(source.getString("locale"));
|
||||
if (source.containsKey("caseLevel")) {
|
||||
collationBuilder.caseLevel(source.getBoolean("caseLevel"));
|
||||
}
|
||||
if (source.containsKey("caseFirst")) {
|
||||
collationBuilder.collationCaseFirst(CollationCaseFirst.fromString(source.getString("caseFirst")));
|
||||
}
|
||||
if (source.containsKey("strength")) {
|
||||
collationBuilder.collationStrength(CollationStrength.fromInt(source.getInteger("strength")));
|
||||
}
|
||||
if (source.containsKey("numericOrdering")) {
|
||||
collationBuilder.numericOrdering(source.getBoolean("numericOrdering"));
|
||||
}
|
||||
if (source.containsKey("alternate")) {
|
||||
collationBuilder.collationAlternate(CollationAlternate.fromString(source.getString("alternate")));
|
||||
}
|
||||
if (source.containsKey("maxVariable")) {
|
||||
collationBuilder.collationMaxVariable(CollationMaxVariable.fromString(source.getString("maxVariable")));
|
||||
}
|
||||
if (source.containsKey("backwards")) {
|
||||
collationBuilder.backwards(source.getBoolean("backwards"));
|
||||
}
|
||||
if (source.containsKey("normalization")) {
|
||||
collationBuilder.normalization(source.getBoolean("normalization"));
|
||||
}
|
||||
|
||||
return collationBuilder.build();
|
||||
}
|
||||
|
||||
private static Converter<Document, IndexInfo> getDocumentIndexInfoConverter() {
|
||||
return ix -> IndexInfo.indexInfoOf(ix);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,8 +20,19 @@ import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
|
||||
import static org.springframework.data.util.Optionals.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bson.Document;
|
||||
@@ -118,6 +129,7 @@ import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoCursor;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.model.CreateCollectionOptions;
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import com.mongodb.client.model.FindOneAndDeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndUpdateOptions;
|
||||
@@ -347,12 +359,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Document mappedFields = queryMapper.getMappedFields(query.getFieldsObject(), persistentEntity);
|
||||
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity);
|
||||
|
||||
FindIterable<Document> cursor = collection.find(mappedQuery).projection(mappedFields);
|
||||
QueryCursorPreparer cursorPreparer = new QueryCursorPreparer(query, entityType);
|
||||
FindIterable<Document> cursor = new QueryCursorPreparer(query, entityType)
|
||||
.prepare(collection.find(mappedQuery).projection(mappedFields));
|
||||
|
||||
ReadDocumentCallback<T> readCallback = new ReadDocumentCallback<T>(mongoConverter, entityType, collectionName);
|
||||
|
||||
return new CloseableIterableCursorAdapter<T>(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback);
|
||||
return new CloseableIterableCursorAdapter<T>(cursor, exceptionTranslator,
|
||||
new ReadDocumentCallback<T>(mongoConverter, entityType, collectionName));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -563,6 +574,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
public <T> T findOne(Query query, Class<T> entityClass, String collectionName) {
|
||||
|
||||
if (query.getSortObject() == null) {
|
||||
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass);
|
||||
} else {
|
||||
@@ -587,7 +599,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass));
|
||||
return execute(collectionName, new FindCallback(mappedQuery)).iterator().hasNext();
|
||||
FindIterable<Document> iterable = execute(collectionName, new FindCallback(mappedQuery));
|
||||
|
||||
if (query.getCollation().isPresent()) {
|
||||
iterable = iterable
|
||||
.collation(query.getCollation().map(org.springframework.data.mongodb.core.Collation::toMongoCollation).get());
|
||||
}
|
||||
|
||||
return iterable.iterator().hasNext();
|
||||
}
|
||||
|
||||
// Find methods that take a Query to express the query and that return a List of objects.
|
||||
@@ -698,8 +717,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
public <T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName) {
|
||||
|
||||
FindAndModifyOptions optionsToUse = FindAndModifyOptions.of(options);
|
||||
|
||||
Optionals.ifAllPresent(query.getCollation(), optionsToUse.getCollation(), (l, r) -> {
|
||||
throw new IllegalArgumentException(
|
||||
"Both Query and FindAndModifyOptions define the collation. Please provide the collation only via one of the two.");
|
||||
});
|
||||
|
||||
query.getCollation().ifPresent(optionsToUse::collation);
|
||||
|
||||
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
|
||||
getMappedSortObject(query, entityClass), entityClass, update, options);
|
||||
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
|
||||
}
|
||||
|
||||
// Find methods that take a Query to express the query and that return a single object that is also removed from the
|
||||
@@ -712,7 +741,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
public <T> T findAndRemove(Query query, Class<T> entityClass, String collectionName) {
|
||||
|
||||
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
|
||||
getMappedSortObject(query, entityClass), entityClass);
|
||||
getMappedSortObject(query, entityClass), query.getCollation().orElse(null), entityClass);
|
||||
}
|
||||
|
||||
public long count(Query query, Class<?> entityClass) {
|
||||
@@ -1151,8 +1180,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
increaseVersionForUpdateIfNecessary(entity, update);
|
||||
|
||||
Document queryObj = query == null ? new Document()
|
||||
: queryMapper.getMappedObject(query.getQueryObject(), entity);
|
||||
UpdateOptions opts = new UpdateOptions();
|
||||
opts.upsert(upsert);
|
||||
|
||||
Document queryObj = new Document();
|
||||
|
||||
if (query != null) {
|
||||
|
||||
queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity));
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(opts::collation);
|
||||
}
|
||||
|
||||
Document updateObj = update == null ? new Document()
|
||||
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
|
||||
@@ -1169,9 +1207,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
entityClass, updateObj, queryObj);
|
||||
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
|
||||
|
||||
UpdateOptions opts = new UpdateOptions();
|
||||
opts.upsert(upsert);
|
||||
|
||||
collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection;
|
||||
|
||||
if (!UpdateMapper.isUpdateObject(updateObj)) {
|
||||
@@ -1331,6 +1366,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
final Optional<? extends MongoPersistentEntity<?>> entity = getPersistentEntity(entityClass);
|
||||
|
||||
return execute(collectionName, new CollectionCallback<DeleteResult>() {
|
||||
|
||||
public DeleteResult doInCollection(MongoCollection<Document> collection)
|
||||
throws MongoException, DataAccessException {
|
||||
|
||||
@@ -1338,8 +1374,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(queryObject, entity);
|
||||
|
||||
DeleteOptions options = new DeleteOptions();
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
|
||||
|
||||
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName,
|
||||
entityClass, null, queryObject);
|
||||
|
||||
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
|
||||
|
||||
DeleteResult dr = null;
|
||||
@@ -1349,9 +1389,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
if (writeConcernToUse == null) {
|
||||
dr = collection.deleteMany(mappedQuery);
|
||||
|
||||
dr = collection.deleteMany(mappedQuery, options);
|
||||
} else {
|
||||
dr = collection.withWriteConcern(writeConcernToUse).deleteMany(mappedQuery);
|
||||
dr = collection.withWriteConcern(writeConcernToUse).deleteMany(mappedQuery, options);
|
||||
}
|
||||
|
||||
maybeEmitEvent(new AfterDeleteEvent<T>(queryObject, entityClass, collectionName));
|
||||
@@ -1366,7 +1407,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
public <T> List<T> findAll(Class<T> entityClass, String collectionName) {
|
||||
return executeFindMultiInternal(new FindCallback(null), null,
|
||||
return executeFindMultiInternal(new FindCallback(null, null), null,
|
||||
new ReadDocumentCallback<T>(mongoConverter, entityClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
@@ -1411,26 +1452,40 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
result = result.filter(queryMapper.getMappedObject(query.getQueryObject(), Optional.empty()));
|
||||
}
|
||||
|
||||
Optional<Collation> collation = query != null ? query.getCollation() : Optional.empty();
|
||||
|
||||
if (mapReduceOptions != null) {
|
||||
|
||||
Optionals.ifAllPresent(collation, mapReduceOptions.getCollation(), (l, r) -> {
|
||||
throw new IllegalArgumentException(
|
||||
"Both Query and MapReduceOptions define the collation. Please provide the collation only via one of the two.");
|
||||
});
|
||||
|
||||
if (mapReduceOptions.getCollation().isPresent()) {
|
||||
collation = mapReduceOptions.getCollation();
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(mapReduceOptions.getScopeVariables())) {
|
||||
Document vars = new Document();
|
||||
vars.putAll(mapReduceOptions.getScopeVariables());
|
||||
result = result.scope(vars);
|
||||
result = result.scope(new Document(mapReduceOptions.getScopeVariables()));
|
||||
}
|
||||
if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit().intValue() > 0) {
|
||||
result = result.limit(mapReduceOptions.getLimit());
|
||||
}
|
||||
if (StringUtils.hasText(mapReduceOptions.getFinalizeFunction())) {
|
||||
result = result.finalizeFunction(mapReduceOptions.getFinalizeFunction());
|
||||
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() != null) {
|
||||
result = result.sharded(mapReduceOptions.getOutputSharded());
|
||||
if (mapReduceOptions.getOutputSharded().isPresent()) {
|
||||
result = result.sharded(mapReduceOptions.getOutputSharded().get());
|
||||
}
|
||||
}
|
||||
|
||||
if (collation.isPresent()) {
|
||||
result = result.collation(collation.map(Collation::toMongoCollation).get());
|
||||
}
|
||||
|
||||
List<T> mappedResults = new ArrayList<T>();
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<T>(mongoConverter, entityClass, inputCollectionName);
|
||||
|
||||
@@ -1709,7 +1764,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
Integer cursorBatchSize = options.getCursorBatchSize();
|
||||
if (cursorBatchSize != null) {
|
||||
cursor.batchSize(cursorBatchSize);
|
||||
cursor = cursor.batchSize(cursorBatchSize);
|
||||
}
|
||||
|
||||
if (options.getCollation().isPresent()) {
|
||||
cursor = cursor.collation(options.getCollation().map(Collation::toMongoCollation).get());
|
||||
}
|
||||
|
||||
return new CloseableIterableCursorAdapter<O>(cursor.iterator(), exceptionTranslator, readCallback);
|
||||
@@ -1806,9 +1865,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
co.maxDocuments(((Number) collectionOptions.get("max")).longValue());
|
||||
}
|
||||
|
||||
if (collectionOptions.containsKey("collation")) {
|
||||
co.collation(IndexConverters.fromDocument(collectionOptions.get("collation", Document.class)));
|
||||
}
|
||||
|
||||
db.createCollection(collectionName, co);
|
||||
|
||||
MongoCollection<Document> coll = db.getCollection(collectionName, Document.class);
|
||||
|
||||
// TODO: Emit a collection created event
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Created collection [{}]", coll.getNamespace().getCollectionName());
|
||||
@@ -1895,6 +1959,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
protected Document convertToDocument(CollectionOptions collectionOptions) {
|
||||
|
||||
Document document = new Document();
|
||||
if (collectionOptions != null) {
|
||||
if (collectionOptions.getCapped() != null) {
|
||||
@@ -1906,6 +1971,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
if (collectionOptions.getMaxDocuments() != null) {
|
||||
document.put("max", collectionOptions.getMaxDocuments().intValue());
|
||||
}
|
||||
|
||||
collectionOptions.getCollation().ifPresent(val -> document.append("collation", val.toDocument()));
|
||||
}
|
||||
return document;
|
||||
}
|
||||
@@ -1922,7 +1989,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @return the List of converted objects.
|
||||
*/
|
||||
protected <T> T doFindAndRemove(String collectionName, Document query, Document fields, Document sort,
|
||||
Class<T> entityClass) {
|
||||
Collation collation, Class<T> entityClass) {
|
||||
|
||||
EntityReader<? super T, Bson> readerToUse = this.mongoConverter;
|
||||
|
||||
@@ -1933,7 +2000,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
|
||||
return executeFindOneInternal(new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort),
|
||||
return executeFindOneInternal(
|
||||
new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort, collation),
|
||||
new ReadDocumentCallback<T>(readerToUse, entityClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
@@ -2210,31 +2278,33 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Risberg
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
private static class FindOneCallback implements CollectionCallback<Document> {
|
||||
|
||||
private final Document query;
|
||||
private final Document fields;
|
||||
private final Optional<Document> fields;
|
||||
|
||||
public FindOneCallback(Document query, Document fields) {
|
||||
this.query = query;
|
||||
this.fields = fields;
|
||||
this.fields = Optional.ofNullable(fields);
|
||||
}
|
||||
|
||||
public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
|
||||
if (fields == null) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("findOne using query: {} in db.collection: {}", serializeToJsonSafely(query),
|
||||
collection.getNamespace().getFullName());
|
||||
}
|
||||
return collection.find(query).first();
|
||||
} else {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query), fields,
|
||||
collection.getNamespace().getFullName());
|
||||
}
|
||||
return collection.find(query).projection(fields).first();
|
||||
|
||||
FindIterable<Document> iterable = collection.find(query);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
|
||||
LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query),
|
||||
serializeToJsonSafely(fields.orElseGet(() -> new Document())), collection.getNamespace().getFullName());
|
||||
}
|
||||
|
||||
if (fields.isPresent()) {
|
||||
iterable = iterable.projection(fields.get());
|
||||
}
|
||||
|
||||
return iterable.first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2244,29 +2314,33 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Risberg
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
private static class FindCallback implements CollectionCallback<FindIterable<Document>> {
|
||||
|
||||
private final Document query;
|
||||
private final Document fields;
|
||||
private final Optional<Document> fields;
|
||||
|
||||
public FindCallback(Document query) {
|
||||
this(query, null);
|
||||
}
|
||||
|
||||
public FindCallback(Document query, Document fields) {
|
||||
this.query = query == null ? new Document() : query;
|
||||
this.fields = fields;
|
||||
|
||||
this.query = query != null ? query : new Document();
|
||||
this.fields = Optional.ofNullable(fields);
|
||||
}
|
||||
|
||||
public FindIterable<Document> doInCollection(MongoCollection<Document> collection)
|
||||
throws MongoException, DataAccessException {
|
||||
|
||||
if (fields == null || fields.isEmpty()) {
|
||||
return collection.find(query);
|
||||
} else {
|
||||
return collection.find(query).projection(fields);
|
||||
FindIterable<Document> iterable = collection.find(query);
|
||||
|
||||
if (fields.filter(val -> !val.isEmpty()).isPresent()) {
|
||||
iterable = iterable.projection(fields.get());
|
||||
}
|
||||
|
||||
return iterable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2281,18 +2355,20 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
private final Document query;
|
||||
private final Document fields;
|
||||
private final Document sort;
|
||||
private final Optional<Collation> collation;
|
||||
|
||||
public FindAndRemoveCallback(Document query, Document fields, Document sort, Collation collation) {
|
||||
|
||||
public FindAndRemoveCallback(Document query, Document fields, Document sort) {
|
||||
this.query = query;
|
||||
this.fields = fields;
|
||||
this.sort = sort;
|
||||
this.collation = Optional.ofNullable(collation);
|
||||
}
|
||||
|
||||
public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
|
||||
|
||||
FindOneAndDeleteOptions opts = new FindOneAndDeleteOptions();
|
||||
opts.sort(sort);
|
||||
opts.projection(fields);
|
||||
FindOneAndDeleteOptions opts = new FindOneAndDeleteOptions().sort(sort).projection(fields);
|
||||
collation.map(Collation::toMongoCollation).ifPresent(opts::collation);
|
||||
|
||||
return collection.findOneAndDelete(query, opts);
|
||||
}
|
||||
@@ -2326,9 +2402,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
if (options.returnNew) {
|
||||
opts.returnDocument(ReturnDocument.AFTER);
|
||||
}
|
||||
|
||||
options.getCollation().map(Collation::toMongoCollation).ifPresent(opts::collation);
|
||||
|
||||
return collection.findOneAndUpdate(query, update, opts);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2435,6 +2513,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
FindIterable<Document> cursorToUse = cursor;
|
||||
|
||||
if (query.getCollation().isPresent()) {
|
||||
cursorToUse = cursorToUse.collation(query.getCollation().map(val -> val.toMongoCollation()).get());
|
||||
}
|
||||
try {
|
||||
if (query.getSkip() > 0) {
|
||||
cursorToUse = cursorToUse.skip((int) query.getSkip());
|
||||
@@ -2442,7 +2523,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
if (query.getLimit() > 0) {
|
||||
cursorToUse = cursorToUse.limit(query.getLimit());
|
||||
}
|
||||
if (query.getSortObject() != null) {
|
||||
if (query.getSortObject() != null && !query.getSortObject().isEmpty()) {
|
||||
Document sort = type != null ? getMappedSortObject(query, type) : query.getSortObject();
|
||||
cursorToUse = cursorToUse.sort(sort);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ 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 reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -88,6 +92,7 @@ import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.mongodb.util.MongoClientVersion;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -116,10 +121,6 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
import com.mongodb.reactivestreams.client.Success;
|
||||
import com.mongodb.util.JSONParseException;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
/**
|
||||
* Primary implementation of {@link ReactiveMongoOperations}. It simplifies the use of Reactive MongoDB usage and helps
|
||||
* to avoid common errors. It executes core MongoDB workflow, leaving application code to provide {@link Document} and
|
||||
@@ -336,11 +337,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#executeCommand(org.bson.Document)
|
||||
*/
|
||||
public Mono<Document> executeCommand(final Document command) {
|
||||
|
||||
Assert.notNull(command, "Command must not be null!");
|
||||
|
||||
return createFlux(db -> readPreference != null ? db.runCommand(command, readPreference) : db.runCommand(command))
|
||||
.next();
|
||||
return executeCommand(command, null);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -350,8 +347,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
Assert.notNull(command, "Command must not be null!");
|
||||
|
||||
return createFlux(db -> readPreference != null ? db.runCommand(command, readPreference) : db.runCommand(command))
|
||||
.next();
|
||||
return createFlux(db -> readPreference != null ? db.runCommand(command, readPreference, Document.class)
|
||||
: db.runCommand(command, Document.class)).next();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -541,8 +538,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
public <T> Mono<T> findOne(Query query, Class<T> entityClass, String collectionName) {
|
||||
|
||||
if (query.getSortObject() == null) {
|
||||
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass);
|
||||
if (ObjectUtils.isEmpty(query.getSortObject())) {
|
||||
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
|
||||
query.getCollation().orElse(null));
|
||||
}
|
||||
|
||||
query.limit(1);
|
||||
@@ -575,7 +573,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return createFlux(collectionName, collection -> {
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass));
|
||||
return collection.find(mappedQuery).limit(1);
|
||||
FindPublisher findPublisher = collection.find(mappedQuery).projection(new Document("_id", 1));
|
||||
|
||||
if (query.getCollation().isPresent()) {
|
||||
findPublisher = findPublisher.collation(query.getCollation().map(Collation::toMongoCollation).get());
|
||||
}
|
||||
|
||||
return findPublisher.limit(1);
|
||||
}).hasElements();
|
||||
}
|
||||
|
||||
@@ -612,11 +616,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
public <T> Mono<T> findById(Object id, Class<T> entityClass, String collectionName) {
|
||||
|
||||
Optional<? extends MongoPersistentEntity<?>> persistentEntity = mappingContext.getPersistentEntity(entityClass);
|
||||
MongoPersistentProperty idProperty = persistentEntity.isPresent() ? persistentEntity.get().getIdProperty().orElse(null) : null;
|
||||
MongoPersistentProperty idProperty = persistentEntity.isPresent()
|
||||
? persistentEntity.get().getIdProperty().orElse(null) : null;
|
||||
|
||||
String idKey = idProperty == null ? ID_FIELD : idProperty.getName();
|
||||
|
||||
return doFindOne(collectionName, new Document(idKey, id), null, entityClass);
|
||||
return doFindOne(collectionName, new Document(idKey, id), null, entityClass, null);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -672,12 +677,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return Flux.empty();
|
||||
}
|
||||
return Flux.fromIterable(l);
|
||||
}).skip(near.getSkip() != null ? near.getSkip() : 0).map(new Function<Document, GeoResult<T>>() {
|
||||
@Override
|
||||
public GeoResult<T> apply(Document object) {
|
||||
return callback.doWith(object);
|
||||
}
|
||||
});
|
||||
}).skip(near.getSkip() != null ? near.getSkip() : 0).map(callback::doWith);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -707,8 +707,18 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
public <T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName) {
|
||||
|
||||
FindAndModifyOptions optionsToUse = FindAndModifyOptions.of(options);
|
||||
|
||||
Optionals.ifAllPresent(query.getCollation(), optionsToUse.getCollation(), (l, r) -> {
|
||||
throw new IllegalArgumentException(
|
||||
"Both Query and FindAndModifyOptions define the collation. Please provide the collation only via one of the two.");
|
||||
});
|
||||
|
||||
query.getCollation().ifPresent(optionsToUse::collation);
|
||||
|
||||
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
|
||||
getMappedSortObject(query, entityClass), entityClass, update, options);
|
||||
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -724,7 +734,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
public <T> Mono<T> findAndRemove(Query query, Class<T> entityClass, String collectionName) {
|
||||
|
||||
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
|
||||
getMappedSortObject(query, entityClass), entityClass);
|
||||
getMappedSortObject(query, entityClass), query.getCollation().orElse(null), entityClass);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -962,8 +972,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor(
|
||||
entity.getPropertyAccessor(objectToSave), mongoConverter.getConversionService());
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty().orElseThrow(() -> new IllegalArgumentException("No id property present!"));
|
||||
MongoPersistentProperty versionProperty = entity.getVersionProperty().orElseThrow(() -> new IllegalArgumentException("No version property present!"));;
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty()
|
||||
.orElseThrow(() -> new IllegalArgumentException("No id property present!"));
|
||||
MongoPersistentProperty versionProperty = entity.getVersionProperty()
|
||||
.orElseThrow(() -> new IllegalArgumentException("No version property present!"));
|
||||
;
|
||||
|
||||
Optional<Object> version = convertingAccessor.getProperty(versionProperty);
|
||||
Optional<Number> versionNumber = convertingAccessor.getProperty(versionProperty, Number.class);
|
||||
@@ -977,7 +990,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
// Create query for entity with the id and old version
|
||||
Optional<Object> id = convertingAccessor.getProperty(idProperty);
|
||||
Query query = new Query(Criteria.where(idProperty.getName()).is(id.get()).and(versionProperty.getName()).is(version.get()));
|
||||
Query query = new Query(
|
||||
Criteria.where(idProperty.getName()).is(id.get()).and(versionProperty.getName()).is(version.get()));
|
||||
|
||||
// Bump version number
|
||||
convertingAccessor.setProperty(versionProperty, Optional.of(versionNumber.orElse(0).longValue() + 1));
|
||||
@@ -1196,6 +1210,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
MongoCollection<Document> collectionToUse = prepareCollection(collection, writeConcernToUse);
|
||||
|
||||
UpdateOptions updateOptions = new UpdateOptions().upsert(upsert);
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(updateOptions::collation);
|
||||
|
||||
if (!UpdateMapper.isUpdateObject(updateObj)) {
|
||||
return collectionToUse.replaceOne(queryObj, updateObj, updateOptions);
|
||||
@@ -1349,8 +1364,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
private void assertUpdateableIdIfNotSet(Object entity) {
|
||||
|
||||
Optional<? extends MongoPersistentEntity<?>> persistentEntity = mappingContext.getPersistentEntity(entity.getClass());
|
||||
Optional<MongoPersistentProperty> idProperty = persistentEntity.isPresent() ? persistentEntity.get().getIdProperty() : Optional.empty();
|
||||
Optional<? extends MongoPersistentEntity<?>> persistentEntity = mappingContext
|
||||
.getPersistentEntity(entity.getClass());
|
||||
Optional<MongoPersistentProperty> idProperty = persistentEntity.isPresent() ? persistentEntity.get().getIdProperty()
|
||||
: Optional.empty();
|
||||
|
||||
if (!idProperty.isPresent()) {
|
||||
return;
|
||||
@@ -1360,8 +1377,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
if (!idValue.isPresent() && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.get().getType())) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("Cannot autogenerate id of type %s for entity of type %s!", idProperty.get().getType().getName(),
|
||||
entity.getClass().getName()));
|
||||
String.format("Cannot autogenerate id of type %s for entity of type %s!",
|
||||
idProperty.get().getType().getName(), entity.getClass().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1414,7 +1431,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
new Object[] { serializeToJsonSafely(dboq), collectionName });
|
||||
}
|
||||
|
||||
query.getCollation().ifPresent(val -> {
|
||||
|
||||
// TODO: add collation support as soon as it's there! See https://jira.mongodb.org/browse/JAVARS-27
|
||||
throw new IllegalArgumentException("DeleteMany does currently not accept collation settings.");
|
||||
});
|
||||
|
||||
return collectionToUse.deleteMany(dboq);
|
||||
|
||||
}).doOnNext(deleteResult -> maybeEmitEvent(new AfterDeleteEvent<T>(queryObject, entityClass, collectionName)))
|
||||
.next();
|
||||
}
|
||||
@@ -1530,7 +1554,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @param entityClass the parameterized type of the returned list.
|
||||
* @return the {@link List} of converted objects.
|
||||
*/
|
||||
protected <T> Mono<T> doFindOne(String collectionName, Document query, Document fields, Class<T> entityClass) {
|
||||
protected <T> Mono<T> doFindOne(String collectionName, Document query, Document fields, Class<T> entityClass,
|
||||
Collation collation) {
|
||||
|
||||
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
Document mappedQuery = queryMapper.getMappedObject(query, entity);
|
||||
@@ -1541,7 +1566,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
serializeToJsonSafely(query), mappedFields, entityClass, collectionName));
|
||||
}
|
||||
|
||||
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields),
|
||||
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields, collation),
|
||||
new ReadDocumentCallback<T>(this.mongoConverter, entityClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
@@ -1624,11 +1649,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*
|
||||
* @param collectionName name of the collection to retrieve the objects from
|
||||
* @param query the query document that specifies the criteria used to find a record
|
||||
* @param collation collation
|
||||
* @param entityClass the parameterized type of the returned list.
|
||||
* @return the List of converted objects.
|
||||
*/
|
||||
protected <T> Mono<T> doFindAndRemove(String collectionName, Document query, Document fields, Document sort,
|
||||
Class<T> entityClass) {
|
||||
Collation collation, Class<T> entityClass) {
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("findAndRemove using query: %s fields: %s sort: %s for class: %s in collection: %s",
|
||||
@@ -1637,19 +1663,15 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
|
||||
return executeFindOneInternal(new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort),
|
||||
return executeFindOneInternal(
|
||||
new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort, collation),
|
||||
new ReadDocumentCallback<T>(this.mongoConverter, entityClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
protected <T> Mono<T> doFindAndModify(String collectionName, Document query, Document fields, Document sort,
|
||||
Class<T> entityClass, Update update, FindAndModifyOptions options) {
|
||||
|
||||
FindAndModifyOptions optionsToUse;
|
||||
if (options == null) {
|
||||
optionsToUse = new FindAndModifyOptions();
|
||||
} else {
|
||||
optionsToUse = options;
|
||||
}
|
||||
FindAndModifyOptions optionsToUse = options != null ? options : new FindAndModifyOptions();
|
||||
|
||||
Optional<? extends MongoPersistentEntity<?>> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
|
||||
@@ -1976,34 +1998,36 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private static class FindOneCallback implements ReactiveCollectionCallback<Document> {
|
||||
|
||||
private final Document query;
|
||||
private final Document fields;
|
||||
private final Optional<Document> fields;
|
||||
private final Optional<Collation> collation;
|
||||
|
||||
FindOneCallback(Document query, Document fields) {
|
||||
FindOneCallback(Document query, Document fields, Collation collation) {
|
||||
this.query = query;
|
||||
this.fields = fields;
|
||||
this.fields = Optional.ofNullable(fields);
|
||||
this.collation = Optional.ofNullable(collation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher<Document> doInCollection(MongoCollection<Document> collection)
|
||||
throws MongoException, DataAccessException {
|
||||
|
||||
if (fields == null) {
|
||||
FindPublisher publisher = collection.find(query);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("findOne using query: %s in db.collection: %s", serializeToJsonSafely(query),
|
||||
collection.getNamespace().getFullName()));
|
||||
}
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
|
||||
return collection.find(query).limit(1).first();
|
||||
} else {
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("findOne using query: %s fields: %s in db.collection: %s",
|
||||
serializeToJsonSafely(query), fields, collection.getNamespace().getFullName()));
|
||||
}
|
||||
|
||||
return collection.find(query).projection(fields).limit(1);
|
||||
LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query),
|
||||
serializeToJsonSafely(fields.orElseGet(() -> new Document())), collection.getNamespace().getFullName());
|
||||
}
|
||||
|
||||
if (fields.isPresent()) {
|
||||
publisher = publisher.projection(fields.get());
|
||||
}
|
||||
|
||||
if (collation.isPresent()) {
|
||||
publisher = publisher.collation(collation.map(Collation::toMongoCollation).get());
|
||||
}
|
||||
|
||||
return publisher.limit(1).first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2056,12 +2080,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private final Document query;
|
||||
private final Document fields;
|
||||
private final Document sort;
|
||||
private final Optional<Collation> collation;
|
||||
|
||||
FindAndRemoveCallback(Document query, Document fields, Document sort) {
|
||||
FindAndRemoveCallback(Document query, Document fields, Document sort, Collation collation) {
|
||||
|
||||
this.query = query;
|
||||
this.fields = fields;
|
||||
this.sort = sort;
|
||||
this.collation = Optional.ofNullable(collation);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2069,6 +2095,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
throws MongoException, DataAccessException {
|
||||
|
||||
FindOneAndDeleteOptions findOneAndDeleteOptions = convertToFindOneAndDeleteOptions(fields, sort);
|
||||
collation.map(Collation::toMongoCollation).ifPresent(findOneAndDeleteOptions::collation);
|
||||
|
||||
return collection.findOneAndDelete(query, findOneAndDeleteOptions);
|
||||
}
|
||||
}
|
||||
@@ -2100,6 +2128,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
if (options.isRemove()) {
|
||||
FindOneAndDeleteOptions findOneAndDeleteOptions = convertToFindOneAndDeleteOptions(fields, sort);
|
||||
|
||||
if (options.getCollation().isPresent()) {
|
||||
findOneAndDeleteOptions = findOneAndDeleteOptions
|
||||
.collation(options.getCollation().map(Collation::toMongoCollation).get());
|
||||
}
|
||||
|
||||
return collection.findOneAndDelete(query, findOneAndDeleteOptions);
|
||||
}
|
||||
|
||||
@@ -2120,6 +2154,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
result = result.returnDocument(ReturnDocument.BEFORE);
|
||||
}
|
||||
|
||||
if (options.getCollation().isPresent()) {
|
||||
result = result.collation(options.getCollation().map(Collation::toMongoCollation).get());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -2255,21 +2293,25 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return findPublisher;
|
||||
}
|
||||
|
||||
if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null
|
||||
&& !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) {
|
||||
return findPublisher;
|
||||
FindPublisher<T> findPublisherToUse = findPublisher;
|
||||
|
||||
if (query.getCollation().isPresent()) {
|
||||
findPublisherToUse = findPublisherToUse.collation(query.getCollation().map(Collation::toMongoCollation).get());
|
||||
}
|
||||
|
||||
FindPublisher<T> findPublisherToUse = findPublisher;
|
||||
if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null
|
||||
&& !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) {
|
||||
return findPublisherToUse;
|
||||
}
|
||||
|
||||
try {
|
||||
if (query.getSkip() > 0) {
|
||||
findPublisherToUse = findPublisherToUse.skip((int)query.getSkip());
|
||||
findPublisherToUse = findPublisherToUse.skip((int) query.getSkip());
|
||||
}
|
||||
if (query.getLimit() > 0) {
|
||||
findPublisherToUse = findPublisherToUse.limit(query.getLimit());
|
||||
}
|
||||
if (query.getSortObject() != null) {
|
||||
if (!ObjectUtils.isEmpty(query.getSortObject())) {
|
||||
Document sort = type != null ? getMappedSortObject(query, type) : query.getSortObject();
|
||||
findPublisherToUse = findPublisherToUse.sort(sort);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.Collation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
@@ -39,10 +42,12 @@ public class AggregationOptions {
|
||||
private static final String CURSOR = "cursor";
|
||||
private static final String EXPLAIN = "explain";
|
||||
private static final String ALLOW_DISK_USE = "allowDiskUse";
|
||||
private static final String COLLATION = "collation";
|
||||
|
||||
private final boolean allowDiskUse;
|
||||
private final boolean explain;
|
||||
private final Document cursor;
|
||||
private final Optional<Document> cursor;
|
||||
private final Optional<Collation> collation;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AggregationOptions}.
|
||||
@@ -52,10 +57,25 @@ public class AggregationOptions {
|
||||
* @param cursor can be {@literal null}, used to pass additional options to the aggregation.
|
||||
*/
|
||||
public AggregationOptions(boolean allowDiskUse, boolean explain, Document cursor) {
|
||||
this(allowDiskUse, explain, cursor, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AggregationOptions}.
|
||||
*
|
||||
* @param allowDiskUse whether to off-load intensive sort-operations to disk.
|
||||
* @param explain whether to get the execution plan for the aggregation instead of the actual results.
|
||||
* @param cursor can be {@literal null}, used to pass additional options (such as {@code batchSize}) to the
|
||||
* aggregation.
|
||||
* @param collation collation for string comparison. Can be {@literal null}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public AggregationOptions(boolean allowDiskUse, boolean explain, Document cursor, Collation collation) {
|
||||
|
||||
this.allowDiskUse = allowDiskUse;
|
||||
this.explain = explain;
|
||||
this.cursor = cursor;
|
||||
this.cursor = Optional.ofNullable(cursor);
|
||||
this.collation = Optional.ofNullable(collation);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +87,7 @@ public class AggregationOptions {
|
||||
* @since 2.0
|
||||
*/
|
||||
public AggregationOptions(boolean allowDiskUse, boolean explain, int cursorBatchSize) {
|
||||
this(allowDiskUse, explain, createCursor(cursorBatchSize));
|
||||
this(allowDiskUse, explain, createCursor(cursorBatchSize), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,23 +101,13 @@ public class AggregationOptions {
|
||||
|
||||
Assert.notNull(document, "Document must not be null!");
|
||||
|
||||
boolean allowDiskUse = false;
|
||||
boolean explain = false;
|
||||
Document cursor = null;
|
||||
boolean allowDiskUse = document.getBoolean(ALLOW_DISK_USE, false);
|
||||
boolean explain = document.getBoolean(EXPLAIN, false);
|
||||
Document cursor = document.get(CURSOR, Document.class);
|
||||
Collation collation = document.containsKey(COLLATION) ? Collation.from(document.get(COLLATION, Document.class))
|
||||
: null;
|
||||
|
||||
if (document.containsKey(ALLOW_DISK_USE)) {
|
||||
allowDiskUse = document.get(ALLOW_DISK_USE, Boolean.class);
|
||||
}
|
||||
|
||||
if (document.containsKey(EXPLAIN)) {
|
||||
explain = (Boolean) document.get(EXPLAIN);
|
||||
}
|
||||
|
||||
if (document.containsKey(CURSOR)) {
|
||||
cursor = document.get(CURSOR, Document.class);
|
||||
}
|
||||
|
||||
return new AggregationOptions(allowDiskUse, explain, cursor);
|
||||
return new AggregationOptions(allowDiskUse, explain, cursor, collation);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,8 +137,8 @@ public class AggregationOptions {
|
||||
*/
|
||||
public Integer getCursorBatchSize() {
|
||||
|
||||
if (cursor != null && cursor.containsKey("batchSize")) {
|
||||
return cursor.get("batchSize", Integer.class);
|
||||
if (cursor.filter(val -> val.containsKey(BATCH_SIZE)).isPresent()) {
|
||||
return cursor.get().get(BATCH_SIZE, Integer.class);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -139,10 +149,20 @@ public class AggregationOptions {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Document getCursor() {
|
||||
public Optional<Document> getCursor() {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collation settings for string comparison.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public Optional<Collation> getCollation() {
|
||||
return collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new potentially adjusted copy for the given {@code aggregationCommandObject} with the configuration
|
||||
* applied.
|
||||
@@ -162,8 +182,12 @@ public class AggregationOptions {
|
||||
result.put(EXPLAIN, explain);
|
||||
}
|
||||
|
||||
if (cursor != null && !result.containsKey(CURSOR)) {
|
||||
result.put(CURSOR, cursor);
|
||||
if (!result.containsKey(CURSOR)) {
|
||||
cursor.ifPresent(val -> result.put(CURSOR, val));
|
||||
}
|
||||
|
||||
if (!result.containsKey(COLLATION)) {
|
||||
collation.map(Collation::toDocument).ifPresent(val -> result.append(COLLATION, val));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -179,7 +203,9 @@ public class AggregationOptions {
|
||||
Document document = new Document();
|
||||
document.put(ALLOW_DISK_USE, allowDiskUse);
|
||||
document.put(EXPLAIN, explain);
|
||||
document.put(CURSOR, cursor);
|
||||
|
||||
cursor.ifPresent(val -> document.put(CURSOR, val));
|
||||
collation.ifPresent(val -> document.append(COLLATION, val.toDocument()));
|
||||
|
||||
return document;
|
||||
}
|
||||
@@ -207,6 +233,7 @@ public class AggregationOptions {
|
||||
private boolean allowDiskUse;
|
||||
private boolean explain;
|
||||
private Document cursor;
|
||||
private Collation collation;
|
||||
|
||||
/**
|
||||
* Defines whether to off-load intensive sort-operations to disk.
|
||||
@@ -257,13 +284,25 @@ public class AggregationOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define collation settings for string comparison.
|
||||
*
|
||||
* @param collation can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Builder collation(Collation collation) {
|
||||
|
||||
this.collation = collation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link AggregationOptions} instance with the given configuration.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public AggregationOptions build() {
|
||||
return new AggregationOptions(allowDiskUse, explain, cursor);
|
||||
return new AggregationOptions(allowDiskUse, explain, cursor, collation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.springframework.data.domain.Range;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility methods for aggregation ooperation implementations.
|
||||
* Utility methods for aggregation operation implementations.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.index;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.Collation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -37,7 +40,8 @@ public class GeospatialIndex implements IndexDefinition {
|
||||
private GeoSpatialIndexType type = GeoSpatialIndexType.GEO_2D;
|
||||
private Double bucketSize = 1.0;
|
||||
private String additionalField;
|
||||
private IndexFilter filter;
|
||||
private Optional<IndexFilter> filter = Optional.empty();
|
||||
private Optional<Collation> collation = Optional.empty();
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeospatialIndex} for the given field.
|
||||
@@ -129,7 +133,23 @@ public class GeospatialIndex implements IndexDefinition {
|
||||
*/
|
||||
public GeospatialIndex partial(IndexFilter filter) {
|
||||
|
||||
this.filter = filter;
|
||||
this.filter = Optional.ofNullable(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link Collation} to specify language-specific rules for string comparison, such as rules for lettercase
|
||||
* and accent marks.<br />
|
||||
* <strong>NOTE:</strong> Only queries using the same {@link Collation} as the {@link Index} actually make use of the
|
||||
* index.
|
||||
*
|
||||
* @param collation can be {@literal null}.
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public GeospatialIndex collation(Collation collation) {
|
||||
|
||||
this.collation = Optional.ofNullable(collation);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -168,9 +188,9 @@ public class GeospatialIndex implements IndexDefinition {
|
||||
return null;
|
||||
}
|
||||
|
||||
Document dbo = new Document();
|
||||
Document document = new Document();
|
||||
if (StringUtils.hasText(name)) {
|
||||
dbo.put("name", name);
|
||||
document.put("name", name);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
@@ -178,13 +198,13 @@ public class GeospatialIndex implements IndexDefinition {
|
||||
case GEO_2D:
|
||||
|
||||
if (min != null) {
|
||||
dbo.put("min", min);
|
||||
document.put("min", min);
|
||||
}
|
||||
if (max != null) {
|
||||
dbo.put("max", max);
|
||||
document.put("max", max);
|
||||
}
|
||||
if (bits != null) {
|
||||
dbo.put("bits", bits);
|
||||
document.put("bits", bits);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -195,16 +215,15 @@ public class GeospatialIndex implements IndexDefinition {
|
||||
case GEO_HAYSTACK:
|
||||
|
||||
if (bucketSize != null) {
|
||||
dbo.put("bucketSize", bucketSize);
|
||||
document.put("bucketSize", bucketSize);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (filter != null) {
|
||||
dbo.put("partialFilterExpression", filter.getFilterObject());
|
||||
}
|
||||
filter.ifPresent(val -> document.put("partialFilterExpression", val.getFilterObject()));
|
||||
collation.ifPresent(val -> document.append("collation", val.toDocument()));
|
||||
|
||||
return dbo;
|
||||
return document;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -18,10 +18,12 @@ package org.springframework.data.mongodb.core.index;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.Collation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -50,7 +52,9 @@ public class Index implements IndexDefinition {
|
||||
|
||||
private long expire = -1;
|
||||
|
||||
private IndexFilter filter;
|
||||
private Optional<IndexFilter> filter = Optional.empty();
|
||||
|
||||
private Optional<Collation> collation = Optional.empty();
|
||||
|
||||
public Index() {}
|
||||
|
||||
@@ -72,7 +76,8 @@ public class Index implements IndexDefinition {
|
||||
* Reject all documents that contain a duplicate value for the indexed field.
|
||||
*
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-unique/">https://docs.mongodb.org/manual/core/index-unique/</a>
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/core/index-unique/">https://docs.mongodb.org/manual/core/index-unique/</a>
|
||||
*/
|
||||
public Index unique() {
|
||||
this.unique = true;
|
||||
@@ -83,7 +88,8 @@ public class Index implements IndexDefinition {
|
||||
* Skip over any document that is missing the indexed field.
|
||||
*
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-sparse/">https://docs.mongodb.org/manual/core/index-sparse/</a>
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/core/index-sparse/">https://docs.mongodb.org/manual/core/index-sparse/</a>
|
||||
*/
|
||||
public Index sparse() {
|
||||
this.sparse = true;
|
||||
@@ -139,7 +145,23 @@ public class Index implements IndexDefinition {
|
||||
*/
|
||||
public Index partial(IndexFilter filter) {
|
||||
|
||||
this.filter = filter;
|
||||
this.filter = Optional.ofNullable(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link Collation} to specify language-specific rules for string comparison, such as rules for lettercase
|
||||
* and accent marks.<br />
|
||||
* <strong>NOTE:</strong> Only queries using the same {@link Collation} as the {@link Index} actually make use of the
|
||||
* index.
|
||||
*
|
||||
* @param collation can be {@literal null}.
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public Index collation(Collation collation) {
|
||||
|
||||
this.collation = Optional.ofNullable(collation);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -180,9 +202,9 @@ public class Index implements IndexDefinition {
|
||||
document.put("expireAfterSeconds", expire);
|
||||
}
|
||||
|
||||
if (filter != null) {
|
||||
document.put("partialFilterExpression", filter.getFilterObject());
|
||||
}
|
||||
filter.ifPresent(val -> document.put("partialFilterExpression", val.getFilterObject()));
|
||||
collation.ifPresent(val -> document.append("collation", val.toDocument()));
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -46,9 +47,9 @@ public class IndexInfo {
|
||||
private final boolean sparse;
|
||||
private final String language;
|
||||
private String partialFilterExpression;
|
||||
private Document collation;
|
||||
|
||||
public IndexInfo(List<IndexField> indexFields, String name, boolean unique, boolean sparse,
|
||||
String language) {
|
||||
public IndexInfo(List<IndexField> indexFields, String name, boolean unique, boolean sparse, String language) {
|
||||
|
||||
this.indexFields = Collections.unmodifiableList(indexFields);
|
||||
this.name = name;
|
||||
@@ -106,10 +107,11 @@ public class IndexInfo {
|
||||
String language = sourceDocument.containsKey("default_language") ? (String) sourceDocument.get("default_language")
|
||||
: "";
|
||||
String partialFilter = sourceDocument.containsKey("partialFilterExpression")
|
||||
? ((Document)sourceDocument.get("partialFilterExpression")).toJson() : "";
|
||||
? ((Document) sourceDocument.get("partialFilterExpression")).toJson() : "";
|
||||
|
||||
IndexInfo info = new IndexInfo(indexFields, name, unique, sparse, language);
|
||||
info.partialFilterExpression = partialFilter;
|
||||
info.collation = sourceDocument.get("collation", Document.class);
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -169,10 +171,21 @@ public class IndexInfo {
|
||||
return partialFilterExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collation information.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public Optional<Document> getCollation() {
|
||||
return Optional.ofNullable(collation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", sparse=" + sparse + ", language=" + language + ", partialFilterExpression="
|
||||
+ partialFilterExpression + "]";
|
||||
return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", sparse=" + sparse
|
||||
+ ", language=" + language + ", partialFilterExpression=" + partialFilterExpression + ", collation=" + collation
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -186,6 +199,7 @@ public class IndexInfo {
|
||||
result = prime * result + (unique ? 1231 : 1237);
|
||||
result = prime * result + ObjectUtils.nullSafeHashCode(language);
|
||||
result = prime * result + ObjectUtils.nullSafeHashCode(partialFilterExpression);
|
||||
result = prime * result + ObjectUtils.nullSafeHashCode(collation);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -227,6 +241,10 @@ public class IndexInfo {
|
||||
if (!ObjectUtils.nullSafeEquals(partialFilterExpression, other.partialFilterExpression)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(collation, collation)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2016 the original author or authors.
|
||||
* Copyright 2010-2017 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,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapreduce;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.Collation;
|
||||
|
||||
/**
|
||||
* Collects the parameters required to perform a group operation on a collection. The query condition and the input
|
||||
@@ -27,80 +30,138 @@ import org.bson.Document;
|
||||
*/
|
||||
public class GroupBy {
|
||||
|
||||
private Document dboKeys;
|
||||
private String keyFunction;
|
||||
private String initial;
|
||||
private Document initialDocument;
|
||||
private String reduce;
|
||||
private String finalize;
|
||||
|
||||
private Optional<Document> dboKeys = Optional.empty();
|
||||
private Optional<String> keyFunction = Optional.empty();
|
||||
private Optional<String> initial = Optional.empty();
|
||||
private Optional<String> finalize = Optional.empty();
|
||||
private Optional<Collation> collation = Optional.empty();
|
||||
|
||||
public GroupBy(String... keys) {
|
||||
|
||||
Document document = new Document();
|
||||
for (String key : keys) {
|
||||
document.put(key, 1);
|
||||
}
|
||||
dboKeys = document;
|
||||
|
||||
dboKeys = Optional.of(document);
|
||||
}
|
||||
|
||||
// NOTE GroupByCommand does not handle keyfunction.
|
||||
|
||||
public GroupBy(String key, boolean isKeyFunction) {
|
||||
|
||||
Document document = new Document();
|
||||
if (isKeyFunction) {
|
||||
keyFunction = key;
|
||||
keyFunction = Optional.ofNullable(key);
|
||||
} else {
|
||||
document.put(key, 1);
|
||||
dboKeys = document;
|
||||
dboKeys = Optional.of(document);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link GroupBy} with the field to group.
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public static GroupBy keyFunction(String key) {
|
||||
return new GroupBy(key, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link GroupBy} with the fields to group.
|
||||
*
|
||||
* @param keys
|
||||
* @return
|
||||
*/
|
||||
public static GroupBy key(String... keys) {
|
||||
return new GroupBy(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the aggregation result document.
|
||||
*
|
||||
* @param initialDocument can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public GroupBy initialDocument(String initialDocument) {
|
||||
initial = initialDocument;
|
||||
|
||||
initial = Optional.ofNullable(initialDocument);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the aggregation result document.
|
||||
*
|
||||
* @param initialDocument can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public GroupBy initialDocument(Document initialDocument) {
|
||||
|
||||
this.initialDocument = initialDocument;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the aggregation function that operates on the documents during the grouping operation
|
||||
*
|
||||
* @param reduceFunction
|
||||
* @return
|
||||
*/
|
||||
public GroupBy reduceFunction(String reduceFunction) {
|
||||
|
||||
reduce = reduceFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the function that runs each item in the result set before db.collection.group() returns the final value.
|
||||
*
|
||||
* @param finalizeFunction
|
||||
* @return
|
||||
*/
|
||||
public GroupBy finalizeFunction(String finalizeFunction) {
|
||||
finalize = finalizeFunction;
|
||||
|
||||
finalize = Optional.ofNullable(finalizeFunction);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the Collation specifying language-specific rules for string comparison.
|
||||
*
|
||||
* @param collation can be {@literal null}.
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public GroupBy collation(Collation collation) {
|
||||
|
||||
this.collation = Optional.ofNullable(collation);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Document} representation of the {@link GroupBy}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Document getGroupByObject() {
|
||||
// return new GroupCommand(dbCollection, dboKeys, condition, initial, reduce, finalize);
|
||||
|
||||
Document document = new Document();
|
||||
if (dboKeys != null) {
|
||||
document.put("key", dboKeys);
|
||||
}
|
||||
if (keyFunction != null) {
|
||||
document.put("$keyf", keyFunction);
|
||||
}
|
||||
|
||||
dboKeys.ifPresent(val -> document.append("key", val));
|
||||
keyFunction.ifPresent(val -> document.append("$keyf", val));
|
||||
|
||||
document.put("$reduce", reduce);
|
||||
|
||||
document.put("initial", initialDocument);
|
||||
if (initial != null) {
|
||||
document.put("initial", initial);
|
||||
}
|
||||
if (finalize != null) {
|
||||
document.put("finalize", finalize);
|
||||
}
|
||||
|
||||
initial.ifPresent(val -> document.append("initial", val));
|
||||
finalize.ifPresent(val -> document.append("finalize", val));
|
||||
collation.ifPresent(val -> document.append("collation", val.toDocument()));
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,10 @@ package org.springframework.data.mongodb.core.mapreduce;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.Collation;
|
||||
|
||||
import com.mongodb.MapReduceCommand;
|
||||
|
||||
@@ -31,23 +33,17 @@ public class MapReduceOptions {
|
||||
|
||||
private String outputCollection;
|
||||
|
||||
private String outputDatabase;
|
||||
|
||||
private Boolean outputSharded;
|
||||
|
||||
private Optional<String> outputDatabase = Optional.empty();
|
||||
private MapReduceCommand.OutputType outputType = MapReduceCommand.OutputType.REPLACE;
|
||||
|
||||
private String finalizeFunction;
|
||||
|
||||
private Map<String, Object> scopeVariables = new HashMap<String, Object>();
|
||||
|
||||
private Map<String, Object> extraOptions = new HashMap<String, Object>();
|
||||
private Boolean jsMode;
|
||||
|
||||
private Boolean verbose = true;
|
||||
|
||||
private Boolean verbose = Boolean.TRUE;
|
||||
private Integer limit;
|
||||
|
||||
private Map<String, Object> extraOptions = new HashMap<String, Object>();
|
||||
private Optional<Boolean> outputSharded = Optional.empty();
|
||||
private Optional<String> finalizeFunction = Optional.empty();
|
||||
private Optional<Collation> collation = Optional.empty();
|
||||
|
||||
/**
|
||||
* Static factory method to create a MapReduceOptions instance
|
||||
@@ -79,6 +75,7 @@ public class MapReduceOptions {
|
||||
* @return MapReduceOptions so that methods can be chained in a fluent API style
|
||||
*/
|
||||
public MapReduceOptions outputCollection(String collectionName) {
|
||||
|
||||
this.outputCollection = collectionName;
|
||||
return this;
|
||||
}
|
||||
@@ -91,7 +88,8 @@ public class MapReduceOptions {
|
||||
* @return MapReduceOptions so that methods can be chained in a fluent API style
|
||||
*/
|
||||
public MapReduceOptions outputDatabase(String outputDatabase) {
|
||||
this.outputDatabase = outputDatabase;
|
||||
|
||||
this.outputDatabase = Optional.ofNullable(outputDatabase);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -103,6 +101,7 @@ public class MapReduceOptions {
|
||||
* @return MapReduceOptions so that methods can be chained in a fluent API style
|
||||
*/
|
||||
public MapReduceOptions outputTypeInline() {
|
||||
|
||||
this.outputType = MapReduceCommand.OutputType.INLINE;
|
||||
return this;
|
||||
}
|
||||
@@ -114,6 +113,7 @@ public class MapReduceOptions {
|
||||
* @return MapReduceOptions so that methods can be chained in a fluent API style
|
||||
*/
|
||||
public MapReduceOptions outputTypeMerge() {
|
||||
|
||||
this.outputType = MapReduceCommand.OutputType.MERGE;
|
||||
return this;
|
||||
}
|
||||
@@ -137,6 +137,7 @@ public class MapReduceOptions {
|
||||
* @return MapReduceOptions so that methods can be chained in a fluent API style
|
||||
*/
|
||||
public MapReduceOptions outputTypeReplace() {
|
||||
|
||||
this.outputType = MapReduceCommand.OutputType.REPLACE;
|
||||
return this;
|
||||
}
|
||||
@@ -149,7 +150,8 @@ public class MapReduceOptions {
|
||||
* @return MapReduceOptions so that methods can be chained in a fluent API style
|
||||
*/
|
||||
public MapReduceOptions outputSharded(boolean outputShared) {
|
||||
this.outputSharded = outputShared;
|
||||
|
||||
this.outputSharded = Optional.of(outputShared);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -160,7 +162,8 @@ public class MapReduceOptions {
|
||||
* @return MapReduceOptions so that methods can be chained in a fluent API style
|
||||
*/
|
||||
public MapReduceOptions finalizeFunction(String finalizeFunction) {
|
||||
this.finalizeFunction = finalizeFunction;
|
||||
|
||||
this.finalizeFunction = Optional.ofNullable(finalizeFunction);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -172,6 +175,7 @@ public class MapReduceOptions {
|
||||
* @return MapReduceOptions so that methods can be chained in a fluent API style
|
||||
*/
|
||||
public MapReduceOptions scopeVariables(Map<String, Object> scopeVariables) {
|
||||
|
||||
this.scopeVariables = scopeVariables;
|
||||
return this;
|
||||
}
|
||||
@@ -184,6 +188,7 @@ public class MapReduceOptions {
|
||||
* @return MapReduceOptions so that methods can be chained in a fluent API style
|
||||
*/
|
||||
public MapReduceOptions javaScriptMode(boolean javaScriptMode) {
|
||||
|
||||
this.jsMode = javaScriptMode;
|
||||
return this;
|
||||
}
|
||||
@@ -194,6 +199,7 @@ public class MapReduceOptions {
|
||||
* @return MapReduceOptions so that methods can be chained in a fluent API style
|
||||
*/
|
||||
public MapReduceOptions verbose(boolean verbose) {
|
||||
|
||||
this.verbose = verbose;
|
||||
return this;
|
||||
}
|
||||
@@ -210,10 +216,24 @@ public class MapReduceOptions {
|
||||
*/
|
||||
@Deprecated
|
||||
public MapReduceOptions extraOption(String key, Object value) {
|
||||
|
||||
extraOptions.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the Collation specifying language-specific rules for string comparison.
|
||||
*
|
||||
* @param collation can be {@literal null}.
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public MapReduceOptions collation(Collation collation) {
|
||||
|
||||
this.collation = Optional.ofNullable(collation);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @deprecated since 1.7
|
||||
@@ -223,7 +243,7 @@ public class MapReduceOptions {
|
||||
return extraOptions;
|
||||
}
|
||||
|
||||
public String getFinalizeFunction() {
|
||||
public Optional<String> getFinalizeFunction() {
|
||||
return this.finalizeFunction;
|
||||
}
|
||||
|
||||
@@ -235,11 +255,11 @@ public class MapReduceOptions {
|
||||
return this.outputCollection;
|
||||
}
|
||||
|
||||
public String getOutputDatabase() {
|
||||
public Optional<String> getOutputDatabase() {
|
||||
return this.outputDatabase;
|
||||
}
|
||||
|
||||
public Boolean getOutputSharded() {
|
||||
public Optional<Boolean> getOutputSharded() {
|
||||
return this.outputSharded;
|
||||
}
|
||||
|
||||
@@ -260,7 +280,18 @@ public class MapReduceOptions {
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Collation specifying language-specific rules for string comparison.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public Optional<Collation> getCollation() {
|
||||
return collation;
|
||||
}
|
||||
|
||||
public Document getOptionsObject() {
|
||||
|
||||
Document cmd = new Document();
|
||||
|
||||
if (verbose != null) {
|
||||
@@ -269,9 +300,7 @@ public class MapReduceOptions {
|
||||
|
||||
cmd.put("out", createOutObject());
|
||||
|
||||
if (finalizeFunction != null) {
|
||||
cmd.put("finalize", finalizeFunction);
|
||||
}
|
||||
finalizeFunction.ifPresent(val -> cmd.append("finalize", val));
|
||||
|
||||
if (scopeVariables != null) {
|
||||
cmd.put("scope", scopeVariables);
|
||||
@@ -285,10 +314,13 @@ public class MapReduceOptions {
|
||||
cmd.putAll(extraOptions);
|
||||
}
|
||||
|
||||
getCollation().ifPresent(val -> cmd.append("collation", val.toDocument()));
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
protected Document createOutObject() {
|
||||
|
||||
Document out = new Document();
|
||||
|
||||
switch (outputType) {
|
||||
@@ -306,13 +338,8 @@ public class MapReduceOptions {
|
||||
break;
|
||||
}
|
||||
|
||||
if (outputDatabase != null) {
|
||||
out.put("db", outputDatabase);
|
||||
}
|
||||
|
||||
if (outputSharded != null) {
|
||||
out.put("sharded", outputSharded);
|
||||
}
|
||||
outputDatabase.ifPresent(val -> out.append("db", val));
|
||||
outputSharded.ifPresent(val -> out.append("sharded", val));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -404,7 +404,9 @@ public final class NearQuery {
|
||||
Document document = new Document();
|
||||
|
||||
if (query != null) {
|
||||
|
||||
document.put("query", query.getQueryObject());
|
||||
query.getCollation().ifPresent(collation -> document.append("collation", collation.toDocument()));
|
||||
}
|
||||
|
||||
if (maxDistance != null) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -32,6 +33,7 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
|
||||
import org.springframework.data.mongodb.core.Collation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -45,8 +47,8 @@ public class Query {
|
||||
|
||||
private static final String RESTRICTED_TYPES_KEY = "_$RESTRICTED_TYPES";
|
||||
|
||||
private final Set<Class<?>> restrictedTypes = new HashSet<Class<?>>();
|
||||
private final Map<String, CriteriaDefinition> criteria = new LinkedHashMap<String, CriteriaDefinition>();
|
||||
private final Set<Class<?>> restrictedTypes = new HashSet<>();
|
||||
private final Map<String, CriteriaDefinition> criteria = new LinkedHashMap<>();
|
||||
private Field fieldSpec = null;
|
||||
private Sort sort = Sort.unsorted();
|
||||
private long skip;
|
||||
@@ -55,6 +57,8 @@ public class Query {
|
||||
|
||||
private Meta meta = new Meta();
|
||||
|
||||
private Optional<Collation> collation = Optional.empty();
|
||||
|
||||
/**
|
||||
* Static factory method to create a {@link Query} using the provided {@link CriteriaDefinition}.
|
||||
*
|
||||
@@ -192,7 +196,7 @@ public class Query {
|
||||
* @return the restrictedTypes
|
||||
*/
|
||||
public Set<Class<?>> getRestrictedTypes() {
|
||||
return restrictedTypes == null ? Collections.<Class<?>> emptySet() : restrictedTypes;
|
||||
return restrictedTypes == null ? Collections.emptySet() : restrictedTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -395,8 +399,31 @@ public class Query {
|
||||
this.meta = meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link Collation} applying language-specific rules for string comparison.
|
||||
*
|
||||
* @param collation can be {@literal null}.
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public Query collation(Collation collation) {
|
||||
|
||||
this.collation = Optional.ofNullable(collation);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Collation} defining language-specific rules for string comparison.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public Optional<Collation> getCollation() {
|
||||
return collation;
|
||||
}
|
||||
|
||||
protected List<CriteriaDefinition> getCriteria() {
|
||||
return new ArrayList<CriteriaDefinition>(this.criteria.values());
|
||||
return new ArrayList<>(this.criteria.values());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -442,8 +469,10 @@ public class Query {
|
||||
boolean skipEqual = this.skip == that.skip;
|
||||
boolean limitEqual = this.limit == that.limit;
|
||||
boolean metaEqual = nullSafeEquals(this.meta, that.meta);
|
||||
boolean collationEqual = nullSafeEquals(this.collation.orElse(null), that.collation.orElse(null));
|
||||
|
||||
return criteriaEqual && fieldsEqual && sortEqual && hintEqual && skipEqual && limitEqual && metaEqual;
|
||||
return criteriaEqual && fieldsEqual && sortEqual && hintEqual && skipEqual && limitEqual && metaEqual
|
||||
&& collationEqual;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -462,6 +491,7 @@ public class Query {
|
||||
result += 31 * skip;
|
||||
result += 31 * limit;
|
||||
result += 31 * nullSafeHashCode(meta);
|
||||
result += 31 * nullSafeHashCode(collation.orElse(null));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2017 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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.Collation.Alternate;
|
||||
import org.springframework.data.mongodb.core.Collation.ICUCaseFirst;
|
||||
import org.springframework.data.mongodb.core.Collation.ICUComparisonLevel;
|
||||
import org.springframework.data.mongodb.core.Collation.ICULocale;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class CollationUnitTests {
|
||||
|
||||
static final Document BINARY_COMPARISON = new Document().append("locale", "simple");
|
||||
static final Document JUST_LOCALE = new Document().append("locale", "en_US");
|
||||
static final Document LOCALE_WITH_VARIANT = new Document().append("locale", "de_AT@collation=phonebook");
|
||||
static final Document WITH_STRENGTH_PRIMARY = new Document(JUST_LOCALE).append("strength", 1);
|
||||
static final Document WITH_STRENGTH_PRIMARY_INCLUDE_CASE = new Document(WITH_STRENGTH_PRIMARY).append("caseLevel",
|
||||
true);
|
||||
static final Document WITH_NORMALIZATION = new Document(JUST_LOCALE).append("normalization", true);
|
||||
static final Document WITH_BACKWARDS = new Document(JUST_LOCALE).append("backwards", true);
|
||||
static final Document WITH_NUMERIC_ORDERING = new Document(JUST_LOCALE).append("numericOrdering", true);
|
||||
static final Document WITH_CASE_FIRST_UPPER = new Document(JUST_LOCALE).append("strength", 3).append("caseFirst",
|
||||
"upper");
|
||||
static final Document WITH_ALTERNATE_SHIFTED = new Document(JUST_LOCALE).append("alternate", "shifted");
|
||||
static final Document WITH_ALTERNATE_SHIFTED_MAX_VARIABLE_PUNCT = new Document(WITH_ALTERNATE_SHIFTED)
|
||||
.append("maxVariable", "punct");
|
||||
static final Document ALL_THE_THINGS = new Document(LOCALE_WITH_VARIANT).append("strength", 1)
|
||||
.append("caseLevel", true).append("backwards", true).append("numericOrdering", true)
|
||||
.append("alternate", "shifted").append("maxVariable", "punct").append("normalization", true);
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void justLocale() {
|
||||
assertThat(Collation.of("en_US").toDocument()).isEqualTo(JUST_LOCALE);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void justLocaleFromDocument() {
|
||||
assertThat(Collation.from(JUST_LOCALE).toDocument()).isEqualTo(JUST_LOCALE);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void localeWithVariant() {
|
||||
assertThat(Collation.of(ICULocale.of("de_AT").variant("phonebook")).toDocument()).isEqualTo(LOCALE_WITH_VARIANT);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void localeWithVariantFromDocument() {
|
||||
assertThat(Collation.from(LOCALE_WITH_VARIANT).toDocument()).isEqualTo(LOCALE_WITH_VARIANT);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void lcaleFromJavaUtilLocale() {
|
||||
assertThat(Collation.of(java.util.Locale.US).toDocument()).isEqualTo(new Document().append("locale", "en"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withStrenghPrimary() {
|
||||
assertThat(Collation.of("en_US").strength(ICUComparisonLevel.primary()).toDocument())
|
||||
.isEqualTo(WITH_STRENGTH_PRIMARY);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withStrenghPrimaryFromDocument() {
|
||||
assertThat(Collation.from(WITH_STRENGTH_PRIMARY).toDocument()).isEqualTo(WITH_STRENGTH_PRIMARY);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withStrenghPrimaryAndIncludeCase() {
|
||||
|
||||
assertThat(Collation.of("en_US").strength(ICUComparisonLevel.primary().includeCase()).toDocument())
|
||||
.isEqualTo(WITH_STRENGTH_PRIMARY_INCLUDE_CASE);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withStrenghPrimaryAndIncludeCaseFromDocument() {
|
||||
|
||||
assertThat(Collation.from(WITH_STRENGTH_PRIMARY_INCLUDE_CASE).toDocument())
|
||||
.isEqualTo(WITH_STRENGTH_PRIMARY_INCLUDE_CASE);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withNormalization() {
|
||||
assertThat(Collation.of("en_US").normalization(true).toDocument()).isEqualTo(WITH_NORMALIZATION);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withNormalizationFromDocument() {
|
||||
assertThat(Collation.from(WITH_NORMALIZATION).toDocument()).isEqualTo(WITH_NORMALIZATION);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withBackwards() {
|
||||
assertThat(Collation.of("en_US").backwards(true).toDocument()).isEqualTo(WITH_BACKWARDS);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withBackwardsFromDocument() {
|
||||
assertThat(Collation.from(WITH_BACKWARDS).toDocument()).isEqualTo(WITH_BACKWARDS);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withNumericOrdering() {
|
||||
assertThat(Collation.of("en_US").numericOrdering(true).toDocument()).isEqualTo(WITH_NUMERIC_ORDERING);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withNumericOrderingFromDocument() {
|
||||
assertThat(Collation.from(WITH_NUMERIC_ORDERING).toDocument()).isEqualTo(WITH_NUMERIC_ORDERING);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withCaseFirst() {
|
||||
assertThat(Collation.of("en_US").caseFirst(ICUCaseFirst.upper()).toDocument()).isEqualTo(WITH_CASE_FIRST_UPPER);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withCaseFirstFromDocument() {
|
||||
assertThat(Collation.from(WITH_CASE_FIRST_UPPER).toDocument()).isEqualTo(WITH_CASE_FIRST_UPPER);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withAlternate() {
|
||||
assertThat(Collation.of("en_US").alternate(Alternate.shifted()).toDocument()).isEqualTo(WITH_ALTERNATE_SHIFTED);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withAlternateFromDocument() {
|
||||
assertThat(Collation.from(WITH_ALTERNATE_SHIFTED).toDocument()).isEqualTo(WITH_ALTERNATE_SHIFTED);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withAlternateAndMaxVariable() {
|
||||
|
||||
assertThat(Collation.of("en_US").alternate(Alternate.shifted().punct()).toDocument())
|
||||
.isEqualTo(WITH_ALTERNATE_SHIFTED_MAX_VARIABLE_PUNCT);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void withAlternateAndMaxVariableFromDocument() {
|
||||
|
||||
assertThat(Collation.from(WITH_ALTERNATE_SHIFTED_MAX_VARIABLE_PUNCT).toDocument())
|
||||
.isEqualTo(WITH_ALTERNATE_SHIFTED_MAX_VARIABLE_PUNCT);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void allTheThings() {
|
||||
|
||||
assertThat(Collation.of(ICULocale.of("de_AT").variant("phonebook"))
|
||||
.strength(ICUComparisonLevel.primary().includeCase()).normalizationEnabled().backwardDiacriticSort()
|
||||
.numericOrderingEnabled().alternate(Alternate.shifted().punct()).toDocument()).isEqualTo(ALL_THE_THINGS);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void allTheThingsFromDocument() {
|
||||
assertThat(Collation.from(ALL_THE_THINGS).toDocument()).isEqualTo(ALL_THE_THINGS);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void justTheDefault() {
|
||||
assertThat(Collation.binary().toDocument()).isEqualTo(BINARY_COMPARISON);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2017 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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.any;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.model.DeleteManyModel;
|
||||
import com.mongodb.client.model.UpdateManyModel;
|
||||
import com.mongodb.client.model.UpdateOneModel;
|
||||
import com.mongodb.client.model.WriteModel;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DefaultBulkOperationsUnitTests {
|
||||
|
||||
@Mock MongoTemplate template;
|
||||
@Mock MongoCollection collection;
|
||||
|
||||
DefaultBulkOperations ops;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
when(template.getCollection(anyString())).thenReturn(collection);
|
||||
ops = new DefaultBulkOperations(template, BulkMode.ORDERED, "collection-1", SomeDomainType.class);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void updateOneShouldUseCollationWhenPresent() {
|
||||
|
||||
ops.updateOne(new BasicQuery("{}").collation(Collation.of("de")), new Update().set("lastName", "targaryen"))
|
||||
.execute();
|
||||
|
||||
ArgumentCaptor<List<WriteModel<Document>>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection).bulkWrite(captor.capture(), any());
|
||||
|
||||
assertThat(captor.getValue().get(0)).isInstanceOf(UpdateOneModel.class);
|
||||
assertThat(((UpdateOneModel) captor.getValue().get(0)).getOptions().getCollation())
|
||||
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("de").build());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void updateMayShouldUseCollationWhenPresent() {
|
||||
|
||||
ops.updateMulti(new BasicQuery("{}").collation(Collation.of("de")), new Update().set("lastName", "targaryen"))
|
||||
.execute();
|
||||
|
||||
ArgumentCaptor<List<WriteModel<Document>>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection).bulkWrite(captor.capture(), any());
|
||||
|
||||
assertThat(captor.getValue().get(0)).isInstanceOf(UpdateManyModel.class);
|
||||
assertThat(((UpdateManyModel) captor.getValue().get(0)).getOptions().getCollation())
|
||||
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("de").build());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void removeShouldUseCollationWhenPresent() {
|
||||
|
||||
ops.remove(new BasicQuery("{}").collation(Collation.of("de"))).execute();
|
||||
|
||||
ArgumentCaptor<List<WriteModel<Document>>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection).bulkWrite(captor.capture(), any());
|
||||
|
||||
assertThat(captor.getValue().get(0)).isInstanceOf(DeleteManyModel.class);
|
||||
assertThat(((DeleteManyModel) captor.getValue().get(0)).getOptions().getCollation())
|
||||
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("de").build());
|
||||
}
|
||||
|
||||
class SomeDomainType {
|
||||
|
||||
@Id String id;
|
||||
Gender gender;
|
||||
@Field("first_name") String firstName;
|
||||
@Field String lastName;
|
||||
}
|
||||
|
||||
enum Gender {
|
||||
M, F
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.hamcrest.core.Is.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.data.mongodb.core.index.PartialIndexFilter.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
@@ -27,6 +27,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.Collation.ICUCaseFirst;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.index.Index;
|
||||
import org.springframework.data.mongodb.core.index.IndexDefinition;
|
||||
@@ -50,6 +51,7 @@ import com.mongodb.client.MongoCollection;
|
||||
public class DefaultIndexOperationsIntegrationTests {
|
||||
|
||||
private static final Version THREE_DOT_TWO = new Version(3, 2);
|
||||
private static final Version THREE_DOT_FOUR = new Version(3, 4);
|
||||
private static Version mongoVersion;
|
||||
static final org.bson.Document GEO_SPHERE_2D = new org.bson.Document("loaction", "2dsphere");
|
||||
|
||||
@@ -83,7 +85,7 @@ public class DefaultIndexOperationsIntegrationTests {
|
||||
collection.createIndex(GEO_SPHERE_2D);
|
||||
|
||||
IndexInfo info = findAndReturnIndexInfo(GEO_SPHERE_2D);
|
||||
assertThat(info.getIndexFields().get(0).isGeo(), is(true));
|
||||
assertThat(info.getIndexFields().get(0).isGeo()).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1467
|
||||
@@ -97,7 +99,7 @@ public class DefaultIndexOperationsIntegrationTests {
|
||||
indexOps.ensureIndex(id);
|
||||
|
||||
IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-criteria");
|
||||
assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"q-t-y\" : { \"$gte\" : 10 } }")));
|
||||
assertThat(info.getPartialFilterExpression()).isEqualTo("{ \"q-t-y\" : { \"$gte\" : 10 } }");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1467
|
||||
@@ -111,7 +113,7 @@ public class DefaultIndexOperationsIntegrationTests {
|
||||
indexOps.ensureIndex(id);
|
||||
|
||||
IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-mapped-criteria");
|
||||
assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"qty\" : { \"$gte\" : 10 } }")));
|
||||
assertThat(info.getPartialFilterExpression()).isEqualTo("{ \"qty\" : { \"$gte\" : 10 } }");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1467
|
||||
@@ -125,7 +127,7 @@ public class DefaultIndexOperationsIntegrationTests {
|
||||
indexOps.ensureIndex(id);
|
||||
|
||||
IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-dbo");
|
||||
assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"qty\" : { \"$gte\" : 10 } }")));
|
||||
assertThat(info.getPartialFilterExpression()).isEqualTo("{ \"qty\" : { \"$gte\" : 10 } }");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1467
|
||||
@@ -143,7 +145,42 @@ public class DefaultIndexOperationsIntegrationTests {
|
||||
indexOps.ensureIndex(id);
|
||||
|
||||
IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-inheritance");
|
||||
assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"a_g_e\" : { \"$gte\" : 10 } }")));
|
||||
assertThat(info.getPartialFilterExpression()).isEqualTo("{ \"a_g_e\" : { \"$gte\" : 10 } }");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void shouldCreateIndexWithCollationCorrectly() {
|
||||
|
||||
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR), is(true));
|
||||
|
||||
IndexDefinition id = new Index().named("with-collation").on("xyz", Direction.ASC)
|
||||
.collation(Collation.of("de_AT").caseFirst(ICUCaseFirst.off()));
|
||||
|
||||
new DefaultIndexOperations(template.getMongoDbFactory(),
|
||||
this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class),
|
||||
new QueryMapper(template.getConverter()), MappingToSameCollection.class);
|
||||
|
||||
indexOps.ensureIndex(id);
|
||||
|
||||
Document expected = new Document("locale", "de_AT") //
|
||||
.append("caseLevel", false) //
|
||||
.append("caseFirst", "off") //
|
||||
.append("strength", 3) //
|
||||
.append("numericOrdering", false) //
|
||||
.append("alternate", "non-ignorable") //
|
||||
.append("maxVariable", "punct") //
|
||||
.append("normalization", false) //
|
||||
.append("backwards", false);
|
||||
|
||||
IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "with-collation");
|
||||
|
||||
assertThat(info.getCollation()).isPresent();
|
||||
|
||||
// version is set by MongoDB server - we remove it to avoid errors when upgrading server version.
|
||||
Document result = info.getCollation().get();
|
||||
result.remove("version");
|
||||
|
||||
assertThat(result).isEqualTo(expected);
|
||||
}
|
||||
|
||||
private IndexInfo findAndReturnIndexInfo(org.bson.Document keys) {
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2017 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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.hamcrest.core.Is.*;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
|
||||
import org.springframework.data.mongodb.core.Collation.ICUCaseFirst;
|
||||
import org.springframework.data.mongodb.core.DefaultIndexOperationsIntegrationTests.DefaultIndexOperationsIntegrationTestsSample;
|
||||
import org.springframework.data.mongodb.core.index.Index;
|
||||
import org.springframework.data.mongodb.core.index.IndexDefinition;
|
||||
import org.springframework.data.util.Version;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import com.mongodb.reactivestreams.client.MongoClients;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class DefaultReactiveIndexOperationsTests {
|
||||
|
||||
@Configuration
|
||||
static class Config extends AbstractReactiveMongoConfiguration {
|
||||
|
||||
@Override
|
||||
public MongoClient mongoClient() {
|
||||
return MongoClients.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDatabaseName() {
|
||||
return "index-ops-tests";
|
||||
}
|
||||
}
|
||||
|
||||
private static final Version THREE_DOT_FOUR = new Version(3, 4);
|
||||
private static Version mongoVersion;
|
||||
|
||||
@Autowired ReactiveMongoTemplate template;
|
||||
|
||||
MongoCollection<Document> collection;
|
||||
DefaultReactiveIndexOperations indexOps;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
queryMongoVersionIfNecessary();
|
||||
String collectionName = this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class);
|
||||
|
||||
this.collection = this.template.getMongoDatabase().getCollection(collectionName, Document.class);
|
||||
this.collection.dropIndexes();
|
||||
this.indexOps = new DefaultReactiveIndexOperations(template, collectionName);
|
||||
}
|
||||
|
||||
private void queryMongoVersionIfNecessary() {
|
||||
|
||||
if (mongoVersion == null) {
|
||||
Document result = template.executeCommand("{ buildInfo: 1 }").block();
|
||||
mongoVersion = Version.parse(result.get("version").toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void shouldCreateIndexWithCollationCorrectly() {
|
||||
|
||||
assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR), is(true));
|
||||
|
||||
IndexDefinition id = new Index().named("with-collation").on("xyz", Direction.ASC)
|
||||
.collation(Collation.of("de_AT").caseFirst(ICUCaseFirst.off()));
|
||||
|
||||
indexOps.ensureIndex(id).subscribe();
|
||||
|
||||
Document expected = new Document("locale", "de_AT") //
|
||||
.append("caseLevel", false) //
|
||||
.append("caseFirst", "off") //
|
||||
.append("strength", 3) //
|
||||
.append("numericOrdering", false) //
|
||||
.append("alternate", "non-ignorable") //
|
||||
.append("maxVariable", "punct") //
|
||||
.append("normalization", false) //
|
||||
.append("backwards", false);
|
||||
|
||||
StepVerifier.create(indexOps.getIndexInfo().filter(val -> val.getName().equals("with-collation")))
|
||||
.consumeNextWith(indexInfo -> {
|
||||
|
||||
assertThat(indexInfo.getCollation()).isPresent();
|
||||
|
||||
// version is set by MongoDB server - we remove it to avoid errors when upgrading server version.
|
||||
Document result = indexInfo.getCollation().get();
|
||||
result.remove("version");
|
||||
|
||||
assertThat(result).isEqualTo(expected);
|
||||
}) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2017 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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
|
||||
import org.springframework.data.mongodb.core.Collation.Alternate;
|
||||
import org.springframework.data.mongodb.core.Collation.ICUComparisonLevel;
|
||||
import org.springframework.data.mongodb.core.Collation.ICULocale;
|
||||
import org.springframework.data.mongodb.test.util.MongoVersionRule;
|
||||
import org.springframework.data.util.Version;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.mongodb.MongoClient;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class MongoTemplateCollationTests {
|
||||
|
||||
public static @ClassRule MongoVersionRule REQUIRES_AT_LEAST_3_4_0 = MongoVersionRule.atLeast(Version.parse("3.4.0"));
|
||||
public static final String COLLECTION_NAME = "collation-1";
|
||||
|
||||
@Configuration
|
||||
static class Config extends AbstractMongoConfiguration {
|
||||
|
||||
@Override
|
||||
public MongoClient mongoClient() {
|
||||
return new MongoClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDatabaseName() {
|
||||
return "collation-tests";
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired MongoTemplate template;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
template.dropCollection(COLLECTION_NAME);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void createCollectionWithCollation() {
|
||||
|
||||
template.createCollection(COLLECTION_NAME, CollectionOptions.just(Collation.of("en_US")));
|
||||
|
||||
Document collation = getCollationInfo(COLLECTION_NAME);
|
||||
assertThat(collation.get("locale")).isEqualTo("en_US");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void createCollectionWithCollationHavingLocaleVariant() {
|
||||
|
||||
template.createCollection(COLLECTION_NAME,
|
||||
CollectionOptions.just(Collation.of(ICULocale.of("de_AT").variant("phonebook"))));
|
||||
|
||||
Document collation = getCollationInfo(COLLECTION_NAME);
|
||||
assertThat(collation.get("locale")).isEqualTo("de_AT@collation=phonebook");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void createCollectionWithCollationHavingStrength() {
|
||||
|
||||
template.createCollection(COLLECTION_NAME,
|
||||
CollectionOptions.just(Collation.of("en_US").strength(ICUComparisonLevel.primary().includeCase())));
|
||||
|
||||
Document collation = getCollationInfo(COLLECTION_NAME);
|
||||
assertThat(collation.get("strength")).isEqualTo(1);
|
||||
assertThat(collation.get("caseLevel")).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void createCollectionWithCollationHavingBackwardsAndNumericOrdering() {
|
||||
|
||||
template.createCollection(COLLECTION_NAME,
|
||||
CollectionOptions.just(Collation.of("en_US").backwardDiacriticSort().numericOrderingEnabled()));
|
||||
|
||||
Document collation = getCollationInfo(COLLECTION_NAME);
|
||||
assertThat(collation.get("backwards")).isEqualTo(true);
|
||||
assertThat(collation.get("numericOrdering")).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void createCollationWithCollationHavingAlternate() {
|
||||
|
||||
template.createCollection(COLLECTION_NAME,
|
||||
CollectionOptions.just(Collation.of("en_US").alternate(Alternate.shifted().punct())));
|
||||
|
||||
Document collation = getCollationInfo(COLLECTION_NAME);
|
||||
assertThat(collation.get("alternate")).isEqualTo("shifted");
|
||||
assertThat(collation.get("maxVariable")).isEqualTo("punct");
|
||||
}
|
||||
|
||||
private Document getCollationInfo(String collectionName) {
|
||||
return getCollectionInfo(collectionName).get("options", Document.class).get("collation", Document.class);
|
||||
}
|
||||
|
||||
private Document getCollectionInfo(String collectionName) {
|
||||
|
||||
return template.execute(db -> {
|
||||
|
||||
Document result = db.runCommand(
|
||||
new Document().append("listCollections", 1).append("filter", new Document("name", collectionName)));
|
||||
return (Document) result.get("cursor", Document.class).get("firstBatch", List.class).get(0);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
|
||||
|
||||
import java.math.BigInteger;
|
||||
@@ -62,6 +63,7 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCre
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
@@ -79,6 +81,8 @@ import com.mongodb.client.MapReduceIterable;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoCursor;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndDeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndUpdateOptions;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
@@ -101,6 +105,9 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Mock MongoCollection<Document> collection;
|
||||
@Mock MongoCursor<Document> cursor;
|
||||
@Mock FindIterable<Document> findIterable;
|
||||
@Mock MapReduceIterable mapReduceIterable;
|
||||
|
||||
Document commandResultDocument = new Document();
|
||||
|
||||
MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
|
||||
MappingMongoConverter converter;
|
||||
@@ -113,9 +120,16 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
when(factory.getDb()).thenReturn(db);
|
||||
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator);
|
||||
when(db.getCollection(Mockito.any(String.class), eq(Document.class))).thenReturn(collection);
|
||||
when(db.runCommand(Mockito.any(), Mockito.any(Class.class))).thenReturn(commandResultDocument);
|
||||
when(collection.find(Mockito.any(org.bson.Document.class))).thenReturn(findIterable);
|
||||
when(collection.mapReduce(Mockito.any(), Mockito.any())).thenReturn(mapReduceIterable);
|
||||
when(findIterable.projection(Mockito.any())).thenReturn(findIterable);
|
||||
when(findIterable.sort(Mockito.any(org.bson.Document.class))).thenReturn(findIterable);
|
||||
when(findIterable.modifiers(Mockito.any(org.bson.Document.class))).thenReturn(findIterable);
|
||||
when(findIterable.collation(Mockito.any())).thenReturn(findIterable);
|
||||
when(findIterable.limit(anyInt())).thenReturn(findIterable);
|
||||
when(mapReduceIterable.collation(Mockito.any())).thenReturn(mapReduceIterable);
|
||||
when(mapReduceIterable.iterator()).thenReturn(cursor);
|
||||
|
||||
this.mappingContext = new MongoMappingContext();
|
||||
this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext);
|
||||
@@ -306,7 +320,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
BasicQuery query = new BasicQuery("{'foo':'bar'}");
|
||||
template.findAllAndRemove(query, VersionedEntity.class);
|
||||
|
||||
verify(collection, times(1)).deleteMany(queryCaptor.capture());
|
||||
verify(collection, times(1)).deleteMany(queryCaptor.capture(), Mockito.any());
|
||||
|
||||
Document idField = DocumentTestUtils.getAsDocument(queryCaptor.getValue(), "_id");
|
||||
assertThat((List<Object>) idField.get("$in"),
|
||||
@@ -345,7 +359,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
.thenReturn(mock(Document.class));
|
||||
template.setReadPreference(ReadPreference.secondary());
|
||||
|
||||
template.aggregate(Aggregation.newAggregation(Aggregation.unwind("foo")), "collection-1", Wrapper.class);
|
||||
template.aggregate(newAggregation(Aggregation.unwind("foo")), "collection-1", Wrapper.class);
|
||||
|
||||
verify(this.db, times(1)).runCommand(Mockito.any(org.bson.Document.class), eq(ReadPreference.secondary()),
|
||||
eq(Document.class));
|
||||
@@ -357,7 +371,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
when(db.runCommand(Mockito.any(org.bson.Document.class), eq(org.bson.Document.class)))
|
||||
.thenReturn(mock(Document.class));
|
||||
|
||||
template.aggregate(Aggregation.newAggregation(Aggregation.unwind("foo")), "collection-1", Wrapper.class);
|
||||
template.aggregate(newAggregation(Aggregation.unwind("foo")), "collection-1", Wrapper.class);
|
||||
|
||||
verify(this.db, times(1)).runCommand(Mockito.any(org.bson.Document.class), eq(org.bson.Document.class));
|
||||
}
|
||||
@@ -615,6 +629,160 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
assertThat(updateCaptor.getValue(), isBsonObject().containing("$set.jon", "snow").notContaining("$isolated"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void executeQueryShouldUseCollationWhenPresent() {
|
||||
|
||||
template.executeQuery(new BasicQuery("{}").collation(Collation.of("fr")), "collection-1", val -> {});
|
||||
|
||||
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void streamQueryShouldUseCollationWhenPresent() {
|
||||
|
||||
template.stream(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class).next();
|
||||
|
||||
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void findShouldUseCollationWhenPresent() {
|
||||
|
||||
template.find(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class);
|
||||
|
||||
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void findOneShouldUseCollationWhenPresent() {
|
||||
|
||||
template.findOne(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class);
|
||||
|
||||
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void existsShouldUseCollationWhenPresent() {
|
||||
|
||||
template.exists(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class);
|
||||
|
||||
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void findAndModfiyShoudUseCollationWhenPresent() {
|
||||
|
||||
template.findAndModify(new BasicQuery("{}").collation(Collation.of("fr")), new Update(), AutogenerateableId.class);
|
||||
|
||||
ArgumentCaptor<FindOneAndUpdateOptions> options = ArgumentCaptor.forClass(FindOneAndUpdateOptions.class);
|
||||
verify(collection).findOneAndUpdate(Mockito.any(), Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void findAndRemoveShouldUseCollationWhenPresent() {
|
||||
|
||||
template.findAndRemove(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class);
|
||||
|
||||
ArgumentCaptor<FindOneAndDeleteOptions> options = ArgumentCaptor.forClass(FindOneAndDeleteOptions.class);
|
||||
verify(collection).findOneAndDelete(Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void findAndRemoveManyShouldUseCollationWhenPresent() {
|
||||
|
||||
template.doRemove("collection-1", new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class);
|
||||
|
||||
ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class);
|
||||
verify(collection).deleteMany(Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void updateOneShouldUseCollationWhenPresent() {
|
||||
|
||||
template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), new Update().set("foo", "bar"),
|
||||
AutogenerateableId.class);
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateOne(Mockito.any(), Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void updateManyShouldUseCollationWhenPresent() {
|
||||
|
||||
template.updateMulti(new BasicQuery("{}").collation(Collation.of("fr")), new Update().set("foo", "bar"),
|
||||
AutogenerateableId.class);
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateMany(Mockito.any(), Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void replaceOneShouldUseCollationWhenPresent() {
|
||||
|
||||
template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), new Update(), AutogenerateableId.class);
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).replaceOne(Mockito.any(), Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void aggregateShouldUseCollationWhenPresent() {
|
||||
|
||||
Aggregation aggregation = newAggregation(project("id"))
|
||||
.withOptions(newAggregationOptions().collation(Collation.of("fr")).build());
|
||||
template.aggregate(aggregation, AutogenerateableId.class, Document.class);
|
||||
|
||||
ArgumentCaptor<Document> cmd = ArgumentCaptor.forClass(Document.class);
|
||||
verify(db).runCommand(cmd.capture(), Mockito.any(Class.class));
|
||||
|
||||
assertThat(cmd.getValue().get("collation", Document.class), equalTo(new Document("locale", "fr")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void mapReduceShouldUseCollationWhenPresent() {
|
||||
|
||||
template.mapReduce("", "", "", MapReduceOptions.options().collation(Collation.of("fr")), AutogenerateableId.class);
|
||||
|
||||
verify(mapReduceIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void geoNearShouldUseCollationWhenPresent() {
|
||||
|
||||
NearQuery query = NearQuery.near(0D, 0D).query(new BasicQuery("{}").collation(Collation.of("fr")));
|
||||
template.geoNear(query, AutogenerateableId.class);
|
||||
|
||||
ArgumentCaptor<Document> cmd = ArgumentCaptor.forClass(Document.class);
|
||||
verify(db).runCommand(cmd.capture(), Mockito.any(Class.class));
|
||||
|
||||
assertThat(cmd.getValue().get("collation", Document.class), equalTo(new Document("locale", "fr")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void groupShouldUseCollationWhenPresent() {
|
||||
|
||||
commandResultDocument.append("retval", Collections.emptySet());
|
||||
template.group("collection-1", GroupBy.key("id").reduceFunction("bar").collation(Collation.of("fr")), AutogenerateableId.class);
|
||||
|
||||
ArgumentCaptor<Document> cmd = ArgumentCaptor.forClass(Document.class);
|
||||
verify(db).runCommand(cmd.capture(), Mockito.any(Class.class));
|
||||
|
||||
assertThat(cmd.getValue().get("group", Document.class).get("collation", Document.class), equalTo(new Document("locale", "fr")));
|
||||
}
|
||||
|
||||
class AutogenerateableId {
|
||||
|
||||
@Id BigInteger id;
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate.QueryCursorPreparer;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.data.mongodb.core.query.Meta;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
@@ -59,6 +60,7 @@ public class QueryCursorPreparerUnitTests {
|
||||
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslatorMock);
|
||||
when(cursor.modifiers(any(Document.class))).thenReturn(cursor);
|
||||
when(cursor.noCursorTimeout(anyBoolean())).thenReturn(cursor);
|
||||
when(cursor.collation(any())).thenReturn(cursor);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-185
|
||||
@@ -132,7 +134,6 @@ public class QueryCursorPreparerUnitTests {
|
||||
assertThat(captor.getValue(), equalTo(new Document("$snapshot", true)));
|
||||
}
|
||||
|
||||
|
||||
@Test // DATAMONGO-1480
|
||||
public void appliesNoCursorTimeoutCorrectly() {
|
||||
|
||||
@@ -143,6 +144,14 @@ public class QueryCursorPreparerUnitTests {
|
||||
verify(cursor).noCursorTimeout(eq(true));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void appliesCollationCorrectly() {
|
||||
|
||||
prepare(new BasicQuery("{}").collation(Collation.of("fr")));
|
||||
|
||||
verify(cursor).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
private FindIterable<Document> prepare(Query query) {
|
||||
|
||||
CursorPreparer preparer = new MongoTemplate(factory).new QueryCursorPreparer(query, null);
|
||||
|
||||
@@ -13,36 +13,60 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.mongodb.core.MongoTemplateUnitTests.AutogenerateableId;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndDeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndUpdateOptions;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.reactivestreams.client.FindPublisher;
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ReactiveMongoTemplate}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||
public class ReactiveMongoTemplateUnitTests {
|
||||
|
||||
ReactiveMongoTemplate template;
|
||||
|
||||
@Mock SimpleReactiveMongoDatabaseFactory factory;
|
||||
@Mock MongoClient mongoClient;
|
||||
@Mock MongoDatabase db;
|
||||
@Mock MongoCollection collection;
|
||||
@Mock FindPublisher findPublisher;
|
||||
@Mock Publisher runCommandPublisher;
|
||||
|
||||
MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
|
||||
MappingMongoConverter converter;
|
||||
@@ -52,10 +76,21 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
public void setUp() {
|
||||
|
||||
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator);
|
||||
when(factory.getMongoDatabase()).thenReturn(db);
|
||||
when(db.getCollection(any())).thenReturn(collection);
|
||||
when(db.getCollection(any(), any())).thenReturn(collection);
|
||||
when(db.runCommand(any(), any(Class.class))).thenReturn(runCommandPublisher);
|
||||
when(collection.find()).thenReturn(findPublisher);
|
||||
when(collection.find(Mockito.any(Document.class))).thenReturn(findPublisher);
|
||||
when(findPublisher.projection(any())).thenReturn(findPublisher);
|
||||
when(findPublisher.limit(anyInt())).thenReturn(findPublisher);
|
||||
when(findPublisher.collation(any())).thenReturn(findPublisher);
|
||||
when(findPublisher.first()).thenReturn(findPublisher);
|
||||
|
||||
this.mappingContext = new MongoMappingContext();
|
||||
this.converter = new MappingMongoConverter(new NoOpDbRefResolver(), mappingContext);
|
||||
this.template = new ReactiveMongoTemplate(factory, converter);
|
||||
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1444
|
||||
@@ -74,4 +109,153 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
assertTrue(ReflectionTestUtils.getField(template, "mongoConverter") instanceof MappingMongoConverter);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void findShouldUseCollationWhenPresent() {
|
||||
|
||||
template.find(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class).subscribe();
|
||||
|
||||
verify(findPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
//
|
||||
@Test // DATAMONGO-1518
|
||||
public void findOneShouldUseCollationWhenPresent() {
|
||||
|
||||
template.findOne(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class).subscribe();
|
||||
|
||||
verify(findPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void existsShouldUseCollationWhenPresent() {
|
||||
|
||||
template.exists(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class).subscribe();
|
||||
|
||||
verify(findPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void findAndModfiyShoudUseCollationWhenPresent() {
|
||||
|
||||
template.findAndModify(new BasicQuery("{}").collation(Collation.of("fr")), new Update(), AutogenerateableId.class)
|
||||
.subscribe();
|
||||
|
||||
ArgumentCaptor<FindOneAndUpdateOptions> options = ArgumentCaptor.forClass(FindOneAndUpdateOptions.class);
|
||||
verify(collection).findOneAndUpdate(Mockito.any(), Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void findAndRemoveShouldUseCollationWhenPresent() {
|
||||
|
||||
template.findAndRemove(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class).subscribe();
|
||||
|
||||
ArgumentCaptor<FindOneAndDeleteOptions> options = ArgumentCaptor.forClass(FindOneAndDeleteOptions.class);
|
||||
verify(collection).findOneAndDelete(Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Ignore("see https://jira.mongodb.org/browse/JAVARS-27")
|
||||
@Test // DATAMONGO-1518
|
||||
public void findAndRemoveManyShouldUseCollationWhenPresent() {
|
||||
|
||||
template.doRemove("collection-1", new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class)
|
||||
.subscribe();
|
||||
|
||||
ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class);
|
||||
// the current mongodb-driver-reactivestreams:1.4.0 driver does not offer deleteMany with options.
|
||||
// verify(collection).deleteMany(Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void updateOneShouldUseCollationWhenPresent() {
|
||||
|
||||
template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), new Update().set("foo", "bar"),
|
||||
AutogenerateableId.class).subscribe();
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateOne(Mockito.any(), Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void updateManyShouldUseCollationWhenPresent() {
|
||||
|
||||
template.updateMulti(new BasicQuery("{}").collation(Collation.of("fr")), new Update().set("foo", "bar"),
|
||||
AutogenerateableId.class).subscribe();
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateMany(Mockito.any(), Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void replaceOneShouldUseCollationWhenPresent() {
|
||||
|
||||
template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), new Update(), AutogenerateableId.class)
|
||||
.subscribe();
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).replaceOne(Mockito.any(), Mockito.any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Ignore("currently no aggregation")
|
||||
@Test // DATAMONGO-1518
|
||||
public void aggregateShouldUseCollationWhenPresent() {
|
||||
|
||||
Aggregation aggregation = newAggregation(project("id"))
|
||||
.withOptions(newAggregationOptions().collation(Collation.of("fr")).build());
|
||||
// template.aggregate(aggregation, AutogenerateableId.class, Document.class).subscribe();
|
||||
|
||||
ArgumentCaptor<Document> cmd = ArgumentCaptor.forClass(Document.class);
|
||||
verify(db).runCommand(cmd.capture(), Mockito.any(Class.class));
|
||||
|
||||
assertThat(cmd.getValue().get("collation", Document.class), equalTo(new Document("locale", "fr")));
|
||||
}
|
||||
|
||||
@Ignore("currently no mapReduce")
|
||||
@Test // DATAMONGO-1518
|
||||
public void mapReduceShouldUseCollationWhenPresent() {
|
||||
|
||||
// template.mapReduce("", "", "", MapReduceOptions.options().collation(Collation.of("fr")),
|
||||
// AutogenerateableId.class).subscribe();
|
||||
//
|
||||
// verify(mapReduceIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1518
|
||||
public void geoNearShouldUseCollationWhenPresent() {
|
||||
|
||||
NearQuery query = NearQuery.near(0D, 0D).query(new BasicQuery("{}").collation(Collation.of("fr")));
|
||||
template.geoNear(query, AutogenerateableId.class).subscribe();
|
||||
|
||||
ArgumentCaptor<Document> cmd = ArgumentCaptor.forClass(Document.class);
|
||||
verify(db).runCommand(cmd.capture(), Mockito.any(Class.class));
|
||||
|
||||
assertThat(cmd.getValue().get("collation", Document.class), equalTo(new Document("locale", "fr")));
|
||||
}
|
||||
|
||||
@Ignore("currently no groupBy")
|
||||
@Test // DATAMONGO-1518
|
||||
public void groupShouldUseCollationWhenPresent() {
|
||||
|
||||
// template.group("collection-1", GroupBy.key("id").reduceFunction("bar").collation(Collation.of("fr")),
|
||||
// AutogenerateableId.class).subscribe();
|
||||
//
|
||||
// ArgumentCaptor<Document> cmd = ArgumentCaptor.forClass(Document.class);
|
||||
// verify(db).runCommand(cmd.capture(), Mockito.any(Class.class));
|
||||
//
|
||||
// assertThat(cmd.getValue().get("group", Document.class).get("collation", Document.class),
|
||||
// equalTo(new Document("locale", "fr")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
import org.bson.Document;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -30,6 +28,7 @@ import org.junit.Test;
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 1.6
|
||||
*/
|
||||
public class AggregationOptionsTests {
|
||||
@@ -49,7 +48,7 @@ public class AggregationOptionsTests {
|
||||
|
||||
assertThat(aggregationOptions.isAllowDiskUse(), is(true));
|
||||
assertThat(aggregationOptions.isExplain(), is(true));
|
||||
assertThat(aggregationOptions.getCursor(), is(new Document("batchSize", 1)));
|
||||
assertThat(aggregationOptions.getCursor().get(), is(new Document("batchSize", 1)));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1637
|
||||
@@ -64,7 +63,7 @@ public class AggregationOptionsTests {
|
||||
|
||||
assertThat(aggregationOptions.isAllowDiskUse(), is(true));
|
||||
assertThat(aggregationOptions.isExplain(), is(true));
|
||||
assertThat(aggregationOptions.getCursor(), is(new Document("batchSize", 1)));
|
||||
assertThat(aggregationOptions.getCursor().get(), is(new Document("batchSize", 1)));
|
||||
assertThat(aggregationOptions.getCursorBatchSize(), is(1));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user