From 94f588f6bd6e8927cad9e4155f950919a6bb5227 Mon Sep 17 00:00:00 2001 From: axgiri <146159445+axgiri@users.noreply.github.com> Date: Sun, 24 May 2026 13:05:14 +0200 Subject: [PATCH 1/5] sync indexes by key and drop by name --- .../core/api/service/mongo/MongoService.java | 213 ++++++++++-------- 1 file changed, 125 insertions(+), 88 deletions(-) diff --git a/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java b/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java index 03187b0e2..1b01c3f91 100644 --- a/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java +++ b/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java @@ -8,6 +8,7 @@ import com.mongodb.client.ClientSession; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoIterable; import com.mongodb.client.model.IndexOptions; import jakarta.inject.Inject; import jakarta.validation.Validation; @@ -16,13 +17,20 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.bson.BsonDocument; +import org.bson.Document; import org.bson.conversions.Bson; import org.eclipse.microprofile.config.inject.ConfigProperty; import tech.ebp.oqm.core.api.model.collectionStats.CollectionStats; import tech.ebp.oqm.core.api.model.object.MainObject; import tech.ebp.oqm.core.api.model.rest.search.SearchObject; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; /** * This is the main mongo class. It specifies top level, commonly shared utilities. @@ -32,92 +40,121 @@ */ @Slf4j public abstract class MongoService, V extends CollectionStats> { - - //TODO:: move to constructor/inject? Remove? - protected static final Validator VALIDATOR; - - static { - try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) { - VALIDATOR = validatorFactory.getValidator(); - } - } - - /** - * Gets the default transaction options to use for client sessions. - * @return The default transaction options. - */ - public static TransactionOptions getDefaultTransactionOptions() { - return TransactionOptions.builder() - .readPreference(ReadPreference.primary()) - .readConcern(ReadConcern.LOCAL) - .writeConcern(WriteConcern.MAJORITY) - .build(); - } - - public ClientSession getNewClientSession(boolean startTransaction) { - ClientSession clientSession = this.getMongoClient().startSession(); - - if(startTransaction){ - clientSession.startTransaction(); - } - - return clientSession; - } - - public ClientSession getNewClientSession() { - return this.getNewClientSession(false); - } - - /** - * The default collection name to use when getting the collection. - * @param clazz The class to get the collection of - * @return The collection name to use when getting the collection. - */ - public static String getCollectionNameFromClass(Class clazz) { - return clazz.getSimpleName(); - } - - /** - * The class this collection is in charge of. Used for logging, and other fun. - */ - @Getter - protected final Class clazz; - - /** - * The MongoDb client. - */ - @Inject - @Getter(AccessLevel.PROTECTED) - MongoClient mongoClient; - - /** - * Mapper to help deal with json updates. - */ - @Inject - @Getter(AccessLevel.PROTECTED) - ObjectMapper objectMapper; - - /** - * The name of the database to access - */ - @Getter - @ConfigProperty(name = "quarkus.mongodb.database") - String databasePrefix; - - public MongoService(Class clazz) { - this.clazz = clazz; - } - - public abstract int getCurrentSchemaVersion(); - - public abstract List getDbIndexes(); - - public abstract void initDb(); - - protected static void setupIndexes(MongoCollection collection, List indexes) { - IndexOptions options = new IndexOptions().background(true); - for (Bson index : indexes) { - collection.createIndex(index, options); - } - } + + // TODO:: move to constructor/inject? Remove? + protected static final Validator VALIDATOR; + + static { + try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) { + VALIDATOR = validatorFactory.getValidator(); + } + } + + /** + * Gets the default transaction options to use for client sessions. + * + * @return The default transaction options. + */ + public static TransactionOptions getDefaultTransactionOptions() { + return TransactionOptions.builder().readPreference(ReadPreference.primary()).readConcern(ReadConcern.LOCAL).writeConcern(WriteConcern.MAJORITY).build(); + } + + public ClientSession getNewClientSession(boolean startTransaction) { + ClientSession clientSession = this.getMongoClient().startSession(); + + if (startTransaction) { + clientSession.startTransaction(); + } + + return clientSession; + } + + public ClientSession getNewClientSession() { + return this.getNewClientSession(false); + } + + /** + * The default collection name to use when getting the collection. + * + * @param clazz The class to get the collection of + * @return The collection name to use when getting the collection. + */ + public static String getCollectionNameFromClass(Class clazz) { + return clazz.getSimpleName(); + } + + /** + * The class this collection is in charge of. Used for logging, and other fun. + */ + @Getter + protected final Class clazz; + + /** + * The MongoDb client. + */ + @Inject + @Getter(AccessLevel.PROTECTED) + MongoClient mongoClient; + + /** + * Mapper to help deal with json updates. + */ + @Inject + @Getter(AccessLevel.PROTECTED) + ObjectMapper objectMapper; + + /** + * The name of the database to access + */ + @Getter + @ConfigProperty(name = "quarkus.mongodb.database") + String databasePrefix; + + public MongoService(Class clazz) { + this.clazz = clazz; + } + + public abstract int getCurrentSchemaVersion(); + + public abstract List getDbIndexes(); + + public abstract void initDb(); + + protected static void setupIndexes(MongoCollection collection, List indexes) { + IndexOptions options = new IndexOptions().background(true); + Map existingIndexes = getExistingIndexes(collection); + Set expectedKeys = new HashSet<>(); + + for (Bson index : indexes) { + BsonDocument expectedKey = index.toBsonDocument(BsonDocument.class, collection.getCodecRegistry()); + expectedKeys.add(expectedKey); + if (!existingIndexes.containsKey(expectedKey)) { + collection.createIndex(index, options); + } + } + + for (Map.Entry existing : existingIndexes.entrySet()) { + String existingName = existing.getValue(); + if (!"_id_".equals(existingName) && !expectedKeys.contains(existing.getKey())) { + collection.dropIndex(existingName); + } + } + } + + private static Map getExistingIndexes(MongoCollection collection) { + MongoIterable existingDocument = collection.listIndexes(); + Map existingIndexes = new HashMap<>(); + for (Document doc : existingDocument) { + Document keyDoc = doc.get("key", Document.class); + if (keyDoc == null) { + continue; + } + BsonDocument key = keyDoc.toBsonDocument(BsonDocument.class, collection.getCodecRegistry()); + String name = doc.getString("name"); + if (name != null) { + existingIndexes.put(key, name); + } + } + return existingIndexes; + } } From 85ea6939766165e1df10a928c6a9ecd8a5db2deb Mon Sep 17 00:00:00 2001 From: axgiri <146159445+axgiri@users.noreply.github.com> Date: Sun, 24 May 2026 20:48:12 +0200 Subject: [PATCH 2/5] feat: javaDoc --- .../core/api/service/mongo/MongoService.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java b/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java index 1b01c3f91..aa426a04c 100644 --- a/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java +++ b/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java @@ -120,6 +120,14 @@ public MongoService(Class clazz) { public abstract void initDb(); + /** + * Sets up indexes for the given MongoDB collection. + * Creates any missing indexes and drops indexes that are no longer expected. + * The default {@code _id_} index is never dropped. + * + * @param collection the MongoDB collection to manage indexes on + * @param indexes the list of indexes that should exist on the collection + */ protected static void setupIndexes(MongoCollection collection, List indexes) { IndexOptions options = new IndexOptions().background(true); Map existingIndexes = getExistingIndexes(collection); @@ -141,6 +149,26 @@ protected static void setupIndexes(MongoCollection collection, List ind } } + /** + * Returns a map of existing indexes on the given collection. + * Input Index: + *
+     * {@code
+     * Indexes.ascending("name")
+     * }
+     * 
+ * + * Would produce: + *
+     * {@code
+     *   Key: {"name": 1}
+     *   Value: "name_1"
+     * }
+     * 
+ * + * @param collection the MongoDB collection to read indexes from + * @return map of index key document to index name + */ private static Map getExistingIndexes(MongoCollection collection) { MongoIterable existingDocument = collection.listIndexes(); Map existingIndexes = new HashMap<>(); From 8bba744208fc7927ab147627d5ee6d250aeb9bc2 Mon Sep 17 00:00:00 2001 From: axgiri <146159445+axgiri@users.noreply.github.com> Date: Mon, 25 May 2026 14:34:43 +0200 Subject: [PATCH 3/5] refactor: improve index management in MongoService --- .../core/api/service/mongo/MongoService.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java b/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java index aa426a04c..ccbbc2db3 100644 --- a/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java +++ b/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java @@ -25,7 +25,6 @@ import tech.ebp.oqm.core.api.model.object.MainObject; import tech.ebp.oqm.core.api.model.rest.search.SearchObject; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -122,7 +121,9 @@ public MongoService(Class clazz) { /** * Sets up indexes for the given MongoDB collection. - * Creates any missing indexes and drops indexes that are no longer expected. + * All expected indexes are (re)created on each call, and if an index already exists, it is dropped + * and recreated to ensure its options are always up to date. + * Indexes no longer present in the expected list are dropped. * The default {@code _id_} index is never dropped. * * @param collection the MongoDB collection to manage indexes on @@ -136,21 +137,21 @@ protected static void setupIndexes(MongoCollection collection, List ind for (Bson index : indexes) { BsonDocument expectedKey = index.toBsonDocument(BsonDocument.class, collection.getCodecRegistry()); expectedKeys.add(expectedKey); - if (!existingIndexes.containsKey(expectedKey)) { - collection.createIndex(index, options); + if (existingIndexes.containsKey(expectedKey)) { + collection.dropIndex(existingIndexes.get(expectedKey)); } + collection.createIndex(index, options); } for (Map.Entry existing : existingIndexes.entrySet()) { - String existingName = existing.getValue(); - if (!"_id_".equals(existingName) && !expectedKeys.contains(existing.getKey())) { - collection.dropIndex(existingName); + if (!expectedKeys.contains(existing.getKey())) { + collection.dropIndex(existing.getValue()); } } } /** - * Returns a map of existing indexes on the given collection. + * Returns a map of existing indexes on the given collection ignoring the default _id_ index. * Input Index: *
      * {@code
@@ -179,7 +180,7 @@ private static Map getExistingIndexes(MongoCollection c
             }
             BsonDocument key = keyDoc.toBsonDocument(BsonDocument.class, collection.getCodecRegistry());
             String name = doc.getString("name");
-            if (name != null) {
+            if (name != null && !name.equals("_id_")) {
                 existingIndexes.put(key, name);
             }
         }

From c232396e7d471460fcb2589d6751e9532446b0a8 Mon Sep 17 00:00:00 2001
From: axgiri <146159445+axgiri@users.noreply.github.com>
Date: Wed, 27 May 2026 10:25:57 +0200
Subject: [PATCH 4/5] fix: handle index creation failure by dropping and
 recreating index

---
 .../ebp/oqm/core/api/service/mongo/MongoService.java | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java b/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java
index ccbbc2db3..bcc311ad8 100644
--- a/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java
+++ b/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java
@@ -1,6 +1,7 @@
 package tech.ebp.oqm.core.api.service.mongo;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mongodb.MongoCommandException;
 import com.mongodb.ReadConcern;
 import com.mongodb.ReadPreference;
 import com.mongodb.TransactionOptions;
@@ -138,9 +139,16 @@ protected static void setupIndexes(MongoCollection collection, List ind
             BsonDocument expectedKey = index.toBsonDocument(BsonDocument.class, collection.getCodecRegistry());
             expectedKeys.add(expectedKey);
             if (existingIndexes.containsKey(expectedKey)) {
-                collection.dropIndex(existingIndexes.get(expectedKey));
+                try {
+                    collection.createIndex(index, options);
+                } catch (MongoCommandException e) {
+                    log.warn("failed to create index with key {}, dropping and recreating index", expectedKey, e);
+                    collection.dropIndex(existingIndexes.get(expectedKey));
+                    collection.createIndex(index, options);
+                }
+            } else {
+                collection.createIndex(index, options);
             }
-            collection.createIndex(index, options);
         }
 
         for (Map.Entry existing : existingIndexes.entrySet()) {

From 7a44896cf58c215e16af8386c4fb3c540b8acb3d Mon Sep 17 00:00:00 2001
From: axgiri <146159445+axgiri@users.noreply.github.com>
Date: Fri, 29 May 2026 22:34:51 +0200
Subject: [PATCH 5/5] feat: add logging for index setup

---
 .../java/tech/ebp/oqm/core/api/service/mongo/MongoService.java | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java b/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java
index bcc311ad8..823fe1a36 100644
--- a/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java
+++ b/software/core/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoService.java
@@ -131,6 +131,7 @@ public MongoService(Class clazz) {
      * @param indexes    the list of indexes that should exist on the collection
      */
     protected static void setupIndexes(MongoCollection collection, List indexes) {
+        log.info("setting up indexes for collection {}", collection.getNamespace());
         IndexOptions options = new IndexOptions().background(true);
         Map existingIndexes = getExistingIndexes(collection);
         Set expectedKeys = new HashSet<>();
@@ -138,6 +139,7 @@ protected static void setupIndexes(MongoCollection collection, List ind
         for (Bson index : indexes) {
             BsonDocument expectedKey = index.toBsonDocument(BsonDocument.class, collection.getCodecRegistry());
             expectedKeys.add(expectedKey);
+            log.info("ensuring index with key {}", expectedKey);
             if (existingIndexes.containsKey(expectedKey)) {
                 try {
                     collection.createIndex(index, options);
@@ -153,6 +155,7 @@ protected static void setupIndexes(MongoCollection collection, List ind
 
         for (Map.Entry existing : existingIndexes.entrySet()) {
             if (!expectedKeys.contains(existing.getKey())) {
+                log.info("dropping index with key {}", existing.getKey());
                 collection.dropIndex(existing.getValue());
             }
         }