Added: jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMakerLargeStringPropertiesLogTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMakerLargeStringPropertiesLogTest.java?rev=1880807&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMakerLargeStringPropertiesLogTest.java (added) +++ jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMakerLargeStringPropertiesLogTest.java Wed Aug 12 13:46:45 2020 @@ -0,0 +1,191 @@ +/* + * 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.elastic.index; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.junit.TemporarySystemProperty; +import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition; +import org.apache.jackrabbit.oak.plugins.index.elastic.util.ElasticIndexDefinitionBuilder; +import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition; +import org.apache.jackrabbit.oak.plugins.index.search.spi.editor.FulltextDocumentMaker; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.security.InvalidParameterException; + +import static java.util.Arrays.asList; +import static org.apache.jackrabbit.oak.InitialContentHelper.INITIAL_CONTENT; +import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class ElasticDocumentMakerLargeStringPropertiesLogTest { + + ListAppender<ILoggingEvent> listAppender = null; + private final String nodeImplLogger = ElasticDocumentMaker.class.getName(); + private final String warnMessage = "String length: {} for property: {} at Node: {} is greater than configured value {}"; + private String customStringPropertyThresholdLimit = "9"; + private String smallStringProperty = "1234567"; + private String largeStringPropertyAsPerCustomThreshold = "1234567890"; + + @Rule + public TemporarySystemProperty temporarySystemProperty = new TemporarySystemProperty(); + + @Before + public void loggingAppenderStart() { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + listAppender = new ListAppender<>(); + listAppender.start(); + context.getLogger(nodeImplLogger).addAppender(listAppender); + } + + @After + public void loggingAppenderStop() { + listAppender.stop(); + } + + private void setThresholdLimit(String threshold) { + System.setProperty(FulltextDocumentMaker.WARN_LOG_STRING_SIZE_THRESHOLD_KEY, threshold); + } + + private ElasticDocumentMaker addPropertyAccordingToType(NodeBuilder test, Type type, String... str) throws IOException { + NodeState root = INITIAL_CONTENT; + ElasticIndexDefinitionBuilder builder = new ElasticIndexDefinitionBuilder(); + builder.indexRule("nt:base") + .property("foo") + .propertyIndex() + .analyzed() + .valueExcludedPrefixes("/jobs"); + + IndexDefinition defn = ElasticIndexDefinition.newBuilder(root, builder.build(), "/foo").build(); + ElasticDocumentMaker docMaker = new ElasticDocumentMaker(null, defn, + defn.getApplicableIndexingRule("nt:base"), "/x"); + + if (Type.STRINGS == type) { + test.setProperty("foo", asList(str), Type.STRINGS); + } else if (Type.STRING == type && str.length == 1) { + test.setProperty("foo", str[0]); + } else { + throw new InvalidParameterException(); + } + return docMaker; + } + + @Test + public void testDefaultThreshold() throws IOException { + NodeBuilder test = EMPTY_NODE.builder(); + ElasticDocumentMaker docMaker = addPropertyAccordingToType(test, Type.STRING, largeStringPropertyAsPerCustomThreshold); + assertNotNull(docMaker.makeDocument(test.getNodeState())); + assertFalse(isWarnMessagePresent(listAppender)); + } + + @Test + public void testNoLoggingOnAddingSmallStringWithCustomThreshold() throws IOException { + setThresholdLimit(customStringPropertyThresholdLimit); + NodeBuilder test = EMPTY_NODE.builder(); + ElasticDocumentMaker docMaker = addPropertyAccordingToType(test, Type.STRING, smallStringProperty); + assertNotNull(docMaker.makeDocument(test.getNodeState())); + assertFalse(isWarnMessagePresent(listAppender)); + } + + @Test + public void testNoLoggingOnAddingSmallStringArrayWithCustomThreshold() throws IOException { + setThresholdLimit(customStringPropertyThresholdLimit); + NodeBuilder test = EMPTY_NODE.builder(); + ElasticDocumentMaker docMaker = addPropertyAccordingToType(test, Type.STRINGS, smallStringProperty, smallStringProperty); + assertNotNull(docMaker.makeDocument(test.getNodeState())); + assertFalse(isWarnMessagePresent(listAppender)); + } + + @Test + public void testNoLoggingOnAddingSmallStringArrayWithoutCustomThreshold() throws IOException { + NodeBuilder test = EMPTY_NODE.builder(); + ElasticDocumentMaker docMaker = addPropertyAccordingToType(test, Type.STRINGS, smallStringProperty, smallStringProperty); + assertNotNull(docMaker.makeDocument(test.getNodeState())); + assertFalse(isWarnMessagePresent(listAppender)); + } + + @Test + public void testLoggingOnAddingLargeStringWithCustomThreshold() throws IOException { + setThresholdLimit(customStringPropertyThresholdLimit); + NodeBuilder test = EMPTY_NODE.builder(); + ElasticDocumentMaker docMaker = addPropertyAccordingToType(test, Type.STRING, largeStringPropertyAsPerCustomThreshold); + assertNotNull(docMaker.makeDocument(test.getNodeState())); + assertTrue(isWarnMessagePresent(listAppender)); + } + + @Test + public void testLoggingOnAddingLargeStringWithoutCustomThreshold() throws IOException { + // setThresholdLimit(null); + NodeBuilder test = EMPTY_NODE.builder(); + ElasticDocumentMaker docMaker = addPropertyAccordingToType(test, Type.STRING, smallStringProperty); + assertNotNull(docMaker.makeDocument(test.getNodeState())); + assertFalse(isWarnMessagePresent(listAppender)); + } + + @Test + public void testLoggingOnAddingLargeStringArrayOneLargePropertyWithoutCustomThreshold() throws IOException { + NodeBuilder test = EMPTY_NODE.builder(); + ElasticDocumentMaker docMaker = addPropertyAccordingToType(test, Type.STRINGS, largeStringPropertyAsPerCustomThreshold, smallStringProperty); + assertNotNull(docMaker.makeDocument(test.getNodeState())); + assertFalse(isWarnMessagePresent(listAppender)); + } + + @Test + public void testLoggingOnAddingLargeStringArrayOneLargePropertyWithCustomThreshold() throws IOException { + setThresholdLimit(customStringPropertyThresholdLimit); + NodeBuilder test = EMPTY_NODE.builder(); + ElasticDocumentMaker docMaker = addPropertyAccordingToType(test, Type.STRINGS, largeStringPropertyAsPerCustomThreshold, smallStringProperty); + assertNotNull(docMaker.makeDocument(test.getNodeState())); + assertTrue(isWarnMessagePresent(listAppender)); + assertEquals(2, listAppender.list.size()); + } + + @Test + public void testLoggingOnAddingLargeStringArrayTwoLargePropertiesWithCustomThreshold() throws IOException { + setThresholdLimit(customStringPropertyThresholdLimit); + NodeBuilder test = EMPTY_NODE.builder(); + ElasticDocumentMaker docMaker = addPropertyAccordingToType(test, Type.STRINGS, largeStringPropertyAsPerCustomThreshold, largeStringPropertyAsPerCustomThreshold); + assertNotNull(docMaker.makeDocument(test.getNodeState())); + assertTrue(isWarnMessagePresent(listAppender)); + // number of logs equal twice the number of large properties once for fulltext indexing + // and once for property indexing. + assertEquals(4, listAppender.list.size()); + } + + private boolean isWarnMessagePresent(ListAppender<ILoggingEvent> listAppender) { + for (ILoggingEvent loggingEvent : listAppender.list) { + if (loggingEvent.getMessage().contains(warnMessage)) { + return true; + } + } + return false; + } + +} \ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticDocumentMakerLargeStringPropertiesLogTest.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: jackrabbit/oak/trunk/oak-search/pom.xml URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/pom.xml?rev=1880807&r1=1880806&r2=1880807&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-search/pom.xml (original) +++ jackrabbit/oak/trunk/oak-search/pom.xml Wed Aug 12 13:46:45 2020 @@ -182,6 +182,11 @@ <type>test-jar</type> <scope>test</scope> </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FunctionIndexCommonTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FunctionIndexCommonTest.java?rev=1880807&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FunctionIndexCommonTest.java (added) +++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FunctionIndexCommonTest.java Wed Aug 12 13:46:45 2020 @@ -0,0 +1,1188 @@ +/* + * 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.Iterables; +import com.google.common.collect.Lists; +import org.apache.jackrabbit.oak.api.Result; +import org.apache.jackrabbit.oak.api.ResultRow; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.junit.LogCustomizer; +import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants; +import org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder; +import org.apache.jackrabbit.oak.query.AbstractQueryTest; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.event.Level; + +import javax.jcr.PropertyType; +import java.text.ParseException; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static java.util.Arrays.asList; +import static org.apache.jackrabbit.oak.api.QueryEngine.NO_BINDINGS; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public abstract class FunctionIndexCommonTest extends AbstractQueryTest { + + protected IndexOptions indexOptions; + protected TestRepository repositoryOptionsUtil; + + @Test + public void noIndexTest() throws Exception { + Tree test = root.getTree("/").addChild("test"); + for (int idx = 0; idx < 3; idx++) { + Tree low = test.addChild("" + (char) ('a' + idx)); + low.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + Tree up = test.addChild("" + (char) ('A' + idx)); + up.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + } + root.commit(); + + String query = "select [jcr:path] from [nt:base] where lower(localname()) = 'b'"; + assertThat(explain(query), containsString("traverse")); + assertQuery(query, Lists.newArrayList("/test/b", "/test/B")); + + String queryXPath = "/jcr:root/test//*[fn:lower-case(fn:local-name()) = 'b']"; + assertThat(explainXpath(queryXPath), containsString("traverse")); + assertQuery(queryXPath, "xpath", Lists.newArrayList("/test/b", "/test/B")); + + queryXPath = "/jcr:root/test//*[fn:lower-case(fn:local-name()) > 'b']"; + assertThat(explainXpath(queryXPath), containsString("traverse")); + assertQuery(queryXPath, "xpath", Lists.newArrayList("/test/c", "/test/C")); + + query = "select [jcr:path] from [nt:base] where lower(localname()) = 'B'"; + assertThat(explain(query), containsString("traverse")); + assertQuery(query, Lists.<String>newArrayList()); + } + + @Test + public void lowerCaseLocalName() throws Exception { + Tree luceneIndex = createIndex("lowerLocalName", Collections.<String>emptySet()); + luceneIndex.setProperty("excludedPaths", + Lists.newArrayList("/jcr:system", "/oak:index"), Type.STRINGS); + Tree func = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES) + .addChild("nt:base") + .addChild(FulltextIndexConstants.PROP_NODE) + .addChild("lowerLocalName"); + func.setProperty(FulltextIndexConstants.PROP_FUNCTION, "lower(localname())"); + + Tree test = root.getTree("/").addChild("test"); + for (int idx = 0; idx < 3; idx++) { + Tree low = test.addChild("" + (char) ('a' + idx)); + low.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + Tree up = test.addChild("" + (char) ('A' + idx)); + up.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + } + root.commit(); + + String query = "select [jcr:path] from [nt:base] where lower(localname()) = 'b'"; + assertThat(explain(query), containsString("lucene:lowerLocalName")); + assertQuery(query, Lists.newArrayList("/test/b", "/test/B")); + + String queryXPath = "/jcr:root//*[fn:lower-case(fn:local-name()) = 'b']"; + assertThat(explainXpath(queryXPath), containsString("lucene:lowerLocalName")); + assertQuery(queryXPath, "xpath", Lists.newArrayList("/test/b", "/test/B")); + + queryXPath = "/jcr:root//*[fn:lower-case(fn:local-name()) > 'b']"; + assertThat(explainXpath(queryXPath), containsString("lucene:lowerLocalName")); + assertQuery(queryXPath, "xpath", Lists.newArrayList("/test/c", "/test/C", "/test")); + + query = "select [jcr:path] from [nt:base] where lower(localname()) = 'B'"; + assertThat(explain(query), containsString("lucene:lowerLocalName")); + assertQuery(query, Lists.<String>newArrayList()); + } + + @Test + public void lengthName() throws Exception { + Tree luceneIndex = createIndex("lengthName", Collections.<String>emptySet()); + luceneIndex.setProperty("excludedPaths", + Lists.newArrayList("/jcr:system", "/oak:index"), Type.STRINGS); + Tree func = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES) + .addChild("nt:base") + .addChild(FulltextIndexConstants.PROP_NODE) + .addChild("lengthName"); + func.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + func.setProperty(FulltextIndexConstants.PROP_TYPE, PropertyType.TYPENAME_LONG); + func.setProperty(FulltextIndexConstants.PROP_FUNCTION, "fn:string-length(fn:name())"); + + Tree test = root.getTree("/").addChild("test"); + for (int idx = 1; idx < 1000; idx *= 10) { + Tree testNode = test.addChild("test" + idx); + testNode.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + } + root.commit(); + + String query = "select [jcr:path] from [nt:base] where length(name()) = 6"; + assertThat(explain(query), containsString("lucene:lengthName")); + assertQuery(query, Lists.newArrayList("/test/test10")); + + String queryXPath = "/jcr:root//*[fn:string-length(fn:name()) = 7]"; + assertThat(explainXpath(queryXPath), containsString("lucene:lengthName")); + assertQuery(queryXPath, "xpath", Lists.newArrayList("/test/test100")); + + queryXPath = "/jcr:root//* order by fn:string-length(fn:name())"; + assertThat(explainXpath(queryXPath), containsString("lucene:lengthName")); + assertQuery(queryXPath, "xpath", Lists.newArrayList( + "/test", "/test/test1", "/test/test10", "/test/test100")); + } + + @Test + public void length() throws Exception { + Tree luceneIndex = createIndex("length", Collections.<String>emptySet()); + luceneIndex.setProperty("excludedPaths", + Lists.newArrayList("/jcr:system", "/oak:index"), Type.STRINGS); + Tree func = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES) + .addChild("nt:base") + .addChild(FulltextIndexConstants.PROP_NODE) + .addChild("lengthName"); + func.setProperty(FulltextIndexConstants.PROP_FUNCTION, "fn:string-length(@value)"); + + Tree test = root.getTree("/").addChild("test"); + for (int idx = 1; idx <= 1000; idx *= 10) { + Tree testNode = test.addChild("test" + idx); + testNode.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + testNode.setProperty("value", new byte[idx]); + } + root.commit(); + + String query = "select [jcr:path] from [nt:base] where length([value]) = 100"; + assertThat(explain(query), containsString("lucene:length")); + assertQuery(query, Lists.newArrayList("/test/test100")); + + String queryXPath = "/jcr:root//*[fn:string-length(@value) = 10]"; + assertThat(explainXpath(queryXPath), containsString("lucene:length")); + assertQuery(queryXPath, "xpath", Lists.newArrayList("/test/test10")); + } + + @Test + public void upperCase() throws Exception { + Tree luceneIndex = createIndex("upper", Collections.<String>emptySet()); + Tree func = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES) + .addChild("nt:base") + .addChild(FulltextIndexConstants.PROP_NODE) + .addChild("upperName"); + func.setProperty(FulltextIndexConstants.PROP_FUNCTION, "fn:upper-case(@name)"); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + List<String> paths = Lists.newArrayList(); + for (int idx = 0; idx < 15; idx++) { + Tree a = test.addChild("n" + idx); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("name", "10% foo"); + paths.add("/test/n" + idx); + } + root.commit(); + + String query = "select [jcr:path] from [nt:unstructured] where upper([name]) = '10% FOO'"; + assertThat(explain(query), containsString("lucene:upper")); + assertQuery(query, paths); + + query = "select [jcr:path] from [nt:unstructured] where upper([name]) like '10\\% FOO'"; + assertThat(explain(query), containsString("lucene:upper")); + assertQuery(query, paths); + } + + @Test + public void testOrdering2() throws Exception { + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, indexOptions.getIndexType()); + TestUtil.useV2(indexDefn); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + Tree upper = TestUtil.enableFunctionIndex(props, "upper([foo])"); + upper.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("n1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "hello"); + + a = test.addChild("n2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "World!"); + a = test.addChild("n3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "Hallo"); + a = test.addChild("n4"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "10%"); + a = test.addChild("n5"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "10 percent"); + + a = test.addChild("n0"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a = test.addChild("n9"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + String query = "select a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo)"; + + root.commit(); + + assertThat(explain(query), containsString("lucene:test-index(/oak:index/test-index)")); + + List<String> result = executeQuery(query, SQL2); + assertEquals("Ordering doesn't match", asList("10 percent", "10%", "Hallo", "hello", "World!"), result); + + } + + + /* + Test order by func(a),func(b) + order by func(b),func(a) + func(a) DESC,func(b) + func(a),func(b)DESC + where both func(a) and func(b) have ordered set = true + Correct ordering is effectively served by the index + */ + @Test + public void testOrdering3() throws Exception { + + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, indexOptions.getIndexType()); + TestUtil.useV2(indexDefn); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + Tree upper = TestUtil.enableFunctionIndex(props, "upper([foo])"); + upper.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + Tree upper2 = TestUtil.enableFunctionIndex(props, "upper([foo2])"); + upper2.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("n1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b2"); + + a = test.addChild("n2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a2"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a3"); + a.setProperty("foo2", "b1"); + + a = test.addChild("n4"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n5"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b1"); + + + String query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo),upper(a.foo2)"; + + root.commit(); + + List<String> result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b1", "a1, b2", "a1, b3", "a2, b3", "a3, b1"), result); + + query = "select a.[foo2],a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo2),upper(a.foo)"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("b1, a1", "b1, a3", "b2, a1", "b3, a1", "b3, a2"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo) DESC, upper(a.foo2)"; + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a3, b1", "a2, b3", "a1, b1", "a1, b2", "a1, b3"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo), upper(a.foo2) DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b3", "a1, b2", "a1, b1", "a2, b3", "a3, b1"), result); + + + } + + /* + Test order by func(a),func(b) + order by func(b),func(a) + func(a) DESC,func(b) + func(a),func(b)DESC + where only func(a) is ordered by index + The effective ordering in this case will be done by QueryEngine + */ + @Test + public void testOrdering4() throws Exception { + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, indexOptions.getIndexType()); + TestUtil.useV2(indexDefn); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + Tree upper = TestUtil.enableFunctionIndex(props, "upper([foo])"); + upper.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + TestUtil.enableFunctionIndex(props, "upper([foo2])"); + + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("n1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b2"); + + a = test.addChild("n2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a2"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a3"); + a.setProperty("foo2", "b1"); + + a = test.addChild("n4"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n5"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b1"); + + + String query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo),upper(a.foo2)"; + + root.commit(); + + List<String> result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b1", "a1, b2", "a1, b3", "a2, b3", "a3, b1"), result); + + query = "select a.[foo2],a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo2),upper(a.foo)"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("b1, a1", "b1, a3", "b2, a1", "b3, a1", "b3, a2"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo) DESC, upper(a.foo2)"; + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a3, b1", "a2, b3", "a1, b1", "a1, b2", "a1, b3"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo), upper(a.foo2) DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b3", "a1, b2", "a1, b1", "a2, b3", "a3, b1"), result); + + } + + /* + Test order by func(a),b + order by b,func(a) + order by func(a) DESC,b + order by func(a),b DESC + where both b and func(a) have ordered=true + */ + @Test + public void testOrdering5() throws Exception { + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, indexOptions.getIndexType()); + TestUtil.useV2(indexDefn); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + Tree upper = TestUtil.enableFunctionIndex(props, "upper([foo])"); + upper.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + + Tree upper2 = TestUtil.enablePropertyIndex(props, "foo2", false); + upper2.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("n1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b2"); + + a = test.addChild("n2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a2"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a3"); + a.setProperty("foo2", "b1"); + + a = test.addChild("n4"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n5"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b1"); + + + String query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo),a.foo2"; + + root.commit(); + + List<String> result = executeQuery(query, SQL2); + + + assertEquals("Ordering doesn't match", asList("a1, b1", "a1, b2", "a1, b3", "a2, b3", "a3, b1"), result); + + query = "select a.[foo2],a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by a.foo2,upper(a.foo)"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("b1, a1", "b1, a3", "b2, a1", "b3, a1", "b3, a2"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo) DESC, a.foo2"; + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a3, b1", "a2, b3", "a1, b1", "a1, b2", "a1, b3"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo), a.foo2 DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b3", "a1, b2", "a1, b1", "a2, b3", "a3, b1"), result); + + } + + /* + Test order by func(a),b + orrder by b,func(a) + order by func(a) DESC,b + order by func(a),b DESC + where func(a) does not have ordered = true + */ + @Test + public void testOrdering6() throws Exception { + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, indexOptions.getIndexType()); + TestUtil.useV2(indexDefn); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + TestUtil.enableFunctionIndex(props, "upper([foo])"); + + + Tree upper2 = TestUtil.enablePropertyIndex(props, "foo2", false); + upper2.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("n1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b2"); + + a = test.addChild("n2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a2"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a3"); + a.setProperty("foo2", "b1"); + + a = test.addChild("n4"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b3"); + + a = test.addChild("n5"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "a1"); + a.setProperty("foo2", "b1"); + + + String query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo),a.foo2"; + + root.commit(); + + List<String> result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b1", "a1, b2", "a1, b3", "a2, b3", "a3, b1"), result); + + query = "select a.[foo2],a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by a.foo2,upper(a.foo)"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("b1, a1", "b1, a3", "b2, a1", "b3, a1", "b3, a2"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo) DESC, a.foo2"; + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a3, b1", "a2, b3", "a1, b1", "a1, b2", "a1, b3"), result); + + query = "select a.[foo],a.[foo2]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.foo is not null and isdescendantnode(a , '/test') order by upper(a.foo), a.foo2 DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("a1, b3", "a1, b2", "a1, b1", "a2, b3", "a3, b1"), result); + } + + /* + Testing order by for + different function implementations + */ + @Test + public void testOrdering7() throws Exception { + Tree index = root.getTree("/"); + Tree indexDefn = createTestIndexNode(index, indexOptions.getIndexType()); + TestUtil.useV2(indexDefn); + indexDefn.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + Tree props = TestUtil.newRulePropTree(indexDefn, "nt:unstructured"); + props.getParent().setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + TestUtil.enableForFullText(props, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + Tree fn = TestUtil.enableFunctionIndex(props, "upper([foo])"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "lower([foo])"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "length([foo])"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + // Any function property trying to sory by length needs to explicitly set the type to Long + fn.setProperty(FulltextIndexConstants.PROP_TYPE, "Long"); + + fn = TestUtil.enableFunctionIndex(props, "coalesce([foo2],[foo])"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "name()"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "localname()"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "lower(coalesce([foo2], coalesce([foo], localname())))"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + + fn = TestUtil.enableFunctionIndex(props, "length(coalesce([foo], coalesce([foo2], localname())))"); + fn.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + fn.setProperty(FulltextIndexConstants.PROP_TYPE, "Long"); + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + + Tree a = test.addChild("d1"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "c"); + + a = test.addChild("d2"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "bbbb"); + a.setProperty("foo2", "22"); + + + a = test.addChild("d3"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "aa"); + + a = test.addChild("jcr:content"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "test"); + a.setProperty("foo2", "11"); + + root.commit(); + + String query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by coalesce([foo2],[foo]) "; + + List<String> result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/jcr:content", "/test/d2", "/test/d3", "/test/d1"), result); + + query = "select a.[foo]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by lower([a].[foo])"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("aa", "bbbb", "c", "test"), result); + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by localname() "; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/jcr:content", "/test/d1", "/test/d2", "/test/d3"), result); + + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by name() "; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/d1", "/test/d2", "/test/d3", "/test/jcr:content"), result); + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by lower(coalesce([a].[foo2], coalesce([a].[foo], localname())))"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/jcr:content", "/test/d2", "/test/d3", "/test/d1"), result); + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where isdescendantnode(a , '/test') order by lower(coalesce([a].[foo2], coalesce([a].[foo], localname()))) DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/d1", "/test/d3", "/test/d2", "/test/jcr:content"), result); + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.[foo] is not null AND isdescendantnode(a , '/test') order by length([a].[foo]) DESC, localname()"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/jcr:content", "/test/d2", "/test/d3", "/test/d1"), result); + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.[foo] is not null AND isdescendantnode(a , '/test') order by length([a].[foo]), localname()"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/d1", "/test/d3", "/test/jcr:content", "/test/d2"), result); + + + query = "select [jcr:path]\n" + + "\t from [nt:unstructured] as a\n" + + "\t where a.[foo] is not null AND isdescendantnode(a , '/test') order by length(coalesce([foo], coalesce([foo2], localname()))), localname() DESC"; + + result = executeQuery(query, SQL2); + + assertEquals("Ordering doesn't match", asList("/test/d1", "/test/d3", "/test/d2", "/test/jcr:content"), result); + + + } + + @Test + public void testOrdering() throws Exception { + Tree luceneIndex = createIndex("upper", Collections.<String>emptySet()); + Tree nonFunc = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES) + .addChild("nt:base") + .addChild(FulltextIndexConstants.PROP_NODE) + .addChild("foo"); + nonFunc.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + nonFunc.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); + nonFunc.setProperty("name", "foo"); + + Tree func = luceneIndex.getChild(FulltextIndexConstants.INDEX_RULES) + .getChild("nt:base") + .getChild(FulltextIndexConstants.PROP_NODE) + .addChild("fooUpper"); + func.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + func.setProperty(FulltextIndexConstants.PROP_FUNCTION, "fn:upper-case(@foo)"); + func.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); + + root.commit(); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + List<String> paths = Lists.newArrayList(); + for (int idx = 0; idx < 10; idx++) { + paths.add("/test/n" + idx); + if (idx % 2 == 0) continue; + Tree a = test.addChild("n" + idx); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "bar" + idx); + + } + for (int idx = 0; idx < 10; idx++) { + if (idx % 2 != 0) continue; + Tree a = test.addChild("n" + idx); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + a.setProperty("foo", "bar" + idx); + } + root.commit(); + + String query = "/jcr:root//element(*, nt:unstructured) [jcr:like(fn:upper-case(@foo),'BAR%')] order by foo"; + assertThat(explainXpath(query), containsString("lucene:upper")); + List<String> result = assertQuery(query, "xpath", paths); + assertEquals("Ordering doesn't match", paths, result); + + + query = "/jcr:root//element(*, nt:unstructured) [jcr:like(fn:upper-case(@foo),'BAR%')] order by fn:upper-case(@foo)"; + assertThat(explainXpath(query), containsString("lucene:upper")); + List<String> result2 = assertQuery(query, "xpath", paths); + assertEquals("Ordering doesn't match", paths, result2); + } + + @Test + public void upperCaseRelative() throws Exception { + Tree luceneIndex = createIndex("upper", Collections.<String>emptySet()); + Tree func = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES) + .addChild("nt:base") + .addChild(FulltextIndexConstants.PROP_NODE) + .addChild("upperName"); + func.setProperty(FulltextIndexConstants.PROP_FUNCTION, "upper([data/name])"); + + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + List<String> paths = Lists.newArrayList(); + for (int idx = 0; idx < 15; idx++) { + Tree a = test.addChild("n" + idx); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + Tree b = a.addChild("data"); + b.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + b.setProperty("name", "foo"); + paths.add("/test/n" + idx); + } + root.commit(); + + String query = "select [jcr:path] from [nt:unstructured] where upper([data/name]) = 'FOO'"; + assertThat(explain(query), containsString("lucene:upper")); + assertQuery(query, paths); + + String queryXPath = "/jcr:root//element(*, nt:unstructured)[fn:upper-case(data/@name) = 'FOO']"; + assertThat(explainXpath(queryXPath), containsString("lucene:upper")); + assertQuery(queryXPath, "xpath", paths); + + for (int idx = 0; idx < 15; idx++) { + Tree a = test.getChild("n" + idx); + Tree b = a.getChild("data"); + b.setProperty("name", "bar"); + } + root.commit(); + + query = "select [jcr:path] from [nt:unstructured] where upper([data/name]) = 'BAR'"; + assertThat(explain(query), containsString("lucene:upper")); + assertQuery(query, paths); + + queryXPath = "/jcr:root//element(*, nt:unstructured)[fn:upper-case(data/@name) = 'BAR']"; + assertThat(explainXpath(queryXPath), containsString("lucene:upper")); + assertQuery(queryXPath, "xpath", paths); + } + + + @Test + public void coalesceOrdering() throws Exception { + + IndexDefinitionBuilder idxb = indexOptions.createIndexDefinitionBuilder().noAsync(); + idxb.indexRule("nt:base").property("foo", null).function( + "coalesce([jcr:content/foo2], [jcr:content/foo])" + ).ordered(); + + Tree idx = root.getTree("/").getChild("oak:index").addChild("test1"); + idxb.build(idx); + root.commit(); + + Tree rootTree = root.getTree("/"); + rootTree.addChild("a").addChild("jcr:content").setProperty("foo2", "a"); + rootTree.addChild("b").addChild("jcr:content").setProperty("foo", "b"); + Tree child = rootTree.addChild("c").addChild("jcr:content"); + child.setProperty("foo", "c"); + child.setProperty("foo2", "a1"); + + root.commit(); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by coalesce([jcr:content/foo2], [jcr:content/foo])", + "lucene:test1(/oak:index/test1)", asList("/a", "/c", "/b")); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by coalesce([jcr:content/foo2], [jcr:content/foo]) DESC", + "lucene:test1(/oak:index/test1)", asList("/b", "/c", "/a")); + } + + @Test + public void coalesce() throws Exception { + IndexDefinitionBuilder idxb = indexOptions.createIndexDefinitionBuilder().noAsync(); + idxb.indexRule("nt:base").property("foo", null).function( + "lower(coalesce([jcr:content/foo2], coalesce([jcr:content/foo], localname())))" + ); + + Tree idx = root.getTree("/").getChild("oak:index").addChild("test1"); + idxb.build(idx); + root.commit(); + + Tree rootTree = root.getTree("/"); + rootTree.addChild("a").addChild("jcr:content").setProperty("foo2", "BAR"); + rootTree.addChild("b").addChild("jcr:content").setProperty("foo", "bAr"); + Tree child = rootTree.addChild("c").addChild("jcr:content"); + child.setProperty("foo", "bar"); + child.setProperty("foo2", "bar1"); + rootTree.addChild("bar"); + + root.commit(); + + assertPlanAndQuery( + "select * from [nt:base] where lower(coalesce([jcr:content/foo2], coalesce([jcr:content/foo], localname()))) = 'bar'", + "lucene:test1(/oak:index/test1)", asList("/a", "/b", "/bar")); + } + + /* + Given an index def with 2 orderable property definitions(Relative) for same property - one with function and one without + Order by should give correct results + */ + @Test + public void sameOrderableRelPropWithAndWithoutFunc_checkOrdering() throws Exception { + + // Index def with same property - ordered - one with function and one without + Tree luceneIndex = createIndex("upper", Collections.<String>emptySet()); + Tree nonFunc = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES) + .addChild("nt:base") + .addChild(FulltextIndexConstants.PROP_NODE) + .addChild("foo"); + nonFunc.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); + nonFunc.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + nonFunc.setProperty("name", "jcr:content/n/foo"); + + Tree func = luceneIndex.getChild(FulltextIndexConstants.INDEX_RULES) + .getChild("nt:base") + .getChild(FulltextIndexConstants.PROP_NODE) + .addChild("testOak"); + func.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + func.setProperty(FulltextIndexConstants.PROP_FUNCTION, "fn:upper-case(jcr:content/n/@foo)"); + + root.commit(); + + + int i = 1; + // Create nodes that will be served by the index definition that follows + for (String node : asList("a", "c", "b", "e", "d")) { + + Tree test = root.getTree("/").addChild(node); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + Tree a = test.addChild("jcr:content"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + Tree b = a.addChild("n"); + + b.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + b.setProperty("foo", "bar" + i); + i++; + } + + root.commit(); + + + // Check ordering works for func and non func properties + assertOrderedPlanAndQuery( + "select * from [nt:base] order by upper([jcr:content/n/foo])", + "lucene:upper(/oak:index/upper)", asList("/a", "/c", "/b", "/e", "/d")); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by [jcr:content/n/foo]", + "lucene:upper(/oak:index/upper)", asList("/a", "/c", "/b", "/e", "/d")); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by upper([jcr:content/n/foo]) DESC", + "lucene:upper(/oak:index/upper)", asList("/d", "/e", "/b", "/c", "/a")); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by [jcr:content/n/foo] DESC", + "lucene:upper(/oak:index/upper)", asList("/d", "/e", "/b", "/c", "/a")); + + + // Now we change the value of foo on already indexed nodes and see if changes get indexed properly. + + i = 5; + for (String node : asList("a", "c", "b", "e", "d")) { + + Tree test = root.getTree("/").getChild(node).getChild("jcr:content").getChild("n"); + + test.setProperty("foo", "bar" + i); + i--; + } + root.commit(); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by upper([jcr:content/n/foo])", + "lucene:upper(/oak:index/upper)", asList("/d", "/e", "/b", "/c", "/a")); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by [jcr:content/n/foo]", + "lucene:upper(/oak:index/upper)", asList("/d", "/e", "/b", "/c", "/a")); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by upper([jcr:content/n/foo]) DESC", + "lucene:upper(/oak:index/upper)", asList("/a", "/c", "/b", "/e", "/d")); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by [jcr:content/n/foo] DESC", + "lucene:upper(/oak:index/upper)", asList("/a", "/c", "/b", "/e", "/d")); + + } + + /* +Given an index def with 2 orderable property definitions(non-relative) for same property - one with function and one without +Indexer should index any changes properly and ordering should work as expected. +*/ + @Test + public void sameOrderablePropertyWithandWithoutFunction() throws Exception { + LogCustomizer customLogs = LogCustomizer.forLogger(getLoggerName()).enable(org.slf4j.event.Level.WARN).create(); + // Create nodes that will be served by the index definition that follows + int i = 1; + // Create nodes that will be served by the index definition that follows + for (String node : asList("a", "c", "b", "e", "d")) { + + Tree test = root.getTree("/").addChild(node); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + test.setProperty("foo", "bar" + i); + i++; + } + + root.commit(); + + // Index def with same property - ordered - one with function and one without + Tree luceneIndex = createIndex("upper", Collections.<String>emptySet()); + Tree nonFunc = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES) + .addChild("nt:base") + .addChild(FulltextIndexConstants.PROP_NODE) + .addChild("foo"); + nonFunc.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + nonFunc.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); + nonFunc.setProperty("name", "foo"); + + Tree func = luceneIndex.getChild(FulltextIndexConstants.INDEX_RULES) + .getChild("nt:base") + .getChild(FulltextIndexConstants.PROP_NODE) + .addChild("testOak"); + func.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + func.setProperty(FulltextIndexConstants.PROP_FUNCTION, "fn:upper-case(@foo)"); + + // Now do some change in the node that are covered by above index definition + try { + customLogs.starting(); + i = 5; + for (String node : asList("a", "c", "b", "e", "d")) { + + Tree test = root.getTree("/").addChild(node); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + test.setProperty("foo", "bar" + i); + i--; + } + + root.commit(); + Assert.assertFalse(customLogs.getLogs().contains("Failed to index the node [/test]")); + Assert.assertTrue(customLogs.getLogs().size() == 0); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by upper([foo])", + "lucene:upper(/oak:index/upper)", asList("/d", "/e", "/b", "/c", "/a")); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by [foo]", + "lucene:upper(/oak:index/upper)", asList("/d", "/e", "/b", "/c", "/a")); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by upper([foo]) DESC", + "lucene:upper(/oak:index/upper)", asList("/a", "/c", "/b", "/e", "/d")); + + assertOrderedPlanAndQuery( + "select * from [nt:base] order by [foo] DESC", + "lucene:upper(/oak:index/upper)", asList("/a", "/c", "/b", "/e", "/d")); + + } finally { + customLogs.finished(); + } + + } + + /* + <OAK-8166> + Given an index def with 2 orderable property definitions(Relative) for same property - one with function and one without + Indexer should not fail to index the nodes covered by this index + */ + @Test + public void sameOrderableRelativePropertyWithAndWithoutFunction() throws Exception { + + LogCustomizer customLogs = LogCustomizer.forLogger(getLoggerName()).enable(Level.WARN).create(); + // Create nodes that will be served by the index definition that follows + Tree test = root.getTree("/").addChild("test"); + test.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + Tree a = test.addChild("jcr:content"); + a.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + + Tree b = a.addChild("n"); + + b.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + b.setProperty("foo", "bar"); + + root.commit(); + + // Index def with same property - ordered - one with function and one without + Tree luceneIndex = createIndex("upper", Collections.<String>emptySet()); + Tree nonFunc = luceneIndex.addChild(FulltextIndexConstants.INDEX_RULES) + .addChild("nt:unstructured") + .addChild(FulltextIndexConstants.PROP_NODE) + .addChild("foo"); + nonFunc.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + nonFunc.setProperty("name", "jcr:content/n/foo"); + + Tree func = luceneIndex.getChild(FulltextIndexConstants.INDEX_RULES) + .getChild("nt:unstructured") + .getChild(FulltextIndexConstants.PROP_NODE) + .addChild("testOak"); + func.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + func.setProperty(FulltextIndexConstants.PROP_FUNCTION, "fn:upper-case(jcr:content/n/@foo)"); + + // Now do some change in the node that are covered by above index definition + try { + customLogs.starting(); + root.getTree("/").getChild("test").getChild("jcr:content").getChild("n").setProperty("foo", "bar2"); + root.commit(); + Assert.assertFalse(customLogs.getLogs().contains("Failed to index the node [/test]")); + Assert.assertTrue(customLogs.getLogs().size() == 0); + } finally { + customLogs.finished(); + } + + } + + + protected String explain(String query) { + String explain = "explain " + query; + return executeQuery(explain, "JCR-SQL2").get(0); + } + + protected String explainXpath(String query) throws ParseException { + String explain = "explain " + query; + Result result = executeQuery(explain, "xpath", NO_BINDINGS); + ResultRow row = Iterables.getOnlyElement(result.getRows()); + String plan = row.getValue("plan").getValue(Type.STRING); + return plan; + } + + private void assertOrderedPlanAndQuery(String query, String planExpectation, List<String> paths) { + List<String> result = assertPlanAndQuery(query, planExpectation, paths); + assertEquals("Ordering doesn't match", paths, result); + } + + private List<String> assertPlanAndQuery(String query, String planExpectation, List<String> paths) { + assertThat(explain(query), containsString(planExpectation)); + return assertQuery(query, paths); + } + + protected Tree createIndex(String name, Set<String> propNames) { + Tree index = root.getTree("/"); + return createIndex(index, name, propNames); + } + + abstract protected Tree createIndex(Tree index, String name, Set<String> propNames); + + abstract protected String getLoggerName(); +} Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FunctionIndexCommonTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexAggregation2CommonTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexAggregation2CommonTest.java?rev=1880807&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexAggregation2CommonTest.java (added) +++ jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexAggregation2CommonTest.java Wed Aug 12 13:46:45 2020 @@ -0,0 +1,388 @@ +/* + * 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.base.Strings; +import com.google.common.collect.Lists; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.api.PropertyValue; +import org.apache.jackrabbit.oak.api.Result; +import org.apache.jackrabbit.oak.api.ResultRow; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.plugins.index.aggregate.SimpleNodeAggregator; +import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants; +import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; +import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; +import org.apache.jackrabbit.oak.query.AbstractQueryTest; +import org.apache.jackrabbit.oak.spi.query.QueryIndex; +import org.jetbrains.annotations.NotNull; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +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_PRIMARYTYPE; +import static org.apache.jackrabbit.JcrConstants.NT_FILE; +import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED; +import static org.apache.jackrabbit.oak.api.QueryEngine.NO_BINDINGS; +import static org.apache.jackrabbit.oak.api.Type.NAME; +import static org.apache.jackrabbit.oak.api.Type.STRING; +import static org.apache.jackrabbit.oak.api.Type.STRINGS; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public abstract class IndexAggregation2CommonTest extends AbstractQueryTest { + private static final Logger LOG = LoggerFactory.getLogger(IndexAggregation2CommonTest.class); + + private static final String NT_TEST_PAGE = "test:Page"; + private static final String NT_TEST_PAGECONTENT = "test:PageContent"; + private static final String NT_TEST_ASSET = "test:Asset"; + private static final String NT_TEST_ASSETCONTENT = "test:AssetContent"; + + protected IndexOptions indexOptions; + protected TestRepository repositoryOptionsUtil; + + + @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")) + .newRuleWithName(NT_TEST_PAGE, newArrayList("jcr:content")) + .newRuleWithName(NT_TEST_PAGECONTENT, newArrayList("*", "*/*", "*/*/*", "*/*/*/*")) + .newRuleWithName(NT_TEST_ASSET, newArrayList("jcr:content")) + .newRuleWithName( + NT_TEST_ASSETCONTENT, + newArrayList("metadata", "renditions", "renditions/original", "comments", + "renditions/original/jcr:content")) + .newRuleWithName("rep:User", newArrayList("profile")); + + Tree originalInclude = indexDefn.getChild(FulltextIndexConstants.AGGREGATES) + .getChild(NT_TEST_ASSET).addChild("includeOriginal"); + originalInclude.setProperty(FulltextIndexConstants.AGG_RELATIVE_NODE, true); + originalInclude.setProperty(FulltextIndexConstants.AGG_PATH, "jcr:content/renditions/original"); + + Tree includeSingleRel = indexDefn.getChild(FulltextIndexConstants.AGGREGATES) + .getChild(NT_TEST_ASSET).addChild("includeFirstLevelChild"); + includeSingleRel.setProperty(FulltextIndexConstants.AGG_RELATIVE_NODE, true); + includeSingleRel.setProperty(FulltextIndexConstants.AGG_PATH, "firstLevelChild"); + + // Include all properties for both assets and pages + Tree assetProps = TestUtil.newRulePropTree(indexDefn, NT_TEST_ASSET); + TestUtil.enableForFullText(assetProps, "jcr:content/metadata/format"); + TestUtil.enableForFullText(assetProps, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + Tree pageProps = TestUtil.newRulePropTree(indexDefn, NT_TEST_PAGE); + TestUtil.enableForFullText(pageProps, FulltextIndexConstants.REGEX_ALL_PROPS, true); + + root.commit(); + } + + protected static QueryIndex.NodeAggregator getNodeAggregator() { + return new SimpleNodeAggregator() + .newRuleWithName(NT_FILE, newArrayList("jcr:content")) + .newRuleWithName(NT_TEST_PAGE, newArrayList("jcr:content")) + .newRuleWithName(NT_TEST_PAGECONTENT, newArrayList("*", "*/*", "*/*/*", "*/*/*/*")) + .newRuleWithName(NT_TEST_ASSET, newArrayList("jcr:content")) + .newRuleWithName( + NT_TEST_ASSETCONTENT, + newArrayList("metadata", "renditions", "renditions/original", "comments", + "renditions/original/jcr:content")) + .newRuleWithName("rep:User", newArrayList("profile")); + } + + @Test + public void oak2226() throws Exception { + setTraversalEnabled(false); + final String statement = "/jcr:root/content//element(*, test:Asset)[" + + "(jcr:contains(., 'mountain')) " + + "and (jcr:contains(jcr:content/metadata/@format, 'image'))]"; + Tree content = root.getTree("/").addChild("content"); + List<String> expected = Lists.newArrayList(); + + /* + * creating structure + * "/content" : { + * "node" : { + * "jcr:primaryType" : "test:Asset", + * "jcr:content" : { + * "jcr:primaryType" : "test:AssetContent", + * "metadata" : { + * "jcr:primaryType" : "nt:unstructured", + * "title" : "Lorem mountain ipsum", + * "format" : "image/jpeg" + * } + * } + * }, + * "mountain-node" : { + * "jcr:primaryType" : "test:Asset", + * "jcr:content" : { + * "jcr:primaryType" : "test:AssetContent", + * "metadata" : { + * "jcr:primaryType" : "nt:unstructured", + * "format" : "image/jpeg" + * } + * } + * } + * } + */ + + + // adding a node with 'mountain' property + Tree node = content.addChild("node"); + node.setProperty(JCR_PRIMARYTYPE, NT_TEST_ASSET, NAME); + expected.add(node.getPath()); + node = node.addChild("jcr:content"); + node.setProperty(JCR_PRIMARYTYPE, NT_TEST_ASSETCONTENT, NAME); + node = node.addChild("metadata"); + node.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, NAME); + node.setProperty("title", "Lorem mountain ipsum", STRING); + node.setProperty("format", "image/jpeg", STRING); + + // adding a node with 'mountain' name but not property + node = content.addChild("mountain-node"); + node.setProperty(JCR_PRIMARYTYPE, NT_TEST_ASSET, NAME); + expected.add(node.getPath()); + node = node.addChild("jcr:content"); + node.setProperty(JCR_PRIMARYTYPE, NT_TEST_ASSETCONTENT, NAME); + node = node.addChild("metadata"); + node.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, NAME); + node.setProperty("format", "image/jpeg", STRING); + + root.commit(); + + assertQuery(statement, "xpath", expected); + setTraversalEnabled(true); + } + + @Test + public void oak2249() throws Exception { + setTraversalEnabled(false); + final String statement = "//element(*, test:Asset)[ " + + "( " + + "jcr:contains(., 'summer') " + + "or " + + "jcr:content/metadata/@tags = 'namespace:season/summer' " + + ") and " + + "jcr:contains(jcr:content/metadata/@format, 'image') " + + "]"; + + Tree content = root.getTree("/").addChild("content"); + List<String> expected = newArrayList(); + + Tree metadata = createAssetStructure(content, "tagged"); + metadata.setProperty("tags", of("namespace:season/summer"), STRINGS); + metadata.setProperty("format", "image/jpeg", STRING); + expected.add("/content/tagged"); + + metadata = createAssetStructure(content, "titled"); + metadata.setProperty("title", "Lorem summer ipsum", STRING); + metadata.setProperty("format", "image/jpeg", STRING); + expected.add("/content/titled"); + + metadata = createAssetStructure(content, "summer-node"); + metadata.setProperty("format", "image/jpeg", STRING); + expected.add("/content/summer-node"); + + // the following is NOT expected + metadata = createAssetStructure(content, "winter-node"); + metadata.setProperty("tags", of("namespace:season/winter"), STRINGS); + metadata.setProperty("title", "Lorem winter ipsum", STRING); + metadata.setProperty("format", "image/jpeg", STRING); + + root.commit(); + + assertQuery(statement, "xpath", expected); + setTraversalEnabled(true); + } + + @Test + public void indexRelativeNode() throws Exception { + setTraversalEnabled(false); + final String statement = "//element(*, test:Asset)[ " + + "jcr:contains(., 'summer') " + + "and jcr:contains(jcr:content/renditions/original, 'fox')" + + "and jcr:contains(jcr:content/metadata/@format, 'image') " + + "]"; + + Tree content = root.getTree("/").addChild("content"); + List<String> expected = newArrayList(); + + Tree metadata = createAssetStructure(content, "tagged"); + metadata.setProperty("tags", of("namespace:season/summer"), STRINGS); + metadata.setProperty("format", "image/jpeg", STRING); + + Tree original = metadata.getParent().addChild("renditions").addChild("original"); + original.setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_FILE); + original.addChild("jcr:content").setProperty(PropertyStates.createProperty(JcrConstants.JCR_MIMETYPE, "text/plain")); + original.addChild("jcr:content").setProperty(PropertyStates.createProperty("jcr:data", "fox jumps".getBytes())); + + expected.add("/content/tagged"); + root.commit(); + + assertQuery(statement, "xpath", expected); + + //Update the reaggregated node and with that parent should be get updated + Tree originalContent = TreeUtil.getTree(root.getTree("/"), "/content/tagged/jcr:content/renditions/original/jcr:content"); + originalContent.setProperty(PropertyStates.createProperty("jcr:data", "kiwi jumps".getBytes())); + root.commit(); + assertQuery(statement, "xpath", Collections.<String>emptyList()); + setTraversalEnabled(true); + } + + @Test + public void indexSingleRelativeNode() throws Exception { + setTraversalEnabled(false); + final String statement = "//element(*, test:Asset)[ " + + "jcr:contains(firstLevelChild, 'summer') ]"; + + List<String> expected = newArrayList(); + + Tree content = root.getTree("/").addChild("content"); + Tree page = content.addChild("pages"); + page.setProperty(JCR_PRIMARYTYPE, NT_TEST_ASSET, NAME); + Tree child = page.addChild("firstLevelChild"); + child.setProperty("tag", "summer is here", STRING); + root.commit(); + + expected.add("/content/pages"); + assertQuery(statement, "xpath", expected); + } + + @Ignore("OAK-6597") + @Test + public void excerpt() throws Exception { + setTraversalEnabled(false); + final String statement = "select [rep:excerpt] from [test:Page] as page where contains(*, '%s*')"; + + Tree content = root.getTree("/").addChild("content"); + Tree pageContent = createPageStructure(content, "foo"); + // contains 'aliq' but not 'tinc' + pageContent.setProperty("bar", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque aliquet odio varius odio " + + "imperdiet, non egestas ex consectetur. Fusce congue ac augue quis finibus. Sed vulputate sollicitudin neque, nec " + + "lobortis nisl varius eget."); + // doesn't contain 'aliq' but 'tinc' + pageContent.getParent().setProperty("bar", "Donec lacinia luctus leo, sed rutrum nulla. Sed sed hendrerit turpis. Donec ex quam, " + + "bibendum et metus at, tristique tincidunt leo. Nam at elit ligula. Etiam ullamcorper, elit sit amet varius molestie, " + + "nisl ex egestas libero, quis elementum enim mi a quam."); + + root.commit(); + + for (String term : new String[]{"tinc", "aliq"}) { + Result result = executeQuery(String.format(statement, term), "JCR-SQL2", NO_BINDINGS); + Iterator<? extends ResultRow> rows = result.getRows().iterator(); + assertTrue(rows.hasNext()); + ResultRow firstHit = rows.next(); + assertFalse(rows.hasNext()); // assert that there is only a single hit + + PropertyValue excerptValue = firstHit.getValue("rep:excerpt"); + assertNotNull(excerptValue); + assertFalse("Excerpt for '" + term + "' is not supposed to be empty.", "".equals(excerptValue.getValue(STRING))); + } + } + + /** + * <p> + * convenience method that create an "asset" structure like + * </p> + * <p> + * <pre> + * "parent" : { + * "nodeName" : { + * "jcr:primaryType" : "test:Asset", + * "jcr:content" : { + * "jcr:primaryType" : "test:AssetContent", + * "metatada" : { + * "jcr:primaryType" : "nt:unstructured" + * } + * } + * } + * } + * </pre> + * <p> + * <p> + * and returns the {@code metadata} node + * </p> + * + * @param parent the parent under which creating the node + * @param nodeName the node name to be used + * @return the {@code metadata} node. See above for details + */ + private static Tree createAssetStructure(@NotNull final Tree parent, + @NotNull final String nodeName) { + checkNotNull(parent); + checkArgument(!Strings.isNullOrEmpty(nodeName), "nodeName cannot be null or empty"); + + Tree node = parent.addChild(nodeName); + node.setProperty(JCR_PRIMARYTYPE, NT_TEST_ASSET, NAME); + node = node.addChild(JCR_CONTENT); + node.setProperty(JCR_PRIMARYTYPE, NT_TEST_ASSETCONTENT, NAME); + node = node.addChild("metadata"); + node.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, NAME); + return node; + } + + /** + * <p> + * convenience method that create an "page" structure like + * </p> + * <p> + * <pre> + * "parent" : { + * "nodeName" : { + * "jcr:primaryType" : "test:Page", + * "jcr:content" : { + * "jcr:primaryType" : "test:PageContent" + * } + * } + * } + * </pre> + * <p> + * <p> + * and returns the {@code jcr:content} node + * </p> + * + * @param parent the parent under which creating the node + * @param nodeName the node name to be used + * @return the {@code jcr:content} node. See above for details + */ + private static Tree createPageStructure(@NotNull final Tree parent, + @NotNull final String nodeName) { + checkNotNull(parent); + checkArgument(!Strings.isNullOrEmpty(nodeName), "nodeName cannot be null or empty"); + + Tree node = parent.addChild(nodeName); + node.setProperty(JCR_PRIMARYTYPE, NT_TEST_PAGE, NAME); + node = node.addChild(JCR_CONTENT); + node.setProperty(JCR_PRIMARYTYPE, NT_TEST_PAGECONTENT, NAME); + + return node; + } +} Propchange: jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexAggregation2CommonTest.java ------------------------------------------------------------------------------ svn:eol-style = native