From commits-return-11135-apmail-jackrabbit-commits-archive=jackrabbit.apache.org@jackrabbit.apache.org Mon May 23 15:17:00 2011 Return-Path: X-Original-To: apmail-jackrabbit-commits-archive@www.apache.org Delivered-To: apmail-jackrabbit-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 1A63D495C for ; Mon, 23 May 2011 15:17:00 +0000 (UTC) Received: (qmail 78310 invoked by uid 500); 23 May 2011 15:17:00 -0000 Delivered-To: apmail-jackrabbit-commits-archive@jackrabbit.apache.org Received: (qmail 78282 invoked by uid 500); 23 May 2011 15:17:00 -0000 Mailing-List: contact commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jackrabbit.apache.org Delivered-To: mailing list commits@jackrabbit.apache.org Received: (qmail 78275 invoked by uid 99); 23 May 2011 15:16:59 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 23 May 2011 15:16:59 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 23 May 2011 15:16:49 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 5C448238890D; Mon, 23 May 2011 15:16:27 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit 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 -0000 To: commits@jackrabbit.apache.org From: alexparvulescu@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20110523151627.5C448238890D@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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 leftConstraints = new ArrayList(); + private final Join source; - private final List rightConstraints = new ArrayList(); + private final List leftConstraints; + + private final List rightConstraints; private boolean isMultiple; - private final List innerConstraints = new ArrayList(); + 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(), + new ArrayList()); } - private ConstraintSplitInfo(QueryObjectModelFactory factory, + private ConstraintSplitInfo(QueryObjectModelFactory factory, Join source, List leftConstraints, List 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(leftConstraints), + new ArrayList(rightConstraints)); + this.leftInnerConstraints = csi1; + + ConstraintSplitInfo csi2 = new ConstraintSplitInfo(factory, source, + new ArrayList(leftConstraints), + new ArrayList(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 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 leftSelectors, - Set rightSelectors) throws RepositoryException { + Set 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 selectors) + private void splitBySelectors(ConstraintSplitInfo constraintSplitInfo, Constraint constraint, Set 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 null 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 excludingOuterJoinRowsSet, Comparator rowComparator) throws RepositoryException { - RowIterator joinRows; + Map> map = buildRightRowValues(rightRows); - Map> map = new HashMap>(); - for (Row row : new RowIterable(rightRows)) { - for (String value : getRightValues(row)) { - List rows = map.get(value); - if (rows == null) { - rows = new ArrayList(); - map.put(value, rows); + if (JCR_JOIN_TYPE_INNER.equals(type) && !map.isEmpty()) { + List rows = new ArrayList(); + for (Row leftRow : new RowIterable(leftRows)) { + for (String value : getLeftValues(leftRow)) { + List 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 rows = new ArrayList(); for (Row leftRow : new RowIterable(leftRows)) { for (String value : getLeftValues(leftRow)) { List 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> buildRightRowValues(RowIterator rightRows) + throws RepositoryException { + Map> map = new HashMap>(); + for (Row row : new RowIterable(rightRows)) { + for (String value : getRightValues(row)) { + List rows = map.get(value); + if (rows == null) { + rows = new ArrayList(); + 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 { + private static class RowComparator implements Comparator { 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 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 leftRows = buildLeftRowsJoin(left, splitter.getConstraintSplitInfo()); - - Source right = join.getRight(); - List 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 buildSimplePathRowComparator() { - return new Comparator() { - - 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 leftCo = new RowPathComparator( + merger.getLeftSelectors()); + Set 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 rightConstraints = merger + .getRightJoinConstraints(leftRows); + Comparator rightCo = new RowPathComparator( + merger.getRightSelectors()); + + boolean isOuterJoin = JCR_JOIN_TYPE_LEFT_OUTER.equalsIgnoreCase(join + .getJoinType()); + + Set rightRows = buildRightRowsJoin(csInfo, rightConstraints, isOuterJoin, + rightCo, printIndentation + printIndentStep); + + // this has to be initialized as null + Set 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 buildLeftRowsJoin(Source left, ConstraintSplitInfo csi) + private Set buildLeftRowsJoin(ConstraintSplitInfo csi, + Comparator comparator, int printIndentation) throws RepositoryException { if (csi.isMultiple()) { - // this *needs* to merge automatically multiple sets of nodes - Set leftRows = new TreeSet(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 leftRows = new TreeSet(comparator); + leftRows.addAll(buildLeftRowsJoin(csi.getLeftInnerConstraints(), + comparator, printIndentation + printIndentStep)); + leftRows.addAll(buildLeftRowsJoin(csi.getRightInnerConstraints(), + comparator, printIndentation + printIndentStep)); return leftRows; } - - Set leftRows = new HashSet(); - Constraint leftConstraint = csi.getLeftConstraint(); - QueryResult leftResult = execute(null, left, leftConstraint, null, 0, - -1); + Set leftRows = new TreeSet(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 buildRightRowsJoin(Source right, ConstraintSplitInfo csi, - List 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 buildRightRowsJoin(ConstraintSplitInfo csi, + List rightConstraints, boolean ignoreWhereConstraints, + Comparator comparator, int printIndentation) + throws RepositoryException { if (csi.isMultiple()) { - // this *needs* to merge automatically multiple sets of nodes - Set rightRows = new TreeSet( - 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 rightRows = new TreeSet(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 rightRows = new HashSet(); + Set rightRows = new TreeSet(comparator); + List 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 rightRows = new HashSet(); + // the 'batch by 500' approach + Set rightRows = new TreeSet(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 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 selectorMap = getSelectorNames(selector); - String[] selectorNames = - selectorMap.keySet().toArray(new String[selectorMap.size()]); + String[] selectorNames = selectorMap.keySet().toArray( + new String[selectorMap.size()]); - Map columnMap = - getColumnMap(columns, selectorMap); - String[] columnNames = - columnMap.keySet().toArray(new String[columnMap.size()]); + Map 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) {