You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by da...@apache.org on 2015/10/21 17:19:16 UTC

svn commit: r1709863 [1/2] - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ oak-core...

Author: davide
Date: Wed Oct 21 15:19:15 2015
New Revision: 1709863

URL: http://svn.apache.org/viewvc?rev=1709863&view=rev
Log:
OAK-1617 - Automatically convert "or" queries to "union" for SQL-2

- implemented functionality
- to enable -Doak.query.sql2optimisation
- disabled by default

Added:
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/MultiPropertyOrTestOptimisation.java
      - copied, changed from r1709862, jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTestSQL2Optimisations.java
      - copied, changed from r1709862, jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryCostOverheadTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/SQL2OptimiseQueryTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/ast/AndImplTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/ast/OrImplTest.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTestSQL2Optimisation.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineSettings.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElement.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ColumnImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ComparisonImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeJoinConditionImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/InImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NativeFunctionImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyExistenceImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SimilarImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SpellcheckImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SuggestImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java
    jackrabbit/oak/trunk/oak-solr-core/pom.xml

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java Wed Oct 21 15:19:15 2015
@@ -16,7 +16,11 @@ package org.apache.jackrabbit.oak.query;
 import java.util.Iterator;
 import java.util.List;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import aQute.bnd.annotation.ProviderType;
+
 import org.apache.jackrabbit.oak.api.PropertyValue;
 import org.apache.jackrabbit.oak.api.Result;
 import org.apache.jackrabbit.oak.api.Tree;
