Index: trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java =================================================================== --- trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java (revision 1622213) +++ trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java (working copy) @@ -31,6 +31,7 @@ import javax.annotation.Nullable; +import com.google.common.collect.Iterables; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.Type; @@ -115,7 +116,9 @@ if (type == null || type.isArray() || !getType().equals(type.getValue(Type.STRING))) { continue; } - if (contains(index.getNames(PROPERTY_NAMES), propertyName)) { + + if (!Iterables.isEmpty(index.getNames(PROPERTY_NAMES)) + && Iterables.get(index.getNames(PROPERTY_NAMES), 0).equals(propertyName)) { NodeState indexContent = index.getChildNode(INDEX_CONTENT_NODE_NAME); if (!indexContent.exists()) { continue; @@ -330,4 +333,4 @@ } return sb; } -} \ No newline at end of file +} Index: trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java =================================================================== --- trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java (revision 1622213) +++ trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java (working copy) @@ -523,7 +523,7 @@ v.visit(content.getChildNode(child.getName())); count += v.getCount(); depthTotal += v.depthTotal; - if (count > max) { + if (count >= max) { break; } } @@ -578,7 +578,7 @@ v.visit(content.getChildNode(child.getName())); count += v.getCount(); depthTotal += v.depthTotal; - if (count > max) { + if (count >= max) { break; } } @@ -1176,4 +1176,4 @@ return lane; } -} \ No newline at end of file +} Index: trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java =================================================================== --- trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java (revision 1622213) +++ trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java (working copy) @@ -799,22 +799,13 @@ sortOrder = null; } } - long maxEntryCount = limit; - if (offset > 0) { - if (offset + limit < 0) { - // long overflow - maxEntryCount = Long.MAX_VALUE; - } else { - maxEntryCount = offset + limit; - } - } + List ipList = advIndex.getPlans( filter, sortOrder, rootState); cost = Double.POSITIVE_INFINITY; for (IndexPlan p : ipList) { - // TODO limit is after all conditions - long entryCount = Math.min(maxEntryCount, p.getEstimatedEntryCount()); - double c = p.getCostPerExecution() + entryCount * p.getCostPerEntry(); + double c = + p.getCostPerExecution() + p.getEstimatedEntryCount() * p.getCostPerEntry(); if (c < cost) { cost = c; indexPlan = p; Index: trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexPlanSelectionTest.java =================================================================== --- trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexPlanSelectionTest.java (revision 0) +++ trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexPlanSelectionTest.java (working copy) @@ -0,0 +1,143 @@ +/* + * 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.property; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import junit.framework.Assert; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.api.QueryEngine; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider; +import org.apache.jackrabbit.oak.plugins.index.IndexUtils; +import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState; +import org.apache.jackrabbit.oak.query.QueryEngineSettings; +import org.apache.jackrabbit.oak.query.ast.Operator; +import org.apache.jackrabbit.oak.query.ast.SelectorImpl; +import org.apache.jackrabbit.oak.query.index.FilterImpl; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; +import org.apache.jackrabbit.oak.spi.commit.EditorHook; +import org.apache.jackrabbit.oak.spi.query.PropertyValues; +import org.apache.jackrabbit.oak.spi.query.QueryIndex; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.junit.Test; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.List; +import java.util.Random; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; +import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME; +import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.JCR_NODE_TYPES; +import static org.junit.Assert.assertNotNull; + +/** + * Created by amitj on 08-09-2014. + */ +public class OrderedIndexPlanSelectionTest extends BasicOrderedPropertyIndexQueryTest { + private static final EditorHook HOOK = new EditorHook(new IndexUpdateProvider( + new OrderedPropertyIndexEditorProvider())); + + /** + * tests the output of a plan where the query is asked with where conditions + * as well as the ORDER BY are on the indexed property + * + * eg: SELECT * FROM [nt:unstructured] WHERE pinned=true AND foo < 2014-08-31T00:00:00.000Z + * ORDER BY + * foo descending + * + * @throws java.lang.Exception + */ + @Test + public void planOrderAndWhereIndexed() throws Exception { + NodeBuilder root = EmptyNodeState.EMPTY_NODE.builder(); + + IndexUtils.createIndexDefinition(root.child(INDEX_DEFINITIONS_NAME), TEST_INDEX_NAME, false, + Lists.newArrayList(ORDERED_PROPERTY, "sortProp", "filterProp"), null, OrderedIndex.TYPE, + ImmutableMap + .of(OrderedIndex.DIRECTION, OrderedIndex.OrderDirection.DESC.getDirection())); + NodeState before = root.getNodeState(); + + Calendar start = midnightFirstJan2013(); + List values = generateOrderedDates(500, OrderedIndex.OrderDirection.DESC, start); + Random rand = new Random(15); + + int counter = 0; + NodeBuilder test = root.child("test"); + while (!values.isEmpty()) { + String v = values.remove(rand.nextInt(values.size())); + test.child(String.format("n%s", counter++)) + .setProperty(ORDERED_PROPERTY, v, Type.DATE) + .setProperty("sortProp", rand.nextBoolean(), Type.BOOLEAN) + .setProperty("filterProp", rand.nextBoolean(), Type.BOOLEAN); + } + + NodeState after = root.getNodeState(); + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + + Calendar searchForCalendar = (Calendar) start.clone(); + searchForCalendar.add(Calendar.HOUR_OF_DAY, 36); + String searchFor = new SimpleDateFormat(ISO_8601_2000).format(searchForCalendar.getTime()); + + final OrderedPropertyIndex index = new OrderedPropertyIndex(); + final String nodeTypeName = JcrConstants.NT_BASE; + FilterImpl filter = createFilter(indexed, nodeTypeName); + filter.restrictProperty(ORDERED_PROPERTY, Operator.LESS_THAN, PropertyValues + .newDate(searchFor)); + filter.restrictProperty("filterProp", Operator.EQUAL, + PropertyValues.newBoolean(true)); + + final QueryIndex.OrderEntry.Order order = QueryIndex.OrderEntry.Order.DESCENDING; + List sortOrder = ImmutableList + .of(createOrderEntry(ORDERED_PROPERTY, order)); + List plans = index.getPlans(filter, sortOrder, indexed); + assertNotNull(plans); + + double cost = Double.POSITIVE_INFINITY; + QueryIndex.IndexPlan indexPlan = null; + + for (QueryIndex.IndexPlan p : plans) { + double c = p.getCostPerExecution() + p.getEstimatedEntryCount() * p.getCostPerEntry(); + if (c < cost) { + cost = c; + indexPlan = p; + } + } + assertNotNull(indexPlan); + assertEquals(ORDERED_PROPERTY, indexPlan.getPropertyRestriction().propertyName); + } + + private static FilterImpl createFilter(NodeState indexed, String nodeTypeName) { + NodeState system = indexed.getChildNode(JCR_SYSTEM); + NodeState types = system.getChildNode(JCR_NODE_TYPES); + NodeState type = types.getChildNode(nodeTypeName); + SelectorImpl selector = new SelectorImpl(type, nodeTypeName); + return new FilterImpl(selector, "SELECT * FROM [" + nodeTypeName + "]", new QueryEngineSettings()); + } + + private static QueryIndex.OrderEntry createOrderEntry(String property, + QueryIndex.OrderEntry.Order order) { + return new QueryIndex.OrderEntry(property, Type.UNDEFINED, order); + } +} Property changes on: trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexPlanSelectionTest.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java =================================================================== --- trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java (revision 1622213) +++ trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java (working copy) @@ -31,10 +31,16 @@ import javax.jcr.query.QueryResult; import javax.jcr.query.RowIterator; +import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest; import org.apache.jackrabbit.oak.jcr.NodeStoreFixture; import org.junit.Ignore; import org.junit.Test; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Random; /** * Tests query plans. @@ -174,6 +180,72 @@ } @Test + public void planOrderAndWhereIndexed() throws Exception, CommitFailedException { + Session session = getAdminSession(); + QueryManager qm = session.getWorkspace().getQueryManager(); + Node testRootNode = session.getRootNode().addNode("test"); + + /* Create Ordered index with multiple properties */ + Node n = session.getRootNode().getNode("oak:index"). + addNode("testIndex", "oak:QueryIndexDefinition"); + n.setProperty("type", "ordered"); + n.setProperty("direction", "descending"); + n.setProperty("propertyNames", new String[]{"dateProp", "sortProp", "filterProp"}, + PropertyType.NAME); + session.save(); + + Calendar start = Calendar.getInstance(); + List values = generateOrderedDates(500, start); + Random rand = new Random(15); + + /* Create nodes */ + int counter = 0; + while (counter < 500) { + Node node = testRootNode.addNode(String.format("n%s", counter++)); + node.setProperty("dateProp", values.remove(rand.nextInt(values.size()))); + node.setProperty("sortProp", rand.nextBoolean()); + node.setProperty("filterProp", rand.nextBoolean()); + } + session.save(); + + /* Execute query */ + String ISO_8601_2000 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + Calendar searchVal = Calendar.getInstance(); + String searchFor = new SimpleDateFormat(ISO_8601_2000).format(searchVal.getTime()); + + String query = "/jcr:root/test//element(*, " + + "nt:unstructured)[((@filterProp = 'true' or not(@filterProp)) and @dateProp < " + + "xs:dateTime('%s'))] order by @dateProp descending"; + query = String.format(query, searchFor); + Query q = qm.createQuery("explain " + query, "xpath"); + QueryResult result = q.execute(); + RowIterator it = result.getRows(); + + assertTrue(it.hasNext()); + String plan = it.nextRow().getValue("plan").getString(); + /* Should use plan with dateProp restriction */ + String planExpected = + "[nt:unstructured] as [a] /* ordered dateProp < %s where ([a]" + + ".[dateProp] < cast('%s' as date)) and (isdescendantnode" + + "([a], [/test])) */"; + planExpected = String.format(planExpected, searchFor, searchFor); + assertEquals(planExpected, plan); + } + + protected static List generateOrderedDates(int amount, + final Calendar start) { + List values = new ArrayList(amount); + + for (int i = 0; i < amount; i++) { + Calendar cal = (Calendar) start.clone(); + values.add(cal); + start.add(Calendar.HOUR_OF_DAY, -12); + } + + return values; + } + + @Test // OAK-1898 public void traversalVersusPropertyIndex() throws Exception { Session session = getAdminSession();