Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndex.java (revision 1583585) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndex.java (working copy) @@ -16,15 +16,16 @@ */ package org.apache.jackrabbit.oak.plugins.index.aggregate; + import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.apache.jackrabbit.oak.api.PropertyValue; -import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.query.fulltext.FullTextAnd; import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression; import org.apache.jackrabbit.oak.query.fulltext.FullTextOr; @@ -32,14 +33,19 @@ import org.apache.jackrabbit.oak.query.fulltext.FullTextVisitor; import org.apache.jackrabbit.oak.query.index.FilterImpl; import org.apache.jackrabbit.oak.spi.query.Cursor; +import org.apache.jackrabbit.oak.spi.query.Cursors; import org.apache.jackrabbit.oak.spi.query.Cursors.AbstractCursor; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.query.IndexRow; import org.apache.jackrabbit.oak.spi.query.QueryIndex.FulltextQueryIndex; import org.apache.jackrabbit.oak.spi.state.NodeState; +import com.google.common.base.Function; import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; /** * A virtual full-text that can aggregate nodes based on aggregate definitions. @@ -58,6 +64,10 @@ if (baseIndex == null) { return Double.POSITIVE_INFINITY; } + FullTextExpression e = filter.getFullTextConstraint(); + if (e != null && hasCompositeExpression(e)) { + return flattenCost(e, filter, baseIndex, rootState); + } return baseIndex.getCost(filter, rootState) - 0.05; } @@ -67,70 +77,203 @@ if (baseIndex.getNodeAggregator() == null) { return baseIndex.query(filter, rootState); } - return new AggregationCursor(baseIndex.query( - newAggregationFilter(filter), rootState), - baseIndex.getNodeAggregator(), rootState); + return newCursor(filter, baseIndex, rootState); } - private static Filter newAggregationFilter(Filter filter) { - FilterImpl f = new FilterImpl(filter); - // disables node type checks for now - f.setMatchesAllTypes(true); + private static Cursor newCursor(Filter f, FulltextQueryIndex index, + NodeState state) { + FullTextExpression e = f.getFullTextConstraint(); + if (hasCompositeExpression(e)) { + Cursor c = flatten(e, f, index, state); + if (c != null) { + return c; + } + } + return new AggregationCursor(index.query(newAggregationFilter(f, null), + state), index.getNodeAggregator(), state); + } - // TODO OAK-828 - // FullTextExpression constraint = filter.getFullTextConstraint(); - // constraint = getFlatConstraint(constraint); - // f.setFullTextConstraint(constraint); + private static boolean hasCompositeExpression(FullTextExpression ft) { + if (ft == null) { + return false; + } + final AtomicReference composite = new AtomicReference(); + composite.set(false); - return f; + ft.accept(new FullTextVisitor() { + + @Override + public boolean visit(FullTextTerm term) { + return true; + } + + @Override + public boolean visit(FullTextAnd and) { + composite.set(true); + return true; + } + + @Override + public boolean visit(FullTextOr or) { + composite.set(true); + return true; + } + }); + return composite.get() && !hasNegativeContains(ft); + } + + private static boolean hasNegativeContains(FullTextExpression ft) { + if (ft == null) { + return false; + } + final AtomicReference hasNegative = new AtomicReference(); + hasNegative.set(false); + + ft.accept(new FullTextVisitor.FullTextVisitorBase() { + + @Override + public boolean visit(FullTextTerm term) { + if (term.isNot()) { + hasNegative.set(true); + } + return true; + } + + }); + return hasNegative.get(); } - static FullTextExpression getFlatConstraint( - FullTextExpression constraint) { + private static Cursor flatten(FullTextExpression constraint, + final Filter filter, final FulltextQueryIndex index, + final NodeState state) { if (constraint == null) { return null; } - final AtomicReference result = new AtomicReference(); + final AtomicReference result = new AtomicReference(); constraint.accept(new FullTextVisitor() { - + @Override public boolean visit(FullTextTerm term) { - String p = term.getPropertyName(); - if (p != null) { - if (PathUtils.getDepth(p) > 1) { - // remove indirection - String name = PathUtils.getName(p); - term = new FullTextTerm(name, term); - } + result.set(filterToCursor(newAggregationFilter(filter, term), + index, state)); + return true; + } + + @Override + public boolean visit(FullTextAnd and) { + List> l = new ArrayList>(); + for (FullTextExpression input : and.list) { + Cursor c = flatten(input, filter, index, state); + Set s = cursorToSet(c); + l.add(s); + } + + Iterator> iterator = l.iterator(); + Set results = iterator.next(); + while (iterator.hasNext()) { + results = Sets.intersection(results, iterator.next()); } - result.set(term); + result.set(Cursors.newPathCursorDistinct(results, + filter.getQueryEngineSettings())); + return true; + } + + @Override + public boolean visit(FullTextOr or) { + List> l = Lists.transform(or.list, + new Function>() { + @Override + public Iterable apply( + FullTextExpression input) { + Cursor c = flatten(input, filter, index, state); + return cursorToIterable(c); + } + }); + Iterable results = Iterables.concat(l); + result.set(Cursors.newPathCursorDistinct(results, + filter.getQueryEngineSettings())); + return true; + } + }); + return result.get(); + } + + private static double flattenCost(FullTextExpression constraint, + final Filter filter, final FulltextQueryIndex index, + final NodeState state) { + if (constraint == null) { + return 10000; + } + final AtomicReference result = new AtomicReference(); + result.set(0d); + constraint.accept(new FullTextVisitor() { + + @Override + public boolean visit(FullTextTerm term) { + result.set(result.get() + index.getCost(newAggregationFilter(filter, term), state)); return true; } @Override public boolean visit(FullTextAnd and) { - ArrayList list = new ArrayList(); - for (FullTextExpression e : and.list) { - list.add(getFlatConstraint(e)); + for (FullTextExpression input : and.list) { + double d = flattenCost(input, filter, index, state); + result.set(result.get() + d); } - result.set(new FullTextAnd(list)); return true; } @Override public boolean visit(FullTextOr or) { - ArrayList list = new ArrayList(); - for (FullTextExpression e : or.list) { - list.add(getFlatConstraint(e)); + for (FullTextExpression input : or.list) { + double d = flattenCost(input, filter, index, state); + result.set(result.get() + d); } - result.set(new FullTextOr(list)); return true; } - }); return result.get(); } + private static Iterable cursorToIterable(final Cursor c) { + return new Iterable() { + @Override + public Iterator iterator() { + return Iterators.transform(c, new Function() { + @Override + public String apply(IndexRow input) { + return input.getPath(); + } + }); + } + }; + } + + private static Set cursorToSet(Cursor c) { + Set rows = Sets.newHashSet(); + while (c.hasNext()) { + IndexRow r = c.next(); + rows.add(r.getPath()); + } + return rows; + } + + private static Cursor filterToCursor(Filter f, FulltextQueryIndex index, + NodeState state) { + return new AggregationCursor(index.query(f, state), + index.getNodeAggregator(), state); + } + + private static Filter newAggregationFilter(Filter filter, FullTextExpression exp) { + FilterImpl f = new FilterImpl(filter); + // disables node type checks for now + f.setMatchesAllTypes(true); + if (exp != null) { + f.setFullTextConstraint(exp); + } + return f; + } + @Override public String getPlan(Filter filter, NodeState rootState) { if (baseIndex == null) { Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest.java =================================================================== --- oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest.java (revision 1583585) +++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest.java (working copy) @@ -34,11 +34,12 @@ import org.apache.jackrabbit.oak.plugins.index.aggregate.AggregateIndexProvider; import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator; import org.apache.jackrabbit.oak.plugins.index.aggregate.SimpleNodeAggregator; + import static org.apache.jackrabbit.oak.plugins.memory.BinaryPropertyState.binaryProperty; + import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent; import org.apache.jackrabbit.oak.query.AbstractQueryTest; import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider; -import org.junit.Ignore; import org.junit.Test; import com.google.common.collect.ImmutableList; @@ -361,7 +362,6 @@ } @Test - @Ignore("OAK-828") public void testDifferentNodes() throws Exception { Tree folder = root.getTree("/").addChild("myFolder");