Author: chetanm
Date: Thu Oct 26 11:26:12 2017
New Revision: 1813387

URL: http://svn.apache.org/viewvc?rev=1813387&view=rev
Log:
OAK-6857 - Lucene unique index should check path validity for uniqueness 
constraint

LuceneIndexEditor does not traverses a deleted subtree so the property
index created by it may not reflect the deletes. So apply the check for
path validity for paths returned by both async and sync index

Modified:
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/UniquenessConstraintValidator.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/PropertyIndexCleanerTest.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/SynchronousPropertyIndexTest.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/UniquenessConstraintValidatorTest.java

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/UniquenessConstraintValidator.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/UniquenessConstraintValidator.java?rev=1813387&r1=1813386&r2=1813387&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/UniquenessConstraintValidator.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/UniquenessConstraintValidator.java
 Thu Oct 26 11:26:12 2017
@@ -19,6 +19,7 @@
 
 package org.apache.jackrabbit.oak.plugins.index.lucene.property;
 
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -35,7 +36,6 @@ import org.apache.jackrabbit.oak.spi.sta
 import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Iterables.filter;
 import static org.apache.jackrabbit.oak.api.CommitFailedException.CONSTRAINT;
 
 /**
@@ -66,11 +66,18 @@ public class UniquenessConstraintValidat
     public void validate() throws CommitFailedException {
         for (Map.Entry<String, String> e : uniqueKeys.entries()) {
             String propertyRelativePath = e.getKey();
-            Iterable<String> indexedPaths = 
getIndexedPaths(propertyRelativePath, e.getValue());
+            String value = e.getValue();
+            Iterable<String> indexedPaths = 
getIndexedPaths(propertyRelativePath, value);
             Set<String> allPaths = ImmutableSet.copyOf(indexedPaths);
+
+            //If more than one match found then filter out stale paths
             if (allPaths.size() > 1) {
-                String msg = String.format("Uniqueness constraint violated for 
property [%s] for " +
-                        "index [%s]. IndexedPaths %s", propertyRelativePath, 
indexPath, allPaths);
+                allPaths = getValidPaths(allPaths, propertyRelativePath, 
value);
+            }
+
+            if (allPaths.size() > 1) {
+                String msg = String.format("Uniqueness constraint violated for 
property [%s] with value [%s] for " +
+                        "index [%s]. Indexed paths %s", propertyRelativePath, 
value, indexPath, allPaths);
                 throw new CommitFailedException(CONSTRAINT, 30, msg);
             }
         }
@@ -83,23 +90,36 @@ public class UniquenessConstraintValidat
     private Iterable<String> getIndexedPaths(String propertyRelativePath, 
String value) {
         return Iterables.concat(
                 firstStore.getIndexedPaths(propertyRelativePath, value),
-                getValidPathsFromSecondStore(propertyRelativePath, value)
+                secondStore.getIndexedPaths(propertyRelativePath, value)
         );
     }
 
-    private Iterable<String> getValidPathsFromSecondStore(String 
propertyRelativePath, String value) {
-        return filter(secondStore.getIndexedPaths(propertyRelativePath, value),
-                path -> {
-                    NodeState node = NodeStateUtils.getNode(rootState, path);
-                    if (!node.exists()) {
-                        return false;
-                    }
-                    PropertyState uniqueProp = getValue(node, 
propertyRelativePath);
-                    if (uniqueProp == null) {
-                        return false;
-                    }
-                    return uniqueProp.getValue(Type.STRING).equals(value);
-                });
+    /**
+     * Paths reported by indexes may be based on stale data. So revalidate by 
checking reported paths are
+     * valid and refers to indexed value or not
+     */
+    private Set<String> getValidPaths(Set<String> allPaths, String 
propertyRelativePath, String value) {
+        Set<String> validPaths = new HashSet<>();
+        for (String path : allPaths) {
+            NodeState node = NodeStateUtils.getNode(rootState, path);
+            if (!node.exists()) {
+                continue;
+            }
+
+            PropertyState uniqueProp = getValue(node, propertyRelativePath);
+            if (uniqueProp == null) {
+                continue;
+            }
+
+            //Property can be MVP. So check if any of them matches
+            for (String v : uniqueProp.getValue(Type.STRINGS)) {
+                if (v.equals(value)) {
+                    validPaths.add(path);
+                    break;
+                }
+            }
+        }
+        return validPaths;
     }
 
     private static PropertyState getValue(NodeState node, String 
propertyRelativePath) {

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/PropertyIndexCleanerTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/PropertyIndexCleanerTest.java?rev=1813387&r1=1813386&r2=1813387&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/PropertyIndexCleanerTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/PropertyIndexCleanerTest.java
 Thu Oct 26 11:26:12 2017
@@ -159,6 +159,7 @@ public class PropertyIndexCleanerTest {
         clock.waitUntil(1000);
 
         NodeBuilder builder = nodeStore.getRoot().builder();
+        builder.child("a").setProperty("foo", "bar");
         PropertyIndexUpdateCallback cb = newCallback(builder, indexPath);
         propertyUpdated(cb, indexPath, "/a", "foo", "bar");
         cb.done();
@@ -167,6 +168,7 @@ public class PropertyIndexCleanerTest {
         clock.waitUntil(1150);
 
         builder = nodeStore.getRoot().builder();
+        builder.child("b").setProperty("foo", "bar2");
         cb = newCallback(builder, indexPath);
         propertyUpdated(cb, indexPath, "/b", "foo", "bar2");
         cb.done();
@@ -191,6 +193,7 @@ public class PropertyIndexCleanerTest {
         assertThat(query(indexPath, "foo", "bar2"), containsInAnyOrder("/b"));
 
         builder = nodeStore.getRoot().builder();
+        builder.child("c").setProperty("foo", "bar2");
         cb = newCallback(builder, indexPath);
         propertyUpdated(cb, indexPath, "/c", "foo", "bar2");
 
@@ -312,7 +315,7 @@ public class PropertyIndexCleanerTest {
     }
 
     private PropertyIndexUpdateCallback newCallback(NodeBuilder builder, 
String indexPath) {
-        return new PropertyIndexUpdateCallback(indexPath, child(builder, 
indexPath), nodeStore.getRoot(), clock);
+        return new PropertyIndexUpdateCallback(indexPath, child(builder, 
indexPath), builder.getNodeState(), clock);
     }
 
     private PropertyDefinition pd(String indexPath, String propName){

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/SynchronousPropertyIndexTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/SynchronousPropertyIndexTest.java?rev=1813387&r1=1813386&r2=1813387&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/SynchronousPropertyIndexTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/SynchronousPropertyIndexTest.java
 Thu Oct 26 11:26:12 2017
@@ -242,6 +242,23 @@ public class SynchronousPropertyIndexTes
     }
 
     @Test
+    public void uniqueProperty_RemoveAndAddInSameCommit() throws Exception{
+        defnb.async("async", "nrt");
+        defnb.indexRule("nt:base").property("foo").propertyIndex().unique();
+
+        addIndex(indexPath, defnb);
+        root.commit();
+
+        createPath("/a").setProperty("foo", "bar");
+        root.commit();
+        runAsyncIndex();
+
+        createPath("/a").remove();
+        createPath("/b").setProperty("foo", "bar");
+        root.commit();
+    }
+
+    @Test
     public void nonUniqueIndex() throws Exception{
         defnb.async("async", "nrt");
         defnb.indexRule("nt:base").property("foo").propertyIndex().sync();

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/UniquenessConstraintValidatorTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/UniquenessConstraintValidatorTest.java?rev=1813387&r1=1813386&r2=1813387&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/UniquenessConstraintValidatorTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/UniquenessConstraintValidatorTest.java
 Thu Oct 26 11:26:12 2017
@@ -31,6 +31,7 @@ import org.junit.Test;
 import static java.util.Collections.singletonList;
 import static org.apache.jackrabbit.oak.InitialContent.INITIAL_CONTENT;
 import static org.apache.jackrabbit.oak.api.CommitFailedException.CONSTRAINT;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil.child;
 import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
 import static 
org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
 import static org.hamcrest.Matchers.containsString;
@@ -48,13 +49,15 @@ public class UniquenessConstraintValidat
     public void singleUniqueProperty() throws Exception{
         defnb.indexRule("nt:base").property("foo").unique();
 
+        updateRootState("/a", "foo", "bar");
+        updateRootState("/b", "foo", "bar");
+
         PropertyUpdateCallback callback = newCallback();
 
         callback.propertyUpdated("/a", "foo", pd("foo"),
                 null, createProperty("foo", "bar"));
         callback.propertyUpdated("/b", "foo", pd("foo"),
                 null, createProperty("foo", "bar"));
-
         try {
             callback.done();
             fail();
@@ -87,16 +90,20 @@ public class UniquenessConstraintValidat
     public void firstStore_PreExist() throws Exception{
         defnb.indexRule("nt:base").property("foo").unique();
 
+        updateRootState("/a", "foo", "bar");
+
         PropertyUpdateCallback callback = newCallback();
         propertyUpdated(callback, "/a", "foo", "bar");
 
-        builder = builder.getNodeState().builder();
+        refreshBuilder();
+        updateRootState("/b", "foo", "bar");
 
         callback = newCallback();
         propertyUpdated(callback, "/b", "foo", "bar");
         callback.done();
     }
-    
+
+
     @Test
     public void secondStore_SamePath() throws Exception{
         defnb.indexRule("nt:base").property("foo").unique();
@@ -115,9 +122,8 @@ public class UniquenessConstraintValidat
     public void secondStore_DiffPath() throws Exception{
         defnb.indexRule("nt:base").property("foo").unique();
 
-        NodeBuilder rootBuilder = root.builder();
-        rootBuilder.child("b").setProperty("foo", "bar");
-        root = rootBuilder.getNodeState();
+        updateRootState("/b", "foo", "bar");
+        updateRootState("/a", "foo", "bar");
 
         PropertyIndexUpdateCallback callback = newCallback();
         propertyUpdated(callback, "/a", "foo", "bar");
@@ -179,9 +185,8 @@ public class UniquenessConstraintValidat
     public void secondStore_RelativeProperty() throws Exception{
         defnb.indexRule("nt:base").property("jcr:content/foo").unique();
 
-        NodeBuilder rootBuilder = root.builder();
-        rootBuilder.child("b").child("jcr:content").setProperty("foo", "bar");
-        root = rootBuilder.getNodeState();
+        updateRootState("/b/jcr:content", "foo", "bar");
+        updateRootState("/a/jcr:content", "foo", "bar");
 
         PropertyIndexUpdateCallback callback = newCallback();
         propertyUpdated(callback, "/a", "jcr:content/foo", "bar");
@@ -201,6 +206,16 @@ public class UniquenessConstraintValidat
         return new PropertyIndexUpdateCallback(indexPath, builder, root);
     }
 
+    private void updateRootState(String nodePath, String propertyName, String 
value) {
+        NodeBuilder rootBuilder = root.builder();
+        child(rootBuilder, nodePath).setProperty(propertyName, value);
+        root = rootBuilder.getNodeState();
+    }
+
+    private void refreshBuilder() {
+        builder = builder.getNodeState().builder();
+    }
+
     private PropertyDefinition pd(String propName){
         IndexDefinition defn = new IndexDefinition(root, defnb.build(), 
indexPath);
         return defn.getApplicableIndexingRule("nt:base").getConfig(propName);


Reply via email to