From 845932d1b46872e1a15fbf3dae9c0b49efd121b8 Mon Sep 17 00:00:00 2001 From: Heiko Kopp Date: Fri, 7 Aug 2026 11:07:14 +0200 Subject: [PATCH] fix(index): remove trailing underscore from auto-generated index names IndexDescription.fromMap() built auto-generated index names by appending "_" after EVERY key entry instead of only BETWEEN entries, producing "campaignNumber_1_" for a single-field index and "campaignNumber_1_fileName_1_" for a two-field one -- both violate MongoDB's own "_" naming convention (joined by "_", no trailing separator). The practical consequence: on any database where the correctly-named index already exists (e.g. created by an older Morphium version, or by MongoDB's own auto-naming when no name was given), Morphium tries to create a same-definition index under a different, wrongly-suffixed name. MongoDB rejects that with "Error 85 - Index already exists with a different name", Morphium only logs it as a warning and moves on, and the index -- including any unique constraint from @Index(options = {"unique:true"}) -- is silently never created. Writes that relied on that uniqueness then fail with E11000 duplicate key errors referencing the never-created, wrongly-named index. Regression status: this is not a 6.3.0 regression. `git blame` traces the trailing-underscore code to 2022-06-30 (commit f9e84d27a3), and it is byte-for-byte identical in the v6.2.5 release tag. It has been silently present for years; it only surfaces now because the reproduction needs an existing database with an index that was already correctly named, which a fresh database never has. So this is a plain bugfix, not a behavior change -- no migration path is needed, since MongoDB will accept the fixed name going forward and index CREATION was always the operation that failed, not an existing index's definition or usage. Checked for a second occurrence of the same name-building logic (item 3 in the report): none found. Morphium#ensureIndicesFor and every other call site (including the quarkus-morphium migration path) go through IndexDescription.fromMaps()/fromMap(), so there is exactly one place that needed fixing. Notably, InMemoryDriver's OWN index-name builder (InMemoryDriver.java ~3227) already joins correctly ("if (b.length() > 0) b.append('_')") and was never affected -- which is also why no existing unit test caught this: every test exercising auto-naming through the InMemDriver path saw correct names from that separate builder, never IndexDescription's. Regression tests added for the exact scenario from the report (single-field "campaignNumber_1", multi-field "campaignNumber_1_fileName_1", and an explicit-name case proving the auto-naming branch is still skipped when a name is supplied). Mutation-proofed: temporarily restoring the old unconditional trailing-underscore append reddens exactly the two new auto-naming tests with the reported symptom ("expected: <...1_fileName_1> but was: <...1_fileName_1_>"), leaving the pre-existing explicit-name tests green; reverted after confirming. Verified: morphium-core module installs clean, IndexDescriptionTest 5/5 green, all index-related suites in morphium-core (IndexMaintenanceTest, InMemoryDriverIndexPlanningTest, CollectionIndexStoreTest, IndexKeyTest, IndexPlannerTest, UniqueIndexTest, InMemUniqueIndexTest, ListIndexesFidelityTest, DropIndexesCommandTest, ConnectionIndexTest) green, morphium-jakarta-data 82/82 green. Targeting the 6.3.0 line per the report, since 6.3.0-SNAPSHOT is already in use by downstream consumers (e.g. datona-ota-authority, which is where this was caught: 14/1794 tests failing with E11000 on an upgraded database). --- .../de/caluga/morphium/IndexDescription.java | 15 ++++++++++- .../suite/base/IndexDescriptionTest.java | 26 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/morphium-core/src/main/java/de/caluga/morphium/IndexDescription.java b/morphium-core/src/main/java/de/caluga/morphium/IndexDescription.java index 672739c3f..146cbe7c8 100644 --- a/morphium-core/src/main/java/de/caluga/morphium/IndexDescription.java +++ b/morphium-core/src/main/java/de/caluga/morphium/IndexDescription.java @@ -45,10 +45,23 @@ public static IndexDescription fromMap(Map incoming) { @SuppressWarnings("unchecked") Map keymap = (Map) incoming.get("key"); for (var k : keymap.keySet()) { + // MongoDB's own naming convention is "_" per key, joined by + // "_" between entries -- there is no separator after the LAST entry. Appending + // "_" unconditionally after every entry (as this used to do) produces a + // trailing underscore ("campaignNumber_1_" instead of "campaignNumber_1"), which + // silently breaks index creation on any database where an index on the same + // field already exists under the correct name: MongoDB rejects the mismatched + // name with "Error 85 - Index already exists with a different name", Morphium + // only logs that as a warning, and the index (with any unique constraint) is + // never created. This bug predates 6.3.0 -- it is present unchanged as far back + // as the v6.2.5 tag -- so it is a plain bugfix, not a behaviour change requiring + // a migration path. + if (sb.length() > 0) { + sb.append("_"); + } sb.append(k); sb.append("_"); sb.append(keymap.get(k).toString()); - sb.append("_"); } incoming.put("name", sb.toString()); } diff --git a/morphium-core/src/test/java/de/caluga/test/mongo/suite/base/IndexDescriptionTest.java b/morphium-core/src/test/java/de/caluga/test/mongo/suite/base/IndexDescriptionTest.java index 5d2a449a5..b6e9ffb1f 100644 --- a/morphium-core/src/test/java/de/caluga/test/mongo/suite/base/IndexDescriptionTest.java +++ b/morphium-core/src/test/java/de/caluga/test/mongo/suite/base/IndexDescriptionTest.java @@ -46,4 +46,30 @@ public void asMapFromMapTest() throws Exception { assertEquals(idx.getHidden(), idx2.getHidden()); assertEquals(idx.getSparse(), idx2.getSparse()); } + + // Regression test: fromMap() used to append a trailing "_" separator after every key + // instead of only BETWEEN keys, producing names like "campaignNumber_1_" instead of the + // MongoDB-standard "campaignNumber_1". That mismatch breaks index creation on any database + // where the correctly-named index already exists (MongoDB rejects it with "Error 85 - Index + // already exists with a different name", which Morphium only logs as a warning). Neither + // pre-existing test above catches this: both set an explicit name, which skips the + // auto-naming branch entirely. + @Test + public void fromMap_singleField_generatesNameWithoutTrailingUnderscore() throws Exception { + var idx = IndexDescription.fromMaps(Doc.of("campaignNumber", 1), null); + assertEquals("campaignNumber_1", idx.getName()); + } + + @Test + public void fromMap_multiField_generatesNameJoinedByUnderscoreWithoutTrailingUnderscore() throws Exception { + var idx = IndexDescription.fromMaps(Doc.of("campaignNumber", 1, "fileName", 1), null); + assertEquals("campaignNumber_1_fileName_1", idx.getName()); + } + + @Test + public void fromMap_explicitName_isNotOverwritten() throws Exception { + var idx = IndexDescription.fromMaps(Doc.of("campaignNumber", 1), + Doc.of("name", "myCustomIndexName")); + assertEquals("myCustomIndexName", idx.getName()); + } }