This is an automated email from the ASF dual-hosted git repository.

thomasm pushed a commit to branch OAK-12010-subset2
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git

commit 577d813a0c8b3a4dc78eef0ead3124ad9570c25a
Author: Thomas Mueller <[email protected]>
AuthorDate: Mon Feb 16 08:59:44 2026 +0100

    OAK-12010 Simplified index management (improvements)
---
 .../oak/plugins/index/diff/DiffIndex.java          |   6 +-
 .../oak/plugins/index/diff/DiffIndexMerger.java    |  84 +++++++++++---
 .../oak/plugins/index/diff/JsonNodeUpdater.java    |  13 ++-
 .../plugins/index/diff/RootIndexesListService.java |   1 +
 .../plugins/index/diff/JsonNodeUpdaterTest.java    | 126 +++++++++++----------
 .../oak/plugins/index/diff/MergeTest.java          | 114 +++++++++++++++++++
 6 files changed, 266 insertions(+), 78 deletions(-)

diff --git 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndex.java
 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndex.java
index 6df72e2ce8..41cb53bca5 100644
--- 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndex.java
+++ 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/DiffIndex.java
@@ -117,7 +117,7 @@ public class DiffIndex {
                 diffIndexDefinition.setProperty("error", message + ": " + 
e.getMessage());
             }
             if (!diffIndexDefinition.hasProperty("info")) {
-                diffIndexDefinition.setProperty("info", "This diff is are 
automatically merged with other indexes. See 
https://oak-indexing.github.io/oakTools/simplified.html";);
+                diffIndexDefinition.setProperty("info", "This diff is 
automatically merged with other indexes. See 
https://oak-indexing.github.io/oakTools/simplified.html";);
             }
         }
         return diffs;
@@ -131,7 +131,7 @@ public class DiffIndex {
      * @param diffs the json object with the combined diffs
      */
     private static void processDiffs(NodeStore store, NodeBuilder 
indexDefinitions, JsonObject diffs) {
-        LOG.info("Processing a diffs");
+        LOG.info("Processing diffs");
         JsonObject repositoryDefinitions = 
RootIndexesListService.getRootIndexDefinitions(indexDefinitions);
         LOG.debug("Index list {}", repositoryDefinitions);
         try {
@@ -236,7 +236,7 @@ public class DiffIndex {
             }
         }
         for (String r : toRemove) {
-            LOG.info("Removing old index  {}", r);
+            LOG.info("Removing old index {}", r);
             definitions.child(r).remove();
             updateNodetypeIndexForPath(definitions, r, false);
         }
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 7183828db9..2dbb72996d 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
@@ -25,7 +25,9 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.TreeMap;
+import java.util.TreeSet;
 
 import org.apache.jackrabbit.oak.commons.StringUtils;
 import org.apache.jackrabbit.oak.commons.json.JsonObject;
@@ -68,6 +70,17 @@ public class DiffIndexMerger {
     // whether to log at info level
     private final static boolean LOG_AT_INFO_LEVEL = 
Boolean.getBoolean("oak.diffIndex.logAtInfoLevel");
 
+    // the set of top-level properties that is not allowed to be added to an 
existing index
+    private final static Set<String> 
REJECTED_TOP_LEVEL_PROPS_FOR_EXISTING_INDEX = Set.of("selectionPolicy", 
"valueRegex", "queryFilterRegex", "excludedPaths", "queryPaths");
+
+    // the set of child properties that is not allowed to be added if the 
property is already indexed
+    // eg. the "name" property may not need to be set if the existing property 
doesn't have it yet (eg. a function-based index),
+    // or the "function" property may not need to be set unless if it already 
exists (eg. a name-based index)
+    private final static Set<String> REJECTED_PROPS_FOR_EXISTING_PROPERTY = 
Set.of("isRegexp", "index", "function", "name");
+
+    // set of properties that are allowed to be changed if the property 
already exists
+    private final static Set<String> ALLOW_CHANGING_EXISTING_PROPERTY = 
Set.of("boost", "weight");
+
     private String[] unsupportedIncludedPaths;
     private boolean deleteCreatesDummyIndex;
     private boolean deleteCopiesOutOfTheBoxIndex;
@@ -106,7 +119,7 @@ public class DiffIndexMerger {
 
         // read the diff.index.optimizer explicitly,
         // because it's a not a regular index definition,
-        // and so in the repositoryDefinitions
+        // and so it is not in the repositoryDefinitions
         if (repositoryNodeStore != null) {
             Map<String, JsonObject> diffInRepo = 
readDiffIndex(repositoryNodeStore, DIFF_INDEX_OPTIMIZER);
             combined.getChildren().putAll(diffInRepo);
@@ -160,7 +173,12 @@ public class DiffIndexMerger {
             String key = e.getKey();
             JsonObject value = e.getValue();
             if (key.startsWith("/oak:index/")) {
-                LOG.warn("The key should contains just the index name, without 
the '/oak:index' prefix for key {}", key);
+
+//                document this:
+//                enumerate error messages
+//                do not log but write to index def
+
+                LOG.warn("The key should contain just the index name, without 
the '/oak:index' prefix for key {}", key);
                 key = key.substring("/oak:index/".length());
             }
             log("Processing {}", key);
@@ -237,6 +255,8 @@ public class DiffIndexMerger {
         for (Entry<String, JsonObject> e : indexDefs.getChildren().entrySet()) 
{
             String key = e.getKey();
             JsonObject value = e.getValue();
+            // merged indexes always contain "-custom-". Other indexes may in 
theory contain that term,
+            // but then they do not contain "mergeInfo".
             if (key.indexOf("-custom-") < 0 || 
!value.getProperties().containsKey("mergeInfo")) {
                 continue;
             }
@@ -327,6 +347,7 @@ public class DiffIndexMerger {
         log("Latest product: {}", latestProductKey);
         log("Latest customized: {}", latestCustomizedKey);
         if (latestProduct == null) {
+            // if it's not a product index, then verify it's a correctly named 
custom index
             if (indexName.indexOf('.') >= 0) {
                 // a fully custom index needs to contains a dot
                 log("Fully custom index {}", indexName);
@@ -378,7 +399,7 @@ public class DiffIndexMerger {
         } else {
             latestIndexVersion = 
combined.getChildren().get(latestCustomizedKey);
         }
-        JsonObject mergedDef = cleanedAndNormalized(switchToLucene(merged));
+        JsonObject mergedDef = 
cleanedAndNormalized(switchToLuceneIfNeeded(merged));
         // compute merge checksum for later, but do not yet add
         String mergeChecksum = computeMergeChecksum(mergedDef);
         // get the merge checksum before cleaning (cleaning removes it) - if 
available
@@ -388,7 +409,7 @@ public class DiffIndexMerger {
             key = prefix + indexName + "-1-custom-1";
         } else {
             String latestMergeChecksum = 
JsonNodeUpdater.oakStringValue(latestIndexVersion, "mergeChecksum");
-            JsonObject latestDef = 
cleanedAndNormalized(switchToLucene(latestIndexVersion));
+            JsonObject latestDef = 
cleanedAndNormalized(switchToLuceneIfNeeded(latestIndexVersion));
             if (isSameIgnorePropertyOrder(mergedDef, latestDef)) {
                 // normal case: no change
                 // (even if checksums do not match: checksums might be missing 
or manipulated)
@@ -510,13 +531,12 @@ public class DiffIndexMerger {
      * @param indexDef the index definition (is not changed by this method)
      * @return the lucene version (a new JSON object)
      */
-    public static JsonObject switchToLucene(JsonObject indexDef) {
+    public static JsonObject switchToLuceneIfNeeded(JsonObject indexDef) {
         JsonObject obj = JsonObject.fromJson(indexDef.toString(), true);
         String type = JsonNodeUpdater.oakStringValue(obj, "type");
-        if (type == null || !"elasticsearch".equals(type) ) {
-            return obj;
+        if ("elasticsearch".equals(type) ) {
+            switchToLuceneChildren(obj);
         }
-        switchToLuceneChildren(obj);
         return obj;
     }
 
@@ -627,13 +647,16 @@ public class DiffIndexMerger {
      */
     public JsonObject processMerge(JsonObject productIndex, JsonObject diff) {
         JsonObject result;
+        boolean isNew;
         if (productIndex == null) {
             // fully custom index
             result = new JsonObject(true);
+            isNew = true;
         } else {
             result = JsonObject.fromJson(productIndex.toString(), true);
+            isNew = false;
         }
-        mergeInto("", diff, result);
+        mergeInto("", diff, result, isNew);
         addPrimaryType("", result);
         return result;
     }
@@ -672,20 +695,51 @@ public class DiffIndexMerger {
      * @param path the path
      * @param diff the diff (what to merge)
      * @param target where to merge into
+     * @param isNew whether the target node is newly created (didn't exist 
before)
      */
-    private void mergeInto(String path, JsonObject diff, JsonObject target) {
+    private void mergeInto(String path, JsonObject diff, JsonObject target, 
boolean isNew) {
         for (String p : diff.getProperties().keySet()) {
             if (path.isEmpty()) {
                 if ("jcr:primaryType".equals(p)) {
                     continue;
                 }
             }
+            if (!isNew) {
+                // for existing nodes, we do a few more checks before the merge
+                if (path.isEmpty() && 
REJECTED_TOP_LEVEL_PROPS_FOR_EXISTING_INDEX.contains(p)) {
+                    // at the top level, some properties (eg. selectionPolicy) 
are not allowed to be added to an existing index
+                    LOG.warn("Ignoring new top-level property {} at {} for 
existing index", p, path);
+                    continue;
+                }
+                if (REJECTED_PROPS_FOR_EXISTING_PROPERTY.contains(p)) {
+                    // the some properties are not allowed to be added if the 
node already exists
+                    LOG.warn("Ignoring new property {} at {} for existing 
child", p, path);
+                    continue;
+                }
+            }
             if (target.getProperties().containsKey(p)) {
-                // we do not currently allow to overwrite most existing 
properties
-                if (p.equals("boost")) {
+                // we do not currently allow to overwrite most existing 
properties,
+                // except for:
+                if (ALLOW_CHANGING_EXISTING_PROPERTY.contains(p)) {
                     // allow overwriting the boost value
                     LOG.info("Overwrite property {} value at {}", p, path);
                     target.getProperties().put(p, diff.getProperties().get(p));
+                } else if (path.isEmpty() && (p.equals("includedPaths") || 
p.equals("queryPaths") || p.equals("tags"))) {
+                    // merge includedPaths, queryPaths, and tags
+                    if (target.getProperties().containsKey(p)) {
+                        // but only if the old value is set, such that it 
contains more entries
+                        // (if the property is not set, we would make it more 
restrictive,
+                        // which is not allowed)
+                        TreeSet<String> oldSet = 
JsonNodeUpdater.getStringSet(target.getProperties().get(p));
+                        TreeSet<String> newSet = 
JsonNodeUpdater.getStringSet(diff.getProperties().get(p));
+                        TreeSet<String> mergedSet = new 
TreeSet<String>(oldSet);
+                        mergedSet.addAll(newSet);
+                        JsopBuilder buff = new JsopBuilder().array();
+                        for(String v : mergedSet) {
+                            buff.value(v);
+                        }
+                        target.getProperties().put(p, 
buff.endArray().toString());
+                    }
                 } else {
                     LOG.warn("Ignoring existing property {} at {}", p, path);
                 }
@@ -694,8 +748,10 @@ public class DiffIndexMerger {
             }
         }
         for (String c : diff.getChildren().keySet()) {
+            boolean childIsNew;
             String targetChildName = c;
             if (!target.getChildren().containsKey(c)) {
+                childIsNew = true;
                 if (path.endsWith("/properties")) {
                     // search for a property with the same "name" value
                     String propertyName = 
diff.getChildren().get(c).getProperties().get("name");
@@ -720,8 +776,10 @@ public class DiffIndexMerger {
                     // only create the child (properties are added below)
                     target.getChildren().put(c, new JsonObject());
                 }
+            } else {
+                childIsNew = false;
             }
-            mergeInto(path + "/" + targetChildName, diff.getChildren().get(c), 
target.getChildren().get(targetChildName));
+            mergeInto(path + "/" + targetChildName, diff.getChildren().get(c), 
target.getChildren().get(targetChildName), childIsNew);
         }
         if (target.getProperties().isEmpty() && 
target.getChildren().isEmpty()) {
             if (deleteCreatesDummyIndex) {
diff --git 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/JsonNodeUpdater.java
 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/JsonNodeUpdater.java
index a5dabdbce7..99e7a74452 100644
--- 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/JsonNodeUpdater.java
+++ 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/JsonNodeUpdater.java
@@ -66,14 +66,16 @@ public class JsonNodeUpdater {
     /**
      * Add a replace a node, including all child nodes, in the node store.
      *
+     * @param builder the builder to add the node to
      * @param nodeStore the target node store
-     * @param targetPath the target path where the node(s) is/are replaced
+     * @param targetPath the target path (relative to the builder) where the 
node(s) is/are replaced
      * @param nodeType the node type of the new node (eg. "nt:unstructured")
      * @param jsonString the json string with the node data
      * @throws CommitFailedException if storing the nodes failed
      * @throws IOException if storing a blob failed
      */
-    public static void addOrReplace(NodeBuilder builder, NodeStore nodeStore, 
String targetPath, String nodeType, String jsonString) throws 
CommitFailedException, IOException {
+    public static void addOrReplace(NodeBuilder builder, NodeStore nodeStore, 
String targetPath, 
+            String nodeType, String jsonString) throws CommitFailedException, 
IOException {
         LOG.info("Storing {}: {}", targetPath, jsonString);
         if (nodeType.contains("/")) {
             throw new IllegalStateException("Illegal node type: " + nodeType);
@@ -245,6 +247,13 @@ public class JsonNodeUpdater {
         }
     }
 
+    /**
+     * Parse a raw JSON value and convert it to a set of strings. Also 
supported is a single string value.
+     * Everything else (numbers, booleans, etc.) is not supported and returns 
null.
+     * 
+     * @param value the raw JSON value
+     * @return a set of strings or null
+     */
     public static TreeSet<String> getStringSet(String value) {
         if (value == null) {
             return null;
diff --git 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/RootIndexesListService.java
 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/RootIndexesListService.java
index 806278f154..f09e495f51 100644
--- 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/RootIndexesListService.java
+++ 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diff/RootIndexesListService.java
@@ -100,6 +100,7 @@ public class RootIndexesListService implements 
IndexPathService {
             return list;
         }
         for (ChildNodeEntry cn : oakIndex.getChildNodeEntries()) {
+            // ignore entries that are not of type oak:QueryIndexDefinition
             if (!IndexConstants.INDEX_DEFINITIONS_NODE_TYPE
                     .equals(cn.getNodeState().getName("jcr:primaryType"))) {
                 continue;
diff --git 
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/JsonNodeUpdaterTest.java
 
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/JsonNodeUpdaterTest.java
index 4bcc5db181..4462820011 100644
--- 
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/JsonNodeUpdaterTest.java
+++ 
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/JsonNodeUpdaterTest.java
@@ -29,6 +29,7 @@ import java.util.TreeSet;
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.commons.json.JsonObject;
+import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
 import org.apache.jackrabbit.oak.json.JsonUtils;
 import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
 import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
@@ -59,24 +60,24 @@ public class JsonNodeUpdaterTest {
         NodeBuilder builder = ns.getRoot().builder();
         JsonNodeUpdater.addOrReplace(builder, ns, "/test", "nt:test", 
json.toString());
         ns.merge(builder, new EmptyHook(), CommitInfo.EMPTY);
-        String json2 = JsonUtils.nodeStateToJson(ns.getRoot(), 5);
-        json2 = json2.replaceAll("jcr:uuid\" : \".*\"", "jcr:uuid\" : 
\"...\"");
+        String json2 = reformatJson(JsonUtils.nodeStateToJson(ns.getRoot(), 
5));
+        json2 = json2.replaceAll("jcr:uuid\": \".*\"", "jcr:uuid\": \"...\"");
         assertEquals("{\n"
-                + "  \"test\" : {\n"
-                + "    \"queryPaths\" : \"/same\",\n"
-                + "    \"includedPaths\" : \"/same\",\n"
-                + "    \"jcr:primaryType\" : \"nt:unstructured\",\n"
-                + "    \"type\" : \"lucene\",\n"
-                + "    \":childOrder\" : [ \"diff.json\" ],\n"
-                + "    \"diff.json\" : {\n"
-                + "      \"jcr:primaryType\" : \"nt:file\",\n"
-                + "      \":childOrder\" : [ \"jcr:content\" ],\n"
-                + "      \"jcr:content\" : {\n"
-                + "        \"jcr:mimeType\" : \"application/json\",\n"
-                + "        \"jcr:data\" : \"test\",\n"
-                + "        \"jcr:primaryType\" : \"nt:resource\",\n"
-                + "        \"jcr:uuid\" : \"...\",\n"
-                + "        \":childOrder\" : [ ]\n"
+                + "  \"test\": {\n"
+                + "    \"queryPaths\": \"/same\",\n"
+                + "    \"includedPaths\": \"/same\",\n"
+                + "    \"jcr:primaryType\": \"nt:unstructured\",\n"
+                + "    \"type\": \"lucene\",\n"
+                + "    \":childOrder\": [\"diff.json\"],\n"
+                + "    \"diff.json\": {\n"
+                + "      \"jcr:primaryType\": \"nt:file\",\n"
+                + "      \":childOrder\": [\"jcr:content\"],\n"
+                + "      \"jcr:content\": {\n"
+                + "        \"jcr:mimeType\": \"application/json\",\n"
+                + "        \"jcr:data\": \"test\",\n"
+                + "        \"jcr:primaryType\": \"nt:resource\",\n"
+                + "        \"jcr:uuid\": \"...\",\n"
+                + "        \":childOrder\": []\n"
                 + "      }\n"
                 + "    }\n"
                 + "  }\n"
@@ -90,18 +91,18 @@ public class JsonNodeUpdaterTest {
         JsonNodeUpdater.addOrReplace(builder, ns, "/test", "nt:test", 
json.toString());
         ns.merge(builder, new EmptyHook(), CommitInfo.EMPTY);
         assertEquals("{\n"
-                + "  \"test\" : {\n"
-                + "    \"number\" : 1,\n"
-                + "    \"double2\" : 1.0,\n"
-                + "    \"jcr:primaryType\" : \"nt:test\",\n"
-                + "    \":childOrder\" : [ \"child2\" ],\n"
-                + "    \"child2\" : {\n"
-                + "      \"y\" : 2,\n"
-                + "      \"jcr:primaryType\" : \"nt:test\",\n"
-                + "      \":childOrder\" : [ ]\n"
+                + "  \"test\": {\n"
+                + "    \"number\": 1,\n"
+                + "    \"double2\": 1.0,\n"
+                + "    \"jcr:primaryType\": \"nt:test\",\n"
+                + "    \":childOrder\": [\"child2\"],\n"
+                + "    \"child2\": {\n"
+                + "      \"y\": 2,\n"
+                + "      \"jcr:primaryType\": \"nt:test\",\n"
+                + "      \":childOrder\": []\n"
                 + "    }\n"
                 + "  }\n"
-                + "}", JsonUtils.nodeStateToJson(ns.getRoot(), 5));
+                + "}", reformatJson(JsonUtils.nodeStateToJson(ns.getRoot(), 
5)));
     }
 
     @Test
@@ -118,21 +119,21 @@ public class JsonNodeUpdaterTest {
         JsonNodeUpdater.addOrReplace(builder, ns, "/test", "nt:test", 
json.toString());
         ns.merge(builder, new EmptyHook(), CommitInfo.EMPTY);
         assertEquals("{\n"
-                + "  \"test\" : {\n"
-                + "    \"number\" : 1,\n"
-                + "    \"blob\" : \"test\",\n"
-                + "    \"string\" : \"hello\",\n"
-                + "    \"array\" : [ \"a\", \"b\" ],\n"
-                + "    \"double\" : 1.0,\n"
-                + "    \"jcr:primaryType\" : \"nt:test\",\n"
-                + "    \":childOrder\" : [ \"child\" ],\n"
-                + "    \"child\" : {\n"
-                + "      \"x\" : 1,\n"
-                + "      \"jcr:primaryType\" : \"nt:test\",\n"
-                + "      \":childOrder\" : [ ]\n"
+                + "  \"test\": {\n"
+                + "    \"number\": 1,\n"
+                + "    \"blob\": \"test\",\n"
+                + "    \"string\": \"hello\",\n"
+                + "    \"array\": [\"a\", \"b\"],\n"
+                + "    \"double\": 1.0,\n"
+                + "    \"jcr:primaryType\": \"nt:test\",\n"
+                + "    \":childOrder\": [\"child\"],\n"
+                + "    \"child\": {\n"
+                + "      \"x\": 1,\n"
+                + "      \"jcr:primaryType\": \"nt:test\",\n"
+                + "      \":childOrder\": []\n"
                 + "    }\n"
                 + "  }\n"
-                + "}", JsonUtils.nodeStateToJson(ns.getRoot(), 5));
+                + "}", reformatJson(JsonUtils.nodeStateToJson(ns.getRoot(), 
5)));
 
         json = JsonObject.fromJson(
                 "{\"number\":1," +
@@ -142,18 +143,23 @@ public class JsonNodeUpdaterTest {
         JsonNodeUpdater.addOrReplace(builder, ns, "/test", "nt:test", 
json.toString());
         ns.merge(builder, new EmptyHook(), CommitInfo.EMPTY);
         assertEquals("{\n"
-                + "  \"test\" : {\n"
-                + "    \"number\" : 1,\n"
-                + "    \"double2\" : 1.0,\n"
-                + "    \"jcr:primaryType\" : \"nt:test\",\n"
-                + "    \":childOrder\" : [ \"child2\" ],\n"
-                + "    \"child2\" : {\n"
-                + "      \"y\" : 2,\n"
-                + "      \"jcr:primaryType\" : \"nt:test\",\n"
-                + "      \":childOrder\" : [ ]\n"
+                + "  \"test\": {\n"
+                + "    \"number\": 1,\n"
+                + "    \"double2\": 1.0,\n"
+                + "    \"jcr:primaryType\": \"nt:test\",\n"
+                + "    \":childOrder\": [\"child2\"],\n"
+                + "    \"child2\": {\n"
+                + "      \"y\": 2,\n"
+                + "      \"jcr:primaryType\": \"nt:test\",\n"
+                + "      \":childOrder\": []\n"
                 + "    }\n"
                 + "  }\n"
-                + "}", JsonUtils.nodeStateToJson(ns.getRoot(), 5));
+                + "}", reformatJson(JsonUtils.nodeStateToJson(ns.getRoot(), 
5)));
+    }
+
+    String reformatJson(String json) {
+        // replace \r\n with \n
+        return JsopBuilder.prettyPrint(json);
     }
 
     @Test
@@ -210,17 +216,17 @@ public class JsonNodeUpdaterTest {
         JsonNodeUpdater.addOrReplace(builder, ns, "/test", "nt:test", 
json.toString());
         ns.merge(builder, new EmptyHook(), CommitInfo.EMPTY);
         assertEquals("{\n"
-                + "  \"test\" : {\n"
-                + "    \"namValue\" : \"acme:Test\",\n"
-                + "    \"boolTrue\" : true,\n"
-                + "    \"boolFalse\" : false,\n"
-                + "    \"datValue\" : \"2024-01-19\",\n"
-                + "    \"escapedArray\" : [ \"/content/path\" ],\n"
-                + "    \"jcr:primaryType\" : \"nt:test\",\n"
-                + "    \"strValue\" : \"hello\",\n"
-                + "    \":childOrder\" : [ ]\n"
+                + "  \"test\": {\n"
+                + "    \"namValue\": \"acme:Test\",\n"
+                + "    \"boolTrue\": true,\n"
+                + "    \"boolFalse\": false,\n"
+                + "    \"datValue\": \"2024-01-19\",\n"
+                + "    \"escapedArray\": [\"/content/path\"],\n"
+                + "    \"jcr:primaryType\": \"nt:test\",\n"
+                + "    \"strValue\": \"hello\",\n"
+                + "    \":childOrder\": []\n"
                 + "  }\n"
-                + "}", JsonUtils.nodeStateToJson(ns.getRoot(), 5));
+                + "}", reformatJson(JsonUtils.nodeStateToJson(ns.getRoot(), 
5)));
     }
 
 }
diff --git 
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/MergeTest.java
 
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/MergeTest.java
index b8e63fe9c3..76cafdc642 100644
--- 
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/MergeTest.java
+++ 
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/diff/MergeTest.java
@@ -160,6 +160,120 @@ public class MergeTest {
                 + "}", merged);
     }
 
+    @Test
+    public void ignoredNewPropertiesForExisting() {
+        // for existing indexes or properties,
+        // some additions are not allowed, as they could result in the index
+        // to be not usable for existing queries
+        // (eg. the selectionPolicy may not be set if the index already exists)
+        String merged = new 
DiffIndexMerger().processMerge(JsonObject.fromJson("{\n"
+                + "        \"jcr:primaryType\": \"nam:oak:IndexDefinition\",\n"
+                + "        \"type\": \"lucene\",\n"
+                + "        \"async\": [\"async\", \"nrt\"],\n"
+                + "        \"indexRules\": {\n"
+                + "            \"dam:Asset\": {\n"
+                + "                \"properties\": {\n"
+                + "                    \"named\": {\n"
+                + "                        \"name\": \"x\"\n"
+                + "                    },\n"
+                + "                    \"functionBased\": {\n"
+                + "                        \"function\": \"upper(x)\"\n"
+                + "                    }\n"
+                + "                }\n"
+                + "            }\n"
+                + "        }\n"
+                + "    }\n"
+                + "", true), JsonObject.fromJson("{ \n"
+                + "        \"tags\": [\"newTag\"],\n"
+                + "        \"selectionPolicy\": \"tag\",\n"
+                + "        \"indexRules\": {\n"
+                + "            \"dam:Asset\": {\n"
+                + "                \"properties\": {\n"
+                + "                    \"named\": {\n"
+                + "                        \"function\": \"upper(y)\",\n"
+                + "                        \"weight\": 10.0\n"
+                + "                    },\n"
+                + "                    \"functionBased\": {\n"
+                + "                        \"name\": \"y\",\n"
+                + "                        \"weight\": 20.0\n"
+                + "                    }\n"
+                + "                }\n"
+                + "            }\n"
+                + "        }\n"
+                + "  }", true)).toString();
+        assertEquals("{\n"
+                + "  \"jcr:primaryType\": \"nam:oak:IndexDefinition\",\n"
+                + "  \"type\": \"lucene\",\n"
+                + "  \"async\": [\"async\", \"nrt\"],\n"
+                + "  \"tags\": [\"newTag\"],\n"
+                + "  \"indexRules\": {\n"
+                + "    \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
+                + "    \"dam:Asset\": {\n"
+                + "      \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
+                + "      \"properties\": {\n"
+                + "        \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
+                + "        \"named\": {\n"
+                + "          \"name\": \"x\",\n"
+                + "          \"weight\": 10.0,\n"
+                + "          \"jcr:primaryType\": \"nam:nt:unstructured\"\n"
+                + "        },\n"
+                + "        \"functionBased\": {\n"
+                + "          \"function\": \"upper(x)\",\n"
+                + "          \"weight\": 20.0,\n"
+                + "          \"jcr:primaryType\": \"nam:nt:unstructured\"\n"
+                + "        }\n"
+                + "      }\n"
+                + "    }\n"
+                + "  }\n"
+                + "}", merged);
+    }
+
+    @Test
+    public void customizeIncludedPathsQueryPathsAndTags() {
+        String merged = new 
DiffIndexMerger().processMerge(JsonObject.fromJson("{\n"
+                + "        \"jcr:primaryType\": \"nam:oak:IndexDefinition\",\n"
+                + "        \"type\": \"lucene\",\n"
+                + "        \"async\": [\"async\", \"nrt\"],\n"
+                + "        \"tags\": [\"abc\", \"def\"],\n"
+                + "        \"includedPaths\": \"/content/dam\",\n"
+                + "        \"indexRules\": {\n"
+                + "            \"dam:Asset\": {\n"
+                + "                \"properties\": {\n"
+                + "                    \"x\": {\n"
+                + "                        \"name\": \"x\",\n"
+                + "                        \"propertyIndex\": true\n"
+                + "                    }\n"
+                + "                }\n"
+                + "            }\n"
+                + "        }\n"
+                + "    }\n"
+                + "", true), JsonObject.fromJson("{ \n"
+                + "    \"includedPaths\": [\"/content/dam\", 
\"/content/additional\" ],\n"
+                + "    \"tags\": [\"def\", \"ghi\" ]\n"
+                + "  }", true)).toString();
+        assertEquals("{\n"
+                + "  \"jcr:primaryType\": \"nam:oak:IndexDefinition\",\n"
+                + "  \"type\": \"lucene\",\n"
+                + "  \"async\": [\"async\", \"nrt\"],\n"
+                + "  \"tags\": [\"abc\", \"def\", \"ghi\"],\n"
+                + "  \"includedPaths\": [\"/content/additional\", 
\"/content/dam\"],\n"
+                + "  \"indexRules\": {\n"
+                + "    \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
+                + "    \"dam:Asset\": {\n"
+                + "      \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
+                + "      \"properties\": {\n"
+                + "        \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
+                + "        \"x\": {\n"
+                + "          \"name\": \"x\",\n"
+                + "          \"propertyIndex\": true,\n"
+                + "          \"jcr:primaryType\": \"nam:nt:unstructured\"\n"
+                + "        }\n"
+                + "      }\n"
+                + "    }\n"
+                + "  }\n"
+                + "}", merged);
+    }
+
     @Test
     public void renamedFunction() {
         // A function might be indexed twice, by adding two children to the 
"properties" node

Reply via email to