Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import com.mongodb.MongoException;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.CreateIndexOptions;
import com.mongodb.client.model.IndexModel;
import com.mongodb.client.model.IndexOptions;

/**
Expand All @@ -53,6 +55,7 @@ public class DefaultIndexOperations implements IndexOperations {
private final String collectionName;
private final QueryMapper mapper;
private final @Nullable Class<?> type;
private final CreateIndexOptions createIndexOptions;

private final MongoOperations mongoOperations;

Expand Down Expand Up @@ -92,6 +95,7 @@ public DefaultIndexOperations(MongoDatabaseFactory mongoDbFactory, String collec
this.collectionName = collectionName;
this.mapper = queryMapper;
this.type = type;
this.createIndexOptions = new CreateIndexOptions();
this.mongoOperations = new MongoTemplate(mongoDbFactory);
}

Expand All @@ -104,14 +108,30 @@ public DefaultIndexOperations(MongoDatabaseFactory mongoDbFactory, String collec
* @since 2.1
*/
public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName, @Nullable Class<?> type) {
this(mongoOperations, collectionName, type, new CreateIndexOptions());
}

/**
* Creates a new {@link DefaultIndexOperations}.
*
* @param mongoOperations must not be {@literal null}.
* @param collectionName must not be {@literal null} or empty.
* @param type can be {@literal null}.
* @param createIndexOptions must not be {@literal null}.
* @since 4.5
*/
public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName, @Nullable Class<?> type,
CreateIndexOptions createIndexOptions) {

Assert.notNull(mongoOperations, "MongoOperations must not be null");
Assert.hasText(collectionName, "Collection name must not be null or empty");
Assert.notNull(createIndexOptions, "CreateIndexOptions must not be null");

this.mongoOperations = mongoOperations;
this.mapper = new QueryMapper(mongoOperations.getConverter());
this.collectionName = collectionName;
this.type = type;
this.createIndexOptions = createIndexOptions;
}

@Override
Expand All @@ -128,7 +148,8 @@ public String createIndex(IndexDefinition indexDefinition) {
indexOptions = addDefaultCollationIfRequired(indexOptions, entity);

Document mappedKeys = mapper.getMappedSort(indexDefinition.getIndexKeys(), entity);
return collection.createIndex(mappedKeys, indexOptions);
IndexModel indexModel = new IndexModel(mappedKeys, indexOptions);
return collection.createIndexes(List.of(indexModel), createIndexOptions).get(0);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import reactor.core.publisher.Mono;

import java.util.Collection;
import java.util.List;

import org.bson.Document;
import org.jspecify.annotations.Nullable;
Expand All @@ -31,6 +32,8 @@
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;

import com.mongodb.client.model.CreateIndexOptions;
import com.mongodb.client.model.IndexModel;
import com.mongodb.client.model.IndexOptions;

/**
Expand All @@ -48,6 +51,7 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
private final String collectionName;
private final QueryMapper queryMapper;
private final @Nullable Class<?> type;
private final CreateIndexOptions createIndexOptions;

/**
* Creates a new {@link DefaultReactiveIndexOperations}.
Expand All @@ -71,15 +75,32 @@ public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, S
*/
public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName,
QueryMapper queryMapper, @Nullable Class<?> type) {
this(mongoOperations, collectionName, queryMapper, type, new CreateIndexOptions());
}

/**
* Creates a new {@link DefaultReactiveIndexOperations}.
*
* @param mongoOperations must not be {@literal null}.
* @param collectionName must not be {@literal null}.
* @param queryMapper must not be {@literal null}.
* @param type used for mapping potential partial index filter expression, must not be {@literal null}.
* @param createIndexOptions must not be {@literal null}.
* @since 4.5
*/
public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName,
QueryMapper queryMapper, @Nullable Class<?> type, CreateIndexOptions createIndexOptions) {

Assert.notNull(mongoOperations, "ReactiveMongoOperations must not be null");
Assert.notNull(collectionName, "Collection must not be null");
Assert.notNull(queryMapper, "QueryMapper must not be null");
Assert.notNull(createIndexOptions, "CreateIndexOptions must not be null");

this.mongoOperations = mongoOperations;
this.collectionName = collectionName;
this.queryMapper = queryMapper;
this.type = type;
this.createIndexOptions = createIndexOptions;
}