@@ -125,4 +129,69 @@ public interface Query {
      * @return if sorted by index
      */
     boolean isSortedByIndex();
+    
+    /**
+     * Perform optimisation on the object itself. To avoid any potential error due to state
+     * variables perfom the optimisation before the {@link #init()}.
+     * 
+     * @return {@code this} if no optimisations are possible or a new instance of a {@link Query}.
+     *         Cannot return null.
+     */
+    @Nonnull
+    Query optimise();
+    
+    /**
+     * <p>
+     * returns a clone of the current object. Will throw an exception in case it's invoked in a non
+     * appropriate moment. For example the default {@link QueryImpl} cannot be cloned once the
+     * {@link #init()} has been executed.
+     * </p>
+     * 
+     * <p>
+     * <strong>May return null if not implemented.</strong>
+     * </p>
+     * @return a clone of self
+     * @throws IllegalStateException
+     */
+    @Nullable
+    Query copyOf() throws IllegalStateException;
+    
+    /**
+     * @return {@code true} if the query has been already initialised. {@code false} otherwise.
+     */
+    boolean isInit();
+    
+    /**
+     * @return {@code true} if the query is a result of optimisations. {@code false} if it's the
+     *         originally computed one.
+     */
+    boolean isOptimised();
+    
+    /**
+     * @return the original statement as it was used to construct the object. If not provided the
+     *         {@link #toString()} will be used instead.
+     */
+    String getStatement();
+    
+    /**
+     * 
+     * @return {@code true} if the current query is internal. {@code false} otherwise.
+     */
+    boolean isInternal();
+
+    /**
+     * <p>
+     * Some queries can bring with them a cost overhead that the query engine could consider when
+     * electing the best query between the original SQL2 and the possible available optimisations.
+     * </p>
+     * <p>
+     * For example for the case of <a href="https://issues.apache.org/jira/browse/OAK-2660" /> if
+     * you have a case where {@code (a = 'v' OR CONTAINS(b, 'v1') OR CONTAINS(c, 'v2')) AND (...)}
+     * currently the query engine does not know how to leverage indexes and post conditions and the
+     * query is better suited with a UNION.
+     * </p>
+     * 
+     * @return a positive number or 0. <strong>Cannot be negative.</strong>
+     */
+    double getCostOverhead();
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java Wed Oct 21 15:19:15 2015
@@ -16,7 +16,9 @@
  */
 package org.apache.jackrabbit.oak.query;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.ImmutableSet.of;
+import static com.google.common.collect.Sets.newHashSet;
 import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM;
 import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.JCR_NODE_TYPES;
 
@@ -27,6 +29,8 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import javax.annotation.Nonnull;
+
 import org.apache.jackrabbit.oak.api.PropertyValue;
 import org.apache.jackrabbit.oak.api.QueryEngine;
 import org.apache.jackrabbit.oak.api.Result;
@@ -43,6 +47,27 @@ import org.slf4j.MDC;
  * The query engine implementation.
  */
 public abstract class QueryEngineImpl implements QueryEngine {
+    
+    /**
+     * used to instruct the {@link QueryEngineImpl} on how to act with respect of the SQL2
+     * optimisation.
+     */
+    public static enum ForceOptimised {
+        /**
+         * will force the original SQL2 query to be executed
+         */
+        ORIGINAL, 
+        
+        /**
+         * will force the computed optimised query to be executed. If available.
+         */
+        OPTIMISED, 
+        
+        /**
+         * will execute the cheapest.
+         */
+        CHEAPEST
+    }
 
     private static final AtomicInteger ID_COUNTER = new AtomicInteger();
     private static final String MDC_QUERY_ID = "oak.query.id";
@@ -68,6 +93,12 @@ public abstract class QueryEngineImpl im
      * disabled for testing purposes.
      */
     private boolean traversalEnabled = true;
+    
+    /**
+     * Whether the query engine should be forced to use the optimised version of the query if
+     * available.
+     */
+    private ForceOptimised forceOptimised = ForceOptimised.CHEAPEST;
 
     /**
      * Get the execution context for a single query execution.
@@ -94,11 +125,12 @@ public abstract class QueryEngineImpl im
     public List<String> getBindVariableNames(
             String statement, String language, Map<String, String> mappings)
             throws ParseException {
-        Query q = parseQuery(statement, language, getExecutionContext(), mappings);
-        return q.getBindVariableNames();
+        Set<Query> qs = parseQuery(statement, language, getExecutionContext(), mappings);
+        
+        return qs.iterator().next().getBindVariableNames();
     }
 
-    private static Query parseQuery(
+    private static Set<Query> parseQuery(
             String statement, String language, ExecutionContext context,
             Map<String, String> mappings) throws ParseException {
         
@@ -123,11 +155,16 @@ public abstract class QueryEngineImpl im
             parser.setAllowNumberLiterals(false);
             parser.setAllowTextLiterals(false);
         }
+        
+        Set<Query> queries = newHashSet();
+        
+        Query q;
+        
         if (SQL2.equals(language) || JQOM.equals(language)) {
-            return parser.parse(statement);
+            q = parser.parse(statement, false);
         } else if (SQL.equals(language)) {
             parser.setSupportSQL1(true);
-            return parser.parse(statement);
+            q = parser.parse(statement, false);
         } else if (XPATH.equals(language)) {
             XPathToSQL2Converter converter = new XPathToSQL2Converter();
             String sql2 = converter.convert(statement);
@@ -135,7 +172,7 @@ public abstract class QueryEngineImpl im
             try {
                 // OAK-874: No artificial XPath selector name in wildcards
                 parser.setIncludeSelectorNameInWildcardColumns(false);
-                return parser.parse(sql2);
+                q = parser.parse(sql2, false);
             } catch (ParseException e) {
                 ParseException e2 = new ParseException(
                         statement + " converted to SQL-2 " + e.getMessage(), 0);
@@ -145,6 +182,34 @@ public abstract class QueryEngineImpl im
         } else {
             throw new ParseException("Unsupported language: " + language, 0);
         }
+        
+        queries.add(q);
+        
+        if (settings.isSql2Optimisation()) {
+            if (q.isInternal()) {
+                LOG.trace("Skipping optimisation as internal query.");
+            } else {
+                LOG.trace("Attempting optimisation");
+                Query q2 = q.optimise();
+                if (q2 != q) {
+                    LOG.debug("Optimised query available. {}", q2);
+                    queries.add(q2);
+                }
+            }
+        }
+        
+        // initialising all the queries.
+        for (Query query : queries) {
+            try {
+                query.init();
+            } catch (Exception e) {
+                ParseException e2 = new ParseException(query.getStatement() + ": " + e.getMessage(), 0);
+                e2.initCause(e);
+                throw e2;
+            }
+        }
+
+        return queries;
     }
     
     @Override
@@ -176,21 +241,25 @@ public abstract class QueryEngineImpl im
         }
 
         ExecutionContext context = getExecutionContext();
-        Query q = parseQuery(statement, language, context, mappings);
-        q.setExecutionContext(context);
-        q.setLimit(limit);
-        q.setOffset(offset);
-        if (bindings != null) {
-            for (Entry<String, ? extends PropertyValue> e : bindings.entrySet()) {
-                q.bindValue(e.getKey(), e.getValue());
+        Set<Query> queries = parseQuery(statement, language, context, mappings);
+        
+        for (Query q : queries) {
+            q.setExecutionContext(context);
+            q.setLimit(limit);
+            q.setOffset(offset);
+            if (bindings != null) {
+                for (Entry<String, ? extends PropertyValue> e : bindings.entrySet()) {
+                    q.bindValue(e.getKey(), e.getValue());
+                }
             }
+            q.setTraversalEnabled(traversalEnabled);            
         }
-        q.setTraversalEnabled(traversalEnabled);
 
         boolean mdc = false;
         try {
-            mdc = setupMDC(q);
-            q.prepare();
+            MdcAndPrepared map = prepareAndGetCheapest(queries); 
+            mdc = map.mdc;
+            Query q = map.query;
             return q.executeQuery();
         } finally {
             if (mdc) {
@@ -199,6 +268,133 @@ public abstract class QueryEngineImpl im
         }
     }
 
+    /**
+     * POJO class used to return the cheapest prepared query from the set and related MDC status
+     */
+    private static class MdcAndPrepared {
+        private final boolean mdc;
+        private final Query query;
+        
+        public MdcAndPrepared(final boolean mdc, @Nonnull final Query q) {
+            this.mdc = mdc;
+            this.query = checkNotNull(q);
+        }
+    }
+    
+    /**
+     * will prepare all the available queries and by based on the {@link ForceOptimised} flag return
+     * the appropriate.
+     * 
+     * @param queries the list of queries to be executed. cannot be null
+     * @return
+     */
+    @Nonnull
+    private MdcAndPrepared prepareAndGetCheapest(@Nonnull final Set<Query> queries) {
+        MdcAndPrepared map = null;
+        Query cheapest = null;
+        
+        
+        if (checkNotNull(queries).size() == 1) {
+            // Optimisation. We only have the original query so we prepare and return it.
+            cheapest = queries.iterator().next();
+            cheapest.prepare();
+            LOG.debug("No optimisations found. Cheapest is the original query: {}", cheapest);
+            map = new MdcAndPrepared(setupMDC(cheapest), cheapest);
+        } else {
+            double bestCost = Double.MAX_VALUE;
+            double originalCost = Double.MAX_VALUE;
+            boolean firstLoop = true;
+            Query original = null;
+            
+            // always prepare all of the queries and compute the cheapest as it's the default behaviour.
+            // It should trigger more errors during unit and integration testing. Changing
+            // `forceOptimised` flag should be in case used only during testing.
+            for (Query q : checkNotNull(queries)) {
+                LOG.debug("Preparing: {}", q);
+                q.prepare();
+                
+                double actualCost = q.getEstimatedCost();
+                double costOverhead = q.getCostOverhead();
+                double overallCost = Math.min(actualCost + costOverhead, Double.MAX_VALUE);
+                
+                LOG.debug("actualCost: {} - costOverhead: {} - overallCost: {}", actualCost,
+                    costOverhead, overallCost);
+                
+                if (firstLoop) {
+                    // first time we're always the best cost. Avoiding situations where the original
+                    // query has an overall cost as Double.MAX_VALUE.
+                    bestCost = overallCost;
+                    cheapest = q;
+                    firstLoop = false;
+                } else if (overallCost < bestCost) {
+                    bestCost = overallCost;
+                    cheapest = q;
+                }
+                if (!q.isOptimised()) {
+                    original = q;
+                    originalCost = overallCost;
+                }
+            }
+            
+            if (original != null && bestCost == originalCost && cheapest != original) {
+                // if the optimised cost is the same as the original SQL2 query we prefer the original. As
+                // we deal with references the `cheapest!=original` should work.
+                LOG.trace("Same cost for original and optimised. Forcing original");
+                cheapest = original;
+            }
+
+            switch (forceOptimised) {
+            case ORIGINAL:
+                LOG.debug("Forcing the original SQL2 query to be executed by flag");
+                for (Query q  : checkNotNull(queries)) {
+                    if (!q.isOptimised()) {
+                        map = new MdcAndPrepared(setupMDC(q), q);
+                    }
+                }
+                break;
+
+            case OPTIMISED:
+                LOG.debug("Forcing the optimised SQL2 query to be executed by flag");
+                for (Query q  : checkNotNull(queries)) {
+                    if (q.isOptimised()) {
+                        map = new MdcAndPrepared(setupMDC(q), q);
+                    }
+                }
+                break;
+
+            // CHEAPEST is the default behaviour
+            case CHEAPEST:
+            default:
+                if (cheapest == null) {
+                    // this should not really happen. Defensive coding.
+                    LOG.debug("Cheapest is null. Returning the original SQL2 query.");
+                    for (Query q  : checkNotNull(queries)) {
+                        if (!q.isOptimised()) {
+                            map = new MdcAndPrepared(setupMDC(q), q);
+                        }
+                    }
+                } else {
+                    LOG.debug("Cheapest cost: {} - query: {}", bestCost, cheapest);
+                    map = new MdcAndPrepared(setupMDC(cheapest), cheapest);                
+                }
+            }
+        }
+
+        
+        if (map == null) {
+            // we should only get here in case of testing forcing weird conditions
+            LOG.trace("`MdcAndPrepared` is null. Falling back to the original query");
+            for (Query q  : checkNotNull(queries)) {
+                if (!q.isOptimised()) {
+                    map = new MdcAndPrepared(setupMDC(q), q);
+                    break;
+                }
+            }
+        }
+        
+        return map;
+    }
+    
     protected void setTraversalEnabled(boolean traversalEnabled) {
         this.traversalEnabled = traversalEnabled;
     }
@@ -222,4 +418,13 @@ public abstract class QueryEngineImpl im
         MDC.remove(OAK_QUERY_ANALYZE);
     }
 
+    /**
+     * Instruct the query engine on how to behave with regards to the SQL2 optimised query if
+     * available.
+     * 
+     * @param forceOptimised cannot be null
+     */
+    protected void setForceOptimised(@Nonnull ForceOptimised forceOptimised) {
+        this.forceOptimised = forceOptimised;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineSettings.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineSettings.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineSettings.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineSettings.java Wed Oct 21 15:19:15 2015
@@ -41,6 +41,8 @@ public class QueryEngineSettings impleme
     private boolean fullTextComparisonWithoutIndex = 
             DEFAULT_FULL_TEXT_COMPARISON_WITHOUT_INDEX;
     
+    private boolean sql2Optimisation = Boolean.getBoolean("oak.query.sql2optimisation");
+    
     /**
      * Get the limit on how many nodes a query may read at most into memory, for
      * "order by" and "distinct" queries. If this limit is exceeded, the query
@@ -93,4 +95,7 @@ public class QueryEngineSettings impleme
         return fullTextComparisonWithoutIndex;
     }
     
+    public boolean isSql2Optimisation() {
+        return sql2Optimisation;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java Wed Oct 21 15:19:15 2015
@@ -13,6 +13,10 @@
  */
 package org.apache.jackrabbit.oak.query;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.jackrabbit.oak.query.ast.AstElementFactory.copyElementAndCheckReference;
+
 import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -24,11 +28,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import javax.annotation.Nonnull;
+
+import com.google.common.base.Strings;
 import com.google.common.collect.AbstractIterator;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-
 import com.google.common.collect.Ordering;
+
 import org.apache.jackrabbit.oak.api.PropertyValue;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
@@ -79,6 +86,7 @@ import org.apache.jackrabbit.oak.query.p
 import org.apache.jackrabbit.oak.query.plan.SelectorExecutionPlan;
 import org.apache.jackrabbit.oak.spi.query.Filter;
 import org.apache.jackrabbit.oak.spi.query.PropertyValues;
+import org.apache.jackrabbit.oak.spi.query.QueryConstants;
 import org.apache.jackrabbit.oak.spi.query.QueryIndex;
 import org.apache.jackrabbit.oak.spi.query.QueryIndex.AdvancedQueryIndex;
 import org.apache.jackrabbit.oak.spi.query.QueryIndex.IndexPlan;
@@ -131,7 +139,7 @@ public class QueryImpl implements Query
     };
 
     SourceImpl source;
-    final String statement;
+    private String statement;
     final HashMap<String, PropertyValue> bindVariableMap = new HashMap<String, PropertyValue>();
     final HashMap<String, Integer> selectorIndexes = new HashMap<String, Integer>();
     final ArrayList<SelectorImpl> selectors = new ArrayList<SelectorImpl>();
@@ -160,6 +168,16 @@ public class QueryImpl implements Query
     private long size = -1;
     private boolean prepared;
     private ExecutionContext context;
+    
+    /**
+     * whether the object has been initialised or not
+     */
+    private boolean init;
+    
+    /**
+     * whether the query is a result of optimisation or original one.
+     */
+    private boolean optimised;
 
     private boolean isSortedByIndex;
 
@@ -175,12 +193,19 @@ public class QueryImpl implements Query
 
     QueryImpl(String statement, SourceImpl source, ConstraintImpl constraint,
             ColumnImpl[] columns, NamePathMapper mapper, QueryEngineSettings settings) {
+        this(statement, source, constraint, columns, mapper, settings, false);
+    }
+
+    QueryImpl(String statement, SourceImpl source, ConstraintImpl constraint,
+        ColumnImpl[] columns, NamePathMapper mapper, QueryEngineSettings settings, 
+        final boolean optimised) {
         this.statement = statement;
         this.source = source;
         this.constraint = constraint;
         this.columns = columns;
         this.namePathMapper = mapper;
         this.settings = settings;
+        this.optimised = optimised;
     }
 
     @Override
@@ -416,6 +441,8 @@ public class QueryImpl implements Query
             }
             distinctColumns[i] = distinct;
         }
