DATAMONGO-2365 - Pass on index name to query hint.

Original pull request: #828.
This commit is contained in:
Christoph Strobl
2020-01-21 13:25:25 +01:00
committed by Mark Paluch
parent c21b35973d
commit 0bc0fff24e
7 changed files with 118 additions and 10 deletions

View File

@@ -114,6 +114,7 @@ import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
import org.springframework.data.mongodb.core.validation.Validator;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.util.CloseableIterator;
import org.springframework.data.util.Optionals;
@@ -1156,7 +1157,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
options.skip((int) query.getSkip());
}
if (StringUtils.hasText(query.getHint())) {
options.hint(Document.parse(query.getHint()));
String hint = query.getHint();
if(BsonUtils.isJsonDocument(hint)) {
options = options.hint(BsonUtils.parse(hint, mongoDbFactory));
} else {
options = options.hintString(hint);
}
}
Document document = queryMapper.getMappedObject(query.getQueryObject(),
@@ -3276,7 +3283,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
if (StringUtils.hasText(query.getHint())) {
cursorToUse = cursorToUse.hint(Document.parse(query.getHint()));
String hint = query.getHint();
if(BsonUtils.isJsonDocument(hint)) {
cursorToUse = cursorToUse.hint(BsonUtils.parse(hint, mongoDbFactory));
} else {
cursorToUse = cursorToUse.hintString(hint);
}
}
if (meta.hasValues()) {

View File

@@ -21,6 +21,7 @@ import com.mongodb.client.result.InsertOneResult;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.data.mongodb.util.BsonUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
@@ -1279,7 +1280,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
options.skip((int) query.getSkip());
}
if (StringUtils.hasText(query.getHint())) {
options.hint(Document.parse(query.getHint()));
String hint = query.getHint();
if(BsonUtils.isJsonDocument(hint)) {
options = options.hint(BsonUtils.parse(hint, mongoDatabaseFactory));
} else {
options = options.hintString(hint);
}
}
operations.forType(entityClass).getCollation(query).map(Collation::toMongoCollation) //
@@ -3276,7 +3283,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
if (StringUtils.hasText(query.getHint())) {
findPublisherToUse = findPublisherToUse.hint(Document.parse(query.getHint()));
String hint = query.getHint();
if(BsonUtils.isJsonDocument(hint)) {
findPublisherToUse = findPublisherToUse.hint(BsonUtils.parse(hint, mongoDatabaseFactory));
} else {
findPublisherToUse = findPublisherToUse.hintString(hint);
}
}
if (meta.hasValues()) {

View File

@@ -141,14 +141,13 @@ public class Query {
}
/**
* Configures the query to use the given hint when being executed. {@code hint} is parsed as {@link Document}.
* Configures the query to use the given hint when being executed. The {@code hint} can either be an index name or a
* json {@link Document} representation.
*
* @param hint must not be {@literal null} or empty.
* @return
* @see Document#parse(String)
* @deprecated since 2.2, use {@link #withHint(Document)}
*/
@Deprecated
public Query withHint(String hint) {
Assert.hasText(hint, "Hint must not be empty or null!");
@@ -312,10 +311,8 @@ public class Query {
/**
* @return
* @deprecated since 2.2. Return type to be changed to {@link Document}.
*/
@Nullable
@Deprecated
public String getHint() {
return hint;
}

View File

@@ -25,11 +25,13 @@ import java.util.stream.StreamSupport;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.codecs.DocumentCodec;
import org.bson.conversions.Bson;
import org.bson.json.JsonParseException;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.CodecRegistryProvider;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -175,6 +177,50 @@ public class BsonUtils {
}
}
/**
* Check if a given String looks like {@link Document#parse(String) parsable} json.
*
* @param value can be {@literal null}.
* @return {@literal true} if the given value looks like a json document.
* @since 3.0
*/
public static boolean isJsonDocument(@Nullable String value) {
return StringUtils.hasText(value) && (value.startsWith("{") && value.endsWith("}"));
}
/**
* Check if a given String looks like {@link org.bson.BsonArray#parse(String) parsable} json array.
*
* @param value can be {@literal null}.
* @return {@literal true} if the given value looks like a json array.
* @since 3.0
*/
public static boolean isJsonArray(@Nullable String value) {
return StringUtils.hasText(value) && (value.startsWith("[") && value.endsWith("]"));
}
/**
* Parse the given {@literal json} to {@link Document} applying transformations as specified by a potentially given
* {@link org.bson.codecs.Codec}.
*
* @param json must not be {@literal null}.
* @param codecRegistryProvider can be {@literal null}. In that case the default {@link DocumentCodec} is used.
* @return never {@literal null}.
* @throws IllegalArgumentException if the required argument is {@literal null}.
* @since 3.0
*/
public static Document parse(String json, @Nullable CodecRegistryProvider codecRegistryProvider) {
Assert.notNull(json, "Json must not be null!");
if (codecRegistryProvider == null) {
return Document.parse(json);
}
return Document.parse(json, codecRegistryProvider.getCodecFor(Document.class)
.orElseGet(() -> new DocumentCodec(codecRegistryProvider.getCodecRegistry())));
}
@Nullable
private static String toJson(@Nullable Object value) {

View File

@@ -19,6 +19,7 @@ import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import static org.springframework.data.mongodb.test.util.Assertions.*;
import com.mongodb.MongoClientSettings;
import lombok.Data;
import java.math.BigInteger;
@@ -158,6 +159,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
when(findIterable.iterator()).thenReturn(cursor);
when(factory.getMongoDatabase()).thenReturn(db);
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator);
when(factory.getCodecRegistry()).thenReturn(MongoClientSettings.getDefaultCodecRegistry());
when(db.getCollection(any(String.class), eq(Document.class))).thenReturn(collection);
when(db.runCommand(any(), any(Class.class))).thenReturn(commandResultDocument);
when(collection.find(any(org.bson.Document.class), any(Class.class))).thenReturn(findIterable);
@@ -955,6 +957,17 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
assertThat(options.getValue().getHint()).isEqualTo(queryHint);
}
@Test // DATAMONGO-2365
public void countShouldApplyQueryHintAsIndexNameIfPresent() {
template.count(new BasicQuery("{}").withHint("idx-1"), AutogenerateableId.class);
ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
verify(collection).countDocuments(any(), options.capture());
assertThat(options.getValue().getHintString()).isEqualTo("idx-1");
}
@Test // DATAMONGO-1733
public void appliesFieldsWhenInterfaceProjectionIsClosedAndQueryDoesNotDefineFields() {

View File

@@ -21,6 +21,7 @@ import static org.springframework.data.mongodb.core.query.Query.*;
import java.util.concurrent.TimeUnit;
import com.mongodb.MongoClientSettings;
import org.bson.Document;
import org.junit.Before;
import org.junit.Test;
@@ -53,6 +54,7 @@ public class QueryCursorPreparerUnitTests {
public void setUp() {
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslatorMock);
when(factory.getCodecRegistry()).thenReturn(MongoClientSettings.getDefaultCodecRegistry());
when(cursor.batchSize(anyInt())).thenReturn(cursor);
when(cursor.comment(anyString())).thenReturn(cursor);
when(cursor.maxTime(anyLong(), any())).thenReturn(cursor);
@@ -70,6 +72,15 @@ public class QueryCursorPreparerUnitTests {
verify(cursor).hint(new Document("age", 1));
}
@Test // DATAMONGO-2365
public void appliesIndexNameAsHintCorrectly() {
Query query = query(where("foo").is("bar")).withHint("idx-1");
prepare(query);
verify(cursor).hintString("idx-1");
}
@Test // DATAMONGO-2319
public void appliesDocumentHintsCorrectly() {

View File

@@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import com.mongodb.MongoClientSettings;
import lombok.Data;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -128,6 +129,7 @@ public class ReactiveMongoTemplateUnitTests {
public void beforeEach() {
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator);
when(factory.getCodecRegistry()).thenReturn(MongoClientSettings.getDefaultCodecRegistry());
when(factory.getMongoDatabase()).thenReturn(db);
when(db.getCollection(any())).thenReturn(collection);
when(db.getCollection(any(), any())).thenReturn(collection);
@@ -434,6 +436,17 @@ public class ReactiveMongoTemplateUnitTests {
assertThat(options.getValue().getHint()).isEqualTo(queryHint);
}
@Test // DATAMONGO-2365
public void countShouldApplyQueryHintAsIndexNameIfPresent() {
template.count(new Query().withHint("idx-1"), Person.class, "star-wars").subscribe();
ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
verify(collection).countDocuments(any(), options.capture());
assertThat(options.getValue().getHintString()).isEqualTo("idx-1");
}
@Test // DATAMONGO-2215
public void updateShouldApplyArrayFilters() {