Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java (date 1435662951000) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java (date 1435898377000) @@ -188,7 +188,7 @@ throws CommitFailedException { if (propertiesChanged || !before.exists()) { String path = getPath(); - if (addOrUpdate(path, after, before.exists())) { + if (addOrUpdate(path, before, after, before.exists())) { long indexed = context.incIndexedNodes(); if (indexed % 1000 == 0) { log.debug("{} => Indexed {} nodes...", context.getDefinition().getIndexName(), indexed); @@ -281,10 +281,10 @@ return null; // no need to recurse down the removed subtree } - private boolean addOrUpdate(String path, NodeState state, boolean isUpdate) + private boolean addOrUpdate(String path, NodeState before, NodeState after, boolean isUpdate) throws CommitFailedException { try { - Document d = makeDocument(path, state, isUpdate); + Document d = makeDocument(path, before, after, isUpdate); if (d != null) { if (log.isTraceEnabled()){ log.trace("Indexed document for {} is {}", path, d); @@ -302,13 +302,14 @@ return false; } - private Document makeDocument(String path, NodeState state, boolean isUpdate) { + private Document makeDocument(String path, NodeState before, NodeState state, boolean isUpdate) { if (!isIndexable()) { return null; } List fields = new ArrayList(); - boolean dirty = false; + boolean dirty = propertiesChanged; + for (PropertyState property : state.getProperties()) { String pname = property.getName(); @@ -327,6 +328,8 @@ } dirty |= indexProperty(path, fields, state, property, pname, pd); + // If the only property was MVP and indexed earlier and has now been emptied + dirty |= !before.hasProperty(pname) || (property.count() == 0); } dirty |= indexAggregates(path, fields, state); Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java (date 1435662951000) +++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java (date 1435898377000) @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.Calendar; import java.util.Iterator; import java.util.List; @@ -27,6 +28,7 @@ import org.apache.jackrabbit.oak.api.ContentRepository; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent; import org.apache.jackrabbit.oak.query.AbstractQueryTest; import org.apache.jackrabbit.oak.spi.commit.Observer; @@ -35,6 +37,7 @@ import org.junit.Ignore; import org.junit.Test; +import static com.google.common.collect.ImmutableList.of; import static java.util.Arrays.asList; import static junit.framework.Assert.assertEquals; import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; @@ -87,7 +90,7 @@ public void sql2() throws Exception { test("sql2.txt"); } - + @Test public void sql2FullText() throws Exception { test("sql2-fulltext.txt"); @@ -139,8 +142,9 @@ root.commit(); Iterator result = executeQuery( - "select p.[jcr:path], p2.[jcr:path] from [nt:base] as p inner join [nt:base] as p2 on ischildnode(p2, p) where p.[jcr:path] = '/'", + "select p.[jcr:path], p2.[jcr:path] from [nt:base] as p inner join [nt:base] as p2 on ischildnode(p2, p) " + + "where p.[jcr:path] = '/'", - "JCR-SQL2").iterator(); + "JCR-SQL2").iterator(); assertTrue(result.hasNext()); assertEquals("/, /children", result.next()); assertEquals("/, /jcr:system", result.next()); @@ -163,8 +167,7 @@ StringBuffer stmt = new StringBuffer(); stmt.append("/jcr:root//*[jcr:contains(., '").append(h); stmt.append("')]"); - assertQuery(stmt.toString(), "xpath", - ImmutableList.of("/test/a", "/test/b")); + assertQuery(stmt.toString(), "xpath", ImmutableList.of("/test/a", "/test/b")); // query 'world' stmt = new StringBuffer(); @@ -183,8 +186,7 @@ test.addChild("c").setProperty("name", "hello"); root.commit(); - assertQuery("/jcr:root//*[jcr:contains(., 'hello-wor*')]", "xpath", - ImmutableList.of("/test/a", "/test/b")); + assertQuery("/jcr:root//*[jcr:contains(., 'hello-wor*')]", "xpath", ImmutableList.of("/test/a", "/test/b")); assertQuery("/jcr:root//*[jcr:contains(., '*hello-wor*')]", "xpath", ImmutableList.of("/test/a", "/test/b")); @@ -304,7 +306,7 @@ @Test public void testRepSimilarAsNativeQuery() throws Exception { - String nativeQueryString = "select [jcr:path] from [nt:base] where " + + String nativeQueryString = "select [jcr:path] from [nt:base] where " + "native('lucene', 'mlt?stream.body=/test/a&mlt.fl=:path&mlt.mindf=0&mlt.mintf=0')"; Tree test = root.getTree("/").addChild("test"); test.addChild("a").setProperty("text", "Hello World"); @@ -318,7 +320,7 @@ assertEquals("/test/b", result.next()); assertFalse(result.hasNext()); } - + @Test public void testRepSimilarQuery() throws Exception { String query = "select [jcr:path] from [nt:base] where similar(., '/test/a')"; @@ -378,16 +380,16 @@ final String description = "description"; final String added = "added"; final String yes = "yes"; - + Tree t; - + // re-define the lucene index t = root.getTree("/oak:index/" + TEST_INDEX_NAME); assertTrue(t.exists()); t.remove(); root.commit(); assertFalse(root.getTree("/oak:index/" + TEST_INDEX_NAME).exists()); - + t = root.getTree("/"); Tree indexDefn = createTestIndexNode(t, LuceneIndexConstants.TYPE_LUCENE); useV2(indexDefn); @@ -398,9 +400,9 @@ TestUtil.enableForFullText(props, surname, false); TestUtil.enableForFullText(props, description, false); TestUtil.enableForOrdered(props, added); - + root.commit(); - + // creating the dataset List expected = Lists.newArrayList(); Tree content = root.getTree("/").addChild("content"); @@ -457,22 +459,49 @@ for (String s : expected) { assertTrue("wrong initial state", root.getTree(s).exists()); } - - final String statement = - "SELECT * " + + + final String statement = + "SELECT * " + "FROM [" + NT_UNSTRUCTURED + "] AS c " + "WHERE " + "( " + "c.[" + name + "] = '" + yes + "' " + - "OR CONTAINS(c.[" + surname + "], '" + yes + "') " + - "OR CONTAINS(c.[" + description + "], '" + yes + "') " + - ") " + + "OR CONTAINS(c.[" + surname + "], '" + yes + "') " + + "OR CONTAINS(c.[" + description + "], '" + yes + "') " + + ") " + "AND ISDESCENDANTNODE(c, '" + content.getPath() + "') " + "ORDER BY " + added + " DESC "; - + assertQuery(statement, SQL2, expected); + } + + @Test + public void testMultiValuedPropUpdate() throws Exception { + Tree test = root.getTree("/").addChild("test"); + String child = "child"; + String mulValuedProp = "prop"; + test.addChild(child).setProperty(mulValuedProp, of("foo","bar"), Type.STRINGS); + root.commit(); + assertQuery( + "/jcr:root//*[jcr:contains(@" + mulValuedProp + ", 'foo')]", + "xpath", of("/test/" + child)); + test.getChild(child).setProperty(mulValuedProp, new ArrayList(), Type.STRINGS); + root.commit(); + assertQuery("/jcr:root//*[jcr:contains(@" + mulValuedProp + ", 'foo')]", "xpath", new ArrayList()); + + test.getChild(child).setProperty(mulValuedProp, of("bar"), Type.STRINGS); + root.commit(); + assertQuery( + "/jcr:root//*[jcr:contains(@" + mulValuedProp + ", 'foo')]", + "xpath", new ArrayList()); + + test.getChild(child).removeProperty(mulValuedProp); + root.commit(); + assertQuery( + "/jcr:root//*[jcr:contains(@" + mulValuedProp + ", 'foo')]", + "xpath", new ArrayList()); } - + @SuppressWarnings("unused") private static void walktree(final Tree t) { System.out.println("+ " + t.getPath());