From cc5efe764f070d51d6f5cab497cdf022b69a6419 Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Wed, 12 Aug 2026 14:14:51 +0200 Subject: [PATCH 01/14] OAK-12353: Group dynamic boost nested docs by boost score Dynamic boost properties are mapped as nested fields, with one nested document per value. When many values share the same boost score, this generates a lot of nested documents which is expensive in Elasticsearch. Group values sharing the same boost score into a single nested document with an array value, behind FT_OAK-12353 (disabled by default). Querying is unaffected since text fields accept arrays natively. --- .../elastic/ElasticIndexProviderService.java | 4 + .../index/elastic/index/ElasticDocument.java | 54 +++++++-- .../elastic/index/ElasticDocumentTest.java | 105 ++++++++++++++++++ 3 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexProviderService.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexProviderService.java index 5ecac5e4078..1a85f6229db 100644 --- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexProviderService.java +++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexProviderService.java @@ -22,6 +22,7 @@ import org.apache.jackrabbit.oak.plugins.index.AsyncIndexInfoService; import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider; import org.apache.jackrabbit.oak.plugins.index.IndexInfoProvider; +import org.apache.jackrabbit.oak.plugins.index.elastic.index.ElasticDocument; import org.apache.jackrabbit.oak.plugins.index.elastic.index.ElasticIndexEditorProvider; import org.apache.jackrabbit.oak.plugins.index.elastic.index.ElasticRetryPolicy; import org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticIndexProvider; @@ -236,6 +237,9 @@ private void activate(BundleContext bundleContext, Config config) { oakRegs.add(whiteboard.register(FeatureToggle.class, new FeatureToggle(ElasticIndexStatistics.FT_OAK_12248, ElasticIndexStatistics.FT_OAK_12248_ENABLE), emptyMap())); + oakRegs.add(whiteboard.register(FeatureToggle.class, + new FeatureToggle(ElasticDocument.FT_OAK_12353, ElasticDocument.FT_OAK_12353_ENABLE), + emptyMap())); if (System.getProperty(QueryEngineSettings.OAK_INFERENCE_ENABLED) != null) { this.isInferenceEnabled = Boolean.parseBoolean(System.getProperty(QueryEngineSettings.OAK_INFERENCE_ENABLED)); } else { diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java index e6012c6d881..e76a6126b3a 100644 --- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java +++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java @@ -35,13 +35,25 @@ import java.util.Map; import java.util.HashMap; import java.util.Set; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.concurrent.atomic.AtomicBoolean; import static org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils.toFloats; @JsonInclude(JsonInclude.Include.NON_EMPTY) public class ElasticDocument { + public static final String FT_OAK_12353 = "FT_OAK-12353"; + /** + * When {@code true}, dynamic boost values sharing the same boost score are grouped into a + * single nested document per property, with {@code value} holding an array of the grouped + * values, instead of one nested document per value. This reduces the number of nested + * documents generated for properties with many dynamic-boost values that share a boost score. + * Default is {@code false} (feature disabled). + */ + public static final AtomicBoolean FT_OAK_12353_ENABLE = new AtomicBoolean(false); + @JsonProperty(FieldNames.PATH) public final String path; @JsonProperty(ElasticIndexDefinition.PATH_RANDOM_VALUE) @@ -66,6 +78,12 @@ public class ElasticDocument { @JsonProperty(ElasticIndexDefinition.LAST_UPDATED) private long lastUpdated; + // fieldName -> boost -> values sharing that boost. Only populated when FT_OAK_12353_ENABLE is + // true, in which case it replaces the corresponding entries that would otherwise be added to + // "properties" directly by addDynamicBoostField. + @JsonIgnore + private final Map>> dynamicBoostGroups; + // Internal set with properties that need to be removed from the document on update operations @JsonIgnore private final Set propertiesToRemove; @@ -87,6 +105,7 @@ public class ElasticDocument { this.dbFullText = new LinkedHashSet<>(); this.similarityTags = new LinkedHashSet<>(); this.propertiesToRemove = new HashSet<>(); + this.dynamicBoostGroups = new LinkedHashMap<>(); } void addFulltext(String value) { @@ -175,12 +194,18 @@ void indexAncestors(String path) { } void addDynamicBoostField(String fieldName, String value, double boost) { - addProperty(fieldName, - Map.of( - ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE, value, - ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST, boost - ) - ); + if (FT_OAK_12353_ENABLE.get()) { + dynamicBoostGroups.computeIfAbsent(fieldName, k -> new LinkedHashMap<>()) + .computeIfAbsent(boost, k -> new LinkedHashSet<>()) + .add(value); + } else { + addProperty(fieldName, + Map.of( + ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE, value, + ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST, boost + ) + ); + } // add value into the dynamic boost specific fulltext field. We cannot add this in the standard // field since dynamic boosted terms require lower weight compared to standard terms @@ -197,7 +222,22 @@ void setLastUpdated(long lastUpdated) { @JsonAnyGetter public Map getProperties() { - return properties; + if (dynamicBoostGroups.isEmpty()) { + return properties; + } + Map merged = new LinkedHashMap<>(properties); + dynamicBoostGroups.forEach((fieldName, boostToValues) -> { + Set nestedDocs = new LinkedHashSet<>(); + boostToValues.forEach((boost, values) -> nestedDocs.add( + Map.of( + ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE, + values.size() == 1 ? values.iterator().next() : new ArrayList<>(values), + ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST, boost + ) + )); + merged.put(fieldName, nestedDocs.size() == 1 ? nestedDocs.iterator().next() : nestedDocs); + }); + return merged; } public void removeProperty(String fieldName) { diff --git a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java new file mode 100644 index 00000000000..d06f54a8bcd --- /dev/null +++ b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.oak.plugins.index.elastic.index; + +import org.junit.After; +import org.junit.Test; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ElasticDocumentTest { + + @After + public void resetToggle() { + ElasticDocument.FT_OAK_12353_ENABLE.set(false); + } + + @Test + public void dynamicBoostValuesAreNotGroupedByDefault() { + ElasticDocument doc = new ElasticDocument("/test"); + doc.addDynamicBoostField("predictedTagsDynamicBoost", "Replacement Cost", 1.0); + doc.addDynamicBoostField("predictedTagsDynamicBoost", "Theft", 1.0); + doc.addDynamicBoostField("predictedTagsDynamicBoost", "GENERAL INSURANCE COMPANY", 0.988); + + Object value = doc.getProperties().get("predictedTagsDynamicBoost"); + assertTrue(value instanceof Set); + @SuppressWarnings("unchecked") + Set> nestedDocs = (Set>) value; + assertEquals(3, nestedDocs.size()); + for (Map nestedDoc : nestedDocs) { + assertTrue(nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE) instanceof String); + } + } + + @Test + public void dynamicBoostValuesAreGroupedByBoostWhenToggleEnabled() { + ElasticDocument.FT_OAK_12353_ENABLE.set(true); + + ElasticDocument doc = new ElasticDocument("/test"); + doc.addDynamicBoostField("predictedTagsDynamicBoost", "Replacement Cost", 1.0); + doc.addDynamicBoostField("predictedTagsDynamicBoost", "Theft", 1.0); + doc.addDynamicBoostField("predictedTagsDynamicBoost", "Alberta", 1.0); + doc.addDynamicBoostField("predictedTagsDynamicBoost", "GENERAL INSURANCE COMPANY", 0.988); + + Object value = doc.getProperties().get("predictedTagsDynamicBoost"); + assertTrue(value instanceof Set); + @SuppressWarnings("unchecked") + Set> nestedDocs = (Set>) value; + // one nested doc for the 3 values sharing boost=1.0, one for the distinct boost=0.988 + assertEquals(2, nestedDocs.size()); + + boolean foundGrouped = false; + boolean foundSingle = false; + for (Map nestedDoc : nestedDocs) { + Object boost = nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST); + Object nestedValue = nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE); + if (Double.valueOf(1.0).equals(boost)) { + assertTrue(nestedValue instanceof List); + @SuppressWarnings("unchecked") + List values = (List) nestedValue; + assertEquals(List.of("Replacement Cost", "Theft", "Alberta"), values); + foundGrouped = true; + } else if (Double.valueOf(0.988).equals(boost)) { + assertEquals("GENERAL INSURANCE COMPANY", nestedValue); + foundSingle = true; + } + } + assertTrue(foundGrouped); + assertTrue(foundSingle); + } + + @Test + public void singleDynamicBoostGroupIsNotWrappedInCollection() { + ElasticDocument.FT_OAK_12353_ENABLE.set(true); + + ElasticDocument doc = new ElasticDocument("/test"); + doc.addDynamicBoostField("predictedTagsDynamicBoost", "Replacement Cost", 1.0); + doc.addDynamicBoostField("predictedTagsDynamicBoost", "Theft", 1.0); + + Object value = doc.getProperties().get("predictedTagsDynamicBoost"); + assertTrue(value instanceof Map); + @SuppressWarnings("unchecked") + Map nestedDoc = (Map) value; + assertEquals(List.of("Replacement Cost", "Theft"), nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE)); + assertEquals(1.0, nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST)); + } +} From e28a7eeb5eeea49498b58b9d2540647f38b5d1a4 Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Wed, 12 Aug 2026 14:54:02 +0200 Subject: [PATCH 02/14] OAK-12353: Enable dynamic boost grouping toggle by default --- .../plugins/index/elastic/index/ElasticDocument.java | 5 +++-- .../index/elastic/index/ElasticDocumentTest.java | 12 +++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java index e76a6126b3a..9da7e5d047e 100644 --- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java +++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java @@ -50,9 +50,10 @@ public class ElasticDocument { * single nested document per property, with {@code value} holding an array of the grouped * values, instead of one nested document per value. This reduces the number of nested * documents generated for properties with many dynamic-boost values that share a boost score. - * Default is {@code false} (feature disabled). + * Default is {@code true} (feature enabled); set to {@code false} via the feature toggle to + * revert to the pre-fix behaviour of one nested document per value. */ - public static final AtomicBoolean FT_OAK_12353_ENABLE = new AtomicBoolean(false); + public static final AtomicBoolean FT_OAK_12353_ENABLE = new AtomicBoolean(true); @JsonProperty(FieldNames.PATH) public final String path; diff --git a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java index d06f54a8bcd..febfbbd0ec2 100644 --- a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java +++ b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java @@ -30,11 +30,13 @@ public class ElasticDocumentTest { @After public void resetToggle() { - ElasticDocument.FT_OAK_12353_ENABLE.set(false); + ElasticDocument.FT_OAK_12353_ENABLE.set(true); } @Test - public void dynamicBoostValuesAreNotGroupedByDefault() { + public void dynamicBoostValuesAreNotGroupedWhenToggleDisabled() { + ElasticDocument.FT_OAK_12353_ENABLE.set(false); + ElasticDocument doc = new ElasticDocument("/test"); doc.addDynamicBoostField("predictedTagsDynamicBoost", "Replacement Cost", 1.0); doc.addDynamicBoostField("predictedTagsDynamicBoost", "Theft", 1.0); @@ -51,9 +53,7 @@ public void dynamicBoostValuesAreNotGroupedByDefault() { } @Test - public void dynamicBoostValuesAreGroupedByBoostWhenToggleEnabled() { - ElasticDocument.FT_OAK_12353_ENABLE.set(true); - + public void dynamicBoostValuesAreGroupedByBoostByDefault() { ElasticDocument doc = new ElasticDocument("/test"); doc.addDynamicBoostField("predictedTagsDynamicBoost", "Replacement Cost", 1.0); doc.addDynamicBoostField("predictedTagsDynamicBoost", "Theft", 1.0); @@ -89,8 +89,6 @@ public void dynamicBoostValuesAreGroupedByBoostWhenToggleEnabled() { @Test public void singleDynamicBoostGroupIsNotWrappedInCollection() { - ElasticDocument.FT_OAK_12353_ENABLE.set(true); - ElasticDocument doc = new ElasticDocument("/test"); doc.addDynamicBoostField("predictedTagsDynamicBoost", "Replacement Cost", 1.0); doc.addDynamicBoostField("predictedTagsDynamicBoost", "Theft", 1.0); From 36939dd727d4ab299c76f094c294609f4b93434a Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Wed, 12 Aug 2026 15:00:20 +0200 Subject: [PATCH 03/14] OAK-12353: Add end-to-end query tests for dynamic boost grouping Cover both the default (grouped) and toggle-disabled (one nested doc per value) behaviour, verifying queries still match on any value grouped into a shared nested document. --- .../elastic/ElasticDynamicBoostTest.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java index 28cc5e999d2..80d9fbdea1a 100644 --- a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java +++ b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java @@ -20,6 +20,8 @@ import org.apache.jackrabbit.oak.api.ContentRepository; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.plugins.index.DynamicBoostCommonTest; +import org.apache.jackrabbit.oak.plugins.index.elastic.index.ElasticDocument; +import org.junit.After; import org.junit.ClassRule; import org.junit.Test; @@ -85,6 +87,63 @@ public void dynamicBoostAnalyzed() throws Exception { }); } + @After + public void resetDynamicBoostGroupingToggle() { + ElasticDocument.FT_OAK_12353_ENABLE.set(true); + } + + /** + * Predicted tags sharing the same boost score are grouped into a single nested document + * (see {@link ElasticDocument#FT_OAK_12353_ENABLE}). This verifies that querying still + * matches on any of the grouped values, both with the grouping enabled (default) and + * disabled. + */ + @Test + public void dynamicBoostQueriesGroupedValuesSharingSameBoostScore() throws Exception { + createAssetsIndexAndProperties(false, false); + + Tree testParent = createNodeWithType(root.getTree("/"), "test", JcrConstants.NT_UNSTRUCTURED, ""); + + Tree predicted1 = createAssetNodeWithPredicted(testParent, "asset1", "flower with a lot of red and a bit of blue"); + createPredictedTag(predicted1, "red", 5.0); + createPredictedTag(predicted1, "blue", 5.0); + createPredictedTag(predicted1, "green", 5.0); + createPredictedTag(predicted1, "special", 9.0); + + root.commit(); + + assertEventually(() -> { + assertQuery("//element(*, dam:Asset)[jcr:contains(., 'red')]", XPATH, List.of("/test/asset1")); + assertQuery("//element(*, dam:Asset)[jcr:contains(., 'blue')]", XPATH, List.of("/test/asset1")); + assertQuery("//element(*, dam:Asset)[jcr:contains(., 'green')]", XPATH, List.of("/test/asset1")); + assertQuery("//element(*, dam:Asset)[jcr:contains(., 'special')]", XPATH, List.of("/test/asset1")); + }); + } + + @Test + public void dynamicBoostQueriesValuesSharingSameBoostScoreWhenGroupingDisabled() throws Exception { + ElasticDocument.FT_OAK_12353_ENABLE.set(false); + + createAssetsIndexAndProperties(false, false); + + Tree testParent = createNodeWithType(root.getTree("/"), "test", JcrConstants.NT_UNSTRUCTURED, ""); + + Tree predicted1 = createAssetNodeWithPredicted(testParent, "asset1", "flower with a lot of red and a bit of blue"); + createPredictedTag(predicted1, "red", 5.0); + createPredictedTag(predicted1, "blue", 5.0); + createPredictedTag(predicted1, "green", 5.0); + createPredictedTag(predicted1, "special", 9.0); + + root.commit(); + + assertEventually(() -> { + assertQuery("//element(*, dam:Asset)[jcr:contains(., 'red')]", XPATH, List.of("/test/asset1")); + assertQuery("//element(*, dam:Asset)[jcr:contains(., 'blue')]", XPATH, List.of("/test/asset1")); + assertQuery("//element(*, dam:Asset)[jcr:contains(., 'green')]", XPATH, List.of("/test/asset1")); + assertQuery("//element(*, dam:Asset)[jcr:contains(., 'special')]", XPATH, List.of("/test/asset1")); + }); + } + @Test public void dynamicBoostNotIncludedInFullText() throws Exception { createAssetsIndexAndProperties(false, false, false); From 54f935a967177a191f1000505919be1e0a139a10 Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Wed, 12 Aug 2026 15:50:33 +0200 Subject: [PATCH 04/14] OAK-12353: simplify logic --- .../oak/plugins/index/elastic/index/ElasticDocument.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java index 9da7e5d047e..de4fe3354f6 100644 --- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java +++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java @@ -232,7 +232,7 @@ public Map getProperties() { boostToValues.forEach((boost, values) -> nestedDocs.add( Map.of( ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE, - values.size() == 1 ? values.iterator().next() : new ArrayList<>(values), + values.size() == 1 ? values.iterator().next() : values, ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST, boost ) )); From 110ad7f78c05d3485580565eeed56d5edf39e539 Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Wed, 12 Aug 2026 16:55:00 +0200 Subject: [PATCH 05/14] OAK-12353: Add time-bombed test to prompt toggle removal Similar to the FT_OAK-12206 test in ElasticIndexWriterTest: fails once the deadline passes, as a reminder to remove FT_OAK-12353 and its guards once the dynamic-boost grouping default has been in production long enough. --- .../elastic/index/ElasticDocumentTest.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java index febfbbd0ec2..7d4fdb62d0d 100644 --- a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java +++ b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java @@ -19,6 +19,9 @@ import org.junit.After; import org.junit.Test; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -73,10 +76,10 @@ public void dynamicBoostValuesAreGroupedByBoostByDefault() { Object boost = nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST); Object nestedValue = nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE); if (Double.valueOf(1.0).equals(boost)) { - assertTrue(nestedValue instanceof List); + assertTrue(nestedValue instanceof Collection); @SuppressWarnings("unchecked") - List values = (List) nestedValue; - assertEquals(List.of("Replacement Cost", "Theft", "Alberta"), values); + Collection values = (Collection) nestedValue; + assertEquals(List.of("Replacement Cost", "Theft", "Alberta"), new ArrayList<>(values)); foundGrouped = true; } else if (Double.valueOf(0.988).equals(boost)) { assertEquals("GENERAL INSURANCE COMPANY", nestedValue); @@ -97,7 +100,18 @@ public void singleDynamicBoostGroupIsNotWrappedInCollection() { assertTrue(value instanceof Map); @SuppressWarnings("unchecked") Map nestedDoc = (Map) value; - assertEquals(List.of("Replacement Cost", "Theft"), nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE)); + @SuppressWarnings("unchecked") + Collection values = (Collection) nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE); + assertEquals(List.of("Replacement Cost", "Theft"), new ArrayList<>(values)); assertEquals(1.0, nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST)); } + + @Test + public void ft_oak_12353_toggleShouldBeRemoved() { + // Time-bombed: if this test fails, the feature toggle FT_OAK-12353 and its guard in + // ElasticDocument#addDynamicBoostField/#getProperties should be removed — the grouping + // has been enabled by default in production long enough. + assertTrue("Feature toggle " + ElasticDocument.FT_OAK_12353 + " is overdue for removal", + LocalDate.now().isBefore(LocalDate.of(2027, 8, 12))); + } } From 4225ea9bf3eb84087666ad454610eaad9003d324 Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Thu, 13 Aug 2026 12:12:17 +0200 Subject: [PATCH 06/14] OAK-12353: (fix) Single boost group unwrapping changes JSON structure from array to object for downstream consumers --- .../index/elastic/index/ElasticDocument.java | 16 ++++++++-------- .../index/elastic/index/ElasticDocumentTest.java | 16 ---------------- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java index de4fe3354f6..5b95d49266a 100644 --- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java +++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java @@ -38,6 +38,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import static org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexUtils.toFloats; @@ -228,15 +229,14 @@ public Map getProperties() { } Map merged = new LinkedHashMap<>(properties); dynamicBoostGroups.forEach((fieldName, boostToValues) -> { - Set nestedDocs = new LinkedHashSet<>(); - boostToValues.forEach((boost, values) -> nestedDocs.add( - Map.of( + Set nestedDocs = boostToValues.entrySet().stream() + .map(entry -> Map.of( ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE, - values.size() == 1 ? values.iterator().next() : values, - ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST, boost - ) - )); - merged.put(fieldName, nestedDocs.size() == 1 ? nestedDocs.iterator().next() : nestedDocs); + entry.getValue().size() == 1 ? entry.getValue().iterator().next() : entry.getValue(), + ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST, entry.getKey() + )) + .collect(Collectors.toCollection(LinkedHashSet::new)); + merged.put(fieldName, nestedDocs); }); return merged; } diff --git a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java index 7d4fdb62d0d..09c142d49d4 100644 --- a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java +++ b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentTest.java @@ -90,22 +90,6 @@ public void dynamicBoostValuesAreGroupedByBoostByDefault() { assertTrue(foundSingle); } - @Test - public void singleDynamicBoostGroupIsNotWrappedInCollection() { - ElasticDocument doc = new ElasticDocument("/test"); - doc.addDynamicBoostField("predictedTagsDynamicBoost", "Replacement Cost", 1.0); - doc.addDynamicBoostField("predictedTagsDynamicBoost", "Theft", 1.0); - - Object value = doc.getProperties().get("predictedTagsDynamicBoost"); - assertTrue(value instanceof Map); - @SuppressWarnings("unchecked") - Map nestedDoc = (Map) value; - @SuppressWarnings("unchecked") - Collection values = (Collection) nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE); - assertEquals(List.of("Replacement Cost", "Theft"), new ArrayList<>(values)); - assertEquals(1.0, nestedDoc.get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_BOOST)); - } - @Test public void ft_oak_12353_toggleShouldBeRemoved() { // Time-bombed: if this test fails, the feature toggle FT_OAK-12353 and its guard in From 9e365f754a6bbef0dc7f9da4d9e4b605454c5a59 Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Thu, 13 Aug 2026 12:21:12 +0200 Subject: [PATCH 07/14] OAK-12353: (fix) 8. toString() does not include dynamicBoostGroups data --- .../oak/plugins/index/elastic/index/ElasticDocument.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java index 5b95d49266a..ec7fe820c3a 100644 --- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java +++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocument.java @@ -263,6 +263,9 @@ public String toString() { if (!dynamicProperties.isEmpty()) { buff.append("dynamicProperties:").append(dynamicProperties).append('\n'); } + if (!dynamicBoostGroups.isEmpty()) { + buff.append("dynamicBoostGroups:").append(dynamicBoostGroups).append('\n'); + } return buff.toString(); } From 375b9fad5e43c029c375a2159d49c481ed968ce0 Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Thu, 13 Aug 2026 13:27:47 +0200 Subject: [PATCH 08/14] OAK-12353: Disable norms on dynamic boost value field Grouping values by boost score makes the nested "value" field's token count vary with group size, which would otherwise skew BM25 length normalization and change ranking based on how many tags happen to share a boost score. Boost is already applied explicitly via field_value_factor, so length normalization on this field isn't meaningful; disabling norms keeps matching scores stable regardless of group size. --- .../elastic/index/ElasticIndexHelper.java | 6 ++++- .../elastic/index/ElasticIndexHelperTest.java | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java index fe484d7b1f6..20a4c034f6b 100644 --- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java +++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java @@ -350,9 +350,13 @@ private static void mapIndexRules(@NotNull TypeMapping.Builder builder, for (PropertyDefinition pd : indexDefinition.getDynamicBoostProperties()) { builder.properties(ElasticIndexUtils.fieldName(pd.nodeName), b1 -> b1.nested( + // norms disabled: values sharing a boost score are grouped into a single nested + // doc (see ElasticDocument#FT_OAK_12353), so field length varies by group size and + // would otherwise skew BM25 length normalization; boost is applied explicitly via + // field_value_factor, so length normalization on this field isn't meaningful anyway. b2 -> b2.properties(DYNAMIC_BOOST_NESTED_VALUE, b3 -> b3.text( - b4 -> b4.analyzer("oak_analyzer"))) + b4 -> b4.analyzer("oak_analyzer").norms(false))) .properties(DYNAMIC_BOOST_NESTED_BOOST, b3 -> b3.double_(f -> f) ) diff --git a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java index 0186f459ef5..220629705b6 100644 --- a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java +++ b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java @@ -59,6 +59,30 @@ public void manyFields() { assertEquals(true, request.settings().index().mapping().ignoreMalformed()); } + @Test + public void dynamicBoostValueFieldHasNormsDisabled() { + IndexDefinitionBuilder builder = new ElasticIndexDefinitionBuilder(); + IndexDefinitionBuilder.IndexRule indexRuleA = builder.indexRule("typeA"); + indexRuleA.property("foo").type("String"); + indexRuleA.property("predictedTagsDynamicBoost", "jcr:content/metadata/predictedTags/.*", true) + .getBuilderTree().setProperty(FulltextIndexConstants.PROP_DYNAMIC_BOOST, true); + NodeState nodeState = builder.build(); + + ElasticIndexDefinition definition = + new ElasticIndexDefinition(nodeState, nodeState, "path", "prefix"); + CreateIndexRequest request = ElasticIndexHelper.createIndexRequest("prefix.path", definition); + + Property dynamicBoostField = request.mappings().properties() + .get(ElasticIndexUtils.fieldName("predictedTagsDynamicBoost")); + assertThat(dynamicBoostField, notNullValue()); + assertThat(dynamicBoostField._kind(), is(Property.Kind.Nested)); + + Property valueField = dynamicBoostField.nested().properties().get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE); + assertThat(valueField, notNullValue()); + assertThat(valueField._kind(), is(Property.Kind.Text)); + assertEquals(false, valueField.text().norms()); + } + @Test public void multiRulesWithSamePropertyNames() { IndexDefinitionBuilder builder = new ElasticIndexDefinitionBuilder(); From 68511d59a01c94a61d482198d9cd62262f84ff3c Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Thu, 13 Aug 2026 13:47:04 +0200 Subject: [PATCH 09/14] OAK-12353: increase minor internal version for elasticsearch --- .../oak/plugins/index/search/FulltextIndexConstants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java index a71df247b56..ebbb55014dc 100644 --- a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java +++ b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java @@ -485,6 +485,6 @@ public static IndexingMode from(String indexingMode) { * needed from an outside process that does not have visibility to the specific index module. */ Map INDEX_VERSION_BY_TYPE = Map.of( - "elasticsearch", "1.4.0" + "elasticsearch", "1.5.0" ); } From 74abfeebbde8892c14f057312a8f2ff544fb7b22 Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Wed, 26 Aug 2026 15:42:41 +0200 Subject: [PATCH 10/14] OAK-12353: re-enabled norms, restructure tests --- .../elastic/index/ElasticIndexHelper.java | 7 +- .../elastic/ElasticDynamicBoostTest.java | 78 +++++++++++-------- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java index 20a4c034f6b..b0b38fa9706 100644 --- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java +++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java @@ -20,6 +20,7 @@ import co.elastic.clients.elasticsearch._types.mapping.DenseVectorProperty; import co.elastic.clients.elasticsearch._types.mapping.DenseVectorSimilarity; import co.elastic.clients.elasticsearch._types.mapping.DynamicMapping; +import co.elastic.clients.elasticsearch._types.mapping.IndexOptions; import co.elastic.clients.elasticsearch._types.mapping.Property; import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; @@ -350,13 +351,9 @@ private static void mapIndexRules(@NotNull TypeMapping.Builder builder, for (PropertyDefinition pd : indexDefinition.getDynamicBoostProperties()) { builder.properties(ElasticIndexUtils.fieldName(pd.nodeName), b1 -> b1.nested( - // norms disabled: values sharing a boost score are grouped into a single nested - // doc (see ElasticDocument#FT_OAK_12353), so field length varies by group size and - // would otherwise skew BM25 length normalization; boost is applied explicitly via - // field_value_factor, so length normalization on this field isn't meaningful anyway. b2 -> b2.properties(DYNAMIC_BOOST_NESTED_VALUE, b3 -> b3.text( - b4 -> b4.analyzer("oak_analyzer").norms(false))) + b4 -> b4.analyzer("oak_analyzer"))) .properties(DYNAMIC_BOOST_NESTED_BOOST, b3 -> b3.double_(f -> f) ) diff --git a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java index 80d9fbdea1a..8a7b7e8aa9b 100644 --- a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java +++ b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java @@ -21,21 +21,41 @@ import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.plugins.index.DynamicBoostCommonTest; import org.apache.jackrabbit.oak.plugins.index.elastic.index.ElasticDocument; -import org.junit.After; +import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import java.util.List; +/** + * Runs every test twice, once with {@link ElasticDocument#FT_OAK_12353_ENABLE} enabled and once + * with it disabled, so both dynamic-boost grouping code paths get the same coverage. + */ +@RunWith(Parameterized.class) public class ElasticDynamicBoostTest extends DynamicBoostCommonTest { @ClassRule public static final ElasticConnectionRule elasticRule = new ElasticConnectionRule(); + @Parameterized.Parameters(name = "dynamicBoostGroupingEnabled={0}") + public static Iterable fixtures() { + return List.of(new Object[]{true}, new Object[]{false}); + } + + @Parameterized.Parameter + public boolean dynamicBoostGroupingEnabled; + public ElasticDynamicBoostTest() { this.indexOptions = new ElasticIndexOptions(); } + @Before + public void setDynamicBoostGroupingToggle() { + ElasticDocument.FT_OAK_12353_ENABLE.set(dynamicBoostGroupingEnabled); + } + @Override protected ContentRepository createRepository() { repositoryOptionsUtil = new ElasticTestRepositoryBuilder(elasticRule).build(); @@ -87,16 +107,11 @@ public void dynamicBoostAnalyzed() throws Exception { }); } - @After - public void resetDynamicBoostGroupingToggle() { - ElasticDocument.FT_OAK_12353_ENABLE.set(true); - } - /** * Predicted tags sharing the same boost score are grouped into a single nested document * (see {@link ElasticDocument#FT_OAK_12353_ENABLE}). This verifies that querying still - * matches on any of the grouped values, both with the grouping enabled (default) and - * disabled. + * matches on any of the grouped values, both with the grouping enabled and disabled + * (see {@link #dynamicBoostGroupingEnabled}). */ @Test public void dynamicBoostQueriesGroupedValuesSharingSameBoostScore() throws Exception { @@ -120,30 +135,6 @@ public void dynamicBoostQueriesGroupedValuesSharingSameBoostScore() throws Excep }); } - @Test - public void dynamicBoostQueriesValuesSharingSameBoostScoreWhenGroupingDisabled() throws Exception { - ElasticDocument.FT_OAK_12353_ENABLE.set(false); - - createAssetsIndexAndProperties(false, false); - - Tree testParent = createNodeWithType(root.getTree("/"), "test", JcrConstants.NT_UNSTRUCTURED, ""); - - Tree predicted1 = createAssetNodeWithPredicted(testParent, "asset1", "flower with a lot of red and a bit of blue"); - createPredictedTag(predicted1, "red", 5.0); - createPredictedTag(predicted1, "blue", 5.0); - createPredictedTag(predicted1, "green", 5.0); - createPredictedTag(predicted1, "special", 9.0); - - root.commit(); - - assertEventually(() -> { - assertQuery("//element(*, dam:Asset)[jcr:contains(., 'red')]", XPATH, List.of("/test/asset1")); - assertQuery("//element(*, dam:Asset)[jcr:contains(., 'blue')]", XPATH, List.of("/test/asset1")); - assertQuery("//element(*, dam:Asset)[jcr:contains(., 'green')]", XPATH, List.of("/test/asset1")); - assertQuery("//element(*, dam:Asset)[jcr:contains(., 'special')]", XPATH, List.of("/test/asset1")); - }); - } - @Test public void dynamicBoostNotIncludedInFullText() throws Exception { createAssetsIndexAndProperties(false, false, false); @@ -178,4 +169,27 @@ public void dynamicBoostNotIncludedInFullText() throws Exception { }); } + + @Test + public void ranking() throws Exception { + createAssetsIndexAndProperties(false, false); + Tree test = createNodeWithType(root.getTree("/"), "test", JcrConstants.NT_UNSTRUCTURED, ""); + + // asset1: three tags sharing one boost group (boost 1) + Tree many = createAssetNodeWithPredicted(test, "asset1", "titleone"); + createPredictedTag(many, "red", 1.0); + createPredictedTag(many, "blue", 1.0); + createPredictedTag(many, "green", 1.0); + + // asset2: one high-boost tag in its own group, the other two effectively zero + Tree single = createAssetNodeWithPredicted(test, "asset2", "titletwo"); + createPredictedTag(single, "red", 4.0); + createPredictedTag(single, "blue", 0.01); + createPredictedTag(single, "green", 0.01); + + root.commit(); + String query = + "select [jcr:path] from [dam:Asset] where contains(*, 'red blue green')"; + assertEventually(() -> assertOrderedQuery(query, List.of("/test/asset2", "/test/asset1"))); + } } From f29e898fb953c872df82534be909f3e9e4058a1d Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Wed, 26 Aug 2026 15:49:38 +0200 Subject: [PATCH 11/14] OAK-12353: remove dynamicBoostValueFieldHasNormsDisabled test --- .../elastic/index/ElasticIndexHelperTest.java | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java index 220629705b6..0186f459ef5 100644 --- a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java +++ b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java @@ -59,30 +59,6 @@ public void manyFields() { assertEquals(true, request.settings().index().mapping().ignoreMalformed()); } - @Test - public void dynamicBoostValueFieldHasNormsDisabled() { - IndexDefinitionBuilder builder = new ElasticIndexDefinitionBuilder(); - IndexDefinitionBuilder.IndexRule indexRuleA = builder.indexRule("typeA"); - indexRuleA.property("foo").type("String"); - indexRuleA.property("predictedTagsDynamicBoost", "jcr:content/metadata/predictedTags/.*", true) - .getBuilderTree().setProperty(FulltextIndexConstants.PROP_DYNAMIC_BOOST, true); - NodeState nodeState = builder.build(); - - ElasticIndexDefinition definition = - new ElasticIndexDefinition(nodeState, nodeState, "path", "prefix"); - CreateIndexRequest request = ElasticIndexHelper.createIndexRequest("prefix.path", definition); - - Property dynamicBoostField = request.mappings().properties() - .get(ElasticIndexUtils.fieldName("predictedTagsDynamicBoost")); - assertThat(dynamicBoostField, notNullValue()); - assertThat(dynamicBoostField._kind(), is(Property.Kind.Nested)); - - Property valueField = dynamicBoostField.nested().properties().get(ElasticIndexHelper.DYNAMIC_BOOST_NESTED_VALUE); - assertThat(valueField, notNullValue()); - assertThat(valueField._kind(), is(Property.Kind.Text)); - assertEquals(false, valueField.text().norms()); - } - @Test public void multiRulesWithSamePropertyNames() { IndexDefinitionBuilder builder = new ElasticIndexDefinitionBuilder(); From 6abfc7d5ef541c801e10d2b97194bade8e09df0b Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Wed, 26 Aug 2026 16:03:47 +0200 Subject: [PATCH 12/14] OAK-12353: ElasticDynamicBoostTest reset fixture --- .../oak/plugins/index/elastic/ElasticDynamicBoostTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java index 8a7b7e8aa9b..bdfd5868254 100644 --- a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java +++ b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticDynamicBoostTest.java @@ -21,6 +21,7 @@ import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.plugins.index.DynamicBoostCommonTest; import org.apache.jackrabbit.oak.plugins.index.elastic.index.ElasticDocument; +import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; @@ -56,6 +57,11 @@ public void setDynamicBoostGroupingToggle() { ElasticDocument.FT_OAK_12353_ENABLE.set(dynamicBoostGroupingEnabled); } + @After + public void resetDynamicBoostGroupingToggle() { + ElasticDocument.FT_OAK_12353_ENABLE.set(true); + } + @Override protected ContentRepository createRepository() { repositoryOptionsUtil = new ElasticTestRepositoryBuilder(elasticRule).build(); From 01a49c3bfd1788cc052841bd4f9ee6d1a2ef2cb8 Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Wed, 26 Aug 2026 16:49:23 +0200 Subject: [PATCH 13/14] OAK-12353: (minor) remove unused import --- .../oak/plugins/index/elastic/index/ElasticIndexHelper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java index b0b38fa9706..fe484d7b1f6 100644 --- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java +++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java @@ -20,7 +20,6 @@ import co.elastic.clients.elasticsearch._types.mapping.DenseVectorProperty; import co.elastic.clients.elasticsearch._types.mapping.DenseVectorSimilarity; import co.elastic.clients.elasticsearch._types.mapping.DynamicMapping; -import co.elastic.clients.elasticsearch._types.mapping.IndexOptions; import co.elastic.clients.elasticsearch._types.mapping.Property; import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; From f0fa8442b59b53c34226012738f5c6ba38f4c7a3 Mon Sep 17 00:00:00 2001 From: fabriziofortino Date: Wed, 26 Aug 2026 16:50:41 +0200 Subject: [PATCH 14/14] OAK-12353: (minor) remove warnings --- .../oak/plugins/index/elastic/index/ElasticIndexHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java index fe484d7b1f6..32b488231ff 100644 --- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java +++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java @@ -393,7 +393,7 @@ private static void checkIndexRules(ElasticIndexDefinition indexDefinition) { .stream() .filter(e -> e.getValue().size() > 1) .filter(e -> e.getValue().stream().map(PropertyDefinition::getType).distinct().count() > 1) - .collect(Collectors.toList()); + .toList(); if (!multiTypesFields.isEmpty()) { String fields = multiTypesFields.stream().map(Map.Entry::getKey).collect(Collectors.joining(", ", "[", "]")); @@ -417,7 +417,7 @@ protected static String convertUpperCamelToLowerUnderscore(String string) { StringBuilder result = new StringBuilder(); for (char c : string.toCharArray()) { // start? - if (result.length() == 0) { + if (result.isEmpty()) { result.append(Character.toLowerCase(c)); } else { if (Character.isUpperCase(c)) {