Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/AggregateTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/AggregateTest.java?rev=1828972&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/AggregateTest.java (added) +++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/AggregateTest.java Thu Apr 12 11:46:17 2018 @@ -0,0 +1,525 @@ +/* + * 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.search; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import com.google.common.base.Function; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.index.search.Aggregate.NodeInclude; +import org.apache.jackrabbit.oak.plugins.index.search.Aggregate.NodeIncludeResult; +import org.apache.jackrabbit.oak.plugins.index.search.Aggregate.PropertyIncludeResult; +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.junit.Test; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.of; +import static com.google.common.collect.Iterables.toArray; +import static com.google.common.collect.Maps.newHashMap; +import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES; +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; +import static org.apache.jackrabbit.oak.InitialContent.INITIAL_CONTENT; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INDEX_RULES; +import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.matchers.JUnitMatchers.hasItems; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +public class AggregateTest { + + private final TestCollector col = new TestCollector(); + private final SimpleMapper mapper = new SimpleMapper(); + private final NodeState root = INITIAL_CONTENT; + private NodeBuilder builder = root.builder(); + + //~---------------------------------< Node Includes > + + @Test + public void oneLevelAll() throws Exception{ + Aggregate ag = new Aggregate("nt:base", of(ni("*"))); + NodeBuilder nb = newNode("nt:base"); + nb.child("a").child("c"); + nb.child("b"); + + ag.collectAggregates(nb.getNodeState(), col); + assertEquals(2, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("a", "b")); + } + + @Test + public void oneLevelNamed() throws Exception{ + Aggregate ag = new Aggregate("nt:base", of(ni("a"))); + NodeBuilder nb = newNode("nt:base"); + nb.child("a"); + nb.child("b"); + + ag.collectAggregates(nb.getNodeState(), col); + assertEquals(1, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("a")); + } + + @Test + public void noOfChildNodeRead() throws Exception{ + Aggregate ag = new Aggregate("nt:base", of(ni("a"))); + NodeBuilder nb = newNode("nt:base"); + nb.child("a"); + for (int i = 0; i < 10; i++) { + nb.child("a"+i); + } + + NodeState state = nb.getNodeState(); + final AtomicInteger counter = new AtomicInteger(); + Iterable<? extends ChildNodeEntry> countingIterator = Iterables.transform(state.getChildNodeEntries(), + new Function<ChildNodeEntry, ChildNodeEntry>() { + @Override + public ChildNodeEntry apply(ChildNodeEntry input) { + counter.incrementAndGet(); + return input; + } + }); + NodeState mocked = spy(state); + doReturn(countingIterator).when(mocked).getChildNodeEntries(); + ag.collectAggregates(mocked, col); + + //Here at max a single call should happen for reading child nodes + assertThat(counter.get(), is(lessThanOrEqualTo(1))); + } + + @Test + public void oneLevelTyped() throws Exception{ + Aggregate ag = new Aggregate("nt:base", of(ni("nt:resource","*", false))); + NodeBuilder nb = newNode("nt:base"); + nb.child("a").setProperty(JCR_PRIMARYTYPE,"nt:resource"); + nb.child("b"); + + ag.collectAggregates(nb.getNodeState(), col); + assertEquals(1, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("a")); + } + + @Test + public void oneLevelTypedMixin() throws Exception{ + Aggregate ag = new Aggregate("nt:base", of(ni("mix:title","*", false))); + NodeBuilder nb = newNode("nt:base"); + nb.child("a").setProperty(JcrConstants.JCR_MIXINTYPES, Collections.singleton("mix:title"), Type.NAMES); + nb.child("b"); + + ag.collectAggregates(nb.getNodeState(), col); + assertEquals(1, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("a")); + } + + @Test + public void multiLevelAll() throws Exception{ + Aggregate ag = new Aggregate("nt:base", of(ni("*"), ni("*/*"))); + NodeBuilder nb = newNode("nt:base"); + nb.child("a").child("c"); + nb.child("b"); + nb.child("d").child("e").child("f"); + + ag.collectAggregates(nb.getNodeState(), col); + assertEquals(5, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("a", "b", "d", "a/c", "d/e")); + } + + @Test + public void multiLevelNamed() throws Exception{ + Aggregate ag = new Aggregate("nt:base", of(ni("a"), ni("d/e"))); + NodeBuilder nb = newNode("nt:base"); + nb.child("a").child("c"); + nb.child("b"); + nb.child("d").child("e").child("f"); + + ag.collectAggregates(nb.getNodeState(), col); + assertEquals(2, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("a", "d/e")); + } + + @Test + public void multiLevelTyped() throws Exception{ + Aggregate ag = new Aggregate("nt:base", of(ni("a"), + ni("nt:resource", "d/*/*", false))); + NodeBuilder nb = newNode("nt:base"); + nb.child("a").child("c"); + nb.child("b"); + nb.child("d").child("e").child("f").setProperty(JCR_PRIMARYTYPE,"nt:resource"); + nb.child("d").child("e").child("f2"); + nb.child("d").child("e2").child("f3").setProperty(JCR_PRIMARYTYPE, "nt:resource"); + + ag.collectAggregates(nb.getNodeState(), col); + assertEquals(3, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("a", "d/e/f", "d/e2/f3")); + } + + @Test + public void multiLevelNamedSubAll() throws Exception{ + Aggregate ag = new Aggregate("nt:base", of(ni("a"), ni("d/*/*"))); + NodeBuilder nb = newNode("nt:base"); + nb.child("a").child("c"); + nb.child("b"); + nb.child("d").child("e").child("f"); + nb.child("d").child("e").child("f2"); + nb.child("d").child("e2").child("f3"); + + ag.collectAggregates(nb.getNodeState(), col); + assertEquals(4, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("a", "d/e/f", "d/e/f2", "d/e2/f3")); + } + + //~---------------------------------< Node include recursive > + + @Test + public void multiAggregateMapping() throws Exception{ + Aggregate ag = new Aggregate("nt:base", of(ni("*"))); + + Aggregate agFile = new Aggregate("nt:file", of(ni("*"), ni("*/*"))); + mapper.add("nt:file", agFile); + + NodeBuilder nb = newNode("nt:base"); + nb.child("a").child("c"); + nb.child("b").setProperty(JCR_PRIMARYTYPE, "nt:file"); + nb.child("b").child("b1").child("b2"); + nb.child("c"); + + ag.collectAggregates(nb.getNodeState(), col); + assertEquals(5, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("a", "b", "c", "b/b1", "b/b1/b2")); + } + + @Test + public void recursionEnabled() throws Exception{ + Aggregate agFile = new Aggregate("nt:file", of(ni("*")), 5); + mapper.add("nt:file", agFile); + + NodeBuilder nb = newNode("nt:file"); + nb.child("a").child("c"); + nb.child("b").setProperty(JCR_PRIMARYTYPE, "nt:file"); + nb.child("b").child("b1").child("b2"); + nb.child("c"); + + agFile.collectAggregates(nb.getNodeState(), col); + assertEquals(4, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("a", "b", "c", "b/b1")); + } + + @Test + public void recursionEnabledWithLimitCheck() throws Exception{ + int limit = 5; + Aggregate agFile = new Aggregate("nt:file", of(ni("*")), limit); + mapper.add("nt:file", agFile); + + List<String> expectedPaths = Lists.newArrayList(); + NodeBuilder nb = newNode("nt:file"); + nb.child("a").child("c"); + + String path = ""; + NodeBuilder fb = nb; + for (int i = 0; i < limit + 2; i++){ + String name = "f "+ i; + path = PathUtils.concat(path, name); + fb = fb.child(name); + fb.setProperty(JCR_PRIMARYTYPE, "nt:file"); + + if (i < limit) { + expectedPaths.add(path); + } + } + expectedPaths.add("a"); + + agFile.collectAggregates(nb.getNodeState(), col); + assertEquals(expectedPaths.size(), col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems(toArray(expectedPaths, String.class))); + } + + @Test + public void includeMatches() throws Exception{ + Aggregate ag = new Aggregate("nt:base", of(ni(null, "*", true), ni(null, "*/*", true))); + assertTrue(ag.hasRelativeNodeInclude("foo")); + assertTrue(ag.hasRelativeNodeInclude("foo/bar")); + assertFalse(ag.hasRelativeNodeInclude("foo/bar/baz")); + + + Aggregate ag2 = new Aggregate("nt:base", of(ni(null, "foo", true), ni(null, "foo/*", true))); + assertTrue(ag2.hasRelativeNodeInclude("foo")); + assertFalse(ag2.hasRelativeNodeInclude("bar")); + assertTrue(ag2.hasRelativeNodeInclude("foo/bar")); + assertFalse(ag2.hasRelativeNodeInclude("foo/bar/baz")); + } + + @Test + public void testReaggregate() throws Exception{ + //Enable relative include for all child nodes of nt:folder + //So indexing would create fulltext field for each relative nodes + Aggregate agFolder = new Aggregate("nt:folder", of(ni("nt:file", "*", true))); + + Aggregate agFile = new Aggregate("nt:file", of(ni(null, "jcr:content", true))); + mapper.add("nt:file", agFile); + mapper.add("nt:folder", agFolder); + + NodeBuilder nb = newNode("nt:folder"); + nb.child("a").child("c"); + createFile(nb, "b", "hello world"); + createFile(nb, "c", "hello world"); + + agFolder.collectAggregates(nb.getNodeState(), col); + assertEquals(4, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("b", "c", "b/jcr:content", "c/jcr:content")); + + assertEquals(2, col.nodeResults.get("b/jcr:content").size()); + + //Check that a result is provided for relative node 'b'. Actual node provided + //is b/jcr:content + assertEquals(1, col.getRelativeNodeResults("b/jcr:content", "b").size()); + } + + @Test + public void testReaggregateMixin() throws Exception{ + //A variant of testReaggregation but using mixin + //instead of normal nodetype. It abuses mix:title + //and treat it like nt:file. Test check if reaggregation + //works for mixins also + + //Enable relative include for all child nodes of nt:folder + //So indexing would create fulltext field for each relative nodes + Aggregate agFolder = new Aggregate("nt:folder", of(ni("mix:title", "*", true))); + + Aggregate agFile = new Aggregate("mix:title", of(ni(null, "jcr:content", true))); + mapper.add("mix:title", agFile); + mapper.add("nt:folder", agFolder); + + NodeBuilder nb = newNode("nt:folder"); + nb.child("a").child("c"); + createFileMixin(nb, "b", "hello world"); + createFileMixin(nb, "c", "hello world"); + + agFolder.collectAggregates(nb.getNodeState(), col); + assertEquals(4, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("b", "c", "b/jcr:content", "c/jcr:content")); + + assertEquals(2, col.nodeResults.get("b/jcr:content").size()); + + //Check that a result is provided for relative node 'b'. Actual node provided + //is b/jcr:content + assertEquals(1, col.getRelativeNodeResults("b/jcr:content", "b").size()); + } + + @Test + public void testRelativeNodeInclude() throws Exception{ + //Enable relative include for all child nodes of nt:folder + //So indexing would create fulltext field for each relative nodes + Aggregate agContent = new Aggregate("app:Page", of(ni(null, "jcr:content", true))); + + mapper.add("app:Page", agContent); + + NodeBuilder nb = newNode("app:Page"); + nb.child("jcr:content").setProperty("foo", "bar"); + + agContent.collectAggregates(nb.getNodeState(), col); + assertEquals(1, col.getNodePaths().size()); + assertThat(col.getNodePaths(), hasItems("jcr:content")); + + assertEquals(2, col.nodeResults.get("jcr:content").size()); + + //Check that a result is provided for relative node 'b'. Actual node provided + //is b/jcr:content + assertEquals(1, col.getRelativeNodeResults("jcr:content", "jcr:content").size()); + } + + private static void createFile(NodeBuilder nb, String fileName, String content){ + nb.child(fileName).setProperty(JCR_PRIMARYTYPE, "nt:file") + .child("jcr:content").setProperty("jcr:data", content.getBytes()); + } + + private static void createFileMixin(NodeBuilder nb, String fileName, String content){ + //Abusing mix:title as its registered by default + nb.child(fileName).setProperty(JCR_MIXINTYPES, Collections.singleton("mix:title"), Type.NAMES) + .child("jcr:content").setProperty("jcr:data", content.getBytes()); + } + + //~---------------------------------< Prop Includes > + + @Test + public void propOneLevelNamed() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + child(rules, "nt:folder/properties/p1") + .setProperty(FulltextIndexConstants.PROP_NAME, "a/p1"); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + Aggregate ag = defn.getApplicableIndexingRule("nt:folder").getAggregate(); + + NodeBuilder nb = newNode("nt:folder"); + nb.child("a").setProperty("p1", "foo"); + nb.child("a").setProperty("p2", "foo"); + nb.child("b").setProperty("p2", "foo"); + + ag.collectAggregates(nb.getNodeState(), col); + assertEquals(1, col.getPropPaths().size()); + assertThat(col.getPropPaths(), hasItems("a/p1")); + } + + @Test + public void propOneLevelRegex() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + child(rules, "nt:folder/properties/p1") + .setProperty(FulltextIndexConstants.PROP_NAME, "a/foo.*") + .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + Aggregate ag = defn.getApplicableIndexingRule("nt:folder").getAggregate(); + + NodeBuilder nb = newNode("nt:folder"); + nb.child("a").setProperty("foo1", "foo"); + nb.child("a").setProperty("foo2", "foo"); + nb.child("a").setProperty("bar1", "foo"); + nb.child("b").setProperty("p2", "foo"); + + ag.collectAggregates(nb.getNodeState(), col); + assertEquals(2, col.getPropPaths().size()); + assertThat(col.getPropPaths(), hasItems("a/foo1", "a/foo2")); + } + + //~---------------------------------< IndexingConfig > + + @Test + public void simpleAggregateConfig() throws Exception{ + NodeBuilder aggregates = builder.child(FulltextIndexConstants.AGGREGATES); + NodeBuilder aggFolder = aggregates.child("nt:folder"); + aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_PATH, "*"); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + Aggregate agg = defn.getAggregate("nt:folder"); + assertNotNull(agg); + assertEquals(1, agg.getIncludes().size()); + } + + @Test + public void aggregateConfig2() throws Exception{ + NodeBuilder aggregates = builder.child(FulltextIndexConstants.AGGREGATES); + NodeBuilder aggFolder = aggregates.child("nt:folder"); + aggFolder.setProperty(FulltextIndexConstants.AGG_RECURSIVE_LIMIT, 42); + aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_PATH, "*"); + aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_PRIMARY_TYPE, "nt:file"); + aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_RELATIVE_NODE, true); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + Aggregate agg = defn.getAggregate("nt:folder"); + assertNotNull(agg); + assertEquals(42, agg.reAggregationLimit); + assertEquals(1, agg.getIncludes().size()); + assertEquals("nt:file", ((NodeInclude)agg.getIncludes().get(0)).primaryType); + assertTrue(((NodeInclude)agg.getIncludes().get(0)).relativeNode); + } + + private static NodeBuilder newNode(String typeName){ + NodeBuilder builder = EMPTY_NODE.builder(); + builder.setProperty(JCR_PRIMARYTYPE, typeName); + return builder; + } + + private static NodeBuilder child(NodeBuilder nb, String path) { + for (String name : PathUtils.elements(checkNotNull(path))) { + nb = nb.child(name); + } + return nb; + } + + private Aggregate.Include ni(String pattern){ + return new NodeInclude(mapper, pattern); + } + + private Aggregate.Include ni(String type, String pattern, boolean relativeNode){ + return new NodeInclude(mapper, type, pattern, relativeNode); + } + + private static class TestCollector implements Aggregate.ResultCollector { + final ListMultimap<String, NodeIncludeResult> nodeResults = ArrayListMultimap.create(); + final Map<String, PropertyIncludeResult> propResults = newHashMap(); + @Override + public void onResult(NodeIncludeResult result) { + nodeResults.put(result.nodePath, result); + } + + @Override + public void onResult(PropertyIncludeResult result) { + propResults.put(result.propertyPath, result); + + } + + public Collection<String> getNodePaths(){ + return nodeResults.keySet(); + } + + public Collection<String> getPropPaths(){ + return propResults.keySet(); + } + + public void reset(){ + nodeResults.clear(); + propResults.clear(); + } + + public List<NodeIncludeResult> getRelativeNodeResults(String path, String rootIncludePath){ + List<NodeIncludeResult> result = Lists.newArrayList(); + + for (NodeIncludeResult nr : nodeResults.get(path)){ + if (rootIncludePath.equals(nr.rootIncludePath)){ + result.add(nr); + } + } + + return result; + } + } + + private static class SimpleMapper implements Aggregate.AggregateMapper { + final Map<String, Aggregate> mapping = newHashMap(); + + @Override + public Aggregate getAggregate(String nodeTypeName) { + return mapping.get(nodeTypeName); + } + + public void add(String type, Aggregate agg){ + mapping.put(type, agg); + } + } + +}
Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/AggregateTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionTest.java?rev=1828972&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionTest.java (added) +++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionTest.java Thu Apr 12 11:46:17 2018 @@ -0,0 +1,1091 @@ +/* + * 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.search; + +import java.util.Collections; +import java.util.List; + +import javax.jcr.PropertyType; + +import com.google.common.collect.ImmutableList; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.index.IndexConstants; +import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.IndexingRule; +import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.IndexingMode; +import org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.junit.Test; + +import static com.google.common.collect.ImmutableSet.of; +import static javax.jcr.PropertyType.TYPENAME_LONG; +import static javax.jcr.PropertyType.TYPENAME_STRING; +import static org.apache.jackrabbit.JcrConstants.NT_BASE; +import static org.apache.jackrabbit.oak.InitialContent.INITIAL_CONTENT; +import static org.apache.jackrabbit.oak.api.Type.NAMES; +import static org.apache.jackrabbit.oak.api.Type.STRINGS; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_NAMES; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_TYPES; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INDEX_RULES; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_NAME; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_NODE; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.TIKA; +import static org.apache.jackrabbit.oak.plugins.index.search.TestUtil.registerTestNodeType; +import static org.apache.jackrabbit.oak.plugins.index.search.util.IndexHelper.newFTIndexDefinition; +import static org.apache.jackrabbit.oak.plugins.index.search.util.IndexHelper.newFTPropertyIndexDefinition; +import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; +import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty; +import static org.apache.jackrabbit.oak.plugins.tree.TreeConstants.OAK_CHILD_ORDER; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class IndexDefinitionTest { + + private NodeState root = INITIAL_CONTENT; + + private NodeBuilder builder = root.builder(); + + @Test + public void defaultConfig() throws Exception{ + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + assertFalse(idxDefn.hasSyncPropertyDefinitions()); + } + + @Test + public void fullTextEnabled() throws Exception{ + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + IndexDefinition.IndexingRule rule = idxDefn.getApplicableIndexingRule(NT_BASE); + assertTrue("By default fulltext is enabled", idxDefn.isFullTextEnabled()); + assertTrue("By default everything is indexed", rule.isIndexed("foo")); + assertTrue("Property types need to be defined", rule.includePropertyType(PropertyType.DATE)); + assertTrue("For fulltext storage is enabled", rule.getConfig("foo").stored); + + assertFalse(rule.getConfig("foo").skipTokenization("foo")); + assertTrue(rule.getConfig("jcr:uuid").skipTokenization("jcr:uuid")); + } + + @Test + public void propertyTypes() throws Exception{ + builder.setProperty(createProperty(INCLUDE_PROPERTY_TYPES, of(TYPENAME_LONG), STRINGS)); + builder.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo" , "bar"), STRINGS)); + builder.setProperty(FulltextIndexConstants.FULL_TEXT_ENABLED, false); + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + IndexDefinition.IndexingRule rule = idxDefn.getApplicableIndexingRule(NT_BASE); + assertFalse(idxDefn.isFullTextEnabled()); + assertFalse("If fulltext disabled then nothing stored", rule.getConfig("foo").stored); + + assertTrue(rule.includePropertyType(PropertyType.LONG)); + assertFalse(rule.includePropertyType(PropertyType.STRING)); + + assertTrue(rule.isIndexed("foo")); + assertTrue(rule.isIndexed("bar")); + assertFalse(rule.isIndexed("baz")); + + assertTrue(rule.getConfig("foo").skipTokenization("foo")); + } + + @Test + public void propertyDefinition() throws Exception{ + builder.child(PROP_NODE).child("foo").setProperty(FulltextIndexConstants.PROP_TYPE, PropertyType.TYPENAME_DATE); + builder.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo" , "bar"), STRINGS)); + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + IndexDefinition.IndexingRule rule = idxDefn.getApplicableIndexingRule(NT_BASE); + + assertTrue(rule.isIndexed("foo")); + assertTrue(rule.isIndexed("bar")); + + assertEquals(PropertyType.DATE, rule.getConfig("foo").getType()); + } + + @Test + public void propertyDefinitionWithExcludes() throws Exception{ + builder.child(PROP_NODE).child("foo").setProperty(FulltextIndexConstants.PROP_TYPE, PropertyType.TYPENAME_DATE); + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + IndexDefinition.IndexingRule rule = idxDefn.getApplicableIndexingRule(NT_BASE); + assertTrue(rule.isIndexed("foo")); + assertTrue(rule.isIndexed("bar")); + + assertEquals(PropertyType.DATE, rule.getConfig("foo").getType()); + } + + @Test + public void indexRuleSanity() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder").setProperty(FulltextIndexConstants.FIELD_BOOST, 2.0); + TestUtil.child(rules, "nt:folder/properties/prop1") + .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0) + .setProperty(FulltextIndexConstants.PROP_TYPE, PropertyType.TYPENAME_BOOLEAN); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + assertNull(defn.getApplicableIndexingRule(asState(newNode("nt:base")))); + + IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertNotNull(rule1); + assertEquals(2.0f, rule1.boost, 0); + + assertTrue(rule1.isIndexed("prop1")); + assertFalse(rule1.isIndexed("prop2")); + + PropertyDefinition pd = rule1.getConfig("prop1"); + assertEquals(3.0f, pd.boost, 0); + assertEquals(PropertyType.BOOLEAN, pd.getType()); + } + + @Test + public void indexRuleInheritance() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + builder.setProperty(PROP_NAME, "testIndex"); + rules.child("nt:hierarchyNode").setProperty(FulltextIndexConstants.FIELD_BOOST, 2.0); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + assertNull(defn.getApplicableIndexingRule(asState(newNode("nt:base")))); + assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:hierarchyNode")))); + assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:folder")))); + } + + @Test + public void indexRuleMixin() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("mix:title"); + TestUtil.child(rules, "mix:title/properties/jcr:title") + .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:folder", "mix:title")))); + assertNull(defn.getApplicableIndexingRule(asState(newNode("nt:folder")))); + } + + @Test + public void indexRuleMixinInheritance() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("mix:mimeType"); + TestUtil.child(rules, "mix:mimeType/properties/jcr:mimeType") + .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:folder", "mix:mimeType")))); + assertNull(defn.getApplicableIndexingRule(asState(newNode("nt:folder")))); + + //nt:resource > mix:mimeType + assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:resource")))); + } + + @Test + public void indexRuleInheritanceDisabled() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + builder.setProperty(PROP_NAME, "testIndex"); + rules.child("nt:hierarchyNode") + .setProperty(FulltextIndexConstants.FIELD_BOOST, 2.0) + .setProperty(FulltextIndexConstants.RULE_INHERITED, false); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + assertNull(defn.getApplicableIndexingRule(asState(newNode("nt:base")))); + assertNotNull(defn.getApplicableIndexingRule(asState(newNode("nt:hierarchyNode")))); + assertNull("nt:folder should not be index as rule is not inheritable", + defn.getApplicableIndexingRule(asState(newNode("nt:folder")))); + } + + @Test + public void indexRuleInheritanceOrdering() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.setProperty(OAK_CHILD_ORDER, ImmutableList.of("nt:hierarchyNode", "nt:base"),NAMES); + rules.child("nt:hierarchyNode").setProperty(FulltextIndexConstants.FIELD_BOOST, 2.0); + rules.child("nt:base").setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + assertEquals(3.0, getRule(defn, "nt:base").boost, 0); + assertEquals(2.0, getRule(defn, "nt:hierarchyNode").boost, 0); + assertEquals(3.0, getRule(defn, "nt:query").boost, 0); + } + @Test + public void indexRuleInheritanceOrdering2() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.setProperty(OAK_CHILD_ORDER, ImmutableList.of("nt:base", "nt:hierarchyNode"),NAMES); + rules.child("nt:hierarchyNode").setProperty(FulltextIndexConstants.FIELD_BOOST, 2.0); + rules.child("nt:base").setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + //As nt:base is defined earlier it would supercede everything + assertEquals(3.0, getRule(defn, "nt:base").boost, 0); + assertEquals(3.0, getRule(defn, "nt:hierarchyNode").boost, 0); + assertEquals(3.0, getRule(defn, "nt:file").boost, 0); + } + + @Test + public void indexRuleWithPropertyRegEx() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + TestUtil.child(rules, "nt:folder/properties/prop1") + .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0); + TestUtil.child(rules, "nt:folder/properties/prop2") + .setProperty(PROP_NAME, "foo.*") + .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true) + .setProperty(FulltextIndexConstants.FIELD_BOOST, 4.0); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertNotNull(rule1); + + assertTrue(rule1.isIndexed("prop1")); + assertFalse(rule1.isIndexed("prop2")); + assertTrue(rule1.isIndexed("fooProp")); + + PropertyDefinition pd = rule1.getConfig("fooProp2"); + assertEquals(4.0f, pd.boost, 0); + } + + @Test + public void indexRuleWithPropertyRegEx2() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + TestUtil.child(rules, "nt:folder/properties/prop1") + .setProperty(PROP_NAME, ".*") + .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true); + TestUtil.child(rules, "nt:folder/properties/prop2") + .setProperty(PROP_NAME, "metadata/.*") + .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true) + .setProperty(FulltextIndexConstants.FIELD_BOOST, 4.0); + + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertNotNull(rule1); + + assertTrue(rule1.isIndexed("prop1")); + assertTrue(rule1.isIndexed("prop2")); + assertFalse(rule1.isIndexed("jcr:content/prop1")); + + assertTrue(rule1.isIndexed("metadata/foo")); + assertFalse(rule1.isIndexed("metadata/foo/bar")); + } + + @Test + public void indexRuleWithPropertyOrdering() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + TestUtil.child(rules, "nt:folder/properties/prop1") + .setProperty(PROP_NAME, "foo.*") + .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true) + .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0); + TestUtil.child(rules, "nt:folder/properties/prop2") + .setProperty(PROP_NAME, ".*") + .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true) + .setProperty(FulltextIndexConstants.FIELD_BOOST, 4.0); + + rules.child("nt:folder").child(PROP_NODE).setProperty(OAK_CHILD_ORDER, ImmutableList.of("prop2", "prop1"), NAMES); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertNotNull(rule1); + + assertTrue(rule1.isIndexed("prop1")); + assertTrue(rule1.isIndexed("fooProp")); + + assertEquals(4.0f, rule1.getConfig("bazProp2").boost, 0); + //As prop2 is ordered before prop1 its regEx is evaluated first + //hence even with a specific regex of foo.* the defn used is from .* + assertEquals(4.0f, rule1.getConfig("fooProp").boost, 0); + + //Order it correctly to get expected result + rules.child("nt:folder").child(PROP_NODE).setProperty(OAK_CHILD_ORDER, ImmutableList.of("prop1", "prop2"), NAMES); + defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertEquals(3.0f, rule1.getConfig("fooProp").boost, 0); + } + + @Test + public void propertyConfigCaseInsensitive() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + TestUtil.child(rules, "nt:folder/properties/foo") + .setProperty(PROP_NAME, "Foo") + .setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); + TestUtil.child(rules, "nt:folder/properties/bar") + .setProperty(PROP_NAME, "BAR") + .setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertNotNull(rule1); + + assertTrue(rule1.isIndexed("Foo")); + assertTrue(rule1.isIndexed("foo")); + assertTrue(rule1.isIndexed("fOO")); + assertTrue(rule1.isIndexed("bar")); + assertFalse(rule1.isIndexed("baz")); + } + + @Test + public void skipTokenization() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + TestUtil.child(rules, "nt:folder/properties/prop2") + .setProperty(PROP_NAME, ".*") + .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true) + .setProperty(FulltextIndexConstants.PROP_ANALYZED, true); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + IndexingRule rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertFalse(rule.getConfig("foo").skipTokenization("foo")); + assertTrue(rule.getConfig(JcrConstants.JCR_UUID).skipTokenization(JcrConstants.JCR_UUID)); + } + + @Test + public void formatUpdate() throws Exception{ + NodeBuilder defnb = newFTIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + "lucene", of(TYPENAME_STRING), of("foo", "Bar"), "async"); + IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertTrue(defn.isOfOldFormat()); + + NodeBuilder updated = IndexDefinition.updateDefinition(defnb.getNodeState().builder()); + IndexDefinition defn2 = new IndexDefinition(root, updated.getNodeState(), "/foo"); + + assertFalse(defn2.isOfOldFormat()); + IndexingRule rule = defn2.getApplicableIndexingRule(asState(newNode("nt:base"))); + assertNotNull(rule); + assertFalse(rule.getConfig("foo").index); + assertFalse(rule.getConfig("Bar").index); + } + + @Test + public void propertyRegExAndRelativeProperty() throws Exception{ + NodeBuilder defnb = newFTIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + "lucene", of(TYPENAME_STRING), of("foo"), "async"); + IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertTrue(defn.isOfOldFormat()); + + NodeBuilder updated = IndexDefinition.updateDefinition(defnb.getNodeState().builder()); + IndexDefinition defn2 = new IndexDefinition(root, updated.getNodeState(), "/foo"); + + IndexingRule rule = defn2.getApplicableIndexingRule(asState(newNode("nt:base"))); + assertNotNull(rule.getConfig("foo")); + assertNull("Property regex used should not allow relative properties", rule.getConfig("foo/bar")); + } + + @Test + public void fulltextEnabledAndAggregate() throws Exception{ + NodeBuilder defnb = newFTPropertyIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + "lucene", of("foo"), "async"); + IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertFalse(defn.isFullTextEnabled()); + + NodeBuilder aggregates = defnb.child(FulltextIndexConstants.AGGREGATES); + NodeBuilder aggFolder = aggregates.child("nt:base"); + aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_PATH, "*"); + + defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertTrue(defn.isFullTextEnabled()); + } + + @Test + public void costConfig() throws Exception { + NodeBuilder defnb = newFTPropertyIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + "lucene", of("foo"), "async"); + IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertEquals(1.0, defn.getCostPerEntry(), 0); + assertEquals(1.0, defn.getCostPerExecution(), 0); + assertEquals(IndexDefinition.DEFAULT_ENTRY_COUNT, defn.getEntryCount()); + assertFalse(defn.isEntryCountDefined()); + + defnb.setProperty(FulltextIndexConstants.COST_PER_ENTRY, 2.0); + defnb.setProperty(FulltextIndexConstants.COST_PER_EXECUTION, 3.0); + defnb.setProperty(IndexConstants.ENTRY_COUNT_PROPERTY_NAME, 500); + + IndexDefinition defn2 = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertEquals(2.0, defn2.getCostPerEntry(), 0); + assertEquals(3.0, defn2.getCostPerExecution(), 0); + assertEquals(500, defn2.getEntryCount()); + } + + @Test + public void fulltextCost() throws Exception{ + NodeBuilder defnb = newFTPropertyIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + "lucene", of("foo"), "async"); + IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertEquals(300, defn.getFulltextEntryCount(300)); + assertEquals(IndexDefinition.DEFAULT_ENTRY_COUNT + 100, + defn.getFulltextEntryCount(IndexDefinition.DEFAULT_ENTRY_COUNT + 100)); + + //Once count is explicitly defined then it would influence the cost + defnb.setProperty(IndexConstants.ENTRY_COUNT_PROPERTY_NAME, 100); + defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertEquals(100, defn.getFulltextEntryCount(300)); + assertEquals(50, defn.getFulltextEntryCount(50)); + } + + @Test + public void customTikaConfig() throws Exception{ + NodeBuilder defnb = newFTIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + "lucene", of(TYPENAME_STRING)); + IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertFalse(defn.hasCustomTikaConfig()); + + defnb.child(FulltextIndexConstants.TIKA) + .child(FulltextIndexConstants.TIKA_CONFIG) + .child(JcrConstants.JCR_CONTENT) + .setProperty(JcrConstants.JCR_DATA, "hello".getBytes()); + defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertTrue(defn.hasCustomTikaConfig()); + } + + @Test + public void customTikaMimeTypes() throws Exception{ + NodeBuilder defnb = newFTIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + "lucene", of(TYPENAME_STRING)); + IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertEquals("application/test", defn.getTikaMappedMimeType("application/test")); + + NodeBuilder app =defnb.child(FulltextIndexConstants.TIKA) + .child(FulltextIndexConstants.TIKA_MIME_TYPES) + .child("application"); + app.child("test").setProperty(FulltextIndexConstants.TIKA_MAPPED_TYPE, "text/plain"); + app.child("test2").setProperty(FulltextIndexConstants.TIKA_MAPPED_TYPE, "text/plain"); + defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertEquals("text/plain", defn.getTikaMappedMimeType("application/test")); + assertEquals("text/plain", defn.getTikaMappedMimeType("application/test2")); + assertEquals("application/test-unmapped", defn.getTikaMappedMimeType("application/test-unmapped")); + } + + @Test + public void maxExtractLength() throws Exception{ + NodeBuilder defnb = newFTIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + "lucene", of(TYPENAME_STRING)); + IndexDefinition defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertEquals(-IndexDefinition.DEFAULT_MAX_EXTRACT_LENGTH * IndexDefinition.DEFAULT_MAX_FIELD_LENGTH, + defn.getMaxExtractLength()); + + + defnb.child(TIKA).setProperty(FulltextIndexConstants.TIKA_MAX_EXTRACT_LENGTH, 1000); + + defn = new IndexDefinition(root, defnb.getNodeState(), "/foo"); + assertEquals(1000, defn.getMaxExtractLength()); + } + + @Test(expected = IllegalStateException.class) + public void nullCheckEnabledWithNtBase() throws Exception{ + builder.child(PROP_NODE).child("foo").setProperty(FulltextIndexConstants.PROP_NULL_CHECK_ENABLED, true); + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + } + + @Test(expected = IllegalStateException.class) + public void nullCheckEnabledWithRegex() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child(TestUtil.NT_TEST); + TestUtil.child(rules, "oak:TestNode/properties/prop2") + .setProperty(PROP_NAME, ".*") + .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true) + .setProperty(FulltextIndexConstants.PROP_NULL_CHECK_ENABLED, true); + root = registerTestNodeType(builder).getNodeState(); + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + } + + @Test + public void nullCheckEnabledWithTestNode() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + TestUtil.child(rules, "oak:TestNode/properties/prop2") + .setProperty(PROP_NAME, "foo") + .setProperty(FulltextIndexConstants.PROP_NULL_CHECK_ENABLED, true); + root = registerTestNodeType(builder).getNodeState(); + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + assertTrue(!idxDefn.getApplicableIndexingRule(TestUtil.NT_TEST).getNullCheckEnabledProperties().isEmpty()); + } + + @Test + public void notNullCheckEnabledWithTestNode() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + TestUtil.child(rules, "oak:TestNode/properties/prop2") + .setProperty(PROP_NAME, "foo") + .setProperty(FulltextIndexConstants.PROP_NOT_NULL_CHECK_ENABLED, true); + root = registerTestNodeType(builder).getNodeState(); + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + assertTrue(!idxDefn.getApplicableIndexingRule(TestUtil.NT_TEST).getNotNullCheckEnabledProperties().isEmpty()); + } + + //OAK-2477 + @Test + public void testSuggestFrequency() throws Exception { + int suggestFreq = 40; + //default config + NodeBuilder indexRoot = builder; + IndexDefinition idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo"); + assertEquals("Default config", 10, idxDefn.getSuggesterUpdateFrequencyMinutes()); + + //namespaced config shadows old method + indexRoot = builder.child("shadowConfigRoot"); + indexRoot.setProperty(FulltextIndexConstants.SUGGEST_UPDATE_FREQUENCY_MINUTES, suggestFreq); + indexRoot.child(FulltextIndexConstants.SUGGESTION_CONFIG); + idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo"); + assertEquals("Namespaced config node should shadow global config", + 10, idxDefn.getSuggesterUpdateFrequencyMinutes()); + + //config for backward config + indexRoot = builder.child("backwardCompatibilityRoot"); + indexRoot.setProperty(FulltextIndexConstants.SUGGEST_UPDATE_FREQUENCY_MINUTES, suggestFreq); + idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo"); + assertEquals("Backward compatibility config", suggestFreq, idxDefn.getSuggesterUpdateFrequencyMinutes()); + + indexRoot = builder.child("indexRoot"); + indexRoot.child(FulltextIndexConstants.SUGGESTION_CONFIG) + .setProperty(FulltextIndexConstants.SUGGEST_UPDATE_FREQUENCY_MINUTES, suggestFreq); + idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo"); + assertEquals("Set config", suggestFreq, idxDefn.getSuggesterUpdateFrequencyMinutes()); + } + + //OAK-2477 + @Test + public void testSuggestAnalyzed() throws Exception { + //default config + NodeBuilder indexRoot = builder; + IndexDefinition idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo"); + assertFalse("Default config", idxDefn.isSuggestAnalyzed()); + + //namespaced config shadows old method + indexRoot = builder.child("shadowConfigRoot"); + indexRoot.setProperty(FulltextIndexConstants.SUGGEST_ANALYZED, true); + indexRoot.child(FulltextIndexConstants.SUGGESTION_CONFIG); + idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo"); + assertFalse("Namespaced config node should shadow global config", idxDefn.isSuggestAnalyzed()); + + //config for backward config + indexRoot = builder.child("backwardCompatibilityRoot"); + indexRoot.setProperty(FulltextIndexConstants.SUGGEST_ANALYZED, true); + idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo"); + assertTrue("Backward compatibility config", idxDefn.isSuggestAnalyzed()); + + indexRoot = builder.child("indexRoot"); + indexRoot.child(FulltextIndexConstants.SUGGESTION_CONFIG) + .setProperty(FulltextIndexConstants.SUGGEST_ANALYZED, true); + idxDefn = new IndexDefinition(root, indexRoot.getNodeState(), "/foo"); + assertTrue("Set config", idxDefn.isSuggestAnalyzed()); + } + + @Test + public void testSuggestEnabledOnNamedProp() throws Exception { + NodeBuilder rules = builder.child(INDEX_RULES); + TestUtil.child(rules, "oak:TestNode/properties/prop2") + .setProperty(PROP_NAME, "foo") + .setProperty(FulltextIndexConstants.PROP_USE_IN_SUGGEST, true); + root = registerTestNodeType(builder).getNodeState(); + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + assertTrue(idxDefn.isSuggestEnabled()); + } + + @Test + public void testSuggestEnabledOnRegexProp() throws Exception { + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child(TestUtil.NT_TEST); + TestUtil.child(rules, "oak:TestNode/properties/prop2") + .setProperty(PROP_NAME, ".*") + .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true) + .setProperty(FulltextIndexConstants.PROP_USE_IN_SUGGEST, true); + root = registerTestNodeType(builder).getNodeState(); + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + assertTrue(idxDefn.isSuggestEnabled()); + } + + @Test + public void testSuggestDisabled() throws Exception { + NodeBuilder rules = builder.child(INDEX_RULES); + TestUtil.child(rules, "oak:TestNode/properties/prop2") + .setProperty(PROP_NAME, "foo"); + root = registerTestNodeType(builder).getNodeState(); + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + assertFalse(idxDefn.isSuggestEnabled()); + } + + @Test + public void analyzedEnabledForBoostedField() throws Exception { + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + TestUtil.child(rules, "nt:folder/properties/prop1") + .setProperty(FulltextIndexConstants.FIELD_BOOST, 3.0) + .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true); + TestUtil.child(rules, "nt:folder/properties/prop2") + .setProperty(FulltextIndexConstants.PROP_ANALYZED, true) + .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true); + TestUtil.child(rules, "nt:folder/properties/prop3") + .setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true) + .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + + IndexingRule rule1 = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertNotNull(rule1); + + PropertyDefinition pd = rule1.getConfig("prop1"); + assertEquals(3.0f, pd.boost, 0); + assertTrue("Analyzed should be assumed to be true for boosted fields", pd.analyzed); + assertFalse(rule1.getConfig("prop3").analyzed); + + assertEquals(2, rule1.getNodeScopeAnalyzedProps().size()); + } + + @Test + public void nodeFullTextIndexed_Regex() throws Exception { + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + TestUtil.child(rules, "nt:folder/properties/prop1") + .setProperty(PROP_NAME, ".*") + .setProperty(FulltextIndexConstants.PROP_ANALYZED, true) + .setProperty(FulltextIndexConstants.PROP_IS_REGEX, true); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + IndexingRule rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertNotNull(rule); + assertFalse(rule.isNodeFullTextIndexed()); + + TestUtil.child(rules, "nt:folder/properties/prop1") + .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true); + defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertTrue(rule.isNodeFullTextIndexed()); + assertTrue(rule.indexesAllNodesOfMatchingType()); + } + + @Test + public void nodeFullTextIndexed_Simple() throws Exception { + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + TestUtil.child(rules, "nt:folder/properties/prop1") + .setProperty(PROP_NAME, "foo") + .setProperty(FulltextIndexConstants.PROP_ANALYZED, true); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + IndexingRule rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertNotNull(rule); + assertFalse(rule.isNodeFullTextIndexed()); + + TestUtil.child(rules, "nt:folder/properties/prop1") + .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true); + defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertTrue(rule.isNodeFullTextIndexed()); + assertTrue(rule.indexesAllNodesOfMatchingType()); + } + + @Test + public void nodeFullTextIndexed_Aggregates() throws Exception { + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + TestUtil.child(rules, "nt:folder/properties/prop1") + .setProperty(PROP_NAME, "foo") + .setProperty(FulltextIndexConstants.PROP_ANALYZED, true); + + NodeBuilder aggregates = builder.child(FulltextIndexConstants.AGGREGATES); + NodeBuilder aggFolder = aggregates.child("nt:folder"); + aggFolder.child("i1").setProperty(FulltextIndexConstants.AGG_PATH, "*"); + + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + IndexingRule rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertNotNull(rule); + assertTrue(rule.isNodeFullTextIndexed()); + assertTrue(rule.indexesAllNodesOfMatchingType()); + } + + @Test + public void nonIndexPropShouldHaveAllOtherConfigDisabled() throws Exception{ + NodeBuilder rules = builder.child(INDEX_RULES); + rules.child("nt:folder"); + TestUtil.child(rules, "nt:folder/properties/prop1") + .setProperty(PROP_NAME, "foo") + .setProperty(FulltextIndexConstants.PROP_INDEX, false) + .setProperty(FulltextIndexConstants.PROP_USE_IN_SUGGEST, true) + .setProperty(FulltextIndexConstants.PROP_USE_IN_SPELLCHECK, true) + .setProperty(FulltextIndexConstants.PROP_NULL_CHECK_ENABLED, true) + .setProperty(FulltextIndexConstants.PROP_NOT_NULL_CHECK_ENABLED, true) + .setProperty(FulltextIndexConstants.PROP_USE_IN_EXCERPT, true) + .setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true) + .setProperty(FulltextIndexConstants.PROP_ORDERED, true) + .setProperty(FulltextIndexConstants.PROP_ANALYZED, true); + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + IndexingRule rule = defn.getApplicableIndexingRule(asState(newNode("nt:folder"))); + assertNotNull(rule); + + PropertyDefinition pd = rule.getConfig("foo"); + //Assert that all other config is false if the index=false for any property + assertFalse(pd.index); + assertFalse(pd.nodeScopeIndex); + assertFalse(pd.useInSuggest); + assertFalse(pd.useInSpellcheck); + assertFalse(pd.nullCheckEnabled); + assertFalse(pd.notNullCheckEnabled); + assertFalse(pd.stored); + assertFalse(pd.ordered); + assertFalse(pd.analyzed); + + } + + @Test + public void costPerEntry() throws Exception{ + builder.setProperty(FulltextIndexConstants.COMPAT_MODE, 2); + IndexDefinition defn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + assertEquals(1.0, defn.getCostPerEntry(), 0.0); + } + + @Test + public void sync() throws Exception{ + TestUtil.enableIndexingMode(builder, IndexingMode.SYNC); + IndexDefinition idxDefn = new IndexDefinition(root, builder.getNodeState(), "/foo"); + assertTrue(idxDefn.isSyncIndexingEnabled()); + } + + @Test + public void hasPersistedIndex() throws Exception{ + assertFalse(IndexDefinition.hasPersistedIndex(builder.getNodeState())); + builder.child(":status"); + assertTrue(IndexDefinition.hasPersistedIndex(builder.getNodeState())); + } + + @Test + public void uniqueIdForFreshIndex() throws Exception{ + IndexDefinition defn = IndexDefinition.newBuilder(root, builder.getNodeState(), "/foo").build(); + assertEquals("0", defn.getUniqueId()); + + builder.child(":status"); + defn = IndexDefinition.newBuilder(root, builder.getNodeState(),"/foo").build(); + assertNull(defn.getUniqueId()); + } + + @Test + public void nodeTypeChange() throws Exception{ + IndexDefinition defn = IndexDefinition.newBuilder(root, builder.getNodeState(), "/foo").build(); + NodeBuilder b2 = root.builder(); + TestUtil.registerNodeType(b2, TestUtil.TEST_NODE_TYPE); + NodeState root2 = b2.getNodeState(); + + + NodeBuilder b3 = root.builder(); + b3.child("x"); + NodeState root3 = b3.getNodeState(); + + assertFalse(defn.hasMatchingNodeTypeReg(root2)); + assertTrue(defn.hasMatchingNodeTypeReg(root3)); + } + + @Test + public void uniqueIsSync() throws Exception{ + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.indexRule("nt:base").property("foo").unique(); + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + assertTrue(defn.getApplicableIndexingRule("nt:base").getConfig("foo").sync); + assertTrue(defn.getApplicableIndexingRule("nt:base").getConfig("foo").unique); + assertTrue(defn.getApplicableIndexingRule("nt:base").getConfig("foo").propertyIndex); + } + + @Test + public void syncIsProperty() throws Exception{ + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.indexRule("nt:base").property("foo").sync(); + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + assertTrue(defn.getApplicableIndexingRule("nt:base").getConfig("foo").sync); + assertTrue(defn.getApplicableIndexingRule("nt:base").getConfig("foo").propertyIndex); + } + + @Test + public void syncPropertyDefinitions() throws Exception{ + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.indexRule("nt:base").property("foo").sync(); + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + assertTrue(defn.hasSyncPropertyDefinitions()); + } + + //~----------------------------------< nodetype > + + + String testNodeTypeDefn = "[oak:TestMixA]\n" + + " mixin\n" + + "\n" + + "[oak:TestSuperType]\n" + + "- * (UNDEFINED) multiple\n" + + "\n" + + "[oak:TestTypeA] > oak:TestSuperType\n" + + "- * (UNDEFINED) multiple\n" + + "\n" + + "[oak:TestTypeB] > oak:TestSuperType, oak:TestMixA\n" + + "- * (UNDEFINED) multiple"; + + @Test + public void nodeTypeIndexed() throws Exception{ + TestUtil.registerNodeType(builder, testNodeTypeDefn); + root = builder.getNodeState(); + + + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.nodeTypeIndex(); + defnb.indexRule("oak:TestSuperType"); + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + assertFalse(defn.hasSyncPropertyDefinitions()); + + IndexingRule ruleSuper = getRule(defn, "oak:TestSuperType"); + assertNotNull(ruleSuper); + assertTrue(defn.isPureNodeTypeIndex()); + assertTrue(ruleSuper.getConfig(JcrConstants.JCR_PRIMARYTYPE).propertyIndex); + assertTrue(ruleSuper.getConfig(JcrConstants.JCR_MIXINTYPES).propertyIndex); + assertTrue(ruleSuper.indexesAllNodesOfMatchingType()); + + assertNotNull(getRule(defn, "oak:TestTypeA")); + assertTrue(getRule(defn, "oak:TestTypeA").indexesAllNodesOfMatchingType()); + assertNotNull(getRule(defn, "oak:TestTypeB")); + assertNull(getRule(defn, "oak:TestMixA")); + } + + @Test + public void nodeTypeIndexedSync() throws Exception{ + TestUtil.registerNodeType(builder, testNodeTypeDefn); + root = builder.getNodeState(); + + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.nodeTypeIndex(); + defnb.indexRule("oak:TestSuperType").sync(); + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + assertTrue(defn.hasSyncPropertyDefinitions()); + + IndexingRule ruleSuper = getRule(defn, "oak:TestSuperType"); + assertNotNull(ruleSuper); + assertTrue(defn.isPureNodeTypeIndex()); + assertTrue(ruleSuper.getConfig(JcrConstants.JCR_PRIMARYTYPE).propertyIndex); + assertTrue(ruleSuper.getConfig(JcrConstants.JCR_PRIMARYTYPE).sync); + assertTrue(ruleSuper.getConfig(JcrConstants.JCR_MIXINTYPES).propertyIndex); + assertTrue(ruleSuper.getConfig(JcrConstants.JCR_MIXINTYPES).sync); + assertTrue(ruleSuper.indexesAllNodesOfMatchingType()); + } + + @Test + public void nodeTypeIndexed_IgnoreOtherProps() throws Exception{ + TestUtil.registerNodeType(builder, testNodeTypeDefn); + root = builder.getNodeState(); + + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.nodeTypeIndex(); + defnb.indexRule("oak:TestSuperType").sync(); + defnb.indexRule("oak:TestSuperType").property("foo").propertyIndex(); + + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + + IndexingRule ruleSuper = getRule(defn, "oak:TestSuperType"); + assertNotNull(ruleSuper); + + assertNull(ruleSuper.getConfig("foo")); + assertNotNull(ruleSuper.getConfig(JcrConstants.JCR_PRIMARYTYPE)); + assertNotNull(ruleSuper.getConfig(JcrConstants.JCR_MIXINTYPES)); + } + + @Test + public void nodeTypeIndexed_IgnoreAggregates() throws Exception{ + TestUtil.registerNodeType(builder, testNodeTypeDefn); + root = builder.getNodeState(); + + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.nodeTypeIndex(); + defnb.indexRule("oak:TestSuperType").sync(); + defnb.aggregateRule("oak:TestSuperType").include("*"); + + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + + IndexingRule ruleSuper = getRule(defn, "oak:TestSuperType"); + assertNotNull(ruleSuper); + + assertNull(ruleSuper.getConfig("foo")); + assertTrue(ruleSuper.getAggregate().getIncludes().isEmpty()); + assertNotNull(ruleSuper.getConfig(JcrConstants.JCR_PRIMARYTYPE)); + assertNotNull(ruleSuper.getConfig(JcrConstants.JCR_MIXINTYPES)); + } + + @Test + public void nodeTypeIndex_mixin() throws Exception{ + TestUtil.registerNodeType(builder, testNodeTypeDefn); + root = builder.getNodeState(); + + + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.nodeTypeIndex(); + defnb.indexRule("oak:TestMixA"); + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + assertFalse(defn.hasSyncPropertyDefinitions()); + + + assertNotNull(getRule(defn, "oak:TestTypeB")); + assertTrue(getRule(defn, "oak:TestTypeB").indexesAllNodesOfMatchingType()); + assertNotNull(getRule(defn, "oak:TestMixA")); + assertTrue(getRule(defn, "oak:TestMixA").indexesAllNodesOfMatchingType()); + + assertNull(getRule(defn, "oak:TestTypeA")); + assertNull(getRule(defn, "oak:TestSuperType")); + } + + @Test + public void mixinAndPrimaryType() throws Exception{ + TestUtil.registerNodeType(builder, testNodeTypeDefn); + root = builder.getNodeState(); + + + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.indexRule("oak:TestMixA").property(JcrConstants.JCR_PRIMARYTYPE).propertyIndex(); + defnb.indexRule("oak:TestSuperType").property(JcrConstants.JCR_PRIMARYTYPE).propertyIndex().sync(); + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + + IndexingRule a = getRule(defn, "oak:TestMixA"); + assertNotNull(a.getConfig(JcrConstants.JCR_PRIMARYTYPE)); + assertNotNull(a.getConfig(JcrConstants.JCR_MIXINTYPES)); + assertFalse(a.getConfig(JcrConstants.JCR_MIXINTYPES).sync); + + IndexingRule b = getRule(defn, "oak:TestSuperType"); + assertNotNull(b.getConfig(JcrConstants.JCR_PRIMARYTYPE)); + assertNotNull(b.getConfig(JcrConstants.JCR_MIXINTYPES)); + assertTrue(b.getConfig(JcrConstants.JCR_PRIMARYTYPE).sync); + assertTrue(b.getConfig(JcrConstants.JCR_MIXINTYPES).sync); + } + + @Test + public void relativeNodeNames_None() { + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.indexRule("nt:base").property("foo").propertyIndex(); + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + assertTrue(defn.getRelativeNodeNames().isEmpty()); + assertFalse(defn.indexesRelativeNodes()); + } + + @Test + public void relativeNodeNames_RelativeProp() { + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.indexRule("nt:base").property("jcr:content/foo").propertyIndex(); + defnb.indexRule("nt:base").property("bar").propertyIndex(); + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + assertThat(defn.getRelativeNodeNames(), containsInAnyOrder("jcr:content")); + assertTrue(defn.indexesRelativeNodes()); + } + + @Test + public void relativeNodeNames_Aggregate() { + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.indexRule("nt:base").property("jcr:content/foo").propertyIndex(); + defnb.aggregateRule("nt:base").include("jcr:content/metadata"); + defnb.aggregateRule("nt:base").include("jcr:content/metadata/type/*"); + defnb.aggregateRule("nt:base").include("*"); + + IndexDefinition defn = IndexDefinition.newBuilder(root, defnb.build(), "/foo").build(); + assertThat(defn.getRelativeNodeNames(), containsInAnyOrder("jcr:content", "metadata", "type")); + assertTrue(defn.indexesRelativeNodes()); + } + + @Test + public void regexAllProps() { + IndexDefinitionBuilder builder = new IndexDefinitionBuilder(); + builder.indexRule("nt:base").property("p"); + builder.indexRule("nt:base").property("all", FulltextIndexConstants.REGEX_ALL_PROPS, true); + + IndexDefinition def = IndexDefinition.newBuilder(root, builder.build(), "/foo").build(); + IndexingRule rule = def.getApplicableIndexingRule(root); + assertNotNull(rule); + + PropertyDefinition pd = rule.getConfig("p"); + assertNotNull(pd); + assertFalse(pd.isRegexp); + assertFalse(pd.relative); + assertEquals(0, pd.ancestors.length); + + pd = rule.getConfig("all"); + assertNotNull(pd); + assertTrue(pd.isRegexp); + assertFalse(pd.relative); + assertEquals(0, pd.ancestors.length); + + assertThat(rule.getAggregate().getIncludes(), is(empty())); + assertFalse(rule.getAggregate().hasNodeAggregates()); + List<Aggregate.Matcher> matchers = rule.getAggregate() + .createMatchers(new TestRoot("/")); + assertThat(matchers, is(empty())); + assertThat(def.getRelativeNodeNames(), is(empty())); + } + + //TODO indexesAllNodesOfMatchingType - with nullCheckEnabled + + private static IndexingRule getRule(IndexDefinition defn, String typeName){ + return defn.getApplicableIndexingRule(asState(newNode(typeName))); + } + + private static NodeState asState(NodeBuilder nb){ + return nb.getNodeState(); + } + + private static NodeBuilder newNode(String typeName){ + NodeBuilder builder = EMPTY_NODE.builder(); + builder.setProperty(JcrConstants.JCR_PRIMARYTYPE, typeName, Type.NAME); + return builder; + } + + private static NodeBuilder newNode(String typeName, String mixins){ + NodeBuilder builder = EMPTY_NODE.builder(); + builder.setProperty(JcrConstants.JCR_PRIMARYTYPE, typeName); + builder.setProperty(JcrConstants.JCR_MIXINTYPES, Collections.singleton(mixins), Type.NAMES); + return builder; + } + + private static class TestRoot implements Aggregate.AggregateRoot { + + private final String path; + + public TestRoot(String path) { + this.path = path; + } + + @Override + public void markDirty() { + } + + @Override + public String getPath() { + return path; + } + } +} Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/TestUtil.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/TestUtil.java?rev=1828972&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/TestUtil.java (added) +++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/TestUtil.java Thu Apr 12 11:46:17 2018 @@ -0,0 +1,274 @@ +/* + * 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.search; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.jcr.PropertyType; +import javax.jcr.Repository; + +import org.apache.commons.io.IOUtils; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.api.JackrabbitRepository; +import org.apache.jackrabbit.oak.api.Blob; +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.index.IndexConstants; +import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.IndexingMode; +import org.apache.jackrabbit.oak.plugins.index.search.util.IndexHelper; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; +import org.apache.jackrabbit.oak.plugins.memory.ModifiedNodeState; +import org.apache.jackrabbit.oak.plugins.name.NamespaceEditorProvider; +import org.apache.jackrabbit.oak.plugins.nodetype.TypeEditorProvider; +import org.apache.jackrabbit.oak.plugins.nodetype.write.NodeTypeRegistry; +import org.apache.jackrabbit.oak.plugins.tree.factories.RootFactory; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; +import org.apache.jackrabbit.oak.spi.commit.CompositeEditorProvider; +import org.apache.jackrabbit.oak.spi.commit.Editor; +import org.apache.jackrabbit.oak.spi.commit.EditorHook; +import org.apache.jackrabbit.oak.spi.commit.EditorProvider; +import org.apache.jackrabbit.oak.spi.state.ApplyDiff; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStore; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableSet.of; +import static org.apache.jackrabbit.JcrConstants.JCR_CONTENT; +import static org.apache.jackrabbit.oak.api.Type.STRINGS; +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.memory.PropertyStates.createProperty; + +public class TestUtil { + private static final AtomicInteger COUNTER = new AtomicInteger(); + + public static final String NT_TEST = "oak:TestNode"; + + public static final String TEST_NODE_TYPE = "[oak:TestNode]\n" + + " - * (UNDEFINED) multiple\n" + + " - * (UNDEFINED)\n" + + " + * (nt:base) = oak:TestNode VERSION"; + + static void useV2(NodeBuilder idxNb) { + if (!IndexFormatVersion.getDefault().isAtLeast(IndexFormatVersion.V2)) { + idxNb.setProperty(FulltextIndexConstants.COMPAT_MODE, IndexFormatVersion.V2.getVersion()); + } + } + + static void useV2(Tree idxTree) { + if (!IndexFormatVersion.getDefault().isAtLeast(IndexFormatVersion.V2)) { + idxTree.setProperty(FulltextIndexConstants.COMPAT_MODE, IndexFormatVersion.V2.getVersion()); + } + } + + public static NodeBuilder newFTIndexDefinitionV2( + @Nonnull NodeBuilder index, @Nonnull String name, String type, + @Nullable Set<String> propertyTypes) { + NodeBuilder nb = IndexHelper.newFTIndexDefinition(index, name, type, propertyTypes, null, null, null); + useV2(nb); + return nb; + } + + public static Tree enableForFullText(Tree props, String propName) { + return enableForFullText(props, propName, false); + } + + public static Tree enableForFullText(Tree props, String propName, boolean regex) { + Tree prop = props.addChild(unique("prop")); + prop.setProperty(FulltextIndexConstants.PROP_NAME, propName); + prop.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); + prop.setProperty(FulltextIndexConstants.PROP_IS_REGEX, regex); + prop.setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true); + prop.setProperty(FulltextIndexConstants.PROP_ANALYZED, true); + prop.setProperty(FulltextIndexConstants.PROP_USE_IN_EXCERPT, true); + prop.setProperty(FulltextIndexConstants.PROP_USE_IN_SPELLCHECK, true); + return prop; + } + + public static Tree enableForOrdered(Tree props, String propName) { + Tree prop = enablePropertyIndex(props, propName, false); + prop.setProperty("ordered", true); + return prop; + } + + public static Tree enablePropertyIndex(Tree props, String propName, boolean regex) { + Tree prop = props.addChild(unique("prop")); + prop.setProperty(FulltextIndexConstants.PROP_NAME, propName); + prop.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); + prop.setProperty(FulltextIndexConstants.PROP_IS_REGEX, regex); + prop.setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, false); + prop.setProperty(FulltextIndexConstants.PROP_ANALYZED, false); + return prop; + } + + public static Tree enableFunctionIndex(Tree props, String function) { + Tree prop = props.addChild(unique("prop")); + prop.setProperty(FulltextIndexConstants.PROP_FUNCTION, function); + return prop; + } + + public static AggregatorBuilder newNodeAggregator(Tree indexDefn){ + return new AggregatorBuilder(indexDefn); + } + + public static Tree newRulePropTree(Tree indexDefn, String typeName){ + Tree rules = indexDefn.addChild(FulltextIndexConstants.INDEX_RULES); + rules.setOrderableChildren(true); + Tree rule = rules.addChild(typeName); + Tree props = rule.addChild(FulltextIndexConstants.PROP_NODE); + props.setOrderableChildren(true); + return props; + } + + public static NodeBuilder child(NodeBuilder nb, String path) { + for (String name : PathUtils.elements(checkNotNull(path))) { + nb = nb.child(name); + } + return nb; + } + + static class AggregatorBuilder { + private final Tree aggs; + + private AggregatorBuilder(Tree indexDefn) { + this.aggs = indexDefn.addChild(FulltextIndexConstants.AGGREGATES); + } + + AggregatorBuilder newRuleWithName(String primaryType, + List<String> includes){ + Tree agg = aggs.addChild(primaryType); + for (String include : includes){ + agg.addChild(unique("include")).setProperty(FulltextIndexConstants.AGG_PATH, include); + } + return this; + } + } + + static String unique(String name){ + return name + COUNTER.getAndIncrement(); + } + + public static NodeBuilder registerTestNodeType(NodeBuilder builder){ + registerNodeType(builder, TEST_NODE_TYPE); + return builder; + } + + public static void registerNodeType(NodeBuilder builder, String nodeTypeDefn){ + //Taken from org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent + NodeState base = ModifiedNodeState.squeeze(builder.getNodeState()); + NodeStore store = new MemoryNodeStore(base); + Root root = RootFactory.createSystemRoot( + store, new EditorHook(new CompositeEditorProvider( + new NamespaceEditorProvider(), + new TypeEditorProvider())), null, null, null); + NodeTypeRegistry.register(root, IOUtils.toInputStream(nodeTypeDefn), "test node types"); + NodeState target = store.getRoot(); + target.compareAgainstBaseState(base, new ApplyDiff(builder)); + } + + public static Tree createNodeWithType(Tree t, String nodeName, String typeName){ + t = t.addChild(nodeName); + t.setProperty(JcrConstants.JCR_PRIMARYTYPE, typeName, Type.NAME); + return t; + } + + public static NodeBuilder createNodeWithType(NodeBuilder builder, String nodeName, String typeName){ + builder = builder.child(nodeName); + builder.setProperty(JcrConstants.JCR_PRIMARYTYPE, typeName, Type.NAME); + return builder; + } + + public static Tree createFileNode(Tree tree, String name, Blob content, String mimeType){ + Tree fileNode = tree.addChild(name); + fileNode.setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_FILE, Type.NAME); + Tree jcrContent = fileNode.addChild(JCR_CONTENT); + jcrContent.setProperty(JcrConstants.JCR_DATA, content); + jcrContent.setProperty(JcrConstants.JCR_MIMETYPE, mimeType); + return jcrContent; + } + + public static Tree createFulltextIndex(Tree index, String name, String type) throws CommitFailedException { + Tree def = index.addChild(INDEX_DEFINITIONS_NAME).addChild(name); + def.setProperty(JcrConstants.JCR_PRIMARYTYPE, + INDEX_DEFINITIONS_NODE_TYPE, Type.NAME); + def.setProperty(TYPE_PROPERTY_NAME, type); + def.setProperty(REINDEX_PROPERTY_NAME, true); + def.setProperty(createProperty(FulltextIndexConstants.INCLUDE_PROPERTY_TYPES, + of(PropertyType.TYPENAME_STRING, PropertyType.TYPENAME_BINARY), STRINGS)); + return index.getChild(INDEX_DEFINITIONS_NAME).getChild(name); + } + + public static void shutdown(Repository repository) { + if (repository instanceof JackrabbitRepository) { + ((JackrabbitRepository) repository).shutdown(); + } + } + + public static NodeBuilder enableIndexingMode(NodeBuilder builder, IndexingMode indexingMode){ + builder.setProperty(createAsyncProperty(indexingMode)); + return builder; + } + + public static Tree enableIndexingMode(Tree tree, IndexingMode indexingMode){ + tree.setProperty(createAsyncProperty(indexingMode)); + return tree; + } + + private static PropertyState createAsyncProperty(String indexingMode) { + return createProperty(IndexConstants.ASYNC_PROPERTY_NAME, of(indexingMode , "async"), STRINGS); + } + + private static PropertyState createAsyncProperty(IndexingMode indexingMode) { + switch(indexingMode) { + case NRT : + case SYNC : + return createAsyncProperty(indexingMode.asyncValueName()); + case ASYNC: + return createProperty(IndexConstants.ASYNC_PROPERTY_NAME, of("async"), STRINGS); + default: + throw new IllegalArgumentException("Unknown mode " + indexingMode); + } + } + + public static class OptionalEditorProvider implements EditorProvider { + public EditorProvider delegate; + + @Override + public Editor getRootEditor(NodeState before, NodeState after, NodeBuilder builder, CommitInfo info) throws CommitFailedException { + if (delegate != null){ + return delegate.getRootEditor(before, after, builder, info); + } + return null; + } + + + } +} Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/TestUtil.java ------------------------------------------------------------------------------ svn:eol-style = native