jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From alexparvule...@apache.org
Subject svn commit: r1126536 - in /jackrabbit/branches/2.2/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/query/lucene/join/ test/java/org/apache/jackrabbit/core/query/
Date Mon, 23 May 2011 15:16:27 GMT
Author: alexparvulescu
Date: Mon May 23 15:16:24 2011
New Revision: 1126536

URL: http://svn.apache.org/viewvc?rev=1126536&view=rev
Log:
2.2: merged revisions 1092683 (JCR-2933)

Added:
    jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/RowPathComparator.java
      - copied unchanged from r1092683, jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/RowPathComparator.java
    jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OuterJoinTest.java
      - copied unchanged from r1092683, jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OuterJoinTest.java
Modified:
    jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitInfo.java
    jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitter.java
    jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java
    jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java

Modified: jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitInfo.java?rev=1126536&r1=1126535&r2=1126536&view=diff
==============================================================================
--- jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitInfo.java
(original)
+++ jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitInfo.java
Mon May 23 15:16:24 2011
@@ -21,39 +21,43 @@ import java.util.List;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.query.qom.Constraint;
-import javax.jcr.query.qom.Or;
+import javax.jcr.query.qom.Join;
 import javax.jcr.query.qom.QueryObjectModelFactory;
 
 class ConstraintSplitInfo {
 
     private final QueryObjectModelFactory factory;
 
-    private final List<Constraint> leftConstraints = new ArrayList<Constraint>();
+    private final Join source;
 
-    private final List<Constraint> rightConstraints = new ArrayList<Constraint>();
+    private final List<Constraint> leftConstraints;
+
+    private final List<Constraint> rightConstraints;
 
     private boolean isMultiple;
 
-    private final List<ConstraintSplitInfo> innerConstraints = new ArrayList<ConstraintSplitInfo>();
+    private ConstraintSplitInfo leftInnerConstraints = null;
 
-    public ConstraintSplitInfo(QueryObjectModelFactory factory) {
-        this.factory = factory;
-        this.isMultiple = false;
+    private ConstraintSplitInfo rightInnerConstraints = null;
+
+    public ConstraintSplitInfo(QueryObjectModelFactory factory, Join source) {
+        this(factory, source, new ArrayList<Constraint>(),
+                new ArrayList<Constraint>());
     }
 
-    private ConstraintSplitInfo(QueryObjectModelFactory factory,
+    private ConstraintSplitInfo(QueryObjectModelFactory factory, Join source,
             List<Constraint> leftConstraints, List<Constraint> rightConstraints)
{
         this.factory = factory;
+        this.source = source;
         this.isMultiple = false;
-        this.leftConstraints.addAll(leftConstraints);
-        this.rightConstraints.addAll(rightConstraints);
+        this.leftConstraints = leftConstraints;
+        this.rightConstraints = rightConstraints;
     }
 
     public void addLeftConstraint(Constraint c) {
         if (isMultiple) {
-            for (ConstraintSplitInfo csi : innerConstraints) {
-                csi.addLeftConstraint(c);
-            }
+            leftInnerConstraints.addLeftConstraint(c);
+            leftInnerConstraints.addRightConstraint(c);
             return;
         }
         leftConstraints.add(c);
@@ -61,35 +65,31 @@ class ConstraintSplitInfo {
 
     public void addRightConstraint(Constraint c) {
         if (isMultiple) {
-            for (ConstraintSplitInfo csi : innerConstraints) {
-                csi.addRightConstraint(c);
-            }
+            rightInnerConstraints.addLeftConstraint(c);
+            rightInnerConstraints.addRightConstraint(c);
             return;
         }
         rightConstraints.add(c);
     }
 
-    public void split(Or or) {
+    public void splitOr() {
+
         if (isMultiple) {
-            for (ConstraintSplitInfo csi : innerConstraints) {
-                csi.split(or);
-            }
+            // this should never happen
             return;
         }
 
         this.isMultiple = true;
+        ConstraintSplitInfo csi1 = new ConstraintSplitInfo(factory, source,
+                new ArrayList<Constraint>(leftConstraints),
+                new ArrayList<Constraint>(rightConstraints));
+        this.leftInnerConstraints = csi1;
+
+        ConstraintSplitInfo csi2 = new ConstraintSplitInfo(factory, source,
+                new ArrayList<Constraint>(leftConstraints),
+                new ArrayList<Constraint>(rightConstraints));
+        this.rightInnerConstraints = csi2;
 
-        ConstraintSplitInfo csi1 = new ConstraintSplitInfo(factory,
-                leftConstraints, rightConstraints);
-        csi1.addLeftConstraint(or.getConstraint1());
-        this.innerConstraints.add(csi1);
-
-        ConstraintSplitInfo csi2 = new ConstraintSplitInfo(factory,
-                leftConstraints, rightConstraints);
-        csi2.addLeftConstraint(or.getConstraint2());
-        this.innerConstraints.add(csi2);
-
-        // would null be better?
         this.leftConstraints.clear();
         this.rightConstraints.clear();
     }
@@ -98,8 +98,16 @@ class ConstraintSplitInfo {
         return isMultiple;
     }
 
-    public List<ConstraintSplitInfo> getInnerConstraints() {
-        return innerConstraints;
+    public ConstraintSplitInfo getLeftInnerConstraints() {
+        return leftInnerConstraints;
+    }
+
+    public ConstraintSplitInfo getRightInnerConstraints() {
+        return rightInnerConstraints;
+    }
+
+    public Join getSource() {
+        return source;
     }
 
     /**
@@ -115,4 +123,17 @@ class ConstraintSplitInfo {
     public Constraint getRightConstraint() throws RepositoryException {
         return Constraints.and(factory, rightConstraints);
     }
+
+    @Override
+    public String toString() {
+        if (isMultiple) {
+            return "ConstraintSplitInfo [multiple=" + ", leftInnerConstraints="
+                    + leftInnerConstraints + ", rightInnerConstraints="
+                    + rightInnerConstraints + "]";
+        }
+        return "ConstraintSplitInfo [single" + ", leftConstraints="
+                + leftConstraints + ", rightConstraints=" + rightConstraints
+                + "]";
+    }
+
 }

Modified: jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitter.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitter.java?rev=1126536&r1=1126535&r2=1126536&view=diff
==============================================================================
--- jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitter.java
(original)
+++ jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitter.java
Mon May 23 15:16:24 2011
@@ -30,6 +30,7 @@ import javax.jcr.query.qom.DescendantNod
 import javax.jcr.query.qom.DynamicOperand;
 import javax.jcr.query.qom.FullTextSearch;
 import javax.jcr.query.qom.FullTextSearchScore;
+import javax.jcr.query.qom.Join;
 import javax.jcr.query.qom.Length;
 import javax.jcr.query.qom.LowerCase;
 import javax.jcr.query.qom.NodeLocalName;
@@ -67,32 +68,36 @@ class ConstraintSplitter {
 
     public ConstraintSplitter(Constraint constraint,
             QueryObjectModelFactory factory, Set<String> leftSelectors,
-            Set<String> rightSelectors) throws RepositoryException {
+            Set<String> rightSelectors, Join join) throws RepositoryException {
         this.factory = factory;
         this.leftSelectors = leftSelectors;
         this.rightSelectors = rightSelectors;
-        constraintSplitInfo = new ConstraintSplitInfo(this.factory);
-
+        constraintSplitInfo = new ConstraintSplitInfo(this.factory, join);
         if (constraint != null) {
-            split(constraint);
+            split(constraintSplitInfo, constraint);
         }
     }
 
-    private void split(Constraint constraint) throws RepositoryException {
+    private void split(ConstraintSplitInfo constraintSplitInfo, Constraint constraint) throws
RepositoryException {
         if (constraint instanceof Not) {
-            splitNot((Not) constraint);
+            splitNot(constraintSplitInfo, (Not) constraint);
         } else if (constraint instanceof And) {
             And and = (And) constraint;
-            split(and.getConstraint1());
-            split(and.getConstraint2());
+            split(constraintSplitInfo, and.getConstraint1());
+            split(constraintSplitInfo, and.getConstraint2());
         } else if (constraint instanceof Or) {
             if (isReferencingBothSides(getSelectorNames(constraint))) {
-                constraintSplitInfo.split((Or) constraint);
+                Or or = (Or) constraint;
+                //the problem here is when you split an OR that has both condition sides
referencing both join sides. 
+                // it should split into 2 joins
+                constraintSplitInfo.splitOr();
+                split(constraintSplitInfo.getLeftInnerConstraints(), or.getConstraint1());
+                split(constraintSplitInfo.getRightInnerConstraints(),or.getConstraint2());
             } else {
-                splitBySelectors(constraint, getSelectorNames(constraint));
+                splitBySelectors(constraintSplitInfo, constraint, getSelectorNames(constraint));
             }
         } else {
-            splitBySelectors(constraint, getSelectorNames(constraint));
+            splitBySelectors(constraintSplitInfo, constraint, getSelectorNames(constraint));
         }
     }
 
@@ -101,24 +106,24 @@ class ConstraintSplitter {
                 && !rightSelectors.containsAll(selectors);
     }
 
-    private void splitNot(Not not) throws RepositoryException {
+    private void splitNot(ConstraintSplitInfo constraintSplitInfo, Not not) throws RepositoryException
{
         Constraint constraint = not.getConstraint();
         if (constraint instanceof Not) {
-            split(((Not) constraint).getConstraint());
+            split(constraintSplitInfo, ((Not) constraint).getConstraint());
         } else if (constraint instanceof And) {
             And and = (And) constraint;
-            split(factory.or(factory.not(and.getConstraint1()),
+            split(constraintSplitInfo, factory.or(factory.not(and.getConstraint1()),
                     factory.not(and.getConstraint2())));
         } else if (constraint instanceof Or) {
             Or or = (Or) constraint;
-            split(factory.and(factory.not(or.getConstraint1()),
+            split(constraintSplitInfo, factory.and(factory.not(or.getConstraint1()),
                     factory.not(or.getConstraint2())));
         } else {
-            splitBySelectors(not, getSelectorNames(constraint));
+            splitBySelectors(constraintSplitInfo, not, getSelectorNames(constraint));
         }
     }
 
-    private void splitBySelectors(Constraint constraint, Set<String> selectors)
+    private void splitBySelectors(ConstraintSplitInfo constraintSplitInfo, Constraint constraint,
Set<String> selectors)
             throws UnsupportedRepositoryOperationException {
         if (leftSelectors.containsAll(selectors)) {
             constraintSplitInfo.addLeftConstraint(constraint);

Modified: jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java?rev=1126536&r1=1126535&r2=1126536&view=diff
==============================================================================
--- jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java
(original)
+++ jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java
Mon May 23 15:16:24 2011
@@ -17,10 +17,12 @@
 package org.apache.jackrabbit.core.query.lucene.join;
 
 import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_JOIN_TYPE_LEFT_OUTER;
+import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_JOIN_TYPE_INNER;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -160,50 +162,125 @@ abstract class JoinMerger {
         }
     }
 
-    public QueryResult merge(RowIterator leftRows, RowIterator rightRows)
+    /**
+     * Merges the left and right dataset of a join query. Take special
+     * precaution for outer joins, as extra checks are needed to distinguish
+     * 'null' nodes vs 'not to be included' nodes
+     * 
+     * 
+     * @param leftRows
+     *            the left dataset of the join
+     * @param rightRows
+     *            the right dataset of the join
+     * @param excludingOuterJoinRowsSet
+     *            if not <code>null</code> must be taken into consideration when
+     *            merging OUTER JOINs
+     * @param rowComparator
+     *            a comparator implementation that has to handle the 'is row
+     *            equal to' problem, in the case of outer joins with
+     *            excludingOuterJoinRowsSet
+     * @return a QueryResult that has the final JOIN resultset
+     * @throws RepositoryException
+     */
+    public QueryResult merge(RowIterator leftRows, RowIterator rightRows,
+            Set<Row> excludingOuterJoinRowsSet, Comparator<Row> rowComparator)
             throws RepositoryException {
-        RowIterator joinRows;
+        Map<String, List<Row>> map = buildRightRowValues(rightRows);
 
-        Map<String, List<Row>> map = new HashMap<String, List<Row>>();
-        for (Row row : new RowIterable(rightRows)) {
-            for (String value : getRightValues(row)) {
-                List<Row> rows = map.get(value);
-                if (rows == null) {
-                    rows = new ArrayList<Row>();
-                    map.put(value, rows);
+        if (JCR_JOIN_TYPE_INNER.equals(type) && !map.isEmpty()) {
+            List<Row> rows = new ArrayList<Row>();
+            for (Row leftRow : new RowIterable(leftRows)) {
+                for (String value : getLeftValues(leftRow)) {
+                    List<Row> matchingRows = map.get(value);
+                    if (matchingRows != null) {
+                        for (Row rightRow : matchingRows) {
+                            rows.add(mergeRow(leftRow, rightRow));
+                        }
+                    }
                 }
-                rows.add(row);
             }
+            return asQueryResult(new RowIteratorAdapter(rows));
         }
 
-        if (!map.isEmpty()) {
+        if (JCR_JOIN_TYPE_LEFT_OUTER.equals(type)) {
+            // there are no RIGHT dataset values
+            if (map.isEmpty()) {
+                // if there are no WHERE conditions, just return everything
+                // else return an empty set
+                if (excludingOuterJoinRowsSet == null) {
+                    return asQueryResult(new RowIteratorAdapter(leftRows) {
+                        @Override
+                        public Object next() {
+                            return mergeRow((Row) super.next(), null);
+                        }
+                    });
+                }
+                return asQueryResult(new RowIteratorAdapter(
+                        Collections.emptySet()));
+            }
+
             List<Row> rows = new ArrayList<Row>();
             for (Row leftRow : new RowIterable(leftRows)) {
                 for (String value : getLeftValues(leftRow)) {
                     List<Row> matchingRows = map.get(value);
                     if (matchingRows != null) {
                         for (Row rightRow : matchingRows) {
-                            rows.add(mergeRow(leftRow, rightRow));
+                            // I have possible WHERE clauses on the join that I
+                            // need to look at for each rightRow
+                            if (excludingOuterJoinRowsSet == null) {
+                                rows.add(mergeRow(leftRow, rightRow));
+                            } else {
+                                boolean isIncluded = false;
+                                // apparently
+                                // 'excludingOuterJoinRowsSet.contains' fails to
+                                // match rows
+
+                                // TODO can 'rightRow.getNode()' break because
+                                // of joins that are bigger than 2 way?
+                                // how does this perform for 3 way joins ?
+                                for (Row r : excludingOuterJoinRowsSet) {
+                                    if(rowComparator.compare(rightRow, r) == 0){
+                                        isIncluded = true;
+                                        break;
+                                    }
+                                }
+                                if (isIncluded) {
+                                    rows.add(mergeRow(leftRow, rightRow));
+                                }
+                            }
+                        }
+                    } else {
+                        // No matches in an outer join -> add a null row, if
+                        // there are no 'WHERE' conditions
+                        if (excludingOuterJoinRowsSet == null) {
+                            rows.add(mergeRow(leftRow, null));
                         }
-                    } else if (JCR_JOIN_TYPE_LEFT_OUTER.equals(type)) {
-                        // No matches in an outer join -> add a null row
-                        rows.add(mergeRow(leftRow, null));
                     }
                 }
             }
-            joinRows = new RowIteratorAdapter(rows);
-        } else if (JCR_JOIN_TYPE_LEFT_OUTER.equals(type)) {
-            joinRows = new RowIteratorAdapter(leftRows) {
-                @Override
-                public Object next() {
-                    return mergeRow((Row) super.next(), null);
-                }
-            };
-        } else {
-            joinRows = new RowIteratorAdapter(Collections.emptySet());
+            return asQueryResult(new RowIteratorAdapter(rows));
         }
+        return asQueryResult(new RowIteratorAdapter(Collections.emptySet()));
+    }
+
+    private QueryResult asQueryResult(RowIterator rowIterator) {
+        return new SimpleQueryResult(columnNames, selectorNames, rowIterator);
+    }
 
-        return new SimpleQueryResult(columnNames, selectorNames, joinRows);
+    private Map<String, List<Row>> buildRightRowValues(RowIterator rightRows)
+            throws RepositoryException {
+        Map<String, List<Row>> map = new HashMap<String, List<Row>>();
+        for (Row row : new RowIterable(rightRows)) {
+            for (String value : getRightValues(row)) {
+                List<Row> rows = map.get(value);
+                if (rows == null) {
+                    rows = new ArrayList<Row>();
+                    map.put(value, rows);
+                }
+                rows.add(row);
+            }
+        }
+        return map;
     }
 
     /**

Modified: jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java?rev=1126536&r1=1126535&r2=1126536&view=diff
==============================================================================
--- jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
(original)
+++ jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
Mon May 23 15:16:24 2011
@@ -22,9 +22,9 @@ import static javax.jcr.query.qom.QueryO
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -55,23 +55,36 @@ import javax.jcr.query.qom.QueryObjectMo
 import javax.jcr.query.qom.Selector;
 import javax.jcr.query.qom.Source;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.commons.iterator.RowIteratorAdapter;
 import org.apache.jackrabbit.core.query.lucene.LuceneQueryFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class QueryEngine {
+    
+    /**
+     * The logger instance for this class
+     */
+    private static final Logger log = LoggerFactory.getLogger(QueryEngine.class);
+
+    private static final int printIndentStep = 4;
 
     /**
      * Row comparator.
      */
-    private class RowComparator implements Comparator<Row> {
+    private static class RowComparator implements Comparator<Row> {
 
         private final ValueComparator comparator = new ValueComparator();
 
         private final Ordering[] orderings;
+        
+        private final OperandEvaluator evaluator;
 
-        private RowComparator(Ordering[] orderings) {
+        private RowComparator(Ordering[] orderings, OperandEvaluator evaluator) {
             this.orderings = orderings;
+            this.evaluator = evaluator;
         }
 
         public int compare(Row a, Row b) {
@@ -118,8 +131,7 @@ public class QueryEngine {
 
     private final OperandEvaluator evaluator;
 
-    public QueryEngine(
-            Session session, LuceneQueryFactory lqf,
+    public QueryEngine(Session session, LuceneQueryFactory lqf,
             Map<String, Value> variables) throws RepositoryException {
         this.lqf = lqf;
 
@@ -131,129 +143,222 @@ public class QueryEngine {
         this.evaluator = new OperandEvaluator(valueFactory, variables);
     }
 
-    public QueryResult execute(
-            Column[] columns, Source source, Constraint constraint,
-            Ordering[] orderings, long offset, long limit)
+    public QueryResult execute(Column[] columns, Source source,
+            Constraint constraint, Ordering[] orderings, long offset, long limit)
             throws RepositoryException {
-        if (source instanceof Selector) {
-            Selector selector = (Selector) source;
-            return execute(
-                    columns, selector, constraint, orderings, offset, limit);
-        } else if (source instanceof Join) {
-            Join join = (Join) source;
-            if (join.getJoinType() == JCR_JOIN_TYPE_RIGHT_OUTER) {
-                // Swap the join sources to normalize all outer joins to left
-                join = qomFactory.join(
-                        join.getRight(), join.getLeft(),
-                        JCR_JOIN_TYPE_LEFT_OUTER, join.getJoinCondition());
-            }
-            return execute(
-                    columns, join, constraint, orderings, offset, limit);
-        } else {
-            throw new UnsupportedRepositoryOperationException(
-                    "Unknown source type: " + source);
-        }
+        long time = System.currentTimeMillis();
+        QueryResult qr = execute(columns, source, constraint, orderings,
+                offset, limit, 2);
+        if (log.isDebugEnabled()) {
+            time = System.currentTimeMillis() - time;
+            log.debug("SQL2 QUERY execute took " + time + " ms.");
+        }
+        return qr;
     }
 
-    protected QueryResult execute(
-            Column[] columns, Join join, Constraint constraint,
-            Ordering[] orderings, long offset, long limit)
-            throws RepositoryException {
-        JoinMerger merger = JoinMerger.getJoinMerger(
-                join, getColumnMap(columns, getSelectorNames(join)),
-                evaluator, qomFactory);
-        ConstraintSplitter splitter = new ConstraintSplitter(
-                constraint, qomFactory,
-                merger.getLeftSelectors(), merger.getRightSelectors());
-
-        Source left = join.getLeft();
-        Set<Row> leftRows = buildLeftRowsJoin(left, splitter.getConstraintSplitInfo());
-
-        Source right = join.getRight();
-        List<Constraint> rightConstraints = merger.getRightJoinConstraints(leftRows);
-        RowIterator rightRows = new RowIteratorAdapter(buildRightRowsJoin(right, splitter.getConstraintSplitInfo(),
rightConstraints));
-
-        QueryResult result = merger.merge(new RowIteratorAdapter(leftRows), rightRows);
-        return sort(result, orderings, offset, limit);
-    }
-
-    private Comparator<Row> buildSimplePathRowComparator() {
-        return new Comparator<Row>() {
-
-            public int compare(Row o1, Row o2) {
-                try {
-                    return o1.getPath().compareTo(o2.getPath());
-                } catch (RepositoryException e) {
-                    throw new RuntimeException("Unable to compare rows " + o1
-                            + " and " + o2, e);
-                }
-            }
-        };
+    protected QueryResult execute(Column[] columns, Source source,
+            Constraint constraint, Ordering[] orderings, long offset,
+            long limit, int printIndentation) throws RepositoryException {
+        if (source instanceof Selector) {
+            return execute(columns, (Selector) source, constraint, orderings,
+                    offset, limit, printIndentation);
+        }
+        if (source instanceof Join) {
+            return execute(columns, (Join) source, constraint, orderings,
+                    offset, limit, printIndentation);
+        }
+        throw new UnsupportedRepositoryOperationException(
+                "Unknown source type: " + source);
+    }
+
+    protected QueryResult execute(Column[] columns, Join join,
+            Constraint constraint, Ordering[] orderings, long offset,
+            long limit, int printIndentation) throws RepositoryException {
+        // Swap the join sources to normalize all outer joins to left
+        if (JCR_JOIN_TYPE_RIGHT_OUTER.equalsIgnoreCase(join.getJoinType())) {
+            if (log.isDebugEnabled()) {
+                log.debug(genString(printIndentation)
+                        + "SQL2 RIGHT OUTER JOIN transformed to LEFT OUTER JOIN.");
+            }
+            Join betterJoin = qomFactory.join(join.getRight(), join.getLeft(),
+                    JCR_JOIN_TYPE_LEFT_OUTER, join.getJoinCondition());
+            return execute(columns, betterJoin, constraint, orderings, offset,
+                    limit, printIndentation);
+        }
+        JoinMerger merger = JoinMerger.getJoinMerger(join,
+                getColumnMap(columns, getSelectorNames(join)), evaluator,
+                qomFactory);
+        ConstraintSplitter splitter = new ConstraintSplitter(constraint,
+                qomFactory, merger.getLeftSelectors(),
+                merger.getRightSelectors(), join);
+        ConstraintSplitInfo csInfo = splitter.getConstraintSplitInfo();
+
+        logQueryAnalysis(csInfo, printIndentation);
+
+        long timeJoinLeftSide = System.currentTimeMillis();
+        Comparator<Row> leftCo = new RowPathComparator(
+                merger.getLeftSelectors());
+        Set<Row> leftRows = buildLeftRowsJoin(csInfo, leftCo, printIndentation
+                + printIndentStep);
+        if (log.isDebugEnabled()) {
+            timeJoinLeftSide = System.currentTimeMillis() - timeJoinLeftSide;
+            log.debug(genString(printIndentation) + "SQL2 JOIN LEFT SIDE took "
+                    + timeJoinLeftSide + " ms. fetched " + leftRows.size()
+                    + " rows.");
+        }
+
+        // The join constraint information is split into:
+        // - rightConstraints selects just the 'ON' constraints
+        // - csInfo has the 'WHERE' constraints
+        //
+        // So, in the case of an OUTER JOIN we'll run 2 queries, one with 'ON'
+        // and one with 'ON' + 'WHERE' conditions
+        // this way, at merge time in case of an outer join we can tell if
+        // it's a 'null' row, or a bad row -> one that must not be returned.
+        // This way at the end we'll have:
+        // - rightRowsSet containing the 'ON' dataset
+        // - excludingOuterJoinRowsSet: the 'ON' + 'WHERE' condition dataset, or
+        // NULL if there is no 'WHERE' condition
+
+        long timeJoinRightSide = System.currentTimeMillis();
+        List<Constraint> rightConstraints = merger
+                .getRightJoinConstraints(leftRows);
+        Comparator<Row> rightCo = new RowPathComparator(
+                merger.getRightSelectors());
+
+        boolean isOuterJoin = JCR_JOIN_TYPE_LEFT_OUTER.equalsIgnoreCase(join
+                .getJoinType());
+
+        Set<Row> rightRows = buildRightRowsJoin(csInfo, rightConstraints, isOuterJoin,
+                rightCo, printIndentation + printIndentStep);
+
+        // this has to be initialized as null
+        Set<Row> excludingOuterJoinRowsSet = null;
+        if (isOuterJoin && csInfo.getRightConstraint() != null) {
+            excludingOuterJoinRowsSet = buildRightRowsJoin(csInfo,
+                    rightConstraints, false, rightCo, printIndentation
+                            + printIndentStep);
+        }
+
+        if (log.isDebugEnabled()) {
+            timeJoinRightSide = System.currentTimeMillis() - timeJoinRightSide;
+            log.debug(genString(printIndentation)
+                    + "SQL2 JOIN RIGHT SIDE took " + timeJoinRightSide
+                    + " ms. fetched" + rightRows.size() + " rows.");
+        }
+
+        long timeMergeAndSort = System.currentTimeMillis();
+
+        // merge left with right datasets
+        QueryResult result = merger.merge(new RowIteratorAdapter(leftRows),
+                new RowIteratorAdapter(rightRows), excludingOuterJoinRowsSet,
+                rightCo);
+        QueryResult sortedResult = sort(result, orderings, evaluator, offset,
+                limit);
+        if (log.isDebugEnabled()) {
+            timeMergeAndSort = System.currentTimeMillis() - timeMergeAndSort;
+            log.debug(genString(printIndentation)
+                    + "SQL2 JOIN MERGE and SORT took " + timeMergeAndSort
+                    + " ms.");
+        }
+        return sortedResult;
     }
 
-    private Set<Row> buildLeftRowsJoin(Source left, ConstraintSplitInfo csi)
+    private Set<Row> buildLeftRowsJoin(ConstraintSplitInfo csi,
+            Comparator<Row> comparator, int printIndentation)
             throws RepositoryException {
 
         if (csi.isMultiple()) {
-            // this *needs* to merge automatically multiple sets of nodes
-            Set<Row> leftRows = new TreeSet<Row>(buildSimplePathRowComparator());
-            for (ConstraintSplitInfo child : csi.getInnerConstraints()) {
-                leftRows.addAll(buildLeftRowsJoin(left, child));
-            }
+            if (log.isDebugEnabled()) {
+                log.debug(genString(printIndentation)
+                        + "SQL2 JOIN LEFT SIDE there are multiple inner splits.");
+            }
+            Set<Row> leftRows = new TreeSet<Row>(comparator);
+            leftRows.addAll(buildLeftRowsJoin(csi.getLeftInnerConstraints(),
+                    comparator, printIndentation + printIndentStep));
+            leftRows.addAll(buildLeftRowsJoin(csi.getRightInnerConstraints(),
+                    comparator, printIndentation + printIndentStep));
             return leftRows;
         }
-
-        Set<Row> leftRows = new HashSet<Row>();
-        Constraint leftConstraint = csi.getLeftConstraint();
-        QueryResult leftResult = execute(null, left, leftConstraint, null, 0,
-                -1);
+        Set<Row> leftRows = new TreeSet<Row>(comparator);
+        QueryResult leftResult = execute(null, csi.getSource().getLeft(),
+                csi.getLeftConstraint(), null, 0, -1, printIndentation);
         for (Row row : JcrUtils.getRows(leftResult)) {
             leftRows.add(row);
         }
         return leftRows;
     }
 
-    private Set<Row> buildRightRowsJoin(Source right, ConstraintSplitInfo csi,
-            List<Constraint> rightConstraints) throws RepositoryException {
+    /**
+     * @param csi
+     *            contains 'WHERE' constraints and the source information
+     * @param rightConstraints
+     *            contains 'ON' constraints
+     * @param ignoreWhereConstraints
+     * @param comparator
+     *            used to merge similar rows together
+     * @param printIndentation
+     *            used in logging
+     * @return the right-side dataset of the join operation
+     * @throws RepositoryException
+     */
+    private Set<Row> buildRightRowsJoin(ConstraintSplitInfo csi,
+            List<Constraint> rightConstraints, boolean ignoreWhereConstraints,
+            Comparator<Row> comparator, int printIndentation)
+            throws RepositoryException {
 
         if (csi.isMultiple()) {
-            // this *needs* to merge automatically multiple sets of nodes
-            Set<Row> rightRows = new TreeSet<Row>(
-                    buildSimplePathRowComparator());
-            for (ConstraintSplitInfo child : csi.getInnerConstraints()) {
-                rightRows.addAll(buildRightRowsJoin(right, child,
-                        rightConstraints));
-            }
+            if (log.isDebugEnabled()) {
+                log.debug(genString(printIndentation)
+                        + "SQL2 JOIN RIGHT SIDE there are multiple inner splits.");
+            }
+            Set<Row> rightRows = new TreeSet<Row>(comparator);
+            rightRows.addAll(buildRightRowsJoin(csi.getLeftInnerConstraints(),
+                    rightConstraints, ignoreWhereConstraints, comparator,
+                    printIndentation + printIndentStep));
+            rightRows.addAll(buildRightRowsJoin(csi.getRightInnerConstraints(),
+                    rightConstraints, ignoreWhereConstraints, comparator,
+                    printIndentation + printIndentStep));
             return rightRows;
         }
 
-        // TODO refactor to page automatically at 500 *if needed*
         if (rightConstraints.size() < 500) {
-            Set<Row> rightRows = new HashSet<Row>();
+            Set<Row> rightRows = new TreeSet<Row>(comparator);
+            List<Constraint> localRightContraints = rightConstraints;
             Constraint rightConstraint = Constraints.and(qomFactory,
-                    Constraints.or(qomFactory, rightConstraints),
+                    Constraints.or(qomFactory, localRightContraints),
                     csi.getRightConstraint());
-            QueryResult rightResult = execute(null, right, rightConstraint,
-                    null, 0, -1);
+            if (ignoreWhereConstraints) {
+                rightConstraint = Constraints.or(qomFactory,
+                        localRightContraints);
+            }
+            QueryResult rightResult = execute(null, csi.getSource().getRight(),
+                    rightConstraint, null, 0, -1, printIndentation);
             for (Row row : JcrUtils.getRows(rightResult)) {
                 rightRows.add(row);
             }
             return rightRows;
         }
 
-        Set<Row> rightRows = new HashSet<Row>();
+        // the 'batch by 500' approach
+        Set<Row> rightRows = new TreeSet<Row>(comparator);
         for (int i = 0; i < rightConstraints.size(); i += 500) {
-            Constraint rightConstraint = Constraints
-                    .and(qomFactory,
-                            Constraints.or(
-                                    qomFactory,
-                                    rightConstraints.subList(
-                                            i,
-                                            Math.min(i + 500,
-                                                    rightConstraints.size()))),
-                            csi.getRightConstraint());
-            QueryResult rightResult = execute(null, right, rightConstraint,
-                    null, 0, -1);
+            if (log.isDebugEnabled()) {
+                log.debug(genString(printIndentation)
+                        + "SQL2 JOIN RIGHT SIDE executing batch # " + i + ".");
+            }
+            List<Constraint> localRightContraints = rightConstraints.subList(i,
+                    Math.min(i + 500, rightConstraints.size()));
+            Constraint rightConstraint = Constraints.and(qomFactory,
+                    Constraints.or(qomFactory, localRightContraints),
+                    csi.getRightConstraint());
+            if (ignoreWhereConstraints) {
+                rightConstraint = Constraints.or(qomFactory,
+                        localRightContraints);
+            }
+
+            QueryResult rightResult = execute(null, csi.getSource().getRight(),
+                    rightConstraint, null, 0, -1, printIndentation);
             for (Row row : JcrUtils.getRows(rightResult)) {
                 rightRows.add(row);
             }
@@ -261,28 +366,96 @@ public class QueryEngine {
         return rightRows;
     }
 
-    protected QueryResult execute(
-            Column[] columns, Selector selector, Constraint constraint,
-            Ordering[] orderings, long offset, long limit)
-            throws RepositoryException {
+    private static String genString(int len) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < len; i++) {
+            sb.append(" ");
+        }
+        return sb.toString();
+    }
+
+    private static void logQueryAnalysis(ConstraintSplitInfo csi,
+            int printIndentation) throws RepositoryException {
+        if (!log.isDebugEnabled()) {
+            return;
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append(genString(printIndentation));
+        sb.append("SQL2 JOIN analysis:");
+        sb.append(IOUtils.LINE_SEPARATOR);
+        sb.append(constraintSplitInfoToString(csi, 2));
+        log.debug(sb.toString());
+    }
+
+    private static String constraintSplitInfoToString(ConstraintSplitInfo csi,
+            int printIndentation) throws RepositoryException {
+
+        if (csi.isMultiple()) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(genString(printIndentation));
+            sb.append("SQL2 JOIN inner split -> ");
+            sb.append(IOUtils.LINE_SEPARATOR);
+            sb.append(genString(printIndentation));
+            sb.append("+");
+            sb.append(IOUtils.LINE_SEPARATOR);
+            sb.append(constraintSplitInfoToString(
+                    csi.getLeftInnerConstraints(), printIndentation
+                            + printIndentStep));
+            sb.append(IOUtils.LINE_SEPARATOR);
+            sb.append(genString(printIndentation));
+            sb.append("+");
+            sb.append(IOUtils.LINE_SEPARATOR);
+            sb.append(constraintSplitInfoToString(
+                    csi.getRightInnerConstraints(), printIndentation
+                            + printIndentStep));
+            return sb.toString();
+        }
+
+        StringBuilder sb = new StringBuilder();
+        sb.append(genString(printIndentation));
+        sb.append("SQL2 JOIN source: ");
+        sb.append(csi.getSource());
+        sb.append(IOUtils.LINE_SEPARATOR);
+        sb.append(genString(printIndentation));
+        sb.append("SQL2 JOIN left constraint: ");
+        sb.append(csi.getLeftConstraint());
+        sb.append(IOUtils.LINE_SEPARATOR);
+        sb.append(genString(printIndentation));
+        sb.append("SQL2 JOIN right constraint: ");
+        sb.append(csi.getRightConstraint());
+        return sb.toString();
+    }
+
+    protected QueryResult execute(Column[] columns, Selector selector,
+            Constraint constraint, Ordering[] orderings, long offset,
+            long limit, int printIndentation) throws RepositoryException {
+        long time = System.currentTimeMillis();
+
         Map<String, NodeType> selectorMap = getSelectorNames(selector);
-        String[] selectorNames =
-            selectorMap.keySet().toArray(new String[selectorMap.size()]);
+        String[] selectorNames = selectorMap.keySet().toArray(
+                new String[selectorMap.size()]);
 
-        Map<String, PropertyValue> columnMap =
-            getColumnMap(columns, selectorMap);
-        String[] columnNames =
-            columnMap.keySet().toArray(new String[columnMap.size()]);
+        Map<String, PropertyValue> columnMap = getColumnMap(columns,
+                selectorMap);
+        String[] columnNames = columnMap.keySet().toArray(
+                new String[columnMap.size()]);
 
         try {
-            RowIterator rows = new RowIteratorAdapter(lqf.execute(
-                    columnMap, selector, constraint));
-            QueryResult result =
-                new SimpleQueryResult(columnNames, selectorNames, rows);
-            return sort(result, orderings, offset, limit);
+            RowIterator rows = new RowIteratorAdapter(lqf.execute(columnMap,
+                    selector, constraint));
+            QueryResult result = new SimpleQueryResult(columnNames,
+                    selectorNames, rows);
+            return sort(result, orderings, evaluator, offset, limit);
         } catch (IOException e) {
-            throw new RepositoryException(
-                    "Failed to access the query index", e);
+            throw new RepositoryException("Failed to access the query index", e);
+        } finally {
+            if (log.isDebugEnabled()) {
+                time = System.currentTimeMillis() - time;
+                log.debug(genString(printIndentation) + "SQL2 SELECT took "
+                        + time + " ms. selector: " + selector
+                        + ", columns: " + Arrays.toString(columnNames)
+                        + ", constraint: " + constraint);
+            }
         }
     }
 
@@ -367,8 +540,8 @@ public class QueryEngine {
      * @return sorted query results
      * @throws RepositoryException if the results can not be sorted
      */
-    public QueryResult sort(
-            QueryResult result, final Ordering[] orderings,
+    protected static QueryResult sort(QueryResult result,
+            final Ordering[] orderings, OperandEvaluator evaluator,
             long offset, long limit) throws RepositoryException {
         if ((orderings != null && orderings.length > 0)
                 || offset != 0 || limit >= 0) {
@@ -380,7 +553,7 @@ public class QueryEngine {
             }
 
             if (orderings != null && orderings.length > 0) {
-                Collections.sort(rows, new RowComparator(orderings));
+                Collections.sort(rows, new RowComparator(orderings, evaluator));
             }
 
             if (offset > 0) {



Mime
View raw message