@Override
Expand All @@ -95,7 +116,8 @@ public Mono<String> createIndex(IndexDefinition indexDefinition) {
indexOptions = addPartialFilterIfPresent(indexOptions, indexDefinition.getIndexOptions(), entity);
indexOptions = addDefaultCollationIfRequired(indexOptions, entity);

return collection.createIndex(indexDefinition.getIndexKeys(), indexOptions);
IndexModel indexModel = new IndexModel(indexDefinition.getIndexKeys(), indexOptions);
return collection.createIndexes(List.of(indexModel), createIndexOptions);

}).next();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
package org.springframework.data.mongodb.core;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import java.util.List;

import org.bson.Document;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -37,6 +40,8 @@

import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.CreateIndexOptions;
import com.mongodb.client.model.IndexModel;
import com.mongodb.client.model.IndexOptions;

/**
Expand Down Expand Up @@ -64,7 +69,7 @@ void setUp() {
when(factory.getMongoDatabase()).thenReturn(db);
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator);
when(db.getCollection(any(), any(Class.class))).thenReturn(collection);
when(collection.createIndex(any(), any(IndexOptions.class))).thenReturn("OK");
when(collection.createIndexes(anyList(), any(CreateIndexOptions.class))).thenReturn(List.of("OK"));

this.mappingContext = new MongoMappingContext();
this.converter = spy(new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext));
Expand All @@ -76,29 +81,31 @@ void indexOperationsMapFieldNameCorrectly() {

indexOpsFor(Jedi.class).ensureIndex(new Index("name", Direction.DESC));

verify(collection).createIndex(eq(new Document("firstname", -1)), any());
ArgumentCaptor<List<IndexModel>> captor = ArgumentCaptor.forClass(List.class);
verify(collection).createIndexes(captor.capture(), any(CreateIndexOptions.class));
assertThat(captor.getValue().get(0).getKeys()).isEqualTo(new Document("firstname", -1));
}

@Test // DATAMONGO-1854
void ensureIndexDoesNotSetCollectionIfNoDefaultDefined() {

indexOpsFor(Jedi.class).ensureIndex(new Index("firstname", Direction.DESC));

ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
ArgumentCaptor<List<IndexModel>> captor = ArgumentCaptor.forClass(List.class);
verify(collection).createIndexes(captor.capture(), any(CreateIndexOptions.class));

assertThat(options.getValue().getCollation()).isNull();
assertThat(captor.getValue().get(0).getOptions().getCollation()).isNull();
}

@Test // DATAMONGO-1854
void ensureIndexUsesDefaultCollationIfNoneDefinedInOptions() {

indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC));

ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
ArgumentCaptor<List<IndexModel>> captor = ArgumentCaptor.forClass(List.class);
verify(collection).createIndexes(captor.capture(), any(CreateIndexOptions.class));

assertThat(options.getValue().getCollation())
assertThat(captor.getValue().get(0).getOptions().getCollation())
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build());
}

Expand All @@ -107,10 +114,10 @@ void ensureIndexDoesNotUseDefaultCollationIfExplicitlySpecifiedInTheIndex() {

indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC).collation(Collation.of("en_US")));

ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
ArgumentCaptor<List<IndexModel>> captor = ArgumentCaptor.forClass(List.class);
verify(collection).createIndexes(captor.capture(), any(CreateIndexOptions.class));

assertThat(options.getValue().getCollation())
assertThat(captor.getValue().get(0).getOptions().getCollation())
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("en_US").build());
}

Expand All @@ -119,7 +126,9 @@ void shouldCreateHashedIndexCorrectly() {

indexOpsFor(Jedi.class).ensureIndex(HashedIndex.hashed("name"));

verify(collection).createIndex(eq(new Document("firstname", "hashed")), any());
ArgumentCaptor<List<IndexModel>> captor = ArgumentCaptor.forClass(List.class);
verify(collection).createIndexes(captor.capture(), any(CreateIndexOptions.class));
assertThat(captor.getValue().get(0).getKeys()).isEqualTo(new Document("firstname", "hashed"));
}

@Test // GH-4698
Expand All @@ -131,6 +140,22 @@ void shouldConsiderGivenCollectionName() {
verify(db).getCollection(eq("foo"), any(Class.class));
}

@Test // GH-4422
void shouldPassCreateIndexOptionsToDriver() {

CreateIndexOptions createIndexOptions = new CreateIndexOptions()
.commitQuorum(com.mongodb.CreateIndexCommitQuorum.MAJORITY);

DefaultIndexOperations operations = new DefaultIndexOperations(template, template.getCollectionName(Jedi.class),
Jedi.class, createIndexOptions);

operations.createIndex(new Index("name", Direction.DESC));

ArgumentCaptor<CreateIndexOptions> optionsCaptor = ArgumentCaptor.forClass(CreateIndexOptions.class);
verify(collection).createIndexes(anyList(), optionsCaptor.capture());
assertThat(optionsCaptor.getValue()).isSameAs(createIndexOptions);
}

private DefaultIndexOperations indexOpsFor(Class<?> type) {
return new DefaultIndexOperations(template, template.getCollectionName(type), type);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@
package org.springframework.data.mongodb.core;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;

import org.bson.Document;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -38,6 +42,8 @@
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Collation;

import com.mongodb.client.model.CreateIndexOptions;
import com.mongodb.client.model.IndexModel;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.reactivestreams.client.MongoCollection;
import com.mongodb.reactivestreams.client.MongoDatabase;
Expand Down Expand Up @@ -66,7 +72,7 @@ void setUp() {
when(factory.getMongoDatabase()).thenReturn(Mono.just(db));
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator);
when(db.getCollection(any(), any(Class.class))).thenReturn(collection);
when(collection.createIndex(any(), any(IndexOptions.class))).thenReturn(publisher);
when(collection.createIndexes(anyList(), any(CreateIndexOptions.class))).thenReturn(Flux.just("OK"));

this.mappingContext = new MongoMappingContext();
this.converter = spy(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext));
Expand All @@ -78,21 +84,21 @@ void ensureIndexDoesNotSetCollectionIfNoDefaultDefined() {

indexOpsFor(Jedi.class).ensureIndex(new Index("firstname", Direction.DESC)).subscribe();

ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
ArgumentCaptor<List<IndexModel>> captor = ArgumentCaptor.forClass(List.class);
verify(collection).createIndexes(captor.capture(), any(CreateIndexOptions.class));

assertThat(options.getValue().getCollation()).isNull();
assertThat(captor.getValue().get(0).getOptions().getCollation()).isNull();
}

@Test // DATAMONGO-1854
void ensureIndexUsesDefaultCollationIfNoneDefinedInOptions() {

indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC)).subscribe();

ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
ArgumentCaptor<List<IndexModel>> captor = ArgumentCaptor.forClass(List.class);
verify(collection).createIndexes(captor.capture(), any(CreateIndexOptions.class));

assertThat(options.getValue().getCollation())
assertThat(captor.getValue().get(0).getOptions().getCollation())
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build());
}

Expand All @@ -102,13 +108,30 @@ void ensureIndexDoesNotUseDefaultCollationIfExplicitlySpecifiedInTheIndex() {
indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC).collation(Collation.of("en_US")))
.subscribe();

ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
ArgumentCaptor<List<IndexModel>> captor = ArgumentCaptor.forClass(List.class);
verify(collection).createIndexes(captor.capture(), any(CreateIndexOptions.class));

assertThat(options.getValue().getCollation())
assertThat(captor.getValue().get(0).getOptions().getCollation())
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("en_US").build());
}

@Test // GH-4422
void shouldPassCreateIndexOptionsToDriver() {

CreateIndexOptions createIndexOptions = new CreateIndexOptions()
.commitQuorum(com.mongodb.CreateIndexCommitQuorum.MAJORITY);

DefaultReactiveIndexOperations operations = new DefaultReactiveIndexOperations(template,
template.getCollectionName(Jedi.class), new QueryMapper(template.getConverter()), Jedi.class,
createIndexOptions);

operations.ensureIndex(new Index("firstname", Direction.DESC)).subscribe();

ArgumentCaptor<CreateIndexOptions> optionsCaptor = ArgumentCaptor.forClass(CreateIndexOptions.class);
verify(collection).createIndexes(anyList(), optionsCaptor.capture());
assertThat(optionsCaptor.getValue()).isSameAs(createIndexOptions);
}

private DefaultReactiveIndexOperations indexOpsFor(Class<?> type) {
return new DefaultReactiveIndexOperations(template, template.getCollectionName(type),
new QueryMapper(template.getConverter()), type);
Expand Down