From d73807df1b5f6a8f17a9d98258957a3d4335c1a2 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 16 Feb 2023 11:52:37 +0100 Subject: [PATCH] Support ReadConcern and ReadPreference via NearQuery. Implement ReadConcernAware and ReadPreferenceAware for NearQuery and make sure those get applied when working with the template API. Original Pull Request: #4288 --- .../data/mongodb/core/MongoTemplate.java | 9 +- .../mongodb/core/ReactiveMongoTemplate.java | 14 +++- .../data/mongodb/core/query/NearQuery.java | 82 +++++++++++++++++-- .../mongodb/core/MongoTemplateUnitTests.java | 15 +++- .../core/ReactiveMongoTemplateUnitTests.java | 23 ++++++ .../core/query/NearQueryUnitTests.java | 58 +++++++++++++ 6 files changed, 188 insertions(+), 13 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 2afbcd0fe..b88b6d6b5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -948,10 +948,13 @@ public class MongoTemplate String distanceField = operations.nearQueryDistanceFieldName(domainType); Builder optionsBuilder = AggregationOptions.builder().collation(near.getCollation()); - Query query = near.getQuery(); - if (query != null && query.hasReadPreference()) { - optionsBuilder.readPreference(query.getReadPreference()); + if (near.hasReadPreference()) { + optionsBuilder.readPreference(near.getReadPreference()); + } + + if(near.hasReadConcern()) { + optionsBuilder.readConcern(near.getReadConcern()); } Aggregation $geoNear = TypedAggregation.newAggregation(domainType, Aggregation.geoNear(near, distanceField)) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java index 1eb27c652..d49dffafe 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java @@ -81,6 +81,7 @@ import org.springframework.data.mongodb.core.QueryOperations.UpdateContext; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; import org.springframework.data.mongodb.core.aggregation.AggregationOptions; +import org.springframework.data.mongodb.core.aggregation.AggregationOptions.Builder; import org.springframework.data.mongodb.core.aggregation.AggregationPipeline; import org.springframework.data.mongodb.core.aggregation.PrefixingDelegatingAggregationOperationContext; import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext; @@ -998,8 +999,19 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati GeoNearResultDocumentCallback callback = new GeoNearResultDocumentCallback<>(distanceField, new ProjectingReadCallback<>(mongoConverter, projection, collection), near.getMetric()); + Builder optionsBuilder = AggregationOptions.builder(); + if (near.hasReadPreference()) { + optionsBuilder.readPreference(near.getReadPreference()); + } + + if(near.hasReadConcern()) { + optionsBuilder.readConcern(near.getReadConcern()); + } + + optionsBuilder.collation(near.getCollation()); + Aggregation $geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, distanceField)) - .withOptions(AggregationOptions.builder().collation(near.getCollation()).build()); + .withOptions(optionsBuilder.build()); return aggregate($geoNear, collection, Document.class) // .concatMap(callback::doWith); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java index 0f5bd3d87..94fc854e9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java @@ -24,11 +24,16 @@ import org.springframework.data.geo.Distance; import org.springframework.data.geo.Metric; import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.ReadConcernAware; +import org.springframework.data.mongodb.core.ReadPreferenceAware; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; +import com.mongodb.ReadConcern; +import com.mongodb.ReadPreference; + /** * Builder class to build near-queries.
* MongoDB {@code $geoNear} operator allows usage of a {@literal GeoJSON Point} or legacy coordinate pair. Though @@ -171,7 +176,7 @@ import org.springframework.util.ObjectUtils; * @author Christoph Strobl * @author Mark Paluch */ -public final class NearQuery { +public final class NearQuery implements ReadConcernAware, ReadPreferenceAware { private final Point point; private @Nullable Query query; @@ -181,6 +186,8 @@ public final class NearQuery { private boolean spherical; private @Nullable Long limit; private @Nullable Long skip; + private @Nullable ReadConcern readConcern; + private @Nullable ReadPreference readPreference; /** * Creates a new {@link NearQuery}. @@ -536,11 +543,6 @@ public final class NearQuery { return this; } - @Nullable - public Query getQuery() { - return query; - } - /** * @return the number of elements to skip. */ @@ -560,6 +562,74 @@ public final class NearQuery { return query != null ? query.getCollation().orElse(null) : null; } + /** + * Configures the query to use the given {@link ReadConcern} unless the underlying {@link #query(Query)} + * {@link Query#hasReadConcern() specifies} another one. + * + * @param readConcern must not be {@literal null}. + * @return this. + * @since 4.1 + */ + public NearQuery withReadConcern(ReadConcern readConcern) { + + Assert.notNull(readConcern, "ReadConcern must not be null"); + this.readConcern = readConcern; + return this; + } + + /** + * Configures the query to use the given {@link ReadPreference} unless the underlying {@link #query(Query)} + * {@link Query#hasReadPreference() specifies} another one. + * + * @param readPreference must not be {@literal null}. + * @return this. + * @since 4.1 + */ + public NearQuery withReadPreference(ReadPreference readPreference) { + + Assert.notNull(readPreference, "ReadPreference must not be null"); + this.readPreference = readPreference; + return this; + } + + /** + * Get the {@link ReadConcern} to use. Will return the underlying {@link #query(Query) queries} + * {@link Query#getReadConcern() ReadConcern} if present or the one defined on the {@link NearQuery#readConcern} + * itself. + * + * @return can be {@literal null} if none set. + * @since 4.1 + * @see ReadConcernAware + */ + @Nullable + @Override + public ReadConcern getReadConcern() { + + if (query != null && query.hasReadConcern()) { + return query.getReadConcern(); + } + return readConcern; + } + + /** + * Get the {@link ReadPreference} to use. Will return the underlying {@link #query(Query) queries} + * {@link Query#getReadPreference() ReadPreference} if present or the one defined on the + * {@link NearQuery#readPreference} itself. + * + * @return can be {@literal null} if none set. + * @since 4.1 + * @see ReadPreferenceAware + */ + @Nullable + @Override + public ReadPreference getReadPreference() { + + if (query != null && query.hasReadPreference()) { + return query.getReadPreference(); + } + return readPreference; + } + /** * Returns the {@link Document} built by the {@link NearQuery}. * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java index ed4064a12..3f4a63232 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java @@ -583,15 +583,24 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { void geoNearShouldHonorReadPreferenceFromQuery() { NearQuery query = NearQuery.near(new Point(1, 1)); + query.withReadPreference(ReadPreference.secondary()); - Query inner = new Query(); - inner.withReadPreference(ReadPreference.secondary()); - query.query(inner); template.geoNear(query, Wrapper.class); verify(collection).withReadPreference(eq(ReadPreference.secondary())); } + @Test // GH-4277 + void geoNearShouldHonorReadConcernFromQuery() { + + NearQuery query = NearQuery.near(new Point(1, 1)); + query.withReadConcern(ReadConcern.SNAPSHOT); + + template.geoNear(query, Wrapper.class); + + verify(collection).withReadConcern(eq(ReadConcern.SNAPSHOT)); + } + @Test // DATAMONGO-1166, DATAMONGO-2264 void geoNearShouldIgnoreReadPreferenceWhenNotSet() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java index 962685890..ec4e9e90f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java @@ -59,6 +59,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.support.StaticApplicationContext; import org.springframework.data.annotation.Id; +import org.springframework.data.geo.Point; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; import org.springframework.data.mapping.context.MappingContext; @@ -384,6 +385,28 @@ public class ReactiveMongoTemplateUnitTests { verify(aggregatePublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build())); } + @Test // GH-4277 + void geoNearShouldHonorReadPreferenceFromQuery() { + + NearQuery query = NearQuery.near(new Point(1, 1)); + query.withReadPreference(ReadPreference.secondary()); + + template.geoNear(query, Wrapper.class).subscribe(); + + verify(collection).withReadPreference(eq(ReadPreference.secondary())); + } + + @Test // GH-4277 + void geoNearShouldHonorReadConcernFromQuery() { + + NearQuery query = NearQuery.near(new Point(1, 1)); + query.withReadConcern(ReadConcern.SNAPSHOT); + + template.geoNear(query, Wrapper.class).subscribe(); + + verify(collection).withReadConcern(eq(ReadConcern.SNAPSHOT)); + } + @Test // DATAMONGO-1719 void appliesFieldsWhenInterfaceProjectionIsClosedAndQueryDoesNotDefineFields() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java index a6d233bed..9993e1900 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java @@ -29,6 +29,10 @@ import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.DocumentTestUtils; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; +import org.springframework.test.util.ReflectionTestUtils; + +import com.mongodb.ReadConcern; +import com.mongodb.ReadPreference; /** * Unit tests for {@link NearQuery}. @@ -229,4 +233,58 @@ public class NearQueryUnitTests { assertThat(query.toDocument()).containsEntry("maxDistance", 1000D).containsEntry("distanceMultiplier", 0.00062137D); } + + @Test // GH-4277 + void fetchesReadPreferenceFromUnderlyingQueryObject() { + + NearQuery nearQuery = NearQuery.near(new Point(0, 0)) + .query(new Query().withReadPreference(ReadPreference.nearest())); + + assertThat(nearQuery.getReadPreference()).isEqualTo(ReadPreference.nearest()); + } + + @Test // GH-4277 + void fetchesReadConcernFromUnderlyingQueryObject() { + + NearQuery nearQuery = NearQuery.near(new Point(0, 0)).query(new Query().withReadConcern(ReadConcern.SNAPSHOT)); + + assertThat(nearQuery.getReadConcern()).isEqualTo(ReadConcern.SNAPSHOT); + } + + @Test // GH-4277 + void usesReadPreferenceFromNearQueryIfUnderlyingQueryDoesNotDefineAny() { + + NearQuery nearQuery = NearQuery.near(new Point(0, 0)).withReadPreference(ReadPreference.nearest()) + .query(new Query()); + + assertThat(((Query) ReflectionTestUtils.getField(nearQuery, "query")).getReadPreference()).isNull(); + assertThat(nearQuery.getReadPreference()).isEqualTo(ReadPreference.nearest()); + } + + @Test // GH-4277 + void usesReadConcernFromNearQueryIfUnderlyingQueryDoesNotDefineAny() { + + NearQuery nearQuery = NearQuery.near(new Point(0, 0)).withReadConcern(ReadConcern.SNAPSHOT).query(new Query()); + + assertThat(((Query) ReflectionTestUtils.getField(nearQuery, "query")).getReadConcern()).isNull(); + assertThat(nearQuery.getReadConcern()).isEqualTo(ReadConcern.SNAPSHOT); + } + + @Test // GH-4277 + void readPreferenceFromUnderlyingQueryOverridesNearQueryOne() { + + NearQuery nearQuery = NearQuery.near(new Point(0, 0)).withReadPreference(ReadPreference.nearest()) + .query(new Query().withReadPreference(ReadPreference.primary())); + + assertThat(nearQuery.getReadPreference()).isEqualTo(ReadPreference.primary()); + } + + @Test // GH-4277 + void readConcernFromUnderlyingQueryOverridesNearQueryOne() { + + NearQuery nearQuery = NearQuery.near(new Point(0, 0)).withReadConcern(ReadConcern.SNAPSHOT) + .query(new Query().withReadConcern(ReadConcern.MAJORITY)); + + assertThat(nearQuery.getReadConcern()).isEqualTo(ReadConcern.MAJORITY); + } }