You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by mk...@apache.org on 2020/08/12 13:46:45 UTC

svn commit: r1880807 [3/4] - in /jackrabbit/oak/trunk: oak-jcr/src/test/java/org/apache/jackrabbit/oak/query/ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/ oak-search-elastic/ oak-search-elastic/src/test/java/org/apache/jackr...

Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexAggregationCommonTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexAggregationCommonTest.java?rev=1880807&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexAggregationCommonTest.java (added)
+++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexAggregationCommonTest.java Wed Aug 12 13:46:45 2020
@@ -0,0 +1,430 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
+import org.apache.jackrabbit.oak.query.AbstractQueryTest;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+
+import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.jackrabbit.JcrConstants.JCR_CONTENT;
+import static org.apache.jackrabbit.JcrConstants.JCR_DATA;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.NT_FILE;
+import static org.apache.jackrabbit.JcrConstants.NT_FOLDER;
+import static org.apache.jackrabbit.oak.plugins.memory.BinaryPropertyState.binaryProperty;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public abstract class IndexAggregationCommonTest extends AbstractQueryTest {
+    protected IndexOptions indexOptions;
+
+    @Override
+    protected void createTestIndexNode() throws Exception {
+        Tree index = root.getTree("/");
+        Tree indexDefn = createTestIndexNode(index, indexOptions.getIndexType());
+        TestUtil.useV2(indexDefn);
+        //Aggregates
+        TestUtil.newNodeAggregator(indexDefn)
+                .newRuleWithName(NT_FILE, newArrayList(JCR_CONTENT, JCR_CONTENT + "/*"))
+                .newRuleWithName(NT_FOLDER, newArrayList("myFile", "subfolder/subsubfolder/file"));
+
+        //Include all properties
+        Tree props = TestUtil.newRulePropTree(indexDefn, "nt:base");
+        TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true);
+
+        root.commit();
+    }
+
+    /**
+     * simple index aggregation from jcr:content to nt:file
+     */
+    @Test
+    public void testNtFileAggregate() throws Exception {
+
+        String sqlBase = "SELECT * FROM [nt:file] as f WHERE";
+        String sqlCat = sqlBase + " CONTAINS (f.*, 'cat')";
+        String sqlDog = sqlBase + " CONTAINS (f.*, 'dog')";
+
+        Tree file = root.getTree("/").addChild("myFile");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+        Tree resource = file.addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+        root.commit();
+        assertQuery(sqlDog, ImmutableList.of("/myFile"));
+
+        // update jcr:data
+        root.getTree("/")
+                .getChild("myFile")
+                .getChild(JCR_CONTENT)
+                .setProperty(
+                        binaryProperty(JCR_DATA,
+                                "the quick brown fox jumps over the lazy cat."));
+        root.commit();
+        assertQuery(sqlDog, new ArrayList<String>());
+        assertQuery(sqlCat, ImmutableList.of("/myFile"));
+
+        // replace jcr:content with unstructured
+        root.getTree("/").getChild("myFile").getChild(JCR_CONTENT).remove();
+
+        Tree unstrContent = root.getTree("/").getChild("myFile")
+                .addChild(JCR_CONTENT);
+        unstrContent.setProperty(JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED,
+                Type.NAME);
+
+        Tree foo = unstrContent.addChild("foo");
+        foo.setProperty(JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED,
+                Type.NAME);
+        foo.setProperty("text", "the quick brown fox jumps over the lazy dog.");
+        root.commit();
+
+        assertQuery(sqlDog, ImmutableList.of("/myFile"));
+        assertQuery(sqlCat, new ArrayList<String>());
+
+        // remove foo
+        root.getTree("/").getChild("myFile").getChild(JCR_CONTENT)
+                .getChild("foo").remove();
+
+        root.commit();
+        assertQuery(sqlDog, new ArrayList<String>());
+        assertQuery(sqlCat, new ArrayList<String>());
+
+        // replace jcr:content again with resource
+        root.getTree("/").getChild("myFile").getChild(JCR_CONTENT).remove();
+
+        resource = root.getTree("/").getChild("myFile").addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy cat."));
+        root.commit();
+        assertQuery(sqlDog, new ArrayList<String>());
+        assertQuery(sqlCat, ImmutableList.of("/myFile"));
+
+    }
+
+    @Test
+    public void testChildNodeWithOr() throws Exception {
+        Tree file = root.getTree("/").addChild("myFile");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+        Tree resource = file.addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+
+        resource.setProperty("jcr:title", "title");
+        resource.setProperty("jcr:description", "description");
+
+        root.commit();
+
+        String matchContentSimple = "//element(*, nt:file)[(jcr:contains(jcr:content, 'dog'))]";
+        assertQuery(matchContentSimple, "xpath", ImmutableList.of("/myFile"));
+
+        String matchContent = " //element(*, nt:file)[(jcr:contains(jcr:content, 'dog') or jcr:contains(jcr:content/@jcr:title, 'invalid') or jcr:contains(jcr:content/@jcr:description, 'invalid'))]";
+        assertQuery(matchContent, "xpath", ImmutableList.of("/myFile"));
+
+        String matchTitle = " //element(*, nt:file)[(jcr:contains(jcr:content, 'invalid') or jcr:contains(jcr:content/@jcr:title, 'title') or jcr:contains(jcr:content/@jcr:description, 'invalid'))]";
+        assertQuery(matchTitle, "xpath", ImmutableList.of("/myFile"));
+
+        String matchDesc = " //element(*, nt:file)[(jcr:contains(jcr:content, 'invalid') or jcr:contains(jcr:content/@jcr:title, 'invalid') or jcr:contains(jcr:content/@jcr:description, 'description'))]";
+        assertQuery(matchDesc, "xpath", ImmutableList.of("/myFile"));
+
+        String matchNone = " //element(*, nt:file)[(jcr:contains(jcr:content, 'invalid') or jcr:contains(jcr:content/@jcr:title, 'invalid') or jcr:contains(jcr:content/@jcr:description, 'invalid'))]";
+        assertQuery(matchNone, "xpath", new ArrayList<String>());
+    }
+
+    @Test
+    public void testChildNodeWithOrComposite() throws Exception {
+
+        Tree folder = root.getTree("/").addChild("myFolder");
+        folder.setProperty(JCR_PRIMARYTYPE, NT_FOLDER, Type.NAME);
+        Tree file = folder.addChild("myFile");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+        file.setProperty("jcr:title", "title");
+        file.setProperty("jcr:description", "description");
+
+        Tree resource = file.addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+
+        root.commit();
+
+        String matchContentSimple = "//element(*, nt:folder)[(jcr:contains(myFile, 'dog'))]";
+        assertQuery(matchContentSimple, "xpath", ImmutableList.of("/myFolder"));
+
+        String matchContent = " //element(*, nt:folder)[(jcr:contains(myFile, 'dog') or jcr:contains(myFile/@jcr:title, 'invalid') or jcr:contains(myFile/@jcr:description, 'invalid'))]";
+        assertQuery(matchContent, "xpath", ImmutableList.of("/myFolder"));
+
+        String matchTitle = " //element(*, nt:folder)[(jcr:contains(myFile, 'invalid') or jcr:contains(myFile/@jcr:title, 'title') or jcr:contains(myFile/@jcr:description, 'invalid'))]";
+        assertQuery(matchTitle, "xpath", ImmutableList.of("/myFolder"));
+
+        String matchDesc = " //element(*, nt:folder)[(jcr:contains(myFile, 'invalid') or jcr:contains(myFile/@jcr:title, 'invalid') or jcr:contains(myFile/@jcr:description, 'description'))]";
+        assertQuery(matchDesc, "xpath", ImmutableList.of("/myFolder"));
+
+        String matchNone = " //element(*, nt:folder)[(jcr:contains(myFile, 'invalid') or jcr:contains(myFile/@jcr:title, 'invalid') or jcr:contains(myFile/@jcr:description, 'invalid'))]";
+        assertQuery(matchNone, "xpath", new ArrayList<String>());
+
+        String matchOnlyTitleOr = " //element(*, nt:folder)[(jcr:contains(myFile/@jcr:title, 'title') or jcr:contains(myFile/@jcr:title, 'unknown') )]";
+        assertQuery(matchOnlyTitleOr, "xpath", ImmutableList.of("/myFolder"));
+    }
+
+    @Test
+    public void testNodeTypes() throws Exception {
+
+        Tree folder = root.getTree("/").addChild("myFolder");
+        folder.setProperty(JCR_PRIMARYTYPE, NT_FOLDER, Type.NAME);
+        Tree file = folder.addChild("myFile");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+        file.setProperty("jcr:title", "title");
+        file.setProperty("jcr:description", "description");
+
+        Tree resource = file.addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+
+        root.commit();
+
+        String matchContentSimple = "//*[( jcr:contains(., 'dog') and @jcr:primaryType = 'nt:file' )]";
+        assertQuery(matchContentSimple, "xpath", ImmutableList.of("/myFolder/myFile"));
+
+        String matchContentDouble = "//*[( jcr:contains(., 'dog') and (@jcr:primaryType = 'nt:file' or @jcr:primaryType = 'nt:folder') )]";
+        assertQuery(matchContentDouble, "xpath", ImmutableList.of("/myFolder", "/myFolder/myFile"));
+    }
+
+    @Test
+    public void testNodeTypesDeep() throws Exception {
+
+        Tree folder = root.getTree("/").addChild("myFolder");
+        folder.setProperty(JCR_PRIMARYTYPE, NT_FOLDER, Type.NAME);
+
+        Tree folder2 = folder.addChild("subfolder");
+        folder2.setProperty(JCR_PRIMARYTYPE, "nt:unstructured", Type.NAME);
+
+        Tree folder3 = folder2.addChild("subsubfolder");
+        folder3.setProperty(JCR_PRIMARYTYPE, "nt:unstructured", Type.NAME);
+        file(folder3, "file");
+
+        root.commit();
+
+        String xpath = "//element(*, nt:folder)[jcr:contains(., 'dog')]";
+        assertQuery(xpath, "xpath", ImmutableList.of("/myFolder"));
+    }
+
+    private static void file(Tree parent, String name) {
+        Tree file = parent.addChild(name);
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+
+        Tree resource = file.addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+    }
+
+    @Test
+    public void testChildNodeProperty() throws Exception {
+        Tree file = root.getTree("/").addChild("myFile");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+        Tree resource = file.addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+
+        resource.setProperty("jcr:title", "title");
+        resource.setProperty("jcr:description", "description");
+
+        root.commit();
+
+        String matchChildSimple = "//*[( jcr:contains(@jcr:title, 'title') )]";
+        assertQuery(matchChildSimple, "xpath", ImmutableList.of("/myFile/jcr:content"));
+
+        String matchChildWithStar = "//*[( jcr:contains(., 'dog') and jcr:contains(@jcr:title, 'title') )]";
+        assertQuery(matchChildWithStar, "xpath", ImmutableList.of("/myFile/jcr:content"));
+
+    }
+
+
+    @Test
+    public void testChildNodeProperty2() throws Exception {
+
+        Tree file = root.getTree("/").addChild("myFile");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+        Tree resource = file.addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+        resource.setProperty("jcr:title", "title");
+        resource.setProperty("jcr:description", "description");
+
+        Tree file2 = root.getTree("/").addChild("myFile2");
+        file2.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+        Tree resource2 = file2.addChild(JCR_CONTENT);
+        resource2.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource2.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+        resource2.setProperty("jcr:title", "other");
+        resource.setProperty("jcr:description", "title");
+
+        root.commit();
+
+        String matchChildSimple = "//*[( jcr:contains(jcr:content/@jcr:title, 'title') )]";
+        assertQuery(matchChildSimple, "xpath", ImmutableList.of("/myFile"));
+
+    }
+
+    @Test
+    public void testPreventDoubleAggregation() throws Exception {
+        Tree file = root.getTree("/").addChild("myFile");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+        file.setProperty("jcr:title", "fox");
+
+        Tree resource = file.addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+        root.commit();
+
+        String matchChildSimple = "//element(*, nt:file)[( jcr:contains(., 'fox') )]";
+        assertQuery(matchChildSimple, "xpath",
+                ImmutableList.of("/myFile"));
+    }
+
+    @Test
+    public void testDifferentNodes() throws Exception {
+
+        Tree folder = root.getTree("/").addChild("myFolder");
+        folder.setProperty(JCR_PRIMARYTYPE, NT_FOLDER, Type.NAME);
+        Tree file = folder.addChild("myFile");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+        file.setProperty("jcr:title", "title");
+        file.setProperty("jcr:description", "description");
+
+        Tree resource = file.addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+
+        root.commit();
+
+        assertQuery(
+                "//element(*, nt:file)[jcr:contains(., 'dog')]",
+                "xpath", ImmutableList.of("/myFolder/myFile"));
+
+        assertQuery(
+                "//element(*, nt:file)[jcr:contains(., 'title')]",
+                "xpath", ImmutableList.of("/myFolder/myFile"));
+
+        assertQuery(
+                "//element(*, nt:file)[jcr:contains(., 'dog') and jcr:contains(., 'title')]",
+                "xpath", ImmutableList.of("/myFolder/myFile"));
+
+        // double aggregation dupes
+        assertQuery(
+                "//*[(jcr:contains(., 'dog') or jcr:contains(jcr:content, 'dog') )]",
+                "xpath", ImmutableList.of("/myFolder", "/myFolder/myFile", "/myFolder/myFile/jcr:content"));
+    }
+
+    @Test
+    public void oak3371AggregateV2() throws CommitFailedException {
+        oak3371();
+    }
+
+    @Test
+    public void oak3371AggregateV1() throws CommitFailedException {
+
+        Tree indexdef = root.getTree("/oak:index/" + TEST_INDEX_NAME);
+        assertNotNull(indexdef);
+        assertTrue(indexdef.exists());
+        indexdef.setProperty(FulltextIndexConstants.COMPAT_MODE, 1L);
+        indexdef.setProperty(IndexConstants.REINDEX_PROPERTY_NAME, true);
+        root.commit();
+
+        oak3371();
+    }
+
+    private void oak3371() throws CommitFailedException {
+        setTraversalEnabled(false);
+        Tree test, t;
+
+        test = root.getTree("/").addChild("test");
+        t = test.addChild("a");
+        t.setProperty(JCR_PRIMARYTYPE, NT_FOLDER, Type.NAME);
+        t.setProperty("foo", "bar");
+        t = test.addChild("b");
+        t.setProperty(JCR_PRIMARYTYPE, NT_FOLDER, Type.NAME);
+        t.setProperty("foo", "cat");
+        t = test.addChild("c");
+        t.setProperty(JCR_PRIMARYTYPE, NT_FOLDER, Type.NAME);
+        t = test.addChild("d");
+        t.setProperty(JCR_PRIMARYTYPE, NT_FOLDER, Type.NAME);
+        t.setProperty("foo", "bar cat");
+        root.commit();
+
+        assertQuery(
+                "SELECT * FROM [nt:folder] WHERE ISDESCENDANTNODE('/test') AND CONTAINS(foo, 'bar')",
+                of("/test/a", "/test/d"));
+        assertQuery(
+                "SELECT * FROM [nt:folder] WHERE ISDESCENDANTNODE('/test') AND NOT CONTAINS(foo, 'bar')",
+                of("/test/b", "/test/c"));
+        assertQuery(
+                "SELECT * FROM [nt:folder] WHERE ISDESCENDANTNODE('/test') AND CONTAINS(foo, 'bar cat')",
+                of("/test/d"));
+        assertQuery(
+                "SELECT * FROM [nt:folder] WHERE ISDESCENDANTNODE('/test') AND NOT CONTAINS(foo, 'bar cat')",
+                of("/test/c"));
+
+        setTraversalEnabled(true);
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexAggregationCommonTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexDescendantSpellcheckCommonTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexDescendantSpellcheckCommonTest.java?rev=1880807&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexDescendantSpellcheckCommonTest.java (added)
+++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexDescendantSpellcheckCommonTest.java Wed Aug 12 13:46:45 2020
@@ -0,0 +1,214 @@
+/*
+ * 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;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexFormatVersion;
+import org.apache.jackrabbit.oak.query.AbstractJcrTest;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import javax.jcr.Node;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.RowIterator;
+import java.util.Set;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static org.apache.jackrabbit.JcrConstants.NT_BASE;
+import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.EVALUATE_PATH_RESTRICTION;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INDEX_RULES;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROPDEF_PROP_NODE_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_USE_IN_SPELLCHECK;
+import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.NT_OAK_UNSTRUCTURED;
+import static org.junit.Assert.assertEquals;
+
+public abstract class IndexDescendantSpellcheckCommonTest extends AbstractJcrTest {
+    protected TestRepository repositoryOptionsUtil;
+    protected Node indexNode;
+    protected IndexOptions indexOptions;
+
+    protected JackrabbitSession session = null;
+    protected Node root = null;
+
+    @Override
+    protected void initialize() {
+        session = (JackrabbitSession) adminSession;
+        try {
+            root = adminSession.getRootNode();
+            createContent();
+            adminSession.save();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    protected void createContent() throws Exception {
+        /*
+        Make content with following structure:
+        * sugg-idx is a simple index to suggest node names on type "oak:Unstructured"
+        * test[1-6] nodes would be "oak:Unstructured".
+        * all other nodes, unless required are "nt:unstructured"
+        * Index on one sub-tree is on nt:base so that we can do sub-tree suggestion test with unambiguous indices
+        */
+
+        //  /oak:index/spellcheck-idx, /test1
+        createSuggestIndex(root, "spellcheck-idx", NT_OAK_UNSTRUCTURED, PROPDEF_PROP_NODE_NAME);
+        root.addNode("test1", NT_OAK_UNSTRUCTURED);
+
+        /*
+            /content1
+                /tree1
+                    /test2
+                /tree2
+                    /test3
+         */
+        Node content1 = root.addNode("content1", NT_UNSTRUCTURED);
+        Node tree1 = content1.addNode("tree1", NT_UNSTRUCTURED);
+        tree1.addNode("test2", NT_OAK_UNSTRUCTURED);
+        Node tree2 = content1.addNode("tree2", NT_UNSTRUCTURED);
+        tree2.addNode("test3", NT_OAK_UNSTRUCTURED);
+
+        //  /content2/oak:index/spellcheck-idx, /content2/test4
+        Node content2 = root.addNode("content2", NT_UNSTRUCTURED);
+        createSuggestIndex(content2, "spellcheck-idx", NT_OAK_UNSTRUCTURED, PROPDEF_PROP_NODE_NAME);
+        content2.addNode("test4", NT_OAK_UNSTRUCTURED);
+
+        //  /content3/oak:index/spellcheck-idx, /content3/test5, /content3/sC/test6
+        Node content3 = root.addNode("content3", NT_UNSTRUCTURED);
+        createSuggestIndex(content3, "spellcheck-idx", NT_BASE, PROPDEF_PROP_NODE_NAME);
+        content3.addNode("test5", NT_OAK_UNSTRUCTURED);
+        Node subChild = content3.addNode("sC", NT_UNSTRUCTURED);
+        subChild.addNode("test6", NT_OAK_UNSTRUCTURED);
+    }
+
+    private void createSuggestIndex(Node rootNode, String name, String indexedNodeType, String indexedPropertyName)
+            throws Exception {
+        Node def = JcrUtils.getOrAddNode(rootNode, INDEX_DEFINITIONS_NAME)
+                .addNode(name, INDEX_DEFINITIONS_NODE_TYPE);
+        def.setProperty(TYPE_PROPERTY_NAME, indexOptions.getIndexType());
+        def.setProperty(REINDEX_PROPERTY_NAME, true);
+        def.setProperty("name", name);
+        def.setProperty(FulltextIndexConstants.COMPAT_MODE, IndexFormatVersion.V2.getVersion());
+        def.setProperty(EVALUATE_PATH_RESTRICTION, true);
+
+        Node propertyIdxDef = def.addNode(INDEX_RULES, JcrConstants.NT_UNSTRUCTURED)
+                .addNode(indexedNodeType, JcrConstants.NT_UNSTRUCTURED)
+                .addNode(FulltextIndexConstants.PROP_NODE, JcrConstants.NT_UNSTRUCTURED)
+                .addNode("indexedProperty", JcrConstants.NT_UNSTRUCTURED);
+        propertyIdxDef.setProperty("analyzed", true);
+        propertyIdxDef.setProperty(PROP_USE_IN_SPELLCHECK, true);
+        propertyIdxDef.setProperty("name", indexedPropertyName);
+    }
+
+    private String createSpellcheckQuery(String nodeTypeName, String suggestFor, String rootPath) {
+        return "SELECT [rep:spellcheck()] as spellcheck, [jcr:score] as score  FROM [" + nodeTypeName + "]" +
+                " WHERE spellcheck('" + suggestFor + "')" +
+                (rootPath == null ? "" : " AND ISDESCENDANTNODE([" + rootPath + "])");
+
+    }
+
+    private Set<String> getSpellchecks(String query) throws Exception {
+        QueryManager queryManager = session.getWorkspace().getQueryManager();
+        QueryResult result = queryManager.createQuery(query, Query.JCR_SQL2).execute();
+        RowIterator rows = result.getRows();
+
+        Set<String> suggestions = newHashSet();
+        while (rows.hasNext()) {
+            suggestions.add(rows.nextRow().getValue("spellcheck").getString());
+        }
+
+        return suggestions;
+    }
+
+    private void validateSpellchecks(String query, Set<String> expected) throws Exception {
+        assertEventually(() -> {
+            Set<String> suggestions = null;
+            try {
+                suggestions = getSpellchecks(query);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+            assertEquals("Incorrect suggestions", expected, suggestions);
+        });
+    }
+
+    @Ignore
+    //TODO ES Failing
+    @Test
+    public void noDescendantSuggestsAll() throws Exception {
+        validateSpellchecks(
+                createSpellcheckQuery(NT_OAK_UNSTRUCTURED, "taste", null),
+                newHashSet("test1", "test2", "test3", "test4", "test5", "test6"));
+    }
+
+    //OAK-3994
+    @Test
+    public void rootIndexWithDescendantConstraint() throws Exception {
+        validateSpellchecks(
+                createSpellcheckQuery(NT_OAK_UNSTRUCTURED, "taste", "/content1"),
+                newHashSet("test2", "test3"));
+    }
+
+    @Ignore
+    //TODO ES Failing: if path restriction is not enabled, all ruggestions should be returned
+    //OAK-3994
+    @Test
+    public void descendantSuggestionRequirePathRestrictionIndex() throws Exception {
+        Node rootIndexDef = root.getNode("oak:index/spellcheck-idx");
+        rootIndexDef.getProperty(EVALUATE_PATH_RESTRICTION).remove();
+        rootIndexDef.setProperty(REINDEX_PROPERTY_NAME, true);
+        session.save();
+
+        //Without path restriction indexing, descendant clause shouldn't be respected
+        validateSpellchecks(
+                createSpellcheckQuery(NT_OAK_UNSTRUCTURED, "taste", "/content1"),
+                newHashSet("test1", "test2", "test3", "test4", "test5", "test6"));
+    }
+
+    //@Ignore
+    //TODO ES Failing
+    //OAK-3994
+    @Test
+    public void unambiguousSubtreeIndexWithDescendantConstraint() throws Exception {
+        validateSpellchecks(
+                createSpellcheckQuery(NT_BASE, "taste", "/content3"),
+                newHashSet("test5", "test6"));
+    }
+
+    //OAK-3994
+    @Test
+    public void unambiguousSubtreeIndexWithSubDescendantConstraint() throws Exception {
+        validateSpellchecks(
+                createSpellcheckQuery(NT_BASE, "taste", "/content3/sC"),
+                newHashSet("test6"));
+    }
+
+    private static void assertEventually(Runnable r) {
+        TestUtils.assertEventually(r, 3000 * 3);
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexDescendantSpellcheckCommonTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexDescendantSuggestionCommonTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexDescendantSuggestionCommonTest.java?rev=1880807&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexDescendantSuggestionCommonTest.java (added)
+++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexDescendantSuggestionCommonTest.java Wed Aug 12 13:46:45 2020
@@ -0,0 +1,238 @@
+/*
+ * 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;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexFormatVersion;
+import org.apache.jackrabbit.oak.query.AbstractJcrTest;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import javax.jcr.Node;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.RowIterator;
+import java.util.Set;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static org.apache.jackrabbit.JcrConstants.NT_BASE;
+import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.EVALUATE_PATH_RESTRICTION;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INDEX_RULES;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROPDEF_PROP_NODE_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_USE_IN_SUGGEST;
+import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.NT_OAK_UNSTRUCTURED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public abstract class IndexDescendantSuggestionCommonTest extends AbstractJcrTest {
+    protected TestRepository repositoryOptionsUtil;
+    protected Node indexNode;
+    protected IndexOptions indexOptions;
+
+    protected JackrabbitSession session = null;
+    protected Node root = null;
+
+    @Override
+    protected void initialize() {
+        session = (JackrabbitSession) adminSession;
+        try {
+            root = adminSession.getRootNode();
+            createContent();
+            adminSession.save();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    private void createContent() throws Exception {
+        /*
+        Make content with following structure:
+        * sugg-idx is a simple index to suggest node names on type "oak:Unstructured"
+        * test[1-6] nodes would be "oak:Unstructured".
+        * all other nodes, unless required are "nt:unstructured"
+        * Index on one sub-tree is on nt:base so that we can do sub-tree suggestion test with unambiguous indices
+        */
+
+        //  /oak:index/sugg-idx, /test1
+        createSuggestIndex(root, "sugg-idx", NT_OAK_UNSTRUCTURED, PROPDEF_PROP_NODE_NAME);
+        root.addNode("test1", NT_OAK_UNSTRUCTURED);
+
+        /*
+            /content1
+                /tree1
+                    /test2
+                /tree2
+                    /test3
+         */
+        Node content1 = root.addNode("content1", NT_UNSTRUCTURED);
+        Node tree1 = content1.addNode("tree1", NT_UNSTRUCTURED);
+        tree1.addNode("test2", NT_OAK_UNSTRUCTURED);
+        Node tree2 = content1.addNode("tree2", NT_UNSTRUCTURED);
+        tree2.addNode("test3", NT_OAK_UNSTRUCTURED);
+
+        //  /content2/oak:index/sugg-idx, /content2/test4
+        Node content2 = root.addNode("content2", NT_UNSTRUCTURED);
+        createSuggestIndex(content2, "sugg-idx", NT_OAK_UNSTRUCTURED, PROPDEF_PROP_NODE_NAME);
+        content2.addNode("test4", NT_OAK_UNSTRUCTURED);
+
+        //  /content3/oak:index/sugg-idx, /content3/test5, /content3/sC/test6
+        Node content3 = root.addNode("content3", NT_UNSTRUCTURED);
+        createSuggestIndex(content3, "sugg-idx", NT_BASE, PROPDEF_PROP_NODE_NAME);
+        content3.addNode("test5", NT_OAK_UNSTRUCTURED);
+        Node subChild = content3.addNode("sC", NT_UNSTRUCTURED);
+        subChild.addNode("test6", NT_OAK_UNSTRUCTURED);
+    }
+
+    protected void createSuggestIndex(Node rootNode, String name, String indexedNodeType, String indexedPropertyName)
+            throws Exception {
+        Node def = JcrUtils.getOrAddNode(rootNode, INDEX_DEFINITIONS_NAME)
+                .addNode(name, INDEX_DEFINITIONS_NODE_TYPE);
+        def.setProperty(TYPE_PROPERTY_NAME, indexOptions.getIndexType());
+        def.setProperty(REINDEX_PROPERTY_NAME, true);
+        def.setProperty("name", name);
+        def.setProperty(FulltextIndexConstants.COMPAT_MODE, IndexFormatVersion.V2.getVersion());
+        def.setProperty(EVALUATE_PATH_RESTRICTION, true);
+
+        Node propertyIdxDef = def.addNode(INDEX_RULES, JcrConstants.NT_UNSTRUCTURED)
+                .addNode(indexedNodeType, JcrConstants.NT_UNSTRUCTURED)
+                .addNode(FulltextIndexConstants.PROP_NODE, JcrConstants.NT_UNSTRUCTURED)
+                .addNode("indexedProperty", JcrConstants.NT_UNSTRUCTURED);
+        propertyIdxDef.setProperty("analyzed", true);
+        propertyIdxDef.setProperty(PROP_USE_IN_SUGGEST, true);
+        propertyIdxDef.setProperty("name", indexedPropertyName);
+    }
+
+    private String createSuggestQuery(String nodeTypeName, String suggestFor, String rootPath) {
+        return "SELECT [rep:suggest()] as suggestion, [jcr:score] as score  FROM [" + nodeTypeName + "]" +
+                " WHERE suggest('" + suggestFor + "')" +
+                (rootPath == null ? "" : " AND ISDESCENDANTNODE([" + rootPath + "])");
+
+    }
+
+    private Set<String> getSuggestions(String query) throws Exception {
+        QueryManager queryManager = session.getWorkspace().getQueryManager();
+        QueryResult result = queryManager.createQuery(query, Query.JCR_SQL2).execute();
+        RowIterator rows = result.getRows();
+
+        Set<String> suggestions = newHashSet();
+        while (rows.hasNext()) {
+            suggestions.add(rows.nextRow().getValue("suggestion").getString());
+        }
+
+        return suggestions;
+    }
+
+    private void validateSuggestions(String query, Set<String> expected) throws Exception {
+        assertEventually(() -> {
+            Set<String> suggestions = null;
+            try {
+                suggestions = getSuggestions(query);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+            assertEquals("Incorrect suggestions", expected, suggestions);
+        });
+    }
+
+    //Don't break suggestions :)
+    @Test
+    public void noDescendantSuggestsAll() throws Exception {
+        validateSuggestions(
+                createSuggestQuery(NT_OAK_UNSTRUCTURED, "te", null),
+                newHashSet("test1", "test2", "test3", "test4", "test5", "test6"));
+    }
+
+    //OAK-3994
+    @Test
+    public void rootIndexWithDescendantConstraint() throws Exception {
+        validateSuggestions(
+                createSuggestQuery(NT_OAK_UNSTRUCTURED, "te", "/content1"),
+                newHashSet("test2", "test3"));
+    }
+
+    @Ignore
+    //TODO ES failing --> if path restrictions are not enabled can we still get results with descendant filter as if path restrictions were enabled?
+    //OAK-3994
+    @Test
+    public void descendantSuggestionRequirePathRestrictionIndex() throws Exception {
+        Node rootIndexDef = root.getNode("oak:index/sugg-idx");
+        rootIndexDef.getProperty(EVALUATE_PATH_RESTRICTION).remove();
+        rootIndexDef.setProperty(REINDEX_PROPERTY_NAME, true);
+        session.save();
+
+        //Without path restriction indexing, descendant clause shouldn't be respected
+        validateSuggestions(
+                createSuggestQuery(NT_OAK_UNSTRUCTURED, "te", "/content1"),
+                newHashSet("test1", "test2", "test3", "test4", "test5", "test6"));
+    }
+
+    @Ignore("OAK-3992")
+    @Test
+    public void ambiguousSubtreeIndexWithDescendantConstraint() throws Exception {
+        String query = createSuggestQuery(NT_OAK_UNSTRUCTURED, "te", "/content2");
+        String explainQuery = "EXPLAIN " + createSuggestQuery(NT_OAK_UNSTRUCTURED, "te", "/content2");
+
+        QueryManager queryManager = session.getWorkspace().getQueryManager();
+        QueryResult result = queryManager.createQuery(explainQuery, Query.JCR_SQL2).execute();
+        RowIterator rows = result.getRows();
+
+        String explanation = rows.nextRow().toString();
+        assertTrue("Subtree index should get picked", explanation.contains("lucene:sugg-idx(/content2/oak:index/sugg-idx)"));
+
+        validateSuggestions(query, newHashSet("test4"));
+    }
+
+    //OAK-3994
+    @Test
+    public void unambiguousSubtreeIndexWithDescendantConstraint() throws Exception {
+        validateSuggestions(
+                createSuggestQuery(NT_BASE, "te", "/content3"),
+                newHashSet("test5", "test6"));
+    }
+
+    //OAK-3994
+    @Test
+    public void unambiguousSubtreeIndexWithSubDescendantConstraint() throws Exception {
+        validateSuggestions(
+                createSuggestQuery(NT_BASE, "te", "/content3/sC"),
+                newHashSet("test6"));
+    }
+
+    @Ignore("OAK-3993")
+    @Test
+    public void unionOnTwoDescendants() throws Exception {
+        validateSuggestions(
+                createSuggestQuery(NT_OAK_UNSTRUCTURED, "te", "/content1") +
+                        " UNION " +
+                        createSuggestQuery(NT_BASE, "te", "/content3"),
+                newHashSet("test2", "test3", "test5"));
+    }
+
+    private static void assertEventually(Runnable r) {
+        TestUtils.assertEventually(r, 3000 * 3);
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexDescendantSuggestionCommonTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexExclusionQueryCommonTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexExclusionQueryCommonTest.java?rev=1880807&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexExclusionQueryCommonTest.java (added)
+++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexExclusionQueryCommonTest.java Wed Aug 12 13:46:45 2020
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.query.AbstractQueryTest;
+import org.junit.Test;
+
+import java.util.List;
+
+import static com.google.common.collect.ImmutableList.of;
+import static javax.jcr.PropertyType.TYPENAME_BINARY;
+import static javax.jcr.PropertyType.TYPENAME_STRING;
+import static org.apache.jackrabbit.JcrConstants.JCR_LASTMODIFIED;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
+import static org.apache.jackrabbit.oak.api.Type.DATE;
+import static org.apache.jackrabbit.oak.api.Type.STRINGS;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.EXCLUDE_PROPERTY_NAMES;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_TYPES;
+
+/**
+ * Tests the IndexProvider exclusion settings
+ */
+public abstract class IndexExclusionQueryCommonTest extends AbstractQueryTest {
+
+    private static final String NOT_IN = "notincluded";
+    protected IndexOptions indexOptions;
+
+    @Override
+    protected void createTestIndexNode() throws Exception {
+        Tree lucene = createTestIndexNode(root.getTree("/"), indexOptions.getIndexType());
+        lucene.setProperty(INCLUDE_PROPERTY_TYPES,
+                of(TYPENAME_BINARY, TYPENAME_STRING), STRINGS);
+        lucene.setProperty(EXCLUDE_PROPERTY_NAMES, of(NOT_IN), STRINGS);
+        TestUtil.useV2(lucene);
+        root.commit();
+    }
+
+    @Test
+    public void ignoreByType() throws Exception {
+        Tree content = root.getTree("/").addChild("content");
+        Tree one = content.addChild("one");
+        one.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED);
+        one.setProperty(JCR_LASTMODIFIED, "2013-04-01T09:58:03.231Z", DATE);
+        one.setProperty("jcr:title", "abc");
+
+        Tree two = content.addChild("two");
+        two.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED);
+        two.setProperty(JCR_LASTMODIFIED, "2014-04-01T09:58:03.231Z", DATE);
+        two.setProperty("jcr:title", "abc");
+
+        root.commit();
+
+        String query = "/jcr:root/content//*[jcr:contains(., 'abc' )"
+                + " and (@" + JCR_LASTMODIFIED
+                + " > xs:dateTime('2014-04-01T08:58:03.231Z')) ]";
+        assertQuery(query, "xpath", of("/content/two"));
+    }
+
+    @Test
+    public void ignoreByName() throws Exception {
+        final List<String> expected = of("/content/two");
+
+        Tree content = root.getTree("/").addChild("content");
+        Tree one = content.addChild("one");
+        one.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED);
+        one.setProperty("jcr:title", "abc");
+        one.setProperty(NOT_IN, "azerty");
+
+        Tree two = content.addChild("two");
+        two.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED);
+        two.setProperty("jcr:title", "abc");
+        two.setProperty(NOT_IN, "querty");
+
+        root.commit();
+
+        String query = "/jcr:root/content//*[jcr:contains(., 'abc' )"
+                + " and (@" + NOT_IN + " = 'querty') ]";
+        assertQuery(query, "xpath", expected);
+    }
+
+}

Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexExclusionQueryCommonTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexQueryCommonTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexQueryCommonTest.java?rev=1880807&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexQueryCommonTest.java (added)
+++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexQueryCommonTest.java Wed Aug 12 13:46:45 2020
@@ -0,0 +1,605 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableList;
+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.index.search.FulltextIndexConstants;
+import org.apache.jackrabbit.oak.query.AbstractQueryTest;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import static com.google.common.collect.ImmutableList.of;
+import static java.util.Arrays.asList;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
+import static org.apache.jackrabbit.oak.api.Type.STRING;
+import static org.apache.jackrabbit.oak.api.Type.STRINGS;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_VALUE_REGEX;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests the query engine using the default index implementation: the
+ * IndexProvider
+ */
+public abstract class IndexQueryCommonTest extends AbstractQueryTest {
+
+    private Tree indexDefn;
+    protected IndexOptions indexOptions;
+    protected TestRepository repositoryOptionsUtil;
+
+    @Override
+    protected void createTestIndexNode() throws Exception {
+        setTraversalEnabled(false);
+        Tree index = root.getTree("/");
+        indexDefn = createTestIndexNode(index, indexOptions.getIndexType());
+        TestUtil.useV2(indexDefn);
+        indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true);
+
+        Tree props = TestUtil.newRulePropTree(indexDefn, "nt:base");
+        props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true);
+        TestUtil.enablePropertyIndex(props, "c1/p", false);
+        TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true);
+        TestUtil.enablePropertyIndex(props, "a/name", false);
+        TestUtil.enablePropertyIndex(props, "b/name", false);
+        TestUtil.enableFunctionIndex(props, "length([name])");
+        TestUtil.enableFunctionIndex(props, "lower([name])");
+        TestUtil.enableFunctionIndex(props, "upper([name])");
+
+        root.commit();
+    }
+
+    @Ignore
+    //TODO ES failing
+    @Test
+    public void sql1() throws Exception {
+        test("sql1.txt");
+    }
+
+    @Ignore
+    //TODO ES Failing
+    @Test
+    public void sql2() throws Exception {
+        test("sql2.txt");
+    }
+
+    //TODO ES test failing
+    @Ignore
+    @Test
+    public void sql2FullText() throws Exception {
+        test("sql2-fulltext.txt");
+    }
+
+    @Ignore
+    @Test
+    public void testValueRegex() throws Exception {
+        Tree test = root.getTree("/").addChild("test");
+        Tree a = test.addChild("a");
+        Tree b = test.addChild("b");
+        a.setProperty("name", "hello");
+        b.setProperty("name", "hello pattern");
+        root.commit();
+
+        final String query = "select [jcr:path] from [nt:base] where isdescendantnode('/test') and contains(*, 'hello')";
+
+        assertEventually(() -> {
+            Iterator<String> result = executeQuery(query, "JCR-SQL2").iterator();
+            List<String> paths = new ArrayList<>();
+            result.forEachRemaining(paths::add);
+            assertEquals(2, paths.size());
+            assertEquals(paths.get(0), a.getPath());
+            assertEquals(paths.get(1), b.getPath());
+        });
+
+        indexDefn.setProperty(PROP_VALUE_REGEX, "pat*");
+        indexDefn.setProperty(REINDEX_PROPERTY_NAME, true);
+        root.commit();
+
+        assertEventually(() -> {
+            Iterator<String> result = executeQuery(query, "JCR-SQL2").iterator();
+            List<String> paths = new ArrayList<>();
+            ;
+            result.forEachRemaining(paths::add);
+            assertEquals(1, paths.size());
+            assertEquals(paths.get(0), b.getPath());
+        });
+
+    }
+
+    @Test
+    public void descendantTest() throws Exception {
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a");
+        test.addChild("b");
+        root.commit();
+
+        assertEventually(() -> {
+            Iterator<String> result = executeQuery(
+                    "select [jcr:path] from [nt:base] where isdescendantnode('/test')",
+                    "JCR-SQL2").iterator();
+            assertTrue(result.hasNext());
+            assertEquals("/test/a", result.next());
+            assertEquals("/test/b", result.next());
+            assertFalse(result.hasNext());
+        });
+    }
+
+    //TODO ES failing
+    @Ignore
+    @Test
+    public void descendantTest2() throws Exception {
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("name", asList("Hello", "World"), STRINGS);
+        test.addChild("b").setProperty("name", "Hello");
+        root.commit();
+
+        assertEventually(() -> {
+            Iterator<String> result = executeQuery(
+                    "select [jcr:path] from [nt:base] where isdescendantnode('/test') and name='World'",
+                    "JCR-SQL2").iterator();
+            assertTrue(result.hasNext());
+            assertEquals("/test/a", result.next());
+            assertFalse(result.hasNext());
+        });
+    }
+
+    //TODO ES Failing
+    @Ignore
+    @Test
+    public void ischildnodeTest() throws Exception {
+        Tree tree = root.getTree("/");
+        Tree parents = tree.addChild("parents");
+        parents.addChild("p0").setProperty("id", "0");
+        parents.addChild("p1").setProperty("id", "1");
+        parents.addChild("p2").setProperty("id", "2");
+        Tree children = tree.addChild("children");
+        children.addChild("c1").setProperty("p", "1");
+        children.addChild("c2").setProperty("p", "2");
+        children.addChild("c3").setProperty("p", "3");
+        children.addChild("c4").setProperty("p", "4");
+        root.commit();
+
+        assertEventually(() -> {
+            Iterator<String> 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] = '/'",
+                    "JCR-SQL2").iterator();
+            assertTrue(result.hasNext());
+            assertEquals("/, /children", result.next());
+            assertEquals("/, /jcr:system", result.next());
+            assertEquals("/, /oak:index", result.next());
+            assertEquals("/, /parents", result.next());
+            assertFalse(result.hasNext());
+        });
+    }
+
+    @Ignore
+    @Test
+    public void contains() throws Exception {
+        String h = "Hello" + System.currentTimeMillis();
+        String w = "World" + System.currentTimeMillis();
+
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("name", asList(h, w), STRINGS);
+        test.addChild("b").setProperty("name", h);
+        root.commit();
+
+        // query 'hello'
+        final StringBuffer stmt = new StringBuffer();
+        stmt.append("/jcr:root//*[jcr:contains(., '").append(h);
+        stmt.append("')]");
+        assertEventually(() -> {
+            assertQuery(stmt.toString(), "xpath",
+                    ImmutableList.of("/test/a", "/test/b"));
+        });
+
+        // query 'world'
+        final StringBuffer stmt1 = new StringBuffer();
+        stmt1.append("/jcr:root//*[jcr:contains(., '").append(w);
+        stmt1.append("')]");
+        assertEventually(() -> {
+            assertQuery(stmt1.toString(), "xpath", ImmutableList.of("/test/a"));
+        });
+
+    }
+
+    //TODO ES failing
+    @Ignore
+    @Test
+    public void containsNot() throws Exception {
+
+        // see also OAK-3371
+        // "if we have only NOT CLAUSES we have to add a match all docs (*.*) for the
+        // query to work"
+
+        executeQuery("/jcr:root//*[jcr:contains(@a,'-test*')]", "xpath", false);
+
+        String planPrefix = "[nt:base] as [a] /* lucene:test-index(/oak:index/test-index) ";
+
+        assertXPathPlan("/jcr:root//*[@a]",
+                planPrefix + "a:[* TO *]");
+
+        assertXPathPlan("/jcr:root//*[jcr:contains(., '*')]",
+                planPrefix + ":fulltext:* ft:(\"*\")");
+
+        assertXPathPlan("/jcr:root//*[jcr:contains(@a,'*')]",
+                planPrefix + "full:a:* ft:(a:\"*\")");
+
+        assertXPathPlan("/jcr:root//*[jcr:contains(@a,'hello -world')]",
+                planPrefix + "+full:a:hello -full:a:world ft:(a:\"hello\" -a:\"world\")");
+
+        assertXPathPlan("/jcr:root//*[jcr:contains(@a,'test*')]",
+                planPrefix + "full:a:test* ft:(a:\"test*\")");
+
+        assertXPathPlan("/jcr:root//*[jcr:contains(@a,'-test')]",
+                planPrefix + "-full:a:test *:* ft:(-a:\"test\")");
+
+        assertXPathPlan("/jcr:root//*[jcr:contains(@a,'-test*')]",
+                planPrefix + "-full:a:test* *:* ft:(-a:\"test*\")");
+
+        assertXPathPlan("/jcr:root//*[jcr:contains(., '-*')]",
+                planPrefix + "-:fulltext:* *:* ft:(-\"*\")");
+
+        assertXPathPlan("/jcr:root//*[jcr:contains(., 'apple - pear')]",
+                planPrefix + "+:fulltext:apple -:fulltext:pear ft:(\"apple\" \"-\" \"pear\")");
+
+        assertXPathPlan("/jcr:root/content//*[jcr:contains(., 'apple - pear')]",
+                planPrefix + "-:fulltext:pear +:fulltext:apple +:ancestors:/content ft:(\"apple\" \"-\" \"pear\")");
+
+    }
+
+    private void assertXPathPlan(String xpathQuery, String expectedPlan) {
+        List<String> result = executeQuery("explain " + xpathQuery, "xpath", false);
+        String plan = result.get(0);
+        int newline = plan.indexOf('\n');
+        if (newline >= 0) {
+            plan = plan.substring(0, newline);
+        }
+        Assert.assertEquals(expectedPlan, plan);
+    }
+
+    @Ignore("OAK-2424")
+    @Test
+    public void containsDash() throws Exception {
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("name", "hello-wor");
+        test.addChild("b").setProperty("name", "hello-world");
+        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"));
+
+    }
+
+    @Ignore("OAK-2424")
+    @Test
+    public void multiPhraseQuery() throws Exception {
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("dc:format", "type:application/pdf");
+        test.addChild("b").setProperty("dc:format", "progress");
+        root.commit();
+
+        assertQuery(
+                "/jcr:root//*[jcr:contains(@dc:format, 'pro*')]",
+                "xpath", ImmutableList.of("/test/b"));
+
+
+        assertQuery(
+                "/jcr:root//*[jcr:contains(@dc:format, 'type:appli*')]",
+                "xpath", ImmutableList.of("/test/a"));
+
+    }
+
+    @Ignore
+    @Test
+    public void containsPath() throws Exception {
+
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("name", "/parent/child/node");
+        root.commit();
+
+        StringBuffer stmt = new StringBuffer();
+        stmt.append("//*[jcr:contains(., '/parent/child')]");
+        assertEventually(() -> {
+            assertQuery(stmt.toString(), "xpath", ImmutableList.of("/test/a"));
+        });
+
+    }
+
+    @Ignore
+    @Test
+    public void containsPathNum() throws Exception {
+
+        Tree test = root.getTree("/").addChild("test");
+        Tree a = test.addChild("a");
+        a.setProperty("name", "/segment1/segment2/segment3");
+        root.commit();
+
+        StringBuffer stmt = new StringBuffer();
+        stmt.append("//*[jcr:contains(., '/segment1/segment2')]");
+        assertEventually(() -> {
+            assertQuery(stmt.toString(), "xpath", ImmutableList.of("/test/a"));
+        });
+
+    }
+
+    @Test
+    public void containsPathStrict() throws Exception {
+        root.getTree("/").addChild("matchOnPath");
+        root.getTree("/").addChild("match_on_path");
+        root.commit();
+
+        StringBuffer stmt = new StringBuffer();
+        stmt.append("//*[jcr:contains(., 'match')]");
+        assertEventually(() -> {
+            assertQuery(stmt.toString(), "xpath",
+                    ImmutableList.of("/match_on_path"));
+        });
+
+    }
+
+    @Test
+    public void containsPathStrictNum() throws Exception {
+        root.getTree("/").addChild("matchOnPath1234");
+        root.getTree("/").addChild("match_on_path1234");
+        root.commit();
+
+        StringBuffer stmt = new StringBuffer();
+        stmt.append("//*[jcr:contains(., 'match')]");
+        assertEventually(() -> {
+            assertQuery(stmt.toString(), "xpath",
+                    ImmutableList.of("/match_on_path1234"));
+        });
+
+    }
+
+    /**
+     * OAK-1208 property existence constraints break queries
+     */
+    //TODO ES failing
+    @Ignore
+    @Test
+    public void testOAK1208() throws Exception {
+        Tree t = root.getTree("/").addChild("containsWithMultipleOr");
+        Tree one = t.addChild("one");
+        one.setProperty("p", "dam/smartcollection");
+        one.setProperty("t", "media");
+
+        Tree two = t.addChild("two");
+        two.setProperty("p", "dam/collection");
+        two.setProperty("t", "media");
+
+        Tree three = t.addChild("three");
+        three.setProperty("p", "dam/hits");
+        three.setProperty("t", "media");
+
+        root.commit();
+
+        StringBuffer stmt = new StringBuffer();
+        stmt.append("//*[jcr:contains(., 'media') and (@p = 'dam/smartcollection' or @p = 'dam/collection') ]");
+        assertEventually(() -> {
+            assertQuery(stmt.toString(), "xpath",
+                    ImmutableList.of(one.getPath(), two.getPath()));
+        });
+    }
+
+    @Test
+    public void testNativeLuceneQuery() throws Exception {
+        String nativeQueryString = "select [jcr:path] from [nt:base] where native('lucene', 'title:foo -title:bar')";
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("title", "foo");
+        test.addChild("b").setProperty("title", "bar");
+        root.commit();
+
+        assertEventually(() -> {
+            Iterator<String> result = executeQuery(nativeQueryString, "JCR-SQL2").iterator();
+            assertTrue(result.hasNext());
+            assertEquals("/test/a", result.next());
+            assertFalse(result.hasNext());
+        });
+    }
+
+    //TODO ES Failing
+    @Ignore
+    @Test
+    public void testRepSimilarAsNativeQuery() throws Exception {
+        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");
+        test.addChild("b").setProperty("text", "He said Hello and then the world said Hello as well.");
+        test.addChild("c").setProperty("text", "He said Hi.");
+        root.commit();
+        assertEventually(() -> {
+            Iterator<String> result = executeQuery(nativeQueryString, "JCR-SQL2").iterator();
+            assertTrue(result.hasNext());
+            assertEquals("/test/a", result.next());
+            assertTrue(result.hasNext());
+            assertEquals("/test/b", result.next());
+            assertFalse(result.hasNext());
+        });
+    }
+
+    //TODO ES Failing
+    @Ignore
+    @Test
+    public void testRepSimilarQuery() throws Exception {
+        String query = "select [jcr:path] from [nt:base] where similar(., '/test/a')";
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("text", "Hello World Hello World");
+        test.addChild("b").setProperty("text", "Hello World");
+        test.addChild("c").setProperty("text", "World");
+        test.addChild("d").setProperty("text", "Hello");
+        test.addChild("e").setProperty("text", "World");
+        test.addChild("f").setProperty("text", "Hello");
+        test.addChild("g").setProperty("text", "World");
+        test.addChild("h").setProperty("text", "Hello");
+        root.commit();
+        assertEventually(() -> {
+            Iterator<String> result = executeQuery(query, "JCR-SQL2").iterator();
+            assertTrue(result.hasNext());
+            assertEquals("/test/a", result.next());
+            assertTrue(result.hasNext());
+            assertEquals("/test/b", result.next());
+            assertTrue(result.hasNext());
+        });
+    }
+
+    //TODO ES failing
+    @Ignore
+    @Test
+    public void testRepSimilarXPathQuery() throws Exception {
+        String query = "//element(*, nt:base)[rep:similar(., '/test/a')]";
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("text", "Hello World Hello World");
+        test.addChild("b").setProperty("text", "Hello World");
+        test.addChild("c").setProperty("text", "World");
+        test.addChild("d").setProperty("text", "Hello");
+        test.addChild("e").setProperty("text", "World");
+        test.addChild("f").setProperty("text", "Hello");
+        test.addChild("g").setProperty("text", "World");
+        test.addChild("h").setProperty("text", "Hello");
+        root.commit();
+        assertEventually(() -> {
+            Iterator<String> result = executeQuery(query, "xpath").iterator();
+            assertTrue(result.hasNext());
+            assertEquals("/test/a", result.next());
+            assertTrue(result.hasNext());
+            assertEquals("/test/b", result.next());
+        });
+    }
+
+    @Ignore
+    @Test
+    public void testTokenizeCN() throws Exception {
+        Tree t = root.getTree("/").addChild("containsCN");
+        Tree one = t.addChild("one");
+        one.setProperty("t", "美女衬衫");
+        root.commit();
+        assertEventually(() -> {
+            assertQuery("//*[jcr:contains(., '美女')]", "xpath",
+                    ImmutableList.of(one.getPath()));
+        });
+    }
+
+    //TODO ES Failing
+    @Ignore
+    @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();
+        assertEventually(() -> {
+            assertQuery(
+                    "/jcr:root//*[jcr:contains(@" + mulValuedProp + ", 'foo')]",
+                    "xpath", of("/test/" + child));
+        });
+        test.getChild(child).setProperty(mulValuedProp, new ArrayList<String>(), Type.STRINGS);
+        root.commit();
+        assertEventually(() -> {
+            assertQuery("/jcr:root//*[jcr:contains(@" + mulValuedProp + ", 'foo')]", "xpath", new ArrayList<String>());
+        });
+        test.getChild(child).setProperty(mulValuedProp, of("bar"), Type.STRINGS);
+        root.commit();
+        assertEventually(() -> {
+            assertQuery(
+                    "/jcr:root//*[jcr:contains(@" + mulValuedProp + ", 'foo')]",
+                    "xpath", new ArrayList<String>());
+        });
+        test.getChild(child).removeProperty(mulValuedProp);
+        root.commit();
+        assertEventually(() -> {
+            assertQuery(
+                    "/jcr:root//*[jcr:contains(@" + mulValuedProp + ", 'foo')]",
+                    "xpath", new ArrayList<String>());
+        });
+    }
+
+    @SuppressWarnings("unused")
+    private static void walktree(final Tree t) {
+        System.out.println("+ " + t.getPath());
+        for (PropertyState p : t.getProperties()) {
+            System.out.println("  -" + p.getName() + "=" + p.getValue(STRING));
+        }
+        for (Tree t1 : t.getChildren()) {
+            walktree(t1);
+        }
+    }
+
+    private static Tree child(Tree t, String n, String type) {
+        Tree t1 = t.addChild(n);
+        t1.setProperty(JCR_PRIMARYTYPE, type, Type.NAME);
+        return t1;
+    }
+
+    //TODO ES TEST Failing
+    @Ignore
+    @Test
+    public void oak3371() throws Exception {
+        setTraversalEnabled(false);
+        Tree t, t1;
+
+        t = root.getTree("/");
+        t = child(t, "test", NT_UNSTRUCTURED);
+        t1 = child(t, "a", NT_UNSTRUCTURED);
+        t1.setProperty("foo", "bar");
+        t1 = child(t, "b", NT_UNSTRUCTURED);
+        t1.setProperty("foo", "cat");
+        t1 = child(t, "c", NT_UNSTRUCTURED);
+        t1 = child(t, "d", NT_UNSTRUCTURED);
+        t1.setProperty("foo", "bar cat");
+
+        root.commit();
+
+        assertEventually(() -> {
+            assertQuery(
+                    "SELECT * FROM [nt:unstructured] WHERE ISDESCENDANTNODE('/test') AND CONTAINS(foo, 'bar')",
+                    of("/test/a", "/test/d"));
+
+            assertQuery(
+                    "SELECT * FROM [nt:unstructured] WHERE ISDESCENDANTNODE('/test') AND NOT CONTAINS(foo, 'bar')",
+                    of("/test/b", "/test/c"));
+
+            assertQuery(
+                    "SELECT * FROM [nt:unstructured] WHERE ISDESCENDANTNODE('/test') AND CONTAINS(foo, 'bar cat')",
+                    of("/test/d"));
+
+            assertQuery(
+                    "SELECT * FROM [nt:unstructured] WHERE ISDESCENDANTNODE('/test') AND NOT CONTAINS(foo, 'bar cat')",
+                    of("/test/c"));
+        });
+        setTraversalEnabled(true);
+    }
+
+    private static void assertEventually(Runnable r) {
+        TestUtils.assertEventually(r, 3000 * 3);
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexQueryCommonTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/SecureFacetCommonTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/SecureFacetCommonTest.java?rev=1880807&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/SecureFacetCommonTest.java (added)
+++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/SecureFacetCommonTest.java Wed Aug 12 13:46:45 2020
@@ -0,0 +1,338 @@
+/*
+ * 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;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PerfLogger;
+import org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder;
+import org.apache.jackrabbit.oak.query.AbstractJcrTest;
+import org.apache.jackrabbit.oak.query.facet.FacetResult;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+import javax.jcr.security.Privilege;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import static org.apache.jackrabbit.commons.JcrUtils.getOrCreateByPath;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.FACETS;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_REFRESH_DEFN;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_SECURE_FACETS;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_SECURE_FACETS_VALUE_INSECURE;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_SECURE_FACETS_VALUE_STATISTICAL;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_STATISTICAL_FACET_SAMPLE_SIZE;
+import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+public abstract class SecureFacetCommonTest extends AbstractJcrTest {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractJcrTest.class);
+    private static final PerfLogger LOG_PERF = new PerfLogger(LOG);
+    protected TestRepository repositoryOptionsUtil;
+    protected Node indexNode;
+    protected IndexOptions indexOptions;
+    private static final String FACET_PROP = "facets";
+
+    private static final int NUM_LEAF_NODES = STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT;
+    private static final int NUM_LABELS = 4;
+    private static final int NUM_LEAF_NODES_FOR_LARGE_DATASET = NUM_LEAF_NODES;
+    private static final int NUM_LEAF_NODES_FOR_SMALL_DATASET = NUM_LEAF_NODES / (2 * NUM_LABELS);
+    private final Map<String, Integer> actualLabelCount = new HashMap<>();
+    private final Map<String, Integer> actualAclLabelCount = new HashMap<>();
+    private final Map<String, Integer> actualAclPar1LabelCount = new HashMap<>();
+
+
+    @Before
+    public void createIndex() throws RepositoryException {
+        String indexName = UUID.randomUUID().toString();
+        IndexDefinitionBuilder builder = indexOptions.createIndex(indexOptions.createIndexDefinitionBuilder(), false);
+        builder.noAsync();
+        IndexDefinitionBuilder.IndexRule indexRule = builder.indexRule(JcrConstants.NT_BASE);
+        indexRule.property("cons").propertyIndex();
+        indexRule.property("foo").propertyIndex().getBuilderTree().setProperty(FACET_PROP, true, Type.BOOLEAN);
+        indexRule.property("bar").propertyIndex().getBuilderTree().setProperty(FACET_PROP, true, Type.BOOLEAN);
+
+        indexOptions.setIndex(adminSession, indexName, builder);
+        indexNode = indexOptions.getIndexNode(adminSession, indexName);
+    }
+
+    private void createDataset(int numberOfLeafNodes) throws RepositoryException {
+        Random rGen = new Random(42);
+        Random rGen1 = new Random(42);
+        int[] foolabelCount = new int[NUM_LABELS];
+        int[] fooaclLabelCount = new int[NUM_LABELS];
+        int[] fooaclPar1LabelCount = new int[NUM_LABELS];
+
+        int[] barlabelCount = new int[NUM_LABELS];
+        int[] baraclLabelCount = new int[NUM_LABELS];
+        int[] baraclPar1LabelCount = new int[NUM_LABELS];
+
+        Node par = allow(getOrCreateByPath("/parent", "oak:Unstructured", adminSession));
+
+        for (int i = 0; i < NUM_LABELS; i++) {
+            Node subPar = par.addNode("par" + i);
+            for (int j = 0; j < numberOfLeafNodes; j++) {
+                Node child = subPar.addNode("c" + j);
+                child.setProperty("cons", "val");
+                // Add a random label out of "l0", "l1", "l2", "l3"
+                int foolabelNum = rGen.nextInt(NUM_LABELS);
+                int barlabelNum = rGen1.nextInt(NUM_LABELS);
+                child.setProperty("foo", "l" + foolabelNum);
+                child.setProperty("bar", "m" + barlabelNum);
+
+                foolabelCount[foolabelNum]++;
+                barlabelCount[barlabelNum]++;
+                if (i != 0) {
+                    fooaclLabelCount[foolabelNum]++;
+                    baraclLabelCount[barlabelNum]++;
+                }
+                if (i == 1) {
+                    fooaclPar1LabelCount[foolabelNum]++;
+                    baraclPar1LabelCount[barlabelNum]++;
+                }
+            }
+
+            // deny access for one sub-parent
+            if (i == 0) {
+                deny(subPar);
+            }
+        }
+        adminSession.save();
+        for (int i = 0; i < foolabelCount.length; i++) {
+            actualLabelCount.put("l" + i, foolabelCount[i]);
+            actualLabelCount.put("m" + i, barlabelCount[i]);
+            actualAclLabelCount.put("l" + i, fooaclLabelCount[i]);
+            actualAclLabelCount.put("m" + i, baraclLabelCount[i]);
+            actualAclPar1LabelCount.put("l" + i, fooaclPar1LabelCount[i]);
+            actualAclPar1LabelCount.put("m" + i, baraclPar1LabelCount[i]);
+        }
+        assertNotEquals("Acl-ed and actual counts mustn't be same", actualLabelCount, actualAclLabelCount);
+    }
+
+    @Test
+    public void secureFacets() throws Exception {
+        createDataset(NUM_LEAF_NODES_FOR_LARGE_DATASET);
+        assertEventually(() -> assertEquals(actualAclLabelCount, getFacets()));
+    }
+
+    @Test
+    public void secureFacets_withOneLabelInaccessible() throws Exception {
+        createDataset(NUM_LEAF_NODES_FOR_LARGE_DATASET);
+        Node inaccessibleChild = deny(adminSession.getNode("/parent").addNode("par4")).addNode("c0");
+        inaccessibleChild.setProperty("cons", "val");
+        inaccessibleChild.setProperty("foo", "l4");
+        adminSession.save();
+        assertEventually(() -> assertEquals(actualAclLabelCount, getFacets()));
+    }
+
+    @Test
+    public void insecureFacets() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + FACETS, "nt:unstructured", adminSession);
+        facetConfig.setProperty(PROP_SECURE_FACETS, PROP_SECURE_FACETS_VALUE_INSECURE);
+        adminSession.save();
+
+        createDataset(NUM_LEAF_NODES_FOR_LARGE_DATASET);
+        assertEventually(() -> assertEquals(actualLabelCount, getFacets()));
+    }
+
+    @Test
+    public void statisticalFacets() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + FACETS, "nt:unstructured", adminSession);
+        facetConfig.setProperty(PROP_SECURE_FACETS, PROP_SECURE_FACETS_VALUE_STATISTICAL);
+        facetConfig.setProperty(PROP_STATISTICAL_FACET_SAMPLE_SIZE, 3000);
+        adminSession.save();
+
+        createDataset(NUM_LEAF_NODES_FOR_LARGE_DATASET);
+
+        assertEventually(() -> assertEquals("Unexpected number of facets", actualAclLabelCount.size(), getFacets().size()));
+
+        for (Map.Entry<String, Integer> facet : actualAclLabelCount.entrySet()) {
+            String facetLabel = facet.getKey();
+            assertEventually(() -> {
+                int facetCount = getFacets().get(facetLabel);
+                float ratio = ((float) facetCount) / facet.getValue();
+                assertTrue("Facet count for label: " + facetLabel + " is outside of 10% margin of error. " +
+                                "Expected: " + facet.getValue() + "; Got: " + facetCount + "; Ratio: " + ratio,
+                        Math.abs(ratio - 1) < 0.1);
+            });
+        }
+    }
+
+    @Test
+    public void statisticalFacetsWithHitCountLessThanSampleSize() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + FACETS, "nt:unstructured", adminSession);
+        facetConfig.setProperty(PROP_SECURE_FACETS, PROP_SECURE_FACETS_VALUE_STATISTICAL);
+        indexNode.setProperty(PROP_REFRESH_DEFN, true);
+        adminSession.save();
+
+        createDataset(NUM_LEAF_NODES_FOR_SMALL_DATASET);
+
+        assertEventually(() -> assertEquals("Unexpected number of facets", actualAclLabelCount.size(), getFacets().size()));
+
+        // Since the hit count is less than sample size -> flow should have switched to secure facet count instead of statistical
+        // and thus the count should be exactly equal
+        assertEventually(() -> assertEquals(actualAclLabelCount, getFacets()));
+    }
+
+    //TODO Test is failing with lucene index.
+    @Ignore
+    @Test
+    public void statisticalFacets_withHitCountSameAsSampleSize() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + FACETS, "nt:unstructured", adminSession);
+        facetConfig.setProperty(PROP_SECURE_FACETS, PROP_SECURE_FACETS_VALUE_STATISTICAL);
+        indexNode.setProperty(PROP_REFRESH_DEFN, true);
+        adminSession.save();
+
+        createDataset(NUM_LEAF_NODES_FOR_LARGE_DATASET);
+
+        assertEventually(() -> {
+            Map<String, Integer> facets = getFacets("/parent/par1");
+            assertEquals("Unexpected number of facets", actualAclPar1LabelCount.size(), facets.size());
+
+            for (Map.Entry<String, Integer> facet : actualAclPar1LabelCount.entrySet()) {
+                String facetLabel = facet.getKey();
+                int facetCount = facets.get(facetLabel);
+                float ratio = ((float) facetCount) / facet.getValue();
+                assertTrue("Facet count for label: " + facetLabel + " is outside of 10% margin of error. " +
+                                "Expected: " + facet.getValue() + "; Got: " + facetCount + "; Ratio: " + ratio,
+                        Math.abs(ratio - 1) < 0.1);
+            }
+        });
+    }
+
+    @Test
+    public void statisticalFacets_withOneLabelInaccessible() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + FACETS, "nt:unstructured", adminSession);
+        facetConfig.setProperty(PROP_SECURE_FACETS, PROP_SECURE_FACETS_VALUE_STATISTICAL);
+        indexNode.setProperty(PROP_REFRESH_DEFN, true);
+        adminSession.save();
+
+        createDataset(NUM_LEAF_NODES_FOR_LARGE_DATASET);
+        Node inaccessibleChild = deny(adminSession.getNode("/parent").addNode("par4")).addNode("c0");
+        inaccessibleChild.setProperty("cons", "val");
+        inaccessibleChild.setProperty("foo", "l4");
+        adminSession.save();
+        assertEventually(() -> {
+            Map<String, Integer> facets = getFacets();
+            assertEquals("Unexpected number of facets", actualAclLabelCount.size(), facets.size());
+        });
+
+        for (Map.Entry<String, Integer> facet : actualAclLabelCount.entrySet()) {
+
+            assertEventually(() -> {
+                String facetLabel = facet.getKey();
+                int facetCount = getFacets().get(facetLabel);
+                float ratio = ((float) facetCount) / facet.getValue();
+                assertTrue("Facet count for label: " + facetLabel + " is outside of 10% margin of error. " +
+                                "Expected: " + facet.getValue() + "; Got: " + facetCount + "; Ratio: " + ratio,
+                        Math.abs(ratio - 1) < 0.1);
+            });
+        }
+    }
+
+    @Test
+    public void secureFacets_withAdminSession() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + FACETS, "nt:unstructured", adminSession);
+        facetConfig.setProperty(PROP_SECURE_FACETS, PROP_SECURE_FACETS_VALUE_INSECURE);
+        indexNode.setProperty(PROP_REFRESH_DEFN, true);
+        adminSession.save();
+        createDataset(NUM_LEAF_NODES_FOR_LARGE_DATASET);
+        qm = adminSession.getWorkspace().getQueryManager();
+        assertEventually(() -> assertEquals(actualLabelCount, getFacets()));
+    }
+
+    @Test
+    public void statisticalFacets_withAdminSession() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + FACETS, "nt:unstructured", adminSession);
+        facetConfig.setProperty(PROP_SECURE_FACETS, PROP_SECURE_FACETS_VALUE_STATISTICAL);
+        indexNode.setProperty(PROP_REFRESH_DEFN, true);
+        adminSession.save();
+        createDataset(NUM_LEAF_NODES_FOR_LARGE_DATASET);
+        qm = adminSession.getWorkspace().getQueryManager();
+        assertEventually(() -> {
+            Map<String, Integer> facets = getFacets();
+            assertEquals("Unexpected number of facets", actualLabelCount.size(), facets.size());
+        });
+
+        for (Map.Entry<String, Integer> facet : actualLabelCount.entrySet()) {
+            assertEventually(() -> {
+                String facetLabel = facet.getKey();
+                int facetCount = getFacets().get(facetLabel);
+                float ratio = ((float) facetCount) / facet.getValue();
+                assertTrue("Facet count for label: " + facetLabel + " is outside of 5% margin of error. " +
+                                "Expected: " + facet.getValue() + "; Got: " + facetCount + "; Ratio: " + ratio,
+                        Math.abs(ratio - 1) < 0.05);
+            });
+        }
+    }
+
+    private Map<String, Integer> getFacets() {
+        return getFacets(null);
+    }
+
+    private Node deny(Node node) throws RepositoryException {
+        AccessControlUtils.deny(node, "anonymous", Privilege.JCR_ALL);
+        return node;
+    }
+
+    private Node allow(Node node) throws RepositoryException {
+        AccessControlUtils.allow(node, "anonymous", Privilege.JCR_READ);
+        return node;
+    }
+
+    private Map<String, Integer> getFacets(String path) {
+        String pathCons = "";
+        if (path != null) {
+            pathCons = " AND ISDESCENDANTNODE('" + path + "')";
+        }
+        String query = "SELECT [rep:facet(foo)], [rep:facet(bar)] FROM [nt:base] WHERE [cons] = 'val'" + pathCons;
+        Query q;
+        QueryResult queryResult;
+        try {
+            q = qm.createQuery(query, Query.JCR_SQL2);
+            queryResult = q.execute();
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+        long start = LOG_PERF.start("Getting the Facet Results...");
+        FacetResult facetResult = new FacetResult(queryResult);
+        LOG_PERF.end(start, -1, "Facet Results fetched");
+
+        return facetResult.getDimensions()
+                .stream()
+                .flatMap(dim -> Objects.requireNonNull(facetResult.getFacets(dim)).stream())
+                .collect(Collectors.toMap(FacetResult.Facet::getLabel, FacetResult.Facet::getCount));
+    }
+
+    protected void assertEventually(Runnable r) {
+        TestUtils.assertEventually(r, ((repositoryOptionsUtil.isAsync() ? repositoryOptionsUtil.defaultAsyncIndexingTimeInSeconds : 0) + 3000) * 5);
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/SecureFacetCommonTest.java
------------------------------------------------------------------------------
    svn:eol-style = native