+ * Using this annotation will wait up to {@code 60 seconds} for the search index to become available.
+ *
* @author Christoph Strobl
+ * @since 4.5.3
+ * @see Tag
*/
-@Target({ ElementType.TYPE, ElementType.METHOD })
+@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Tag("vector-search")
@ExtendWith(MongoServerCondition.class)
public @interface EnableIfVectorSearchAvailable {
+ /**
+ * @return the name of the collection used to run the {@literal $listSearchIndexes} aggregation.
+ */
+ String collectionName() default "";
+
+ /**
+ * @return the type for resolving the name of the collection used to run the {@literal $listSearchIndexes}
+ * aggregation. The {@link #collectionName()} has precedence over the type.
+ */
+ Class> collection() default Object.class;
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoClientExtension.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoClientExtension.java
index 357a87168e..6e2c694ed7 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoClientExtension.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoClientExtension.java
@@ -47,7 +47,7 @@
* @see Client
* @see ReplSetClient
*/
-public class MongoClientExtension implements Extension, BeforeAllCallback, AfterAllCallback, ParameterResolver {
+class MongoClientExtension implements Extension, BeforeAllCallback, AfterAllCallback, ParameterResolver {
private static final Log LOGGER = LogFactory.getLog(MongoClientExtension.class);
@@ -157,7 +157,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte
return getMongoClient(parameterType, extensionContext, replSet);
}
- static class SyncClientHolder implements Store.CloseableResource {
+ static class SyncClientHolder implements AutoCloseable {
final MongoClient client;
@@ -175,7 +175,7 @@ public void close() {
}
}
- static class ReactiveClientHolder implements Store.CloseableResource {
+ static class ReactiveClientHolder implements AutoCloseable {
final com.mongodb.reactivestreams.client.MongoClient client;
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoExtensions.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoExtensions.java
index c90f7e999b..864bb6aa5d 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoExtensions.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoExtensions.java
@@ -31,7 +31,7 @@ static class Client {
static final String REACTIVE_REPLSET_KEY = "mongo.client.replset.reactive";
}
- static class Termplate {
+ static class Template {
static final Namespace NAMESPACE = Namespace.create(MongoTemplateExtension.class);
static final String SYNC = "mongo.template.sync";
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoServerCondition.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoServerCondition.java
index d811e0a1ef..a1536d01d2 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoServerCondition.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoServerCondition.java
@@ -15,17 +15,26 @@
*/
package org.springframework.data.mongodb.test.util;
+import java.time.Duration;
+
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.data.mongodb.MongoCollectionUtils;
import org.springframework.data.util.Version;
+import org.springframework.util.NumberUtils;
+import org.springframework.util.StringUtils;
+import org.testcontainers.shaded.org.awaitility.Awaitility;
+
+import com.mongodb.Function;
+import com.mongodb.client.MongoClient;
/**
* @author Christoph Strobl
*/
-public class MongoServerCondition implements ExecutionCondition {
+class MongoServerCondition implements ExecutionCondition {
private static final Namespace NAMESPACE = Namespace.create("mongodb", "server");
@@ -42,10 +51,15 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
}
}
- if(context.getTags().contains("vector-search")) {
- if(!atlasEnvironment(context)) {
+ if (context.getTags().contains("vector-search")) {
+
+ if (!atlasEnvironment(context)) {
return ConditionEvaluationResult.disabled("Disabled for servers not supporting Vector Search.");
}
+
+ if (!isSearchIndexAvailable(context)) {
+ return ConditionEvaluationResult.disabled("Search index unavailable.");
+ }
}
if (context.getTags().contains("version-specific") && context.getElement().isPresent()) {
@@ -90,8 +104,55 @@ private Version serverVersion(ExtensionContext context) {
Version.class);
}
+ private boolean isSearchIndexAvailable(ExtensionContext context) {
+
+ EnableIfVectorSearchAvailable vectorSearchAvailable = AnnotatedElementUtils
+ .findMergedAnnotation(context.getElement().get(), EnableIfVectorSearchAvailable.class);
+
+ if (vectorSearchAvailable == null) {
+ return true;
+ }
+
+ String collectionName = StringUtils.hasText(vectorSearchAvailable.collectionName())
+ ? vectorSearchAvailable.collectionName()
+ : MongoCollectionUtils.getPreferredCollectionName(vectorSearchAvailable.collection());
+
+ return context.getStore(NAMESPACE).getOrComputeIfAbsent("search-index-%s-available".formatted(collectionName),
+ (key) -> {
+ try {
+ doWithClient(client -> {
+ Awaitility.await().atMost(Duration.ofSeconds(60)).pollInterval(Duration.ofMillis(200)).until(() -> {
+ return MongoTestUtils.isSearchIndexReady(client, null, collectionName);
+ });
+ return "done waiting for search index";
+ });
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }, Boolean.class);
+
+ }
+
private boolean atlasEnvironment(ExtensionContext context) {
- return context.getStore(NAMESPACE).getOrComputeIfAbsent(Version.class, (key) -> MongoTestUtils.isVectorSearchEnabled(),
- Boolean.class);
+
+ return context.getStore(NAMESPACE).getOrComputeIfAbsent("mongodb-atlas",
+ (key) -> doWithClient(MongoTestUtils::isVectorSearchEnabled), Boolean.class);
+ }
+
+ private