jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From alexparvule...@apache.org
Subject svn commit: r1138142 - 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 Tue, 21 Jun 2011 19:22:14 GMT
Author: alexparvulescu
Date: Tue Jun 21 19:22:13 2011
New Revision: 1138142

URL: http://svn.apache.org/viewvc?rev=1138142&view=rev
Log:
2.2: merged revision 1138141 (JCR-3000 SQL2 Join with OR clause still has some issues)

Added:
    jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitInfo.java
  (with props)
    jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
  (with props)
    jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/JoinTest.java
  (with props)
Modified:
    jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java

Added: 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=1138142&view=auto
==============================================================================
--- jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitInfo.java
(added)
+++ jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitInfo.java
Tue Jun 21 19:22:13 2011
@@ -0,0 +1,158 @@
+/*
+ * 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.core.query.lucene.join;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.query.qom.Constraint;
+import javax.jcr.query.qom.Join;
+import javax.jcr.query.qom.QueryObjectModelFactory;
+
+class ConstraintSplitInfo {
+
+    private final QueryObjectModelFactory factory;
+
+    private final Join source;
+
+    private final List<Constraint> leftConstraints;
+
+    private final List<Constraint> rightConstraints;
+
+    private boolean isMultiple;
+    
+    private boolean hasLeftConstraints;
+    
+    private boolean hasRightConstraints;
+
+    private ConstraintSplitInfo leftInnerConstraints = null;
+
+    private ConstraintSplitInfo rightInnerConstraints = null;
+
+    public ConstraintSplitInfo(QueryObjectModelFactory factory, Join source) {
+        this(factory, source, new ArrayList<Constraint>(),
+                new ArrayList<Constraint>());
+    }
+
+    private ConstraintSplitInfo(QueryObjectModelFactory factory, Join source,
+            List<Constraint> leftConstraints, List<Constraint> rightConstraints)
{
+        this.factory = factory;
+        this.source = source;
+        this.leftConstraints = leftConstraints;
+        this.rightConstraints = rightConstraints;
+        this.isMultiple = false;
+        this.hasLeftConstraints = false;
+        this.hasRightConstraints = false;
+    }
+
+    public void addLeftConstraint(Constraint c) {
+        if (isMultiple) {
+            leftInnerConstraints.addLeftConstraint(c);
+            rightInnerConstraints.addLeftConstraint(c);
+            return;
+        }
+        leftConstraints.add(c);
+        this.hasLeftConstraints = true;
+    }
+
+    public void addRightConstraint(Constraint c) {
+        if (isMultiple) {
+            leftInnerConstraints.addRightConstraint(c);
+            rightInnerConstraints.addRightConstraint(c);
+            return;
+        }
+        rightConstraints.add(c);
+        this.hasRightConstraints = true;
+    }
+
+    public void splitOr() {
+
+        if (isMultiple) {
+            // 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;
+
+        this.leftConstraints.clear();
+        this.rightConstraints.clear();
+        this.hasLeftConstraints = false;
+        this.hasRightConstraints = false;
+    }
+
+    public boolean isMultiple() {
+        return isMultiple;
+    }
+
+    public ConstraintSplitInfo getLeftInnerConstraints() {
+        return leftInnerConstraints;
+    }
+
+    public ConstraintSplitInfo getRightInnerConstraints() {
+        return rightInnerConstraints;
+    }
+
+    public Join getSource() {
+        return source;
+    }
+
+    /**
+     * @return the left constraint
+     */
+    public Constraint getLeftConstraint() throws RepositoryException {
+        return Constraints.and(factory, leftConstraints);
+    }
+
+    /**
+     * @return the right constraint
+     */
+    public Constraint getRightConstraint() throws RepositoryException {
+        return Constraints.and(factory, rightConstraints);
+    }
+
+    public boolean isHasLeftConstraints() {
+        return hasLeftConstraints;
+    }
+
+    public boolean isHasRightConstraints() {
+        return hasRightConstraints;
+    }
+
+    @Override
+    public String toString() {
+        if (isMultiple) {
+            return "ConstraintSplitInfo [multiple=" + ", leftInnerConstraints="
+                    + leftInnerConstraints + ", rightInnerConstraints="
+                    + rightInnerConstraints + "]";
+        }
+        return "ConstraintSplitInfo [single" + ", leftConstraints="
+                + leftConstraints + ", rightConstraints=" + rightConstraints
+                + ", hasLeftConstraints=" + hasLeftConstraints
+                + ", hasRightConstraints=" + hasRightConstraints + "]";
+    }
+
+}