+        
+        init = true;
     }
 
     @Override
@@ -1138,8 +1165,9 @@ public class QueryImpl implements Query
         return Math.min(limit, source.getSize(precision, max));
     }
 
+    @Override
     public String getStatement() {
-        return statement;
+        return Strings.isNullOrEmpty(statement) ? toString() : statement;
     }
 
     public QueryEngineSettings getSettings() {
@@ -1170,4 +1198,171 @@ public class QueryImpl implements Query
         return sum.min(max).max(min).longValue();
     }
 
+    @Override
+    public Query optimise() {
+        // optimising for UNION
+        Query optimised = this;
+        
+        if (constraint != null) {
+            Set<ConstraintImpl> unionList = constraint.simplifyForUnion();
+            if (unionList.size() > 1) {
+                // there are some cases where multiple ORs simplify into a single one. If we get a
+                // union list of just one we don't really have to UNION anything.
+                QueryImpl left = null;
+                Query right = null;
+                // we have something to do here.
+                for (ConstraintImpl c : unionList) {
+                    if (right != null) {
+                        right = newOptimisedUnionQuery(left, right);
+                    } else {
+                        // pulling left to the right
+                        if (left != null) {
+                            right = left;
+                        }
+                    }
+                    
+                    // cloning original query
+                    left = (QueryImpl) this.copyOf(true);
+                    
+                    // cloning the constraints and assigning to new query
+                    left.constraint = (ConstraintImpl) copyElementAndCheckReference(c);
+                    // re-composing the statement for better debug messages
+                    left.statement = recomposeStatement(left);
+                }
+                
+                optimised = newOptimisedUnionQuery(left, right);
+            }
+        }
+        
+        return optimised;
+    }
+    
+    private static String recomposeStatement(@Nonnull QueryImpl query) {
+        checkNotNull(query);
+        String original = query.getStatement();
+        String origUpper = original.toUpperCase();
+        StringBuilder recomputed = new StringBuilder();
+        final String where = " WHERE ";
+        final String orderBy = " ORDER BY ";
+        int whereOffset = where.length();
+        
+        if (query.getConstraint() == null) {
+            recomputed.append(original);
+        } else {
+            recomputed.append(original.substring(0, origUpper.indexOf(where) + whereOffset));
+            recomputed.append(query.getConstraint());
+            if (origUpper.indexOf(orderBy) > -1) {
+                recomputed.append(original.substring(origUpper.indexOf(orderBy)));
+            }
+        }
+        return recomputed.toString();
+    }
+    
+    /**
+     * convenience method for creating a UnionQueryImpl with proper settings.
+     * 
+     * @param left
+     * @param right
+     * @return
+     */
+    private UnionQueryImpl newOptimisedUnionQuery(@Nonnull Query left, @Nonnull Query right) {
+        UnionQueryImpl u = new UnionQueryImpl(
+            false, 
+            checkNotNull(left, "`left` cannot be null"), 
+            checkNotNull(right, "`right` cannot be null"),
+            this.settings, 
+            true);
+        u.setExplain(explain);
+        return u;
+    }
+    
+    @Override
+    public Query copyOf() throws IllegalStateException {
+        return copyOf(false);
+    }
+    
+    private Query copyOf(final boolean optimised) {
+        if (isInit()) {
+            throw new IllegalStateException("QueryImpl cannot be cloned once initialised.");
+        }
+        
+        List<ColumnImpl> cols = newArrayList();
+        for (ColumnImpl c : columns) {
+            cols.add((ColumnImpl) copyElementAndCheckReference(c));
+        }
+                
+        QueryImpl copy = new QueryImpl(
+            this.statement, 
+            (SourceImpl) copyElementAndCheckReference(this.source),
+            this.constraint,
+            cols.toArray(new ColumnImpl[0]),
+            this.namePathMapper,
+            this.settings,
+            optimised);
+        copy.explain = this.explain;
+        copy.distinct = this.distinct;
+        
+        return copy;        
+    }
+
+    @Override
+    public boolean isInit() {
+        return init;
+    }
+
+    @Override
+    public boolean isOptimised() {
+        return optimised;
+    }
+
+    @Override
+    public boolean isInternal() {
+        return isInternal;
+    }
+
+    @Override
+    public double getCostOverhead() {
+        return oak2660CostOverhead(getConstraint());
+    }
+
+    /**
+     * compute a cost overhead for the OAK-2660 use case. The query engine better perform/compute
+     * the use case `(a = 'v' OR CONTAINS(b, 'v1') OR CONTAINS(c, 'v2') AND (...)` as a UNION query
+     * to leverage different indexes. In this case we return an 'Infinity' overhead for make the
+     * query engine choose a union query instead.
+     * 
+     * @param constraint the constraint to analyse. Cannot be null.
+     * @return
+     */
+    private double oak2660CostOverhead(@Nonnull ConstraintImpl constraint) {
+        if (checkNotNull(constraint) instanceof OrImpl) {
+            boolean fulltext = false, plain = false;
+            for (ConstraintImpl c : constraint.getConstraints()) {
+                if (c instanceof FullTextSearchImpl) {
+                    fulltext = true;
+                } else {
+                    plain = true;
+                }
+                
+                if (fulltext && plain) {
+                    return Double.MAX_VALUE;
+                }
+            }
+        } else {
+            List<ConstraintImpl> cs = constraint.getConstraints();
+            if (cs == null) {
+                return 0;
+            } else {
+                double cost = 0;
+                for (ConstraintImpl c : cs) {
+                    cost += oak2660CostOverhead(c);
+                    if (cost == Double.MAX_VALUE) {
+                        return cost;
+                    }
+                }
+                return cost;
+            }
+        }
+        return 0;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java Wed Oct 21 15:19:15 2015
@@ -125,10 +125,11 @@ public class SQL2Parser {
      * Parse the statement and return the query.
      *
      * @param query the query string
+     * @param initialise if performing the query init ({@code true}) or not ({@code false})
      * @return the query
      * @throws ParseException if parsing fails
      */
-    public Query parse(String query) throws ParseException {
+    public Query parse(final String query, final boolean initialise) throws ParseException {
         // TODO possibly support union,... as available at
         // http://docs.jboss.org/modeshape/latest/manuals/reference/html/jcr-query-and-search.html
 
@@ -164,17 +165,32 @@ public class SQL2Parser {
         q.setOrderings(orderings);
         q.setExplain(explain);
         q.setMeasure(measure);
-        try {
-            q.init();
-        } catch (Exception e) {
-            ParseException e2 = new ParseException(query + ": " + e.getMessage(), 0);
-            e2.initCause(e);
-            throw e2;
-        }
         q.setInternal(isInternal(query));
+
+        if (initialise) {
+            try {
+                q.init();
+            } catch (Exception e) {
+                ParseException e2 = new ParseException(statement + ": " + e.getMessage(), 0);
+                e2.initCause(e);
+                throw e2;
+            }
+        }
+
         return q;
     }
     
+    /**
+     * as {@link #parse(String, boolean)} by providing {@code true} to the initialisation flag.
+     * 
+     * @param query
+     * @return
+     * @throws ParseException
+     */
+    public Query parse(final String query) throws ParseException {
+        return parse(query, true);
+    }
+    
     private QueryImpl parseSelect() throws ParseException {
         read("SELECT");
         boolean distinct = readIf("DISTINCT");

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java Wed Oct 21 15:19:15 2015
@@ -56,11 +56,22 @@ public class UnionQueryImpl implements Q
     private final QueryEngineSettings settings;
     private boolean isInternal;
     
+    /**
+     * whether the query is a result of optimisation or not
+     */
+    private boolean optimised;
+    
     UnionQueryImpl(boolean unionAll, Query left, Query right, QueryEngineSettings settings) {
+        this(unionAll, left, right, settings, false);
+    }
+
+    UnionQueryImpl(final boolean unionAll, final Query left, final Query right,
+                   final QueryEngineSettings settings, final boolean optimised) {
         this.unionAll = unionAll;
         this.left = left;
         this.right = right;
         this.settings = settings;
+        this.optimised = optimised;
     }
 
     @Override
@@ -355,4 +366,40 @@ public class UnionQueryImpl implements Q
     public boolean isSortedByIndex() {
         return left.isSortedByIndex() && right.isSortedByIndex();
     }
+
+    @Override
+    public Query optimise() {
+        return this;
+    }
+
+    @Override
+    public Query copyOf() throws IllegalStateException {
+        return null;
+    }
+
+    @Override
+    public boolean isInit() {
+        return left.isInit() || right.isInit();
+    }
+
+    @Override
+    public boolean isOptimised() {
+        return optimised;
+    }
+
+    @Override
+    public String getStatement() {
+        return toString();
+    }
+
+    @Override
+    public boolean isInternal() {
+        return left.isInternal() || right.isInternal();
+    }
+
+    @Override
+    public double getCostOverhead() {
+        // for now we don't really have any case where a union query should suffer from overheads.
+        return 0;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AndImpl.java Wed Oct 21 15:19:15 2015
@@ -22,7 +22,9 @@ import static com.google.common.base.Pre
 import static com.google.common.collect.Lists.newArrayList;
 import static com.google.common.collect.Sets.newHashSet;
 import static com.google.common.collect.Sets.newLinkedHashSet;
+import static org.apache.jackrabbit.oak.query.ast.AstElementFactory.copyElementAndCheckReference;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -32,6 +34,8 @@ import org.apache.jackrabbit.oak.query.f
 import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
 
+import com.google.common.collect.Sets;
+
 /**
  * An AND condition.
  */
@@ -48,6 +52,7 @@ public class AndImpl extends ConstraintI
         this(Arrays.asList(constraint1, constraint2));
     }
 
+    @Override
     public List<ConstraintImpl> getConstraints() {
         return constraints;
     }
@@ -205,4 +210,43 @@ public class AndImpl extends ConstraintI
         return constraints.hashCode();
     }
 
+    @Override
+    public AstElement copyOf() {
+        List<ConstraintImpl> clone = new ArrayList<ConstraintImpl>(constraints.size());
+        for (ConstraintImpl c : constraints) {
+            clone.add((ConstraintImpl) copyElementAndCheckReference(c));
+        }
+        return new AndImpl(clone);
+    }
+
+    @Override
+    public Set<ConstraintImpl> simplifyForUnion() {
+        Set<ConstraintImpl> union = Sets.newHashSet();
+        Set<ConstraintImpl> result = Sets.newHashSet();
+        Set<ConstraintImpl> nonUnion = Sets.newHashSet();
+        
+        for (ConstraintImpl c : getConstraints()) {
+            Set<ConstraintImpl> ccc = c.simplifyForUnion();
+            if (ccc.isEmpty()) {
+                nonUnion.add(c);
+            } else {
+                union.addAll(ccc);
+            }
+        }
+        if (!union.isEmpty() && nonUnion.size() == 1) {
+            // this is the simplest case where, for example, out of the two AND operands at least
+            // one is a non-union. For example WHERE (a OR b OR c) AND d
+            ConstraintImpl right = nonUnion.iterator().next();
+            for (ConstraintImpl c : union) {
+                result.add(new AndImpl(c, right));
+            }
+        } else {
+            // in this case prefer to be conservative and don't optimise. This could happen when for
+            // example: WHERE (a OR b) AND (c OR d).
+            // This should be translated into a AND c, a AND d, b AND c, b AND d.
+        }
+        
+        return result;
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElement.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElement.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElement.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElement.java Wed Oct 21 15:19:15 2015
@@ -18,6 +18,8 @@
  */
 package org.apache.jackrabbit.oak.query.ast;
 
+import javax.annotation.Nonnull;
+
 import org.apache.jackrabbit.oak.api.PropertyValue;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.query.QueryImpl;
@@ -27,7 +29,6 @@ import org.apache.jackrabbit.oak.spi.que
  * The base class for all abstract syntax tree nodes.
  */
 abstract class AstElement {
-
     protected QueryImpl query;
 
     abstract boolean accept(AstVisitor v);
@@ -147,5 +148,13 @@ abstract class AstElement {
         return path;
     }
 
+    /**
+     * @return a clone of self. Default implementation in {@link AstElement} returns same reference
+     *         to {@code this}.
+     */
+    @Nonnull
+    public AstElement copyOf() {
+        return this;
+    }    
 }
 

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java Wed Oct 21 15:19:15 2015
@@ -13,15 +13,22 @@
  */
 package org.apache.jackrabbit.oak.query.ast;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.util.ArrayList;
 
+import javax.annotation.Nonnull;
+
 import org.apache.jackrabbit.oak.api.PropertyValue;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A factory for syntax tree elements.
  */
 public class AstElementFactory {
+    private static final Logger LOG = LoggerFactory.getLogger(AstElementFactory.class);
 
     public AndImpl and(ConstraintImpl constraint1, ConstraintImpl constraint2) {
         return new AndImpl(constraint1, constraint2);
@@ -164,4 +171,28 @@ public class AstElementFactory {
     public ConstraintImpl suggest(String selectorName, StaticOperandImpl expression) {
         return new SuggestImpl(selectorName, expression);
     }
+    
+    /**
+     * <p>
+     * as the {@link AstElement#copyOf()} can return {@code this} is the cloning is not implemented
+     * by the subclass, this method add some spice around it by checking for this case and tracking
+     * a DEBUG message in the logs.
+     * </p>
+     * 
+     * @param e the element to be cloned. Cannot be null.
+     * @return same as {@link AstElement#copyOf()}
+     */
+    @Nonnull
+    public static AstElement copyElementAndCheckReference(@Nonnull final AstElement e) {
+        AstElement clone = checkNotNull(e).copyOf();
+        
+        if (clone == e && LOG.isDebugEnabled()) {
+            LOG.debug(
+                "Failed to clone the AstElement. Returning same reference; the client may fail. {} - {}",
+                e.getClass().getName(), e);
+        }
+        
+        return clone;
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeImpl.java Wed Oct 21 15:19:15 2015
@@ -95,4 +95,8 @@ public class ChildNodeImpl extends Const
         }
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new ChildNodeImpl(selectorName, parentPath);
+    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ChildNodeJoinConditionImpl.java Wed Oct 21 15:19:15 2015
@@ -103,4 +103,8 @@ public class ChildNodeJoinConditionImpl
         return available.contains(childSelector) && available.contains(parentSelector);
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new ChildNodeJoinConditionImpl(childSelectorName, parentSelectorName);
+    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ColumnImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ColumnImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ColumnImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ColumnImpl.java Wed Oct 21 15:19:15 2015
@@ -67,4 +67,8 @@ public class ColumnImpl extends AstEleme
         return selector;
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new ColumnImpl(selectorName, propertyName, columnName);
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ComparisonImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ComparisonImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ComparisonImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ComparisonImpl.java Wed Oct 21 15:19:15 2015
@@ -30,12 +30,15 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.query.fulltext.LikePattern;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
 import org.apache.jackrabbit.oak.spi.query.PropertyValues;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A comparison operation (including "like").
  */
 public class ComparisonImpl extends ConstraintImpl {
-
+    private static final Logger LOG = LoggerFactory.getLogger(ComparisonImpl.class);
+    
     private final DynamicOperandImpl operand1;
     private final Operator operator;
     private final StaticOperandImpl operand2;
@@ -195,4 +198,8 @@ public class ComparisonImpl extends Cons
         }
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new ComparisonImpl(operand1.createCopy(), operator, operand2);
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/ConstraintImpl.java Wed Oct 21 15:19:15 2015
@@ -16,8 +16,13 @@
  */
 package org.apache.jackrabbit.oak.query.ast;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
 
@@ -135,5 +140,35 @@ public abstract class ConstraintImpl ext
     public int hashCode() {
         return toString().hashCode();
     }
-
+    
+    /**
+     * 
+     * @return the list of {@link ConstraintImpl} that the current constraint could hold. Default
+     *         implementation returns {@code null}.
+     */
+    @Nullable
+    public List<ConstraintImpl> getConstraints() {
+        return null;
+    }
+    
+    /**
+     * <p>
+     * Compute a Set of sub-constraints that could be used for composing UNION statements. For
+     * example in case of {@code OR (c1, c2)} it will return to the caller {@code [c1, c2]}. Those
+     * can be later on used for re-composing conditions.
+     * </p>
+     * <p>
+     * If no union optimisations are possible it must return an empty set.
+     * </p>
+     * <p>
+     * Default implementation in {@link ConstraintImpl#simplifyForUnion()} always return an empty
+     * set.
+     * </p>
+     * 
+     * @return
+     */
+    @Nonnull
+    public Set<ConstraintImpl> simplifyForUnion() {
+        return Collections.emptySet();
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeImpl.java Wed Oct 21 15:19:15 2015
@@ -92,4 +92,8 @@ public class DescendantNodeImpl extends
         }
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new DescendantNodeImpl(selectorName, ancestorPath);
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeJoinConditionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeJoinConditionImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeJoinConditionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/DescendantNodeJoinConditionImpl.java Wed Oct 21 15:19:15 2015
@@ -97,4 +97,9 @@ public class DescendantNodeJoinCondition
         return available.contains(descendantSelector) && available.contains(ancestorSelector);
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new DescendantNodeJoinConditionImpl(descendantSelectorName, ancestorSelectorName);
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java Wed Oct 21 15:19:15 2015
@@ -172,4 +172,8 @@ public class EquiJoinConditionImpl exten
         return available.contains(selector1) && available.contains(selector2);
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new EquiJoinConditionImpl(selector1Name, property1Name, selector2Name, property2Name);
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/FullTextSearchImpl.java Wed Oct 21 15:19:15 2015
@@ -303,4 +303,9 @@ public class FullTextSearchImpl extends
     void restrictPropertyOnFilter(String propertyName, FilterImpl f) {
         f.restrictProperty(propertyName, Operator.NOT_EQUAL, null);
     }
+
+    @Override
+    public AstElement copyOf() {
+        return new FullTextSearchImpl(selectorName, propertyName, fullTextSearchExpression);
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/InImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/InImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/InImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/InImpl.java Wed Oct 21 15:19:15 2015
@@ -179,4 +179,8 @@ public class InImpl extends ConstraintIm
         return operand1.hashCode();
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new InImpl(operand1.createCopy(), operand2);
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/JoinImpl.java Wed Oct 21 15:19:15 2015
@@ -13,6 +13,8 @@
  */
 package org.apache.jackrabbit.oak.query.ast;
 
+import static org.apache.jackrabbit.oak.query.ast.AstElementFactory.copyElementAndCheckReference;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -27,7 +29,6 @@ import org.apache.jackrabbit.oak.spi.sta
  * source, the join type, and the join condition.
  */
 public class JoinImpl extends SourceImpl {
-
     private final JoinConditionImpl joinCondition;
     private JoinType joinType;
     private SourceImpl left;
@@ -285,4 +286,13 @@ public class JoinImpl extends SourceImpl
         return -1;
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new JoinImpl(
+            (SourceImpl) copyElementAndCheckReference(left),
+            (SourceImpl) copyElementAndCheckReference(right),
+            joinType,
+            (JoinConditionImpl) copyElementAndCheckReference(joinCondition)
+            );
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NativeFunctionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NativeFunctionImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NativeFunctionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NativeFunctionImpl.java Wed Oct 21 15:19:15 2015
@@ -107,4 +107,8 @@ public class NativeFunctionImpl extends
         return nativeSearchExpression;
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new NativeFunctionImpl(selectorName, language, nativeSearchExpression);
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/NotImpl.java Wed Oct 21 15:19:15 2015
@@ -18,6 +18,9 @@
  */
 package org.apache.jackrabbit.oak.query.ast;
 
+import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.jackrabbit.oak.query.ast.AstElementFactory.copyElementAndCheckReference;
+
 import java.util.Collections;
 import java.util.Set;
 
@@ -110,4 +113,8 @@ public class NotImpl extends ConstraintI
         // TODO convert NOT conditions
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new NotImpl((ConstraintImpl) copyElementAndCheckReference(constraint));
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/OrImpl.java Wed Oct 21 15:19:15 2015
@@ -23,6 +23,7 @@ import static com.google.common.collect.
 import static com.google.common.collect.Maps.newLinkedHashMap;
 import static com.google.common.collect.Sets.newHashSet;
 import static com.google.common.collect.Sets.newLinkedHashSet;
+import static org.apache.jackrabbit.oak.query.ast.AstElementFactory.copyElementAndCheckReference;
 import static org.apache.jackrabbit.oak.query.ast.Operator.EQUAL;
 
 import java.util.Arrays;
@@ -37,6 +38,8 @@ import org.apache.jackrabbit.oak.query.f
 import org.apache.jackrabbit.oak.query.fulltext.FullTextOr;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
 
+import com.google.common.collect.Sets;
+
 /**
  * An "or" condition.
  */
@@ -53,6 +56,7 @@ public class OrImpl extends ConstraintIm
         this(Arrays.asList(constraint1, constraint2));
     }
 
+    @Override
     public List<ConstraintImpl> getConstraints() {
         return constraints;
     }
@@ -345,4 +349,26 @@ public class OrImpl extends ConstraintIm
         return constraints.hashCode();
     }
 
+    @Override
+    public AstElement copyOf() {
+        List<ConstraintImpl> clone = newArrayList();
+        for (ConstraintImpl c : constraints) {
+            clone.add((ConstraintImpl) copyElementAndCheckReference(c));
+        }
+        return new OrImpl(clone);
+    }
+
+    @Override
+    public Set<ConstraintImpl> simplifyForUnion() {
+        Set<ConstraintImpl> cc = Sets.newHashSet();
+        for (ConstraintImpl c : getConstraints()) {
+            Set<ConstraintImpl> ccc = c.simplifyForUnion(); 
+            if (ccc.isEmpty()) {
+                cc.add(c);
+            } else {
+                cc.addAll(ccc);
+            }
+        }
+        return cc;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyExistenceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyExistenceImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyExistenceImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyExistenceImpl.java Wed Oct 21 15:19:15 2015
@@ -114,4 +114,8 @@ public class PropertyExistenceImpl exten
         return a == null || b == null ? a == b : a.equals(b);
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new PropertyExistenceImpl(selectorName, propertyName);
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java Wed Oct 21 15:19:15 2015
@@ -165,4 +165,8 @@ public class PropertyInexistenceImpl ext
         return a == null || b == null ? a == b : a.equals(b);
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new PropertyInexistenceImpl(selectorName, propertyName);
+    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java Wed Oct 21 15:19:15 2015
@@ -155,5 +155,4 @@ public class PropertyValueImpl extends D
     public PropertyValueImpl createCopy() {
         return new PropertyValueImpl(selectorName, propertyName);
     }
-
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SameNodeImpl.java Wed Oct 21 15:19:15 2015
@@ -84,4 +84,8 @@ public class SameNodeImpl extends Constr
         }
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new SameNodeImpl(selectorName, path);
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java Wed Oct 21 15:19:15 2015
@@ -786,5 +786,9 @@ public class SelectorImpl extends Source
         }
         return cursor.getSize(precision, max);
     }
-    
+
+    @Override
+    public SourceImpl copyOf() {
+        return new SelectorImpl(nodeType, selectorName);
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SimilarImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SimilarImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SimilarImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SimilarImpl.java Wed Oct 21 15:19:15 2015
@@ -127,4 +127,9 @@ public class SimilarImpl extends Constra
         return pathExpression;
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new SimilarImpl(selectorName, propertyName, pathExpression);
+    }
+
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SourceImpl.java Wed Oct 21 15:19:15 2015
@@ -170,5 +170,4 @@ public abstract class SourceImpl extends
      * @return the size, or -1 if unknown
      */
     public abstract long getSize(SizePrecision precision, long max);
-
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SpellcheckImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SpellcheckImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SpellcheckImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SpellcheckImpl.java Wed Oct 21 15:19:15 2015
@@ -114,4 +114,9 @@ public class SpellcheckImpl extends Cons
         return expression;
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new SpellcheckImpl(selectorName, expression);
+    }
+
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SuggestImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SuggestImpl.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SuggestImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SuggestImpl.java Wed Oct 21 15:19:15 2015
@@ -114,4 +114,8 @@ public class SuggestImpl extends Constra
         return expression;
     }
 
+    @Override
+    public AstElement copyOf() {
+        return new SuggestImpl(selectorName, expression);
+    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java Wed Oct 21 15:19:15 2015
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("2.3")
+@Version("2.4")
 @Export(optional = "provide:=true")
 package org.apache.jackrabbit.oak.query;
 

Copied: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/MultiPropertyOrTestOptimisation.java (from r1709862, jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/MultiPropertyOrTestOptimisation.java?p2=jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/MultiPropertyOrTestOptimisation.java&p1=jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java&r1=1709862&r2=1709863&rev=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/MultiPropertyOrTestOptimisation.java Wed Oct 21 15:19:15 2015
@@ -6,7 +6,7 @@
  * (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
+ *      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,
@@ -14,9 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("2.3")
-@Export(optional = "provide:=true")
-package org.apache.jackrabbit.oak.query;
+package org.apache.jackrabbit.oak.plugins.index.property;
 
-import aQute.bnd.annotation.Version;
-import aQute.bnd.annotation.Export;
+import static org.apache.jackrabbit.oak.query.QueryEngineImpl.ForceOptimised.OPTIMISED;
+
+import org.junit.Before;
+
+/**
+ * should be executing the {@link MultiPropertyOrTest} by forcing the optimisation in place.
+ */
+public class MultiPropertyOrTestOptimisation extends MultiPropertyOrTest {
+    
+    @Override
+    @Before
+    public void before() throws Exception {
+        super.before();
+        setForceOptimised(OPTIMISED);
+        setTraversalEnabled(false);
+    }    
+}

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTest.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTest.java Wed Oct 21 15:19:15 2015
@@ -52,13 +52,23 @@ public class PropertyIndexQueryTest exte
 
     @Override
     protected ContentRepository createRepository() {
-        return new Oak().with(new InitialContent())
-                .with(new OpenSecurityProvider())
-                .with(new PropertyIndexProvider())
-                .with(new PropertyIndexEditorProvider())
-                .createContentRepository();
+        return getOakRepositoryInstance().createContentRepository();
     }
 
+    /**
+     * return an instance of {@link Oak} repository ready to be built with
+     * {@link Oak#createContentRepository()}.
+     * 
+     * @return
+     */
+    @Nonnull
+    Oak getOakRepositoryInstance() {
+        return new Oak().with(new InitialContent())
+            .with(new OpenSecurityProvider())
+            .with(new PropertyIndexProvider())
+            .with(new PropertyIndexEditorProvider());
+    }
+    
     @Test
     public void nativeQuery() throws Exception {
         test("sql2_native.txt");

Copied: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTestSQL2Optimisations.java (from r1709862, jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTestSQL2Optimisations.java?p2=jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTestSQL2Optimisations.java&p1=jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java&r1=1709862&r2=1709863&rev=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/package-info.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTestSQL2Optimisations.java Wed Oct 21 15:19:15 2015
@@ -6,7 +6,7 @@
  * (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
+ *      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,
@@ -14,9 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("2.3")
-@Export(optional = "provide:=true")
-package org.apache.jackrabbit.oak.query;
+package org.apache.jackrabbit.oak.plugins.index.property;
 
-import aQute.bnd.annotation.Version;
-import aQute.bnd.annotation.Export;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.query.QueryEngineSettings;
+
+/**
+ * checks the same as {@link PropertyIndexQueryTest} enabling the feature for optimising SQL2
+ * statements.
+ */
+public class PropertyIndexQueryTestSQL2Optimisations extends PropertyIndexQueryTest {
+
+    @Override
+    Oak getOakRepositoryInstance() {
+        return super.getOakRepositoryInstance()
+            .with(new QueryEngineSettings() {
+                @Override
+                public boolean isSql2Optimisation() {
+                    return true;
+                }
+            });
+    }
+}

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java?rev=1709863&r1=1709862&r2=1709863&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java Wed Oct 21 15:19:15 2015
@@ -32,6 +32,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 
+import javax.annotation.Nonnull;
 import javax.jcr.PropertyType;
 
 import com.google.common.collect.Lists;
@@ -55,9 +56,11 @@ import org.apache.jackrabbit.oak.json.Ty
 import org.apache.jackrabbit.oak.plugins.memory.BooleanPropertyState;
 import org.apache.jackrabbit.oak.plugins.memory.StringPropertyState;
 import org.apache.jackrabbit.oak.plugins.value.Conversions;
+import org.apache.jackrabbit.oak.query.QueryEngineImpl.ForceOptimised;
 import org.apache.jackrabbit.oak.query.xpath.XPathToSQL2Converter;
 import org.junit.Before;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.apache.jackrabbit.oak.api.QueryEngine.NO_BINDINGS;
 import static org.apache.jackrabbit.oak.api.QueryEngine.NO_MAPPINGS;
 import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
@@ -308,18 +311,27 @@ public abstract class AbstractQueryTest
     protected List<String> assertQuery(String sql, String language,
                                        List<String> expected, boolean skipSort) {
         List<String> paths = executeQuery(sql, language, true, skipSort);
-        for (String p : expected) {
-            assertTrue("Expected path " + p + " not found, got " + paths, paths.contains(p));
-        }
-        assertEquals("Result set size is different", expected.size(),
-                paths.size());
+        assertResult(expected, paths);
         return paths;
 
     }
+    
+    protected static void assertResult(@Nonnull List<String> expected, @Nonnull List<String> actual) {
+        for (String p : checkNotNull(expected)) {
+            assertTrue("Expected path " + p + " not found, got " + actual, checkNotNull(actual)
+                .contains(p));
+        }
+        assertEquals("Result set size is different", expected.size(),
+                actual.size());
+    }
 
     protected void setTraversalEnabled(boolean traversalEnabled) {
         ((QueryEngineImpl) qe).setTraversalEnabled(traversalEnabled);
     }
+    
+    protected void setForceOptimised(@Nonnull ForceOptimised forceOptimised) {
+        ((QueryEngineImpl) qe).setForceOptimised(checkNotNull(forceOptimised));
+    }
 
     protected static String readRow(ResultRow row, boolean pathOnly) {
         if (pathOnly) {

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryCostOverheadTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryCostOverheadTest.java?rev=1709863&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryCostOverheadTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryCostOverheadTest.java Wed Oct 21 15:19:15 2015
@@ -0,0 +1,101 @@
+/*
+ * 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;
+
+import static com.google.common.collect.ImmutableList.of;
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.jackrabbit.oak.query.ast.AndImpl;
+import org.apache.jackrabbit.oak.query.ast.ComparisonImpl;
+import org.apache.jackrabbit.oak.query.ast.ConstraintImpl;
+import org.apache.jackrabbit.oak.query.ast.DescendantNodeImpl;
+import org.apache.jackrabbit.oak.query.ast.FullTextSearchImpl;
+import org.apache.jackrabbit.oak.query.ast.OrImpl;
+import org.junit.Test;
+
+public class QueryCostOverheadTest {
+    @Test
+    public void getCostOverhead() {
+        final double allowedDelta = 10;
+        QueryImpl query;
+        UnionQueryImpl union;
+        ConstraintImpl c, c1, c2, c3, c4, c5;
+        
+        union = new UnionQueryImpl(false, null, null, null);
+        assertEquals("we always expect 0 from a `UnionQueryImpl`", 0, union.getCostOverhead(),
+            allowedDelta);
+        
+        c = mock(OrImpl.class);
+        c1 = mock(ComparisonImpl.class);
+        c2 = mock(FullTextSearchImpl.class);
+        when(c.getConstraints()).thenReturn(of(c1, c2));
+        query = new QueryImpl(null, null, c, null, null, null);
+        assertEquals(Double.MAX_VALUE, query.getCostOverhead(), allowedDelta);
+
+        c = mock(OrImpl.class);
+        c1 = mock(ComparisonImpl.class);
+        c2 = mock(FullTextSearchImpl.class);
+        c3 = mock(FullTextSearchImpl.class);
+        when(c.getConstraints()).thenReturn(of(c1, c2, c3));
+        query = new QueryImpl(null, null, c, null, null, null);
+        assertEquals(Double.MAX_VALUE, query.getCostOverhead(), allowedDelta);
+        
+        c1 = mock(OrImpl.class);
+        c2 = mock(FullTextSearchImpl.class);
+        c3 = mock(FullTextSearchImpl.class);
+        c4 = mock(ComparisonImpl.class);
+        when(c1.getConstraints()).thenReturn(of(c2, c3, c4));
+        c = mock(AndImpl.class);
+        c5 = mock(DescendantNodeImpl.class);
+        when(c.getConstraints()).thenReturn(of(c1, c5));
+        query = new QueryImpl(null, null, c, null, null, null);
+        assertEquals(Double.MAX_VALUE, query.getCostOverhead(), allowedDelta);
+        
+        c = mock(FullTextSearchImpl.class);
+        query = new QueryImpl(null, null, c, null, null, null);
+        assertEquals(0, query.getCostOverhead(), allowedDelta);
+
+        c = mock(OrImpl.class);
+        c1 = mock(FullTextSearchImpl.class);
+        c2 = mock(FullTextSearchImpl.class);
+        c3 = mock(FullTextSearchImpl.class);
+        when(c.getConstraints()).thenReturn(of(c1, c2, c3));
+        query = new QueryImpl(null, null, c, null, null, null);
+        assertEquals(0, query.getCostOverhead(), allowedDelta);
+        
+        c = mock(AndImpl.class);
+        c1 = mock(ComparisonImpl.class);
+        c2 = mock(FullTextSearchImpl.class);
+        c3 = mock(FullTextSearchImpl.class);
+        when(c.getConstraints()).thenReturn(of(c1, c2, c3));
+        query = new QueryImpl(null, null, c, null, null, null);
+        assertEquals(0, query.getCostOverhead(), allowedDelta);
+
+        c = mock(AndImpl.class);
+        c1 = mock(ComparisonImpl.class);
+        c2 = mock(ComparisonImpl.class);
+        when(c.getConstraints()).thenReturn(of(c1, c2, c3));
+        query = new QueryImpl(null, null, c, null, null, null);
+        assertEquals(0, query.getCostOverhead(), allowedDelta);
+
+        c2 = mock(ComparisonImpl.class);
+        query = new QueryImpl(null, null, c, null, null, null);
+        assertEquals(0, query.getCostOverhead(), allowedDelta);
+    }
+}