Index: oak-core/pom.xml =================================================================== --- oak-core/pom.xml (revision 1405804) +++ oak-core/pom.xml (working copy) @@ -50,6 +50,7 @@ org.apache.jackrabbit.oak.plugins.identifier, org.apache.jackrabbit.oak.plugins.index, org.apache.jackrabbit.oak.plugins.index.lucene, + org.apache.jackrabbit.oak.plugins.index.nodetype, org.apache.jackrabbit.oak.plugins.index.property, org.apache.jackrabbit.oak.plugins.memory, org.apache.jackrabbit.oak.plugins.name, Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndex.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndex.java (revision 0) @@ -0,0 +1,124 @@ +/* + * 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.nodetype; + +import java.util.Set; + +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.NodeTypeIterator; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.core.ReadOnlyTree; +import org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants; +import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager; +import org.apache.jackrabbit.oak.spi.query.Cursor; +import org.apache.jackrabbit.oak.spi.query.Cursors; +import org.apache.jackrabbit.oak.spi.query.Filter; +import org.apache.jackrabbit.oak.spi.query.QueryIndex; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import com.google.common.collect.Sets; + +/** + * NodeTypeIndex... + */ +class NodeTypeIndex implements QueryIndex, JcrConstants { + + @Override + public double getCost(Filter filter, NodeState root) { + if (!hasNodeTypeRestriction(filter)) { + // this is not an appropriate index if the filter + // doesn't have a node type restriction + return Double.MAX_VALUE; + } + NodeTypeIndexLookup lookup = new NodeTypeIndexLookup(root); + if (lookup.isIndexed(filter.getPath())) { + return lookup.getCost(resolveNodeType(root, filter.getNodeType())); + } else { + return Double.MAX_VALUE; + } + } + + @Override + public Cursor query(Filter filter, NodeState root) { + NodeTypeIndexLookup lookup = new NodeTypeIndexLookup(root); + if (!hasNodeTypeRestriction(filter) || !lookup.isIndexed(filter.getPath())) { + return Cursors.newTraversingCursor("?", filter, root); + } + return Cursors.newPathCursor(lookup.find( + resolveNodeType(root, filter.getNodeType()))); + } + + @Override + public String getPlan(Filter filter, NodeState root) { + // TODO: return plan according to query() + return null; + } + + @Override + public String getIndexName() { + return "nodetype"; + } + + //----------------------------< internal >---------------------------------- + + private static boolean hasNodeTypeRestriction(Filter filter) { + return filter.getNodeType() != null + && !filter.getNodeType().equals(NT_BASE); + } + + private static Iterable resolveNodeType(NodeState root, String nodeType) { + ReadOnlyNodeTypeManager ntMgr = new NTManager(root); + Set ntNames = Sets.newHashSet(); + try { + NodeType nt = ntMgr.getNodeType(nodeType); + for (NodeTypeIterator types = nt.getSubtypes(); types.hasNext(); ) { + ntNames.add(types.nextNodeType().getName()); + } + ntNames.add(nodeType); + } catch (RepositoryException e) { + // unknown node type + } + return ntNames; + } + + private static final class NTManager extends ReadOnlyNodeTypeManager { + + private final NodeState root; + + NTManager(NodeState root) { + this.root = root; + } + + @Override + protected Tree getTypes() { + Tree t = new ReadOnlyTree(root); + for (String name : PathUtils.elements(NodeTypeConstants.NODE_TYPES_PATH)) { + t = t.getChild(name); + if (t == null) { + break; + } + } + return t; + } + } +} Property changes on: oak-core\src\main\java\org\apache\jackrabbit\oak\plugins\index\nodetype\NodeTypeIndex.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision Rev URL Added: svn:eol-style + native Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexLookup.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexLookup.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexLookup.java (revision 0) @@ -0,0 +1,90 @@ +/* + * 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.nodetype; + +import java.util.Set; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.api.PropertyValue; +import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexLookup; +import org.apache.jackrabbit.oak.spi.query.PropertyValues; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import com.google.common.collect.Sets; + +/** + * NodeTypeIndexLookup... + */ +class NodeTypeIndexLookup implements JcrConstants { + + private final NodeState root; + + public NodeTypeIndexLookup(NodeState root) { + this.root = root; + } + + /** + * Returns true if a node type index lookup exists at the given + * path or further up the tree. + * + * @param path the path to check. + * @return true if a node type index exists; false + * otherwise. + */ + public boolean isIndexed(String path) { + PropertyIndexLookup lookup = new PropertyIndexLookup(root); + if (lookup.isIndexed(JCR_PRIMARYTYPE, path) + && lookup.isIndexed(JCR_MIXINTYPES, path)) { + return true; + } + + if (path.startsWith("/")) { + path = path.substring(1); + } + int slash = path.indexOf('/'); + if (slash == -1) { + return false; + } + + NodeState child = root.getChildNode(path.substring(0, slash)); + return new NodeTypeIndexLookup(child).isIndexed( + path.substring(slash)); + } + + public double getCost(Iterable nodeTypes) { + PropertyValue ntNames = PropertyValues.newName(nodeTypes); + PropertyIndexLookup lookup = new PropertyIndexLookup(root); + return lookup.getCost(JCR_PRIMARYTYPE, ntNames) + + lookup.getCost(JCR_MIXINTYPES, ntNames); + } + + /** + * Returns the paths that match the given node types. + * + * @param nodeTypes the names of the node types to match. + * @return the set of matched paths. + */ + public Set find(Iterable nodeTypes) { + Set paths = Sets.newHashSet(); + PropertyIndexLookup lookup = new PropertyIndexLookup(root); + PropertyValue ntNames = PropertyValues.newName(nodeTypes); + paths.addAll(lookup.find(JCR_PRIMARYTYPE, ntNames)); + paths.addAll(lookup.find(JCR_MIXINTYPES, ntNames)); + return paths; + } + +} Property changes on: oak-core\src\main\java\org\apache\jackrabbit\oak\plugins\index\nodetype\NodeTypeIndexLookup.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision Rev URL Added: svn:eol-style + native Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexProvider.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexProvider.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexProvider.java (revision 0) @@ -0,0 +1,41 @@ +/* + * 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.nodetype; + +import java.util.List; + +import javax.annotation.Nonnull; + +import org.apache.jackrabbit.oak.spi.query.QueryIndex; +import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import com.google.common.collect.ImmutableList; + +/** + * NodeTypeIndexProvider... + */ +public class NodeTypeIndexProvider implements QueryIndexProvider { + + @Nonnull + @Override + public List getQueryIndexes(NodeState nodeState) { + return ImmutableList.of(new NodeTypeIndex()); + } +} Property changes on: oak-core\src\main\java\org\apache\jackrabbit\oak\plugins\index\nodetype\NodeTypeIndexProvider.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision Rev URL Added: svn:eol-style + native Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java (revision 1405804) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java (working copy) @@ -19,27 +19,21 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.Type; -import org.apache.jackrabbit.oak.query.index.IndexRowImpl; -import org.apache.jackrabbit.oak.query.index.TraversingCursor; import org.apache.jackrabbit.oak.spi.query.Cursor; +import org.apache.jackrabbit.oak.spi.query.Cursors; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.query.Filter.PropertyRestriction; -import org.apache.jackrabbit.oak.spi.query.IndexRow; import org.apache.jackrabbit.oak.spi.query.QueryIndex; import org.apache.jackrabbit.oak.spi.state.NodeState; import com.google.common.base.Charsets; import com.google.common.collect.Sets; -import static org.apache.jackrabbit.oak.commons.PathUtils.isAbsolute; - /** * Provides a QueryIndex that does lookups against a property index * @@ -113,8 +107,16 @@ @Override public double getCost(Filter filter, NodeState root) { - // TODO: proper cost calculation - return 1.0; + PropertyIndexLookup lookup = new PropertyIndexLookup(root); + for (PropertyRestriction pr : filter.getPropertyRestrictions()) { + if (pr.firstIncluding && pr.lastIncluding + && pr.first.equals(pr.last) // TODO: range queries + && lookup.isIndexed(pr.propertyName, "/")) { // TODO: path + return lookup.getCost(pr.propertyName, pr.first); + } + } + // not an appropriate index + return Double.MAX_VALUE; } @Override @@ -136,9 +138,9 @@ } if (paths != null) { - return new PathCursor(paths); + return Cursors.newPathCursor(paths); } else { - return new TraversingCursor("?", filter, root); + return Cursors.newTraversingCursor("?", filter, root); } } @@ -146,34 +148,4 @@ public String getPlan(Filter filter, NodeState root) { return "oak:index"; // TODO: better plans } - - private static class PathCursor implements Cursor { - - private final Iterator iterator; - - private String path; - - public PathCursor(Collection paths) { - this.iterator = paths.iterator(); - } - - @Override - public boolean next() { - if (iterator.hasNext()) { - path = iterator.next(); - return true; - } else { - path = null; - return false; - } - } - - @Override - public IndexRow currentRow() { - // TODO support jcr:score and possibly rep:exceprt - return new IndexRowImpl(isAbsolute(path) ? path : "/" + path); - } - - } - } \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java (revision 1405804) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java (working copy) @@ -158,6 +158,23 @@ return paths; } + public double getCost(String name, PropertyValue value) { + double cost = 0.0; + NodeState state = getIndexDefinitionNode(name); + if (state != null && state.getChildNode(":index") != null) { + state = state.getChildNode(":index"); + for (String p : PropertyIndex.encode(value)) { + PropertyState property = state.getProperty(p); + if (property != null) { + cost += property.count(); + } + } + } else { + cost = Double.MAX_VALUE; + } + return cost; + } + @Nullable private NodeState getIndexDefinitionNode(String name) { NodeState state = root.getChildNode(INDEX_DEFINITIONS_NAME); @@ -175,5 +192,4 @@ } return null; } - } \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/InitialContent.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/InitialContent.java (revision 1405804) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/InitialContent.java (working copy) @@ -18,15 +18,19 @@ import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Service; +import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.core.RootImpl; +import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer; import org.apache.jackrabbit.oak.spi.security.user.UserConstants; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeStore; import org.apache.jackrabbit.oak.spi.state.NodeStoreBranch; +import com.google.common.collect.ImmutableList; + /** * {@code InitialContent} implements a {@link RepositoryInitializer} and * registers built-in node types when the micro kernel becomes available. @@ -96,11 +100,14 @@ .setProperty("propertyNames", "jcr:uuid") .setProperty("reindex", true) .setProperty("unique", true); - index.child("primaryType") + index.child("nodetype") .setProperty("jcr:primaryType", "oak:queryIndexDefinition", Type.NAME) .setProperty("type", "property") .setProperty("reindex", true) - .setProperty("propertyNames", "jcr:primaryType"); + .setProperty(PropertyStates.createProperty( + "propertyNames", + ImmutableList.of(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.JCR_MIXINTYPES), + Type.STRINGS)); // FIXME: user-mgt related unique properties (rep:authorizableId, rep:principalName) are implementation detail and not generic for repo // FIXME OAK-396: rep:principalName only needs to be unique if defined with user/group nodes -> add defining nt-info to uniqueness constraint otherwise ac-editing will fail. index.child("authorizableId") Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java (revision 1405804) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java (working copy) @@ -64,13 +64,17 @@ @Override public void restrict(FilterImpl f) { - String p = parentSelector.currentPath(); - String c = childSelector.currentPath(); - if (f.getSelector() == parentSelector && c != null) { - f.restrictPath(PathUtils.getParentPath(c), Filter.PathRestriction.EXACT); + if (f.getSelector() == parentSelector) { + String c = childSelector.currentPath(); + if (c != null) { + f.restrictPath(PathUtils.getParentPath(c), Filter.PathRestriction.EXACT); + } } - if (f.getSelector() == childSelector && p != null) { - f.restrictPath(p, Filter.PathRestriction.DIRECT_CHILDREN); + if (f.getSelector() == childSelector) { + String p = parentSelector.currentPath(); + if (p != null) { + f.restrictPath(p, Filter.PathRestriction.DIRECT_CHILDREN); + } } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java (revision 1405804) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java (working copy) @@ -95,18 +95,22 @@ @Override public void restrict(FilterImpl f) { - PropertyValue p1 = selector1.currentProperty(property1Name); - PropertyValue p2 = selector2.currentProperty(property2Name); - if (f.getSelector() == selector1 && p2 != null) { - if (!p2.isArray()) { - // TODO support join on multi-valued properties - f.restrictProperty(property1Name, Operator.EQUAL, p2); + if (f.getSelector() == selector1) { + PropertyValue p2 = selector2.currentProperty(property2Name); + if (p2 != null) { + if (!p2.isArray()) { + // TODO support join on multi-valued properties + f.restrictProperty(property1Name, Operator.EQUAL, p2); + } } } - if (f.getSelector() == selector2 && p1 != null) { - if (!p1.isArray()) { - // TODO support join on multi-valued properties - f.restrictProperty(property2Name, Operator.EQUAL, p1); + if (f.getSelector() == selector2) { + PropertyValue p1 = selector1.currentProperty(property1Name); + if (p1 != null) { + if (!p1.isArray()) { + // TODO support join on multi-valued properties + f.restrictProperty(property2Name, Operator.EQUAL, p1); + } } } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java (revision 1405804) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java (working copy) @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map.Entry; +import javax.annotation.CheckForNull; import javax.jcr.PropertyType; import org.apache.jackrabbit.oak.api.PropertyValue; @@ -98,6 +99,7 @@ } @Override + @CheckForNull public String getNodeType() { return nodeType; } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingCursor.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingCursor.java (revision 1405804) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingCursor.java (working copy) @@ -1,141 +0,0 @@ -/* - * 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.query.index; - -import static org.apache.jackrabbit.oak.spi.query.Filter.PathRestriction.ALL_CHILDREN; - -import java.util.Deque; -import java.util.Iterator; -import org.apache.jackrabbit.oak.commons.PathUtils; -import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry; -import org.apache.jackrabbit.oak.spi.query.Cursor; -import org.apache.jackrabbit.oak.spi.query.Filter; -import org.apache.jackrabbit.oak.spi.query.IndexRow; -import org.apache.jackrabbit.oak.spi.query.Filter.PathRestriction; -import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; -import org.apache.jackrabbit.oak.spi.state.NodeState; -import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.Iterators; -import com.google.common.collect.Queues; - -/** - * A cursor that reads all nodes in a given subtree. - */ -public class TraversingCursor implements Cursor { - - private static final Logger LOG = LoggerFactory.getLogger(TraversingIndex.class); - - private final String statement; - - private final Filter filter; - - private final Deque> nodeIterators = - Queues.newArrayDeque(); - - private String parentPath; - - private String currentPath; - - private long readCount; - - public TraversingCursor(String statement, Filter filter, NodeState root) { - this.statement = statement; - this.filter = filter; - - String path = filter.getPath(); - parentPath = null; - currentPath = "/"; - NodeState parent = null; - NodeState node = root; - if (!path.equals("/")) { - for (String name : path.substring(1).split("/")) { - parentPath = currentPath; - currentPath = PathUtils.concat(parentPath, name); - - parent = node; - node = parent.getChildNode(name); - - if (node == null) { - // nothing can match this filter, leave nodes empty - return; - } - } - } - PathRestriction restriciton = filter.getPathRestriction(); - switch (restriciton) { - case EXACT: - case ALL_CHILDREN: - nodeIterators.add(Iterators.singletonIterator( - new MemoryChildNodeEntry(currentPath, node))); - parentPath = ""; - break; - case PARENT: - if (parent != null) { - nodeIterators.add(Iterators.singletonIterator( - new MemoryChildNodeEntry(parentPath, parent))); - parentPath = ""; - } - break; - case DIRECT_CHILDREN: - nodeIterators.add(node.getChildNodeEntries().iterator()); - parentPath = currentPath; - break; - default: - throw new IllegalArgumentException("Unknown restriction: " + restriciton); - } - } - - @Override - public IndexRow currentRow() { - return new IndexRowImpl(currentPath); - } - - @Override - public boolean next() { - while (!nodeIterators.isEmpty()) { - Iterator iterator = nodeIterators.getLast(); - if (iterator.hasNext()) { - ChildNodeEntry entry = iterator.next(); - - readCount++; - if (readCount % 100 == 0) { - LOG.warn("Traversed " + readCount + " nodes with filter " + filter + " for query " + statement + "; consider creating an index or changing the query"); - } - - NodeState node = entry.getNodeState(); - - String name = entry.getName(); - if (NodeStateUtils.isHidden(name)) { - continue; - } - currentPath = PathUtils.concat(parentPath, name); - - if (filter.getPathRestriction() == ALL_CHILDREN) { - nodeIterators.addLast(node.getChildNodeEntries().iterator()); - parentPath = currentPath; - } - return true; - } else { - nodeIterators.removeLast(); - parentPath = PathUtils.getParentPath(parentPath); - } - } - currentPath = null; - return false; - } - -} Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java (revision 1405804) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java (working copy) @@ -20,6 +20,7 @@ import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.spi.query.Cursor; +import org.apache.jackrabbit.oak.spi.query.Cursors; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.query.QueryIndex; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -37,7 +38,7 @@ @Override public Cursor query(Filter filter, NodeState root) { - return new TraversingCursor(statement, filter, root); + return Cursors.newTraversingCursor(statement, filter, root); } @Override Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java (revision 1405804) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java (working copy) @@ -125,7 +125,7 @@ for (QueryIndex index : getIndexes()) { double cost = index.getCost(filter, root); if (LOG.isDebugEnabled()) { - LOG.debug("cost for " + index + " is " + cost); + LOG.debug("cost for " + index.getIndexName() + " is " + cost); } if (cost < bestCost) { bestCost = cost; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursors.java (revision 0) @@ -0,0 +1,211 @@ +/* + * 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.spi.query; + +import java.util.Deque; +import java.util.Iterator; + +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry; +import org.apache.jackrabbit.oak.query.index.IndexRowImpl; +import org.apache.jackrabbit.oak.query.index.TraversingIndex; +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Iterators; +import com.google.common.collect.Queues; + +import static org.apache.jackrabbit.oak.commons.PathUtils.isAbsolute; +import static org.apache.jackrabbit.oak.spi.query.Filter.PathRestriction.ALL_CHILDREN; + +/** + * Cursors provides factory methods to create {@link Cursor}s. + */ +public class Cursors { + + private Cursors() { + } + + /** + * Creates a {@link Cursor} over paths. + * + * @param paths the paths to iterate over. + * @return the Cursor. + */ + public static Cursor newPathCursor(Iterable paths) { + return new PathCursor(paths); + } + + /** + * Returns a traversing cursor based on the path restriction in the given + * {@link Filter}. + * + * @param statement the query statement. This string is only used for + * logging purposes. + * @param filter the filter. + * @param root the root {@link NodeState}. + * @return the {@link Cursor}. + */ + public static Cursor newTraversingCursor(String statement, + Filter filter, + NodeState root) { + return new TraversingCursor(statement, filter, root); + } + + /** + * PathCursor implements a simple {@link Cursor} that iterates + * over a {@link String} based path {@link Iterable}. + */ + private static class PathCursor implements Cursor { + + private final Iterator iterator; + + private String path; + + public PathCursor(Iterable paths) { + this.iterator = paths.iterator(); + } + + @Override + public boolean next() { + if (iterator.hasNext()) { + path = iterator.next(); + return true; + } else { + path = null; + return false; + } + } + + @Override + public IndexRow currentRow() { + // TODO support jcr:score and possibly rep:exceprt + return new IndexRowImpl(isAbsolute(path) ? path : "/" + path); + } + } + + /** + * A cursor that reads all nodes in a given subtree. + */ + public static class TraversingCursor implements Cursor { + + private static final Logger LOG = LoggerFactory.getLogger(TraversingIndex.class); + + private final String statement; + + private final Filter filter; + + private final Deque> nodeIterators = + Queues.newArrayDeque(); + + private String parentPath; + + private String currentPath; + + private long readCount; + + public TraversingCursor(String statement, Filter filter, NodeState root) { + this.statement = statement; + this.filter = filter; + + String path = filter.getPath(); + parentPath = null; + currentPath = "/"; + NodeState parent = null; + NodeState node = root; + if (!path.equals("/")) { + for (String name : path.substring(1).split("/")) { + parentPath = currentPath; + currentPath = PathUtils.concat(parentPath, name); + + parent = node; + node = parent.getChildNode(name); + + if (node == null) { + // nothing can match this filter, leave nodes empty + return; + } + } + } + Filter.PathRestriction restriciton = filter.getPathRestriction(); + switch (restriciton) { + case EXACT: + case ALL_CHILDREN: + nodeIterators.add(Iterators.singletonIterator( + new MemoryChildNodeEntry(currentPath, node))); + parentPath = ""; + break; + case PARENT: + if (parent != null) { + nodeIterators.add(Iterators.singletonIterator( + new MemoryChildNodeEntry(parentPath, parent))); + parentPath = ""; + } + break; + case DIRECT_CHILDREN: + nodeIterators.add(node.getChildNodeEntries().iterator()); + parentPath = currentPath; + break; + default: + throw new IllegalArgumentException("Unknown restriction: " + restriciton); + } + } + + @Override + public IndexRow currentRow() { + return new IndexRowImpl(currentPath); + } + + @Override + public boolean next() { + while (!nodeIterators.isEmpty()) { + Iterator iterator = nodeIterators.getLast(); + if (iterator.hasNext()) { + ChildNodeEntry entry = iterator.next(); + + readCount++; + if (readCount % 100 == 0) { + LOG.warn("Traversed " + readCount + " nodes with filter " + filter + " for query " + statement + "; consider creating an index or changing the query"); + } + + NodeState node = entry.getNodeState(); + + String name = entry.getName(); + if (NodeStateUtils.isHidden(name)) { + continue; + } + currentPath = PathUtils.concat(parentPath, name); + + if (filter.getPathRestriction() == ALL_CHILDREN) { + nodeIterators.addLast(node.getChildNodeEntries().iterator()); + parentPath = currentPath; + } + return true; + } else { + nodeIterators.removeLast(); + parentPath = PathUtils.getParentPath(parentPath); + } + } + currentPath = null; + return false; + } + + } +} Property changes on: oak-core\src\main\java\org\apache\jackrabbit\oak\spi\query\Cursors.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision Rev URL Added: svn:eol-style + native Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java (revision 1405804) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java (working copy) @@ -20,6 +20,7 @@ import java.util.Collection; +import javax.annotation.CheckForNull; import javax.jcr.PropertyType; import org.apache.jackrabbit.oak.api.PropertyValue; @@ -65,6 +66,10 @@ */ String getPath(); + /** + * @return the node type restriction or null if none is set. + */ + @CheckForNull String getNodeType(); /** Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/PropertyValues.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/PropertyValues.java (revision 1405804) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/PropertyValues.java (working copy) @@ -36,6 +36,7 @@ import org.apache.jackrabbit.oak.plugins.memory.DoublePropertyState; import org.apache.jackrabbit.oak.plugins.memory.GenericPropertyState; import org.apache.jackrabbit.oak.plugins.memory.LongPropertyState; +import org.apache.jackrabbit.oak.plugins.memory.MultiGenericPropertyState; import org.apache.jackrabbit.oak.plugins.memory.MultiStringPropertyState; import org.apache.jackrabbit.oak.plugins.memory.StringPropertyState; @@ -107,6 +108,11 @@ } @Nonnull + public static PropertyValue newName(Iterable value) { + return new PropertyStateValue(MultiGenericPropertyState.nameProperty("", value)); + } + + @Nonnull public static PropertyValue newPath(String value) { return new PropertyStateValue(GenericPropertyState.pathProperty("", value)); } Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java (revision 1405804) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java (working copy) @@ -29,6 +29,7 @@ import org.apache.jackrabbit.oak.plugins.index.IndexHookManager; import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexHookProvider; import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexProvider; +import org.apache.jackrabbit.oak.plugins.index.nodetype.NodeTypeIndexProvider; import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexHookProvider; import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider; import org.apache.jackrabbit.oak.plugins.name.NameValidatorProvider; @@ -79,6 +80,7 @@ with(new AnnotatingConflictHandler()); with(new PropertyIndexProvider()); + with(new NodeTypeIndexProvider()); with(new LuceneIndexProvider()); } Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java =================================================================== --- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java (revision 1405804) +++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java (working copy) @@ -21,7 +21,13 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertFalse; + +import java.io.ByteArrayInputStream; +import java.util.Arrays; +import java.util.HashSet; import java.util.NoSuchElementException; +import java.util.Set; + import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; @@ -32,6 +38,9 @@ import javax.jcr.query.QueryResult; import javax.jcr.query.Row; import javax.jcr.query.RowIterator; + +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.jackrabbit.commons.iterator.RowIterable; import org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest; import org.junit.Test; @@ -196,4 +205,24 @@ } } + @Test + public void nodeTypeConstraint() throws Exception { + Session session = getAdminSession(); + Node root = session.getRootNode(); + Node folder1 = root.addNode("folder1", "nt:folder"); + Node folder2 = root.addNode("folder2", "nt:folder"); + JcrUtils.putFile(folder1, "file", "text/plain", + new ByteArrayInputStream("foo bar".getBytes("UTF-8"))); + folder2.addNode("folder3", "nt:folder"); + session.save(); + + QueryManager qm = session.getWorkspace().getQueryManager(); + Query q = qm.createQuery("//element(*, nt:folder)", Query.XPATH); + Set paths = new HashSet(); + for (Row r : new RowIterable(q.execute().getRows())) { + paths.add(r.getPath()); + } + assertEquals(new HashSet(Arrays.asList("/folder1", "/folder2", "/folder2/folder3")), + paths); + } }