Propchange: jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitInfo.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: 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=1138142&view=auto
==============================================================================
--- jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
(added)
+++ jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
Tue Jun 21 19:22:13 2011
@@ -0,0 +1,585 @@
+/*
+ * 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.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_RIGHT_OUTER;
+import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_ORDER_DESCENDING;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.Workspace;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NodeTypeManager;
+import javax.jcr.nodetype.PropertyDefinition;
+import javax.jcr.query.InvalidQueryException;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+import javax.jcr.query.RowIterator;
+import javax.jcr.query.qom.Column;
+import javax.jcr.query.qom.Constraint;
+import javax.jcr.query.qom.Join;
+import javax.jcr.query.qom.Operand;
+import javax.jcr.query.qom.Ordering;
+import javax.jcr.query.qom.PropertyValue;
+import javax.jcr.query.qom.QueryObjectModelFactory;
+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 static class RowComparator implements Comparator<Row> {
+
+        private final ValueComparator comparator = new ValueComparator();
+
+        private final Ordering[] orderings;
+        
+        private final OperandEvaluator evaluator;
+
+        private RowComparator(Ordering[] orderings, OperandEvaluator evaluator) {
+            this.orderings = orderings;
+            this.evaluator = evaluator;
+        }
+
+        public int compare(Row a, Row b) {
+            try {
+                for (Ordering ordering : orderings) {
+                    Operand operand = ordering.getOperand();
+                    Value[] va = evaluator.getValues(operand, a);
+                    Value[] vb = evaluator.getValues(operand, b);
+                    int d = compare(va, vb);
+                    if (d != 0) {
+                        if (JCR_ORDER_DESCENDING.equals(ordering.getOrder())) {
+                            return -d;
+                        } else {
+                            return d;
+                        }
+                    }
+                }
+                return 0;
+            } catch (RepositoryException e) {
+                throw new RuntimeException(
+                        "Unable to compare rows " + a + " and " + b, e);
+            }
+        }
+
+        private int compare(Value[] a, Value[] b) {
+            for (int i = 0; i < a.length && i < b.length; i++) {
+                int d = comparator.compare(a[i], b[i]);
+                if (d != 0) {
+                    return d;
+                }
+            }
+            return a.length - b.length;
+        }
+
+    }
+
+    private final LuceneQueryFactory lqf;
+
+    private final NodeTypeManager ntManager;
+
+    private final QueryObjectModelFactory qomFactory;
+
+    private final ValueFactory valueFactory;
+
+    private final OperandEvaluator evaluator;
+
+    public QueryEngine(Session session, LuceneQueryFactory lqf,
+            Map<String, Value> variables) throws RepositoryException {
+        this.lqf = lqf;
+
+        Workspace workspace = session.getWorkspace();
+        this.ntManager = workspace.getNodeTypeManager();
+        this.qomFactory = workspace.getQueryManager().getQOMFactory();
+        this.valueFactory = session.getValueFactory();
+
+        this.evaluator = new OperandEvaluator(valueFactory, variables);
+    }
+
+    public QueryResult execute(Column[] columns, Source source,
+            Constraint constraint, Ordering[] orderings, long offset, long limit)
+            throws RepositoryException {
+        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, 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(ConstraintSplitInfo csi,
+            Comparator<Row> comparator, int printIndentation)
+            throws RepositoryException {
+
+        if (csi.isMultiple()) {
+            if (log.isDebugEnabled()) {
+                log.debug(genString(printIndentation)
+                        + "SQL2 JOIN LEFT SIDE there are multiple inner splits.");
+            }
+            Set<Row> leftRows = new TreeSet<Row>(comparator);
+            if (csi.getLeftInnerConstraints().isHasLeftConstraints()) {
+                leftRows.addAll(buildLeftRowsJoin(
+                        csi.getLeftInnerConstraints(), comparator,
+                        printIndentation + printIndentStep));
+            }
+            if (csi.getRightInnerConstraints().isHasLeftConstraints()) {
+                leftRows.addAll(buildLeftRowsJoin(
+                        csi.getRightInnerConstraints(), comparator,
+                        printIndentation + printIndentStep));
+            }
+            return leftRows;
+        }
+        
+        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;
+    }
+
+    /**
+     * @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()) {
+            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;
+        }
+
+        if (rightConstraints.size() < 500) {
+            Set<Row> rightRows = new TreeSet<Row>(comparator);
+            List<Constraint> localRightContraints = rightConstraints;
+            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);
+            }
+            return rightRows;
+        }
+
+        // the 'batch by 500' approach
+        Set<Row> rightRows = new TreeSet<Row>(comparator);
+        for (int i = 0; i < rightConstraints.size(); i += 500) {
+            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);
+            }
+        }
+        return rightRows;
+    }
+
+    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()]);
+
+        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, evaluator, offset, limit);
+        } catch (IOException 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);
+            }
+        }
+    }
+
+    private Map<String, PropertyValue> getColumnMap(
+            Column[] columns, Map<String, NodeType> selectors)
+            throws RepositoryException {
+        Map<String, PropertyValue> map =
+            new LinkedHashMap<String, PropertyValue>();
+        if (columns != null && columns.length > 0) {
+            for (int i = 0; i < columns.length; i++) {
+                String name = columns[i].getColumnName();
+                if (name != null) {
+                    map.put(name, qomFactory.propertyValue(
+                            columns[i].getSelectorName(),
+                            columns[i].getPropertyName()));
+                } else {
+                    String selector = columns[i].getSelectorName();
+                    map.putAll(getColumnMap(selector, selectors.get(selector)));
+                }
+            }
+        } else {
+            for (Map.Entry<String, NodeType> selector : selectors.entrySet()) {
+                map.putAll(getColumnMap(
+                        selector.getKey(), selector.getValue()));
+            }
+        }
+        return map;
+    }
+
+    private Map<String, PropertyValue> getColumnMap(
+            String selector, NodeType type) throws RepositoryException {
+        Map<String, PropertyValue> map =
+            new LinkedHashMap<String, PropertyValue>();
+        for (PropertyDefinition definition : type.getPropertyDefinitions()) {
+            String name = definition.getName();
+            if (!definition.isMultiple() && !"*".equals(name)) {
+                // TODO: Add proper quoting
+                map.put(selector + "." + name,
+                        qomFactory.propertyValue(selector, name));
+            }
+        }
+        return map;
+    }
+
+    private Map<String, NodeType> getSelectorNames(Source source)
+            throws RepositoryException {
+        if (source instanceof Selector) {
+            Selector selector = (Selector) source;
+            return Collections.singletonMap(
+                    selector.getSelectorName(), getNodeType(selector));
+        } else if (source instanceof Join) {
+            Join join = (Join) source;
+            Map<String, NodeType> map = new LinkedHashMap<String, NodeType>();
+            map.putAll(getSelectorNames(join.getLeft()));
+            map.putAll(getSelectorNames(join.getRight()));
+            return map;
+        } else {
+            throw new UnsupportedRepositoryOperationException(
+                    "Unknown source type: " + source);
+        }
+    }
+
+    private NodeType getNodeType(Selector selector) throws RepositoryException {
+        try {
+            return ntManager.getNodeType(selector.getNodeTypeName());
+        } catch (NoSuchNodeTypeException e) {
+            throw new InvalidQueryException(
+                    "Selected node type does not exist: " + selector, e);
+        }
+    }
+
+    /**
+     * Sorts the given query results according to the given QOM orderings.
+     * If one or more orderings have been specified, this method will iterate
+     * through the entire original result set, order the collected rows, and
+     * return a new result set based on the sorted collection of rows.
+     *
+     * @param result original query results
+     * @param orderings QOM orderings
+     * @param offset result offset
+     * @param limit result limit
+     * @return sorted query results
+     * @throws RepositoryException if the results can not be sorted
+     */
+    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) {
+            List<Row> rows = new ArrayList<Row>();
+
+            RowIterator iterator = result.getRows();
+            while (iterator.hasNext()) {
+                rows.add(iterator.nextRow());
+            }
+
+            if (orderings != null && orderings.length > 0) {
+                Collections.sort(rows, new RowComparator(orderings, evaluator));
+            }
+
+            if (offset > 0) {
+                int size = rows.size();
+                rows = rows.subList((int) Math.min(offset, size), size);
+            }
+            if (limit >= 0) {
+                int size = rows.size();
+                rows = rows.subList(0, (int) Math.min(limit, size));
+            }
+
+            return new SimpleQueryResult(
+                    result.getColumnNames(), result.getSelectorNames(),
+                    new RowIteratorAdapter(rows));
+        } else {
+            return result;
+        }
+    }
+
+}

Propchange: jackrabbit/branches/2.2/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/JoinTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/JoinTest.java?rev=1138142&view=auto
==============================================================================
--- jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/JoinTest.java
(added)
+++ jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/JoinTest.java
Tue Jun 21 19:22:13 2011
@@ -0,0 +1,167 @@
+/*
+ * 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.core.query;
+
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+
+/**
+ * Test case for JOIN queries with JCR_SQL2
+ */
+public class JoinTest extends AbstractQueryTest {
+
+    private Node node;
+
+    private Node n1;
+
+    private Node n2;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        node = testRootNode.addNode("jointest", "nt:unstructured");
+
+        n1 = node.addNode("node1", "nt:unstructured");
+        n1.addMixin(NodeType.MIX_REFERENCEABLE);
+
+        n2 = node.addNode("node2", "nt:unstructured");
+        n2.addMixin(NodeType.MIX_REFERENCEABLE);
+
+        Node n3 = node.addNode("node3", "nt:unstructured");
+        n3.addMixin(NodeType.MIX_REFERENCEABLE);
+        n3.setProperty("testref",
+                new String[] { n1.getIdentifier(), n2.getIdentifier() },
+                PropertyType.REFERENCE);
+
+        Node parent2 = testRootNode
+                .addNode("jointest_other", "nt:unstructured");
+        parent2.setProperty("p", "abc");
+
+        Node p2n1 = parent2.addNode("node4", "nt:unstructured");
+        p2n1.setProperty("p", "abc");
+
+        parent2.addNode("node5", "nt:unstructured");
+
+        testRootNode.getSession().save();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        node.remove();
+        testRootNode.getSession().save();
+        super.tearDown();
+    }
+
+    /**
+     * Test case for <a
+     * href="https://issues.apache.org/jira/browse/JCR-2718">JCR-2718</a>
+     */
+    public void testMultiValuedReferenceJoin() throws Exception {
+        String join = "SELECT a.*, b.*"
+                + " FROM [nt:unstructured] AS a"
+                + " INNER JOIN [nt:unstructured] AS b ON a.[jcr:uuid] = b.testref";
+        checkResult(qm.createQuery(join, Query.JCR_SQL2).execute(), 2);
+    }
+
+    /**
+     * Test case for <a
+     * href="https://issues.apache.org/jira/browse/JCR-2852">JCR-2852</a>
+     */
+    public void testJoinWithOR() throws Exception {
+
+        String join = "SELECT a.*, b.*"
+                + " FROM [nt:unstructured] AS a"
+                + " INNER JOIN [nt:unstructured] AS b ON a.[jcr:uuid] = b.testref WHERE "
+                + "a.[jcr:primaryType] IS NOT NULL OR b.[jcr:primaryType] IS NOT NULL";
+
+        Query q = qm.createQuery(join, Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        checkResult(result, 2);
+    }
+
+    public void testJoinWithOR2() throws Exception {
+
+        String join = "SELECT a.*, b.*"
+                + " FROM [nt:unstructured] AS a"
+                + " INNER JOIN [nt:unstructured] AS b ON ISCHILDNODE(b, a) WHERE "
+                + "a.[p] = 'abc' or b.[p] = 'abc' ";
+
+        Query q = qm.createQuery(join, Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        checkResult(result, 2);
+    }
+
+    public void testJoinWithOR3() throws Exception {
+        StringBuilder join = new StringBuilder(
+                "SELECT a.* FROM [nt:unstructured] AS a");
+        join.append("  INNER JOIN [nt:unstructured] AS b ON ISCHILDNODE(b, a) ");
+        join.append("  WHERE  ");
+        join.append("  ( CONTAINS(b.*, 'abc' ) OR CONTAINS(a.*, 'abc') )  ");
+        join.append("  AND ");
+        join.append("  NAME(b) = 'node4' ");
+
+        Query q = qm.createQuery(join.toString(), Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        checkResult(result, 1);
+    }
+
+    /**
+     * Test case for <a
+     * href="https://issues.apache.org/jira/browse/JCR-2852">JCR-2852</a> <br>
+     * <p>
+     * Test inspired by <a
+     * href="http://markmail.org/message/gee5yyygozestsml">this discussion</a>
+     */
+    public void testMegaJoin() throws Exception {
+
+        // WHERE
+        // ( (ISSAMENODE(projects,
+        // '/repository/projects/U970f5509-54de-46d8-88bd-bc1a94ab85eb')))
+        // AND
+        // ( ( ISDESCENDANTNODE( projects, '/repository/projects') AND
+        // eventclassassociations.active = true )
+        // or
+        // ( ISDESCENDANTNODE( projects, '/repository/template') )
+        // )
+        // AND ((NAME(parentRelationshipStatus) = 'parentRelationshipStatus'))
+
+        StringBuilder join = new StringBuilder(
+                "SELECT a.*, b.* FROM [nt:unstructured] AS a");
+        join.append("  INNER JOIN [nt:unstructured] AS b ON a.[jcr:uuid] = b.testref ");
+        join.append("  WHERE  ");
+        join.append("  ISSAMENODE(b, '/testroot/jointest/node3') ");
+        join.append("  AND ");
+        join.append("  ( ");
+        join.append("    ( ");
+        join.append("    ISDESCENDANTNODE(b, '/testroot/jointest') ");
+        join.append("    AND ");
+        join.append("    b.testref IS NOT NULL ");
+        join.append("    ) ");
+        join.append("    OR ");
+        join.append("    ISDESCENDANTNODE(a, '/testroot/jointest') ");
+        join.append("  ) ");
+        join.append("  AND ");
+        join.append(" (NAME(b) = 'node3') ");
+
+        Query q = qm.createQuery(join.toString(), Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        checkResult(result, 2);
+    }
+}

Propchange: jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/JoinTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java?rev=1138142&r1=1138141&r2=1138142&view=diff
==============================================================================
--- jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java
(original)
+++ jackrabbit/branches/2.2/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java
Tue Jun 21 19:22:13 2011
@@ -65,6 +65,9 @@ public class TestAll extends TestCase {
         suite.addTestSuite(SimilarQueryTest.class);
         suite.addTestSuite(FulltextSQL2QueryTest.class);
         suite.addTestSuite(LimitAndOffsetTest.class);
+        suite.addTestSuite(SQL2NodeLocalNameTest.class);
+        suite.addTestSuite(SQL2OuterJoinTest.class);
+        suite.addTestSuite(SQL2PathEscapingTest.class);
 
         return suite;
     }



Mime
View raw message