This is an automated email from the ASF dual-hosted git repository.
thomasmueller pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new aeaf421c8e OAK-12262 Simplified index mgmt: support indexes without
version number (#2961)
aeaf421c8e is described below
commit aeaf421c8ee0cee435e480f0e2f377e649cc8cc3
Author: Thomas Mueller <[email protected]>
AuthorDate: Fri Jun 19 16:18:52 2026 +0200
OAK-12262 Simplified index mgmt: support indexes without version number
(#2961)
---
.../jackrabbit/oak/plugins/index/IndexName.java | 7 +-
.../oak/plugins/index/diff/DiffIndexMerger.java | 13 ++-
.../oak/plugins/index/IndexNameTest.java | 10 ++
.../oak/plugins/index/diff/DiffIndexTest.java | 107 +++++++++++++++++++++
.../oak/index/merge/IndexDefMergerUtils.java | 4 +-
.../index/merge/IndexDefMergerScenariosTest.java | 4 +-
.../oak/index/merge/unversioned-index.json | 42 ++++++++
7 files changed, 176 insertions(+), 11 deletions(-)
diff --git
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexName.java
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexName.java
index b453804ebf..f6153fadc7 100644
---
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexName.java
+++
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexName.java
@@ -186,7 +186,9 @@ public class IndexName implements Comparable<IndexName> {
IndexName latest = null;
for (IndexName n : all) {
if (n.baseName.equals(baseName)) {
- if (compareTo(n) > 0 && n.customerVersion == 0) {
+ // for unversioned indexes, allow equal (the index is its own
ancestor)
+ boolean sameOrOlder = isVersioned ? compareTo(n) > 0 :
compareTo(n) >= 0;
+ if (sameOrOlder && n.customerVersion == 0) {
if (latest == null || n.compareTo(latest) > 0) {
latest = n;
}
@@ -283,6 +285,9 @@ public class IndexName implements Comparable<IndexName> {
}
public String nextCustomizedName() {
+ if (productVersion == 0) {
+ return baseName + "-custom-" + (customerVersion + 1);
+ }
return baseName + "-" + productVersion + "-custom-" + (customerVersion
+ 1);
}
diff --git
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndexMerger.java
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndexMerger.java
index ca2e31f854..25346fc9e6 100644
---
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndexMerger.java
+++
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndexMerger.java
@@ -340,10 +340,6 @@ public class DiffIndexMerger {
String prefix = "/oak:index/";
for (String key : combined.getChildren().keySet()) {
IndexName name = IndexName.parse(key.substring(prefix.length()));
- if (!name.isVersioned()) {
- log("Ignoring unversioned index {}", name);
- continue;
- }
if (!name.getBaseName().equals(indexName)) {
continue;
}
@@ -457,9 +453,12 @@ public class DiffIndexMerger {
"-custom-" + (latestCustomized.getCustomerVersion() +
1);
} else {
// customized OOTB index: use the latest product as the base
- key = prefix + indexName +
- "-" + latestProduct.getProductVersion() +
- "-custom-";
+ int productVersion = latestProduct.getProductVersion();
+ if (productVersion == 0) {
+ key = prefix + indexName + "-custom-";
+ } else {
+ key = prefix + indexName + "-" + productVersion +
"-custom-";
+ }
if (latestCustomized != null) {
key += (latestCustomized.getCustomerVersion() + 1);
} else {
diff --git
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexNameTest.java
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexNameTest.java
index 21715b20c3..cebbdd517c 100644
---
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexNameTest.java
+++
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexNameTest.java
@@ -116,6 +116,16 @@ public class IndexNameTest {
}
}
+ @Test
+ public void nextCustomizedName() {
+ // versioned index: product version is preserved
+ assertEquals("asset-1-custom-1",
IndexName.parse("asset-1").nextCustomizedName());
+ assertEquals("asset-1-custom-2",
IndexName.parse("asset-1-custom-1").nextCustomizedName());
+ // unversioned index: no "-0-" in the result
+ assertEquals("asset-custom-1",
IndexName.parse("asset").nextCustomizedName());
+ assertEquals("asset-custom-2",
IndexName.parse("asset-custom-1").nextCustomizedName());
+ }
+
@Test
public void filterNewestIndexes() {
diff --git
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndexTest.java
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndexTest.java
index 50f9e2efd2..edefd828f1 100644
---
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndexTest.java
+++
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndexTest.java
@@ -22,6 +22,7 @@ import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_DISABL
import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
@@ -259,6 +260,112 @@ public class DiffIndexTest {
assertSameJson("{}", repositoryDefinitions.toString());
}
+ @Test
+ public void testDiffIndexUpdateUnversionedOotbExplicit() throws Exception {
+ NodeStore store = new MemoryNodeStore(INITIAL_CONTENT);
+
+ List<IndexEditorProvider> indexEditors = List.of(
+ new ReferenceEditorProvider(), new
PropertyIndexEditorProvider(), new NodeCounterEditorProvider());
+ IndexEditorProvider provider =
CompositeIndexEditorProvider.compose(indexEditors);
+ EditorHook hook = new EditorHook(new IndexUpdateProvider(provider));
+
+ // seed the store with an unversioned OOTB index /oak:index/test (type
lucene)
+ NodeBuilder builder = store.getRoot().builder();
+ builder.child(INDEX_DEFINITIONS_NAME).child("test")
+ .setProperty("jcr:primaryType",
IndexConstants.INDEX_DEFINITIONS_NODE_TYPE, Type.NAME)
+ .setProperty(TYPE_PROPERTY_NAME, "lucene");
+ store.merge(builder, hook, CommitInfo.EMPTY);
+
+ // diff adds indexRules to the product index
+ storeDiff(store, "2026-01-01T00:00:00.000Z",
+ "{ \"test\": {"
+ + " \"indexRules\": {"
+ + " \"nt:base\": {"
+ + " \"properties\": {"
+ + " \"prop1\": { \"name\": \"prop1\",
\"propertyIndex\": true }"
+ + " }"
+ + " }"
+ + " }"
+ + "} }");
+
+ // expect test-custom-1 with:
+ // - type "lucene" from the product index
+ // - indexRules/nt:base/properties/prop1 from the diff
+ assertSameJson("{\n"
+ + " \"/oak:index/test-custom-1\": {\n"
+ + " \"mergeChecksum\":
\"2e4b9003683d6c179c72f94393a91b2bedc3fa67d1a60f6f7876002dbb40c070\",\n"
+ + " \"mergeInfo\": \"This index was auto-merged. See also
https://oak-indexing.github.io/oakTools/simplified.html\",\n"
+ + " \"reindex\": true,\n"
+ + " \"jcr:primaryType\":
\"nam:oak:QueryIndexDefinition\",\n"
+ + " \"type\": \"lucene\",\n"
+ + " \"merges\": [\"/oak:index/test\"],\n"
+ + " \"indexRules\": {\n"
+ + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
+ + " \"nt:base\": {\n"
+ + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
+ + " \"properties\": {\n"
+ + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
+ + " \"prop1\": {\n"
+ + " \"name\": \"prop1\",\n"
+ + " \"propertyIndex\": true,\n"
+ + " \"jcr:primaryType\": \"nam:nt:unstructured\"\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + " },\n"
+ + " \"/oak:index/test\": {\n"
+ + " \"reindex\": true,\n"
+ + " \"jcr:primaryType\":
\"nam:oak:QueryIndexDefinition\",\n"
+ + " \"type\": \"lucene\"\n"
+ + " }\n"
+ + "}",
+ RootIndexesListService.getRootIndexDefinitions(store,
"lucene").toString());
+ }
+
+ @Test
+ public void testDiffIndexUpdateUnversionedOotb() throws Exception {
+ NodeStore store = new MemoryNodeStore(INITIAL_CONTENT);
+
+ // Seed the store with an unversioned OOTB index "/oak:index/test"
+ NodeBuilder builder = store.getRoot().builder();
+ NodeBuilder indexDefs = builder.child(INDEX_DEFINITIONS_NAME);
+ indexDefs.child("test")
+ .setProperty("jcr:primaryType",
IndexConstants.INDEX_DEFINITIONS_NODE_TYPE, Type.NAME)
+ .setProperty(TYPE_PROPERTY_NAME, "lucene");
+ store.merge(builder, new EditorHook(new IndexUpdateProvider(
+ CompositeIndexEditorProvider.compose(List.of(
+ new ReferenceEditorProvider(),
+ new PropertyIndexEditorProvider(),
+ new NodeCounterEditorProvider())))),
+ CommitInfo.EMPTY);
+
+ // Apply a diff that adds indexRules to the unversioned index
+ storeDiff(store, "2026-01-01T00:00:00.000Z",
+ "{ \"test\": {"
+ + " \"indexRules\": {"
+ + " \"nt:base\": {"
+ + " \"properties\": {"
+ + " \"prop1\": { \"name\": \"prop1\",
\"propertyIndex\": true }"
+ + " }"
+ + " }"
+ + " }"
+ + "} }");
+
+ JsonObject repositoryDefinitions =
RootIndexesListService.getRootIndexDefinitions(store, "lucene");
+ // expect test-custom-1, not test-0-custom-1
+ JsonObject merged =
repositoryDefinitions.getChildren().get("/oak:index/test-custom-1");
+ assertNotNull("Expected test-custom-1 to be created, got: " +
repositoryDefinitions, merged);
+ // property from the product index
+ assertEquals("\"lucene\"", merged.getProperties().get("type"));
+ // indexRules child and prop1 entry from the diff
+ JsonObject prop1 = merged.getChildren().get("indexRules")
+ .getChildren().get("nt:base")
+ .getChildren().get("properties")
+ .getChildren().get("prop1");
+ assertNotNull("prop1 from diff should be present in the merged index",
prop1);
+ }
+
private void assertSameJson(String a, String b) {
JsonObject ja = JsonObject.fromJson(a, true);
JsonObject jb = JsonObject.fromJson(b, true);
diff --git
a/oak-run/src/main/java/org/apache/jackrabbit/oak/index/merge/IndexDefMergerUtils.java
b/oak-run/src/main/java/org/apache/jackrabbit/oak/index/merge/IndexDefMergerUtils.java
index 6a107ee01d..dafc2171fe 100644
---
a/oak-run/src/main/java/org/apache/jackrabbit/oak/index/merge/IndexDefMergerUtils.java
+++
b/oak-run/src/main/java/org/apache/jackrabbit/oak/index/merge/IndexDefMergerUtils.java
@@ -413,8 +413,8 @@ public class IndexDefMergerUtils {
IndexName latest = n.getLatestCustomized(allNames);
IndexName ancestor = n.getLatestProduct(allNames);
if (latest != null && ancestor != null) {
- if (n.compareTo(latest) <= 0 || n.compareTo(ancestor) <=
0) {
- // ignore older versions of indexes
+ if (n.isVersioned() && (n.compareTo(latest) <= 0 ||
n.compareTo(ancestor) <= 0)) {
+ // ignore older versions of indexes (not applicable to
unversioned indexes)
continue;
}
JsonObject latestCustomized =
allIndexes.getChildren().get(latest.getNodeName());
diff --git
a/oak-run/src/test/java/org/apache/jackrabbit/oak/index/merge/IndexDefMergerScenariosTest.java
b/oak-run/src/test/java/org/apache/jackrabbit/oak/index/merge/IndexDefMergerScenariosTest.java
index ac11a2aa00..63d3d8d012 100644
---
a/oak-run/src/test/java/org/apache/jackrabbit/oak/index/merge/IndexDefMergerScenariosTest.java
+++
b/oak-run/src/test/java/org/apache/jackrabbit/oak/index/merge/IndexDefMergerScenariosTest.java
@@ -57,7 +57,9 @@ public class IndexDefMergerScenariosTest extends
ParameterizedMergingTestBase {
testCase("should not remove child nodes from product index
missing from custom index",
"missing-child.json"),
testCase("should support removing adding changing properties
from product index in custom index",
- "removed-property.json")
+ "removed-property.json"),
+ testCase("should merge customization of unversioned index into
test-custom-1",
+ "unversioned-index.json")
});
}
diff --git
a/oak-run/src/test/resources/org/apache/jackrabbit/oak/index/merge/unversioned-index.json
b/oak-run/src/test/resources/org/apache/jackrabbit/oak/index/merge/unversioned-index.json
new file mode 100644
index 0000000000..6fee8af5b3
--- /dev/null
+++
b/oak-run/src/test/resources/org/apache/jackrabbit/oak/index/merge/unversioned-index.json
@@ -0,0 +1,42 @@
+{
+ "run": {
+ "/oak:index/test": {
+ "type": "lucene"
+ },
+ "/oak:index/test-custom-1": {
+ "type": "lucene",
+ "indexRules": {
+ "nt:base": {
+ "properties": {
+ "prop1": {
+ "name": "prop1"
+ }
+ }
+ }
+ }
+ }
+ },
+ "build": {
+ "/oak:index/test": {
+ "type": "lucene"
+ }
+ },
+ "expected": {
+ "/oak:index/test": {
+ "type": "lucene"
+ },
+ "/oak:index/test-custom-1": {
+ "type": "lucene",
+ "merges": ["/oak:index/test", "/oak:index/test-custom-1"],
+ "indexRules": {
+ "nt:base": {
+ "properties": {
+ "prop1": {
+ "name": "prop1"
+ }
+ }
+ }
+ }
+ }
+ }
+}