You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2010/10/18 14:55:40 UTC
svn commit: r1023777 [1/2] -
/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/
Author: jukka
Date: Mon Oct 18 12:55:39 2010
New Revision: 1023777
URL: http://svn.apache.org/viewvc?rev=1023777&view=rev
Log:
JCR-2715: Improved join query performance
Use a more efficient join merging mechanism instead of the previous O(n^2) scan.
Added:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoinMerger.java (with props)
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitter.java (with props)
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoinMerger.java (with props)
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoinMerger.java (with props)
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java (with props)
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/OperandEvaluator.java (with props)
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/SameNodeJoinMerger.java (with props)
Modified:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Constraints.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoinMerger.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoinMerger.java?rev=1023777&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoinMerger.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoinMerger.java Mon Oct 18 12:55:39 2010
@@ -0,0 +1,84 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.Row;
+import javax.jcr.query.qom.ChildNodeJoinCondition;
+import javax.jcr.query.qom.Constraint;
+import javax.jcr.query.qom.Join;
+import javax.jcr.query.qom.PropertyValue;
+import javax.jcr.query.qom.QueryObjectModelFactory;
+
+class ChildNodeJoinMerger extends JoinMerger {
+
+ private final String childSelector;
+
+ private final String parentSelector;
+
+ public ChildNodeJoinMerger(
+ Join join, Map<String, PropertyValue> columns,
+ OperandEvaluator evaluator, QueryObjectModelFactory factory,
+ ChildNodeJoinCondition condition)
+ throws RepositoryException {
+ super(join, columns, evaluator, factory);
+ this.childSelector = condition.getChildSelectorName();
+ this.parentSelector = condition.getParentSelectorName();
+ }
+
+ @Override
+ public Set<String> getLeftValues(Row row) throws RepositoryException {
+ return getValues(leftSelectors, row);
+ }
+
+ @Override
+ public Set<String> getRightValues(Row row) throws RepositoryException {
+ return getValues(rightSelectors, row);
+ }
+
+ @Override
+ public Constraint getRightJoinConstraint(List<Row> leftRows)
+ throws RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ private Set<String> getValues(Set<String> selectors, Row row)
+ throws RepositoryException {
+ if (selectors.contains(childSelector)) {
+ Node node = row.getNode(childSelector);
+ if (node != null && node.getDepth() > 0) {
+ return Collections.singleton(node.getParent().getPath());
+ }
+ } else if (selectors.contains(parentSelector)) {
+ Node node = row.getNode(parentSelector);
+ if (node != null) {
+ return Collections.singleton(node.getPath());
+ }
+ } else {
+ throw new RepositoryException("Invalid child node join");
+ }
+ return Collections.emptySet();
+ }
+
+}
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoinMerger.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitter.java?rev=1023777&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitter.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitter.java Mon Oct 18 12:55:39 2010
@@ -0,0 +1,237 @@
+/*
+ * 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.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.query.qom.And;
+import javax.jcr.query.qom.ChildNode;
+import javax.jcr.query.qom.Comparison;
+import javax.jcr.query.qom.Constraint;
+import javax.jcr.query.qom.DescendantNode;
+import javax.jcr.query.qom.DynamicOperand;
+import javax.jcr.query.qom.FullTextSearchScore;
+import javax.jcr.query.qom.Length;
+import javax.jcr.query.qom.LowerCase;
+import javax.jcr.query.qom.NodeLocalName;
+import javax.jcr.query.qom.NodeName;
+import javax.jcr.query.qom.Not;
+import javax.jcr.query.qom.Or;
+import javax.jcr.query.qom.PropertyExistence;
+import javax.jcr.query.qom.PropertyValue;
+import javax.jcr.query.qom.QueryObjectModelFactory;
+import javax.jcr.query.qom.SameNode;
+import javax.jcr.query.qom.UpperCase;
+
+/**
+ * Returns a mapped constraint that only refers to the given set of
+ * selectors. The returned constraint is guaranteed to match an as small
+ * as possible superset of the node tuples matched by the given original
+ * constraints.
+ *
+ * @param constraint original constraint
+ * @param selectors target selectors
+ * @return mapped constraint
+ * @throws RepositoryException if the constraint mapping fails
+ */
+class ConstraintSplitter {
+
+ private final QueryObjectModelFactory factory;
+
+ private final Set<String> leftSelectors;
+
+ private final Set<String> rightSelectors;
+
+ private final List<Constraint> leftConstraints =
+ new ArrayList<Constraint>();
+
+ private final List<Constraint> rightConstraints =
+ new ArrayList<Constraint>();
+
+ public ConstraintSplitter(
+ Constraint constraint, QueryObjectModelFactory factory,
+ Set<String> leftSelectors, Set<String> rightSelectors)
+ throws RepositoryException {
+ this.factory = factory;
+ this.leftSelectors = leftSelectors;
+ this.rightSelectors = rightSelectors;
+
+ if (constraint != null) {
+ split(constraint);
+ }
+ }
+
+ /**
+ * @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);
+ }
+
+ private void split(Constraint constraint) throws RepositoryException {
+ if (constraint instanceof Not) {
+ splitNot((Not) constraint);
+ } else if (constraint instanceof And) {
+ And and = (And) constraint;
+ split(and.getConstraint1());
+ split(and.getConstraint2());
+ } else {
+ splitBySelectors(constraint, getSelectorNames(constraint));
+ }
+ }
+
+ private void splitNot(Not not) throws RepositoryException {
+ Constraint constraint = not.getConstraint();
+ if (constraint instanceof Not) {
+ split(((Not) constraint).getConstraint());
+ } else if (constraint instanceof And) {
+ And and = (And) constraint;
+ split(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()),
+ factory.not(or.getConstraint2())));
+ } else {
+ splitBySelectors(not, getSelectorNames(constraint));
+ }
+ }
+
+ private void splitBySelectors(Constraint constraint, Set<String> selectors)
+ throws UnsupportedRepositoryOperationException {
+ if (leftSelectors.containsAll(selectors)) {
+ leftConstraints.add(constraint);
+ } else if (rightSelectors.containsAll(selectors)) {
+ rightConstraints.add(constraint);
+ } else {
+ throw new UnsupportedRepositoryOperationException(
+ "Unable to split a constraint that references"
+ + " both sides of a join: " + constraint);
+ }
+ }
+
+ /**
+ * Returns the names of the selectors referenced by the given constraint.
+ *
+ * @param constraint constraint
+ * @return referenced selector names
+ * @throws UnsupportedRepositoryOperationException
+ * if the constraint type is unknown
+ */
+ private Set<String> getSelectorNames(Constraint constraint)
+ throws UnsupportedRepositoryOperationException {
+ if (constraint instanceof And) {
+ And and = (And) constraint;
+ return getSelectorNames(and.getConstraint1(), and.getConstraint2());
+ } else if (constraint instanceof Or) {
+ Or or = (Or) constraint;
+ return getSelectorNames(or.getConstraint1(), or.getConstraint2());
+ } else if (constraint instanceof Not) {
+ Not not = (Not) constraint;
+ return getSelectorNames(not.getConstraint());
+ } else if (constraint instanceof PropertyExistence) {
+ PropertyExistence pe = (PropertyExistence) constraint;
+ return Collections.singleton(pe.getSelectorName());
+ } else if (constraint instanceof Comparison) {
+ Comparison c = (Comparison) constraint;
+ return Collections.singleton(getSelectorName(c.getOperand1()));
+ } else if (constraint instanceof SameNode) {
+ SameNode sn = (SameNode) constraint;
+ return Collections.singleton(sn.getSelectorName());
+ } else if (constraint instanceof ChildNode) {
+ ChildNode cn = (ChildNode) constraint;
+ return Collections.singleton(cn.getSelectorName());
+ } else if (constraint instanceof DescendantNode) {
+ DescendantNode dn = (DescendantNode) constraint;
+ return Collections.singleton(dn.getSelectorName());
+ } else {
+ throw new UnsupportedRepositoryOperationException(
+ "Unknown constraint type: " + constraint);
+ }
+ }
+
+ /**
+ * Returns the combined set of selector names referenced by the given
+ * two constraint.
+ *
+ * @param a first constraint
+ * @param b second constraint
+ * @return selector names
+ * @throws UnsupportedRepositoryOperationException
+ * if the constraint types are unknown
+ */
+ private Set<String> getSelectorNames(Constraint a, Constraint b)
+ throws UnsupportedRepositoryOperationException {
+ Set<String> set = new HashSet<String>();
+ set.addAll(getSelectorNames(a));
+ set.addAll(getSelectorNames(b));
+ return set;
+ }
+
+ /**
+ * Returns the selector name referenced by the given dynamic operand.
+ *
+ * @param operand dynamic operand
+ * @return selector name
+ * @throws UnsupportedRepositoryOperationException
+ * if the operand type is unknown
+ */
+ private String getSelectorName(DynamicOperand operand)
+ throws UnsupportedRepositoryOperationException {
+ if (operand instanceof FullTextSearchScore) {
+ FullTextSearchScore ftss = (FullTextSearchScore) operand;
+ return ftss.getSelectorName();
+ } else if (operand instanceof Length) {
+ Length length = (Length) operand;
+ return getSelectorName(length.getPropertyValue());
+ } else if (operand instanceof LowerCase) {
+ LowerCase lower = (LowerCase) operand;
+ return getSelectorName(lower.getOperand());
+ } else if (operand instanceof NodeLocalName) {
+ NodeLocalName local = (NodeLocalName) operand;
+ return local.getSelectorName();
+ } else if (operand instanceof NodeName) {
+ NodeName name = (NodeName) operand;
+ return name.getSelectorName();
+ } else if (operand instanceof PropertyValue) {
+ PropertyValue value = (PropertyValue) operand;
+ return value.getSelectorName();
+ } else if (operand instanceof UpperCase) {
+ UpperCase upper = (UpperCase) operand;
+ return getSelectorName(upper.getOperand());
+ } else {
+ throw new UnsupportedRepositoryOperationException(
+ "Unknown dynamic operand type: " + operand);
+ }
+ }
+
+}
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ConstraintSplitter.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Constraints.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Constraints.java?rev=1023777&r1=1023776&r2=1023777&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Constraints.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Constraints.java Mon Oct 18 12:55:39 2010
@@ -16,62 +16,56 @@
*/
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.QueryObjectModelFactory;
public class Constraints {
- public static final Constraint TRUE = new Constraint() {};
-
- public static final Constraint FALSE = new Constraint() {};
-
public static Constraint and(
- QueryObjectModelFactory factory, Constraint... constraints)
+ QueryObjectModelFactory factory, List<Constraint> constraints)
throws RepositoryException {
- Constraint constraint = TRUE;
- for (int i = 0; constraints != null && i < constraints.length; i++) {
- if (constraints[i] == FALSE) {
- return FALSE;
- } else if (constraints[i] != TRUE) {
- if (constraint == TRUE) {
- constraint = constraints[i];
- } else {
- constraint = factory.and(constraint, constraints[i]);
- }
- }
+ int n = constraints.size();
+ if (n == 0) {
+ return null;
+ } else if (n == 1) {
+ return constraints.get(0);
+ } else {
+ int m = n / 2;
+ return factory.and(
+ and(factory, constraints.subList(0, m)),
+ and(factory, constraints.subList(m + 1, n)));
}
- return constraint;
}
-
- public static Constraint or(
+ public static Constraint and(
QueryObjectModelFactory factory, Constraint... constraints)
throws RepositoryException {
- Constraint constraint = FALSE;
- for (int i = 0; constraints != null && i < constraints.length; i++) {
- if (constraints[i] == TRUE) {
- return TRUE;
- } else if (constraints[i] != FALSE) {
- if (constraint == FALSE) {
- constraint = constraints[i];
- } else {
- constraint = factory.or(constraint, constraints[i]);
- }
+ List<Constraint> list = new ArrayList<Constraint>(constraints.length);
+ for (Constraint constraint : constraints) {
+ if (constraint != null) {
+ list.add(constraint);
}
}
- return constraint;
+ return and(factory, list);
}
- public static Constraint not(
- QueryObjectModelFactory factory, Constraint constraint)
+ public static Constraint or(
+ QueryObjectModelFactory factory, List<Constraint> constraints)
throws RepositoryException {
- if (constraint == TRUE) {
- return FALSE;
- } else if (constraint == FALSE) {
- return TRUE;
+ int n = constraints.size();
+ if (n == 0) {
+ return null;
+ } else if (n == 1) {
+ return constraints.get(0);
} else {
- return factory.not(constraint);
+ int m = n / 2;
+ return factory.or(
+ or(factory, constraints.subList(0, m)),
+ or(factory, constraints.subList(m + 1, n)));
}
}
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoinMerger.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoinMerger.java?rev=1023777&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoinMerger.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoinMerger.java Mon Oct 18 12:55:39 2010
@@ -0,0 +1,91 @@
+/*
+ * 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.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.Row;
+import javax.jcr.query.qom.Constraint;
+import javax.jcr.query.qom.DescendantNodeJoinCondition;
+import javax.jcr.query.qom.EquiJoinCondition;
+import javax.jcr.query.qom.Join;
+import javax.jcr.query.qom.PropertyValue;
+import javax.jcr.query.qom.QueryObjectModelFactory;
+
+class DescendantNodeJoinMerger extends JoinMerger {
+
+ private final String descendantSelector;
+
+ private final String ancestorSelector;
+
+ public DescendantNodeJoinMerger(
+ Join join, Map<String, PropertyValue> columns,
+ OperandEvaluator evaluator, QueryObjectModelFactory factory,
+ DescendantNodeJoinCondition condition)
+ throws RepositoryException {
+ super(join, columns, evaluator, factory);
+ this.descendantSelector = condition.getDescendantSelectorName();
+ this.ancestorSelector = condition.getAncestorSelectorName();
+ }
+
+ @Override
+ public Set<String> getLeftValues(Row row) throws RepositoryException {
+ return getValues(leftSelectors, row);
+ }
+
+ @Override
+ public Set<String> getRightValues(Row row) throws RepositoryException {
+ return getValues(rightSelectors, row);
+ }
+
+ @Override
+ public Constraint getRightJoinConstraint(List<Row> leftRows)
+ throws RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ private Set<String> getValues(Set<String> selectors, Row row)
+ throws RepositoryException {
+ if (selectors.contains(descendantSelector)) {
+ Node node = row.getNode(descendantSelector);
+ if (node != null) {
+ Set<String> values = new HashSet<String>();
+ while (node.getDepth() > 0) {
+ node = node.getParent();
+ values.add(node.getPath());
+ }
+ return values;
+ }
+ } else if (selectors.contains(ancestorSelector)) {
+ Node node = row.getNode(ancestorSelector);
+ if (node != null) {
+ return Collections.singleton(node.getPath());
+ }
+ } else {
+ throw new RepositoryException("Invalid descendant node join");
+ }
+ return Collections.emptySet();
+ }
+
+}
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoinMerger.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoinMerger.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoinMerger.java?rev=1023777&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoinMerger.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoinMerger.java Mon Oct 18 12:55:39 2010
@@ -0,0 +1,107 @@
+/*
+ * 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_OPERATOR_EQUAL_TO;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.query.Row;
+import javax.jcr.query.qom.Constraint;
+import javax.jcr.query.qom.EquiJoinCondition;
+import javax.jcr.query.qom.Join;
+import javax.jcr.query.qom.Literal;
+import javax.jcr.query.qom.PropertyValue;
+import javax.jcr.query.qom.QueryObjectModelFactory;
+
+class EquiJoinMerger extends JoinMerger {
+
+ private final PropertyValue leftProperty;
+
+ private final PropertyValue rightProperty;
+
+ public EquiJoinMerger(
+ Join join, Map<String, PropertyValue> columns,
+ OperandEvaluator evaluator, QueryObjectModelFactory factory,
+ EquiJoinCondition condition) throws RepositoryException {
+ super(join, columns, evaluator, factory);
+
+ PropertyValue property1 = factory.propertyValue(
+ condition.getSelector1Name(), condition.getProperty1Name());
+ PropertyValue property2 = factory.propertyValue(
+ condition.getSelector2Name(), condition.getProperty2Name());
+
+ if (leftSelectors.contains(property1.getSelectorName())
+ && rightSelectors.contains(property2.getSelectorName())) {
+ leftProperty = property1;
+ rightProperty = property2;
+ } else if (leftSelectors.contains(property2.getSelectorName())
+ && rightSelectors.contains(property1.getSelectorName())) {
+ leftProperty = property1;
+ rightProperty = property2;
+ } else {
+ throw new RepositoryException("Invalid equi-join");
+ }
+ }
+
+ @Override
+ public Set<String> getLeftValues(Row row) throws RepositoryException {
+ return getValues(leftProperty, row);
+ }
+
+ @Override
+ public Set<String> getRightValues(Row row) throws RepositoryException {
+ return getValues(rightProperty, row);
+ }
+
+ @Override
+ public Constraint getRightJoinConstraint(List<Row> leftRows)
+ throws RepositoryException {
+ Map<String, Literal> literals = new HashMap<String, Literal>();
+ for (Row leftRow : leftRows) {
+ for (Value value : evaluator.getValues(leftProperty, leftRow)) {
+ literals.put(value.getString(), factory.literal(value));
+ }
+ }
+
+ List<Constraint> constraints =
+ new ArrayList<Constraint>(literals.size());
+ for (Literal literal : literals.values()) {
+ constraints.add(factory.comparison(
+ rightProperty, JCR_OPERATOR_EQUAL_TO, literal));
+ }
+
+ return Constraints.or(factory, constraints);
+ }
+
+ private Set<String> getValues(PropertyValue property, Row row)
+ throws RepositoryException {
+ Set<String> strings = new HashSet<String>();
+ for (Value value : evaluator.getValues(property, row)) {
+ strings.add(value.getString());
+ }
+ return strings;
+ }
+
+}
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoinMerger.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java?rev=1023777&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java Mon Oct 18 12:55:39 2010
@@ -0,0 +1,263 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+import javax.jcr.query.RowIterator;
+import javax.jcr.query.qom.ChildNodeJoinCondition;
+import javax.jcr.query.qom.Constraint;
+import javax.jcr.query.qom.DescendantNodeJoinCondition;
+import javax.jcr.query.qom.EquiJoinCondition;
+import javax.jcr.query.qom.Join;
+import javax.jcr.query.qom.JoinCondition;
+import javax.jcr.query.qom.PropertyValue;
+import javax.jcr.query.qom.QueryObjectModelFactory;
+import javax.jcr.query.qom.SameNodeJoinCondition;
+import javax.jcr.query.qom.Selector;
+import javax.jcr.query.qom.Source;
+
+import org.apache.jackrabbit.commons.iterator.RowIterable;
+import org.apache.jackrabbit.commons.iterator.RowIteratorAdapter;
+
+abstract class JoinMerger {
+
+ public static JoinMerger getJoinMerger(
+ Join join, Map<String, PropertyValue> columns,
+ OperandEvaluator evaluator, QueryObjectModelFactory factory)
+ throws RepositoryException {
+ JoinCondition condition = join.getJoinCondition();
+ if (condition instanceof EquiJoinCondition) {
+ return new EquiJoinMerger(
+ join, columns, evaluator, factory,
+ (EquiJoinCondition) condition);
+ } else if (condition instanceof SameNodeJoinCondition) {
+ return new SameNodeJoinMerger(
+ join, columns, evaluator, factory,
+ (SameNodeJoinCondition) condition);
+ } else if (condition instanceof ChildNodeJoinCondition) {
+ return new ChildNodeJoinMerger(
+ join, columns, evaluator, factory,
+ (ChildNodeJoinCondition) condition);
+ } else if (condition instanceof DescendantNodeJoinCondition) {
+ return new DescendantNodeJoinMerger(
+ join, null, evaluator, factory,
+ (DescendantNodeJoinCondition) condition);
+ } else {
+ throw new UnsupportedRepositoryOperationException(
+ "Unsupported join condition type: " + condition);
+ }
+ }
+
+ private final String type;
+
+ protected final Set<String> leftSelectors;
+
+ protected final Set<String> rightSelectors;
+
+ private final String[] selectorNames;
+
+ private final String[] columnNames;
+
+ private final PropertyValue[] operands;
+
+ protected final OperandEvaluator evaluator;
+
+ protected final QueryObjectModelFactory factory;
+
+ protected JoinMerger(
+ Join join, Map<String, PropertyValue> columns,
+ OperandEvaluator evaluator, QueryObjectModelFactory factory)
+ throws RepositoryException {
+ this.type = join.getJoinType();
+
+ this.leftSelectors = getSelectorNames(join.getLeft());
+ this.rightSelectors = getSelectorNames(join.getRight());
+
+ Set<String> selectors = new LinkedHashSet<String>();
+ selectors.addAll(leftSelectors);
+ selectors.addAll(rightSelectors);
+ this.selectorNames =
+ selectors.toArray(new String[selectors.size()]);
+
+ this.columnNames =
+ columns.keySet().toArray(new String[columns.size()]);
+ this.operands =
+ columns.values().toArray(new PropertyValue[columns.size()]);
+
+ this.evaluator = evaluator;
+ this.factory = factory;
+ }
+
+ public Set<String> getLeftSelectors() {
+ return leftSelectors;
+ }
+
+ public Set<String> getRightSelectors() {
+ return rightSelectors;
+ }
+
+ private Set<String> getSelectorNames(Source source)
+ throws RepositoryException {
+ if (source instanceof Selector) {
+ Selector selector = (Selector) source;
+ return Collections.singleton(selector.getSelectorName());
+ } else if (source instanceof Join) {
+ Join join = (Join) source;
+ Set<String> set = new LinkedHashSet<String>();
+ set.addAll(getSelectorNames(join.getLeft()));
+ set.addAll(getSelectorNames(join.getRight()));
+ return set;
+ } else {
+ throw new UnsupportedRepositoryOperationException(
+ "Unknown source type: " + source);
+ }
+ }
+
+ public QueryResult merge(
+ RowIterator leftRows, RowIterator rightRows)
+ throws RepositoryException {
+ if (JCR_JOIN_TYPE_RIGHT_OUTER.equals(type)) {
+ Map<String, List<Row>> map = new HashMap<String, List<Row>>();
+ for (Row row : new RowIterable(leftRows)) {
+ for (String value : getLeftValues(row)) {
+ List<Row> rows = map.get(value);
+ if (rows == null) {
+ rows = new ArrayList<Row>();
+ map.put(value, rows);
+ }
+ rows.add(row);
+ }
+ }
+ return mergeRight(map, rightRows);
+ } else {
+ Map<String, List<Row>> map = new HashMap<String, List<Row>>();
+ for (Row row : new RowIterable(leftRows)) {
+ for (String value : getLeftValues(row)) {
+ List<Row> rows = map.get(value);
+ if (rows == null) {
+ rows = new ArrayList<Row>();
+ map.put(value, rows);
+ }
+ rows.add(row);
+ }
+ }
+ boolean outer = JCR_JOIN_TYPE_LEFT_OUTER.equals(type);
+ return mergeLeft(leftRows, map, outer);
+ }
+ }
+
+ private QueryResult mergeLeft(
+ RowIterator leftRows, Map<String, List<Row>> rightRowMap,
+ boolean outer) throws RepositoryException {
+ List<Row> rows = new ArrayList<Row>();
+ for (Row leftRow : new RowIterable(leftRows)) {
+ for (String value : getLeftValues(leftRow)) {
+ List<Row> rightRows = rightRowMap.get(value);
+ if (leftRows != null) {
+ for (Row rightRow : rightRows) {
+ rows.add(mergeRow(leftRow, rightRow));
+ }
+ } else if (outer) {
+ rows.add(mergeRow(leftRow, null));
+ }
+ }
+ }
+ return new SimpleQueryResult(
+ columnNames, selectorNames, new RowIteratorAdapter(rows));
+ }
+
+ private QueryResult mergeRight(
+ Map<String, List<Row>> leftRowMap, RowIterator rightRows)
+ throws RepositoryException {
+ List<Row> rows = new ArrayList<Row>();
+ for (Row rightRow : new RowIterable(rightRows)) {
+ for (String value : getRightValues(rightRow)) {
+ List<Row> leftRows = leftRowMap.get(value);
+ if (leftRows != null) {
+ for (Row leftRow : leftRows) {
+ rows.add(mergeRow(leftRow, rightRow));
+ }
+ } else {
+ rows.add(mergeRow(null, rightRow));
+ }
+ }
+ }
+ return new SimpleQueryResult(
+ columnNames, selectorNames, new RowIteratorAdapter(rows));
+ }
+
+ /**
+ * Merges the given left and right rows to a single joined row.
+ *
+ * @param left left row, possibly <code>null</code> in a right outer join
+ * @param right right row, possibly <code>null</code> in a left outer join
+ * @return joined row
+ * @throws RepositoryException if the rows can't be joined
+ */
+ private Row mergeRow(Row left, Row right) throws RepositoryException {
+ Node[] nodes = new Node[selectorNames.length];
+ double[] scores = new double[selectorNames.length];
+ for (int i = 0; i < selectorNames.length; i++) {
+ String selector = selectorNames[i];
+ if (left != null && leftSelectors.contains(selector)) {
+ nodes[i] = left.getNode(selector);
+ scores[i] = left.getScore(selector);
+ } else if (right != null && rightSelectors.contains(selector)) {
+ nodes[i] = right.getNode(selector);
+ scores[i] = right.getScore(selector);
+ } else {
+ nodes[i] = null;
+ scores[i] = 0.0;
+ }
+ }
+
+ Value[] values = new Value[operands.length];
+ Row row = new SimpleRow(
+ columnNames, values, selectorNames, nodes, scores);
+ for (int i = 0; i < operands.length; i++) {
+ values[i] = evaluator.getValue(operands[i], row);
+ }
+
+ return row;
+ }
+
+ public abstract Set<String> getLeftValues(Row row)
+ throws RepositoryException;
+
+ public abstract Set<String> getRightValues(Row row)
+ throws RepositoryException;
+
+ public abstract Constraint getRightJoinConstraint(List<Row> leftRows)
+ throws RepositoryException;
+
+}
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/OperandEvaluator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/OperandEvaluator.java?rev=1023777&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/OperandEvaluator.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/OperandEvaluator.java Mon Oct 18 12:55:39 2010
@@ -0,0 +1,292 @@
+/*
+ * 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 java.util.Locale.ENGLISH;
+import static javax.jcr.PropertyType.NAME;
+
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.query.Row;
+import javax.jcr.query.qom.BindVariableValue;
+import javax.jcr.query.qom.FullTextSearchScore;
+import javax.jcr.query.qom.Length;
+import javax.jcr.query.qom.Literal;
+import javax.jcr.query.qom.LowerCase;
+import javax.jcr.query.qom.NodeLocalName;
+import javax.jcr.query.qom.NodeName;
+import javax.jcr.query.qom.Operand;
+import javax.jcr.query.qom.PropertyValue;
+import javax.jcr.query.qom.UpperCase;
+
+class OperandEvaluator {
+
+ private final ValueFactory factory;
+
+ private final Map<String, Value> variables;
+
+ public OperandEvaluator(
+ ValueFactory factory, Map<String, Value> variables) {
+ this.factory = factory;
+ this.variables = variables;
+ }
+
+ public Value getValue(Operand operand, Row row) throws RepositoryException {
+ Value[] values = getValues(operand, row);
+ switch (values.length) {
+ case 0:
+ return factory.createValue("");
+ case 1:
+ return values[0];
+ default:
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < values.length; i++) {
+ if (i > 0) {
+ builder.append(' ');
+ }
+ builder.append(values[i].getString());
+ }
+ return factory.createValue(builder.toString());
+ }
+ }
+
+ /**
+ * Evaluates the given operand against the given row.
+ *
+ * @param operand operand
+ * @param row row
+ * @return values of the operand at the given row
+ * @throws RepositoryException if the operand can't be evaluated
+ */
+ public Value[] getValues(Operand operand, Row row)
+ throws RepositoryException {
+ if (operand instanceof BindVariableValue) {
+ return getBindVariableValues((BindVariableValue) operand);
+ } else if (operand instanceof FullTextSearchScore) {
+ return getFullTextSearchScoreValues(
+ (FullTextSearchScore) operand, row);
+ } else if (operand instanceof Length) {
+ return getLengthValues((Length) operand, row);
+ } else if (operand instanceof Literal) {
+ return getLiteralValues((Literal) operand);
+ } else if (operand instanceof LowerCase) {
+ return getLowerCaseValues((LowerCase) operand, row);
+ } else if (operand instanceof NodeLocalName) {
+ return getNodeLocalNameValues((NodeLocalName) operand, row);
+ } else if (operand instanceof NodeName) {
+ return getNodeNameValues((NodeName) operand, row);
+ } else if (operand instanceof PropertyValue) {
+ return getPropertyValues((PropertyValue) operand, row);
+ } else if (operand instanceof UpperCase) {
+ return getUpperCaseValues((UpperCase) operand, row);
+ } else {
+ throw new UnsupportedRepositoryOperationException(
+ "Unknown operand type: " + operand);
+ }
+ }
+
+ /**
+ * Returns the value of the given variable value operand at the given row.
+ *
+ * @param operand variable value operand
+ * @return value of the operand at the given row
+ */
+ private Value[] getBindVariableValues(BindVariableValue operand) {
+ Value value = variables.get(operand.getBindVariableName());
+ if (value != null) {
+ return new Value[] { value };
+ } else {
+ return new Value[0];
+ }
+ }
+
+ /**
+ * Returns the value of the given search score operand at the given row.
+ *
+ * @param operand search score operand
+ * @param row row
+ * @return value of the operand at the given row
+ * @throws RepositoryException if the operand can't be evaluated
+ */
+ private Value[] getFullTextSearchScoreValues(
+ FullTextSearchScore operand, Row row) throws RepositoryException {
+ double score = row.getScore(operand.getSelectorName());
+ return new Value[] { factory.createValue(score) };
+ }
+
+ /**
+ * Returns the values of the given value length operand at the given row.
+ *
+ * @see #getProperty(PropertyValue, Row)
+ * @param operand value length operand
+ * @param row row
+ * @return values of the operand at the given row
+ * @throws RepositoryException if the operand can't be evaluated
+ */
+ private Value[] getLengthValues(Length operand, Row row)
+ throws RepositoryException {
+ Property property = getProperty(operand.getPropertyValue(), row);
+ if (property == null) {
+ return new Value[0];
+ } else if (property.isMultiple()) {
+ long[] lengths = property.getLengths();
+ Value[] values = new Value[lengths.length];
+ for (int i = 0; i < lengths.length; i++) {
+ values[i] = factory.createValue(lengths[i]);
+ }
+ return values;
+ } else {
+ long length = property.getLength();
+ return new Value[] { factory.createValue(length) };
+ }
+ }
+
+ /**
+ * Returns the value of the given literal value operand.
+ *
+ * @param operand literal value operand
+ * @return value of the operand
+ */
+ private Value[] getLiteralValues(Literal operand) {
+ return new Value[] { operand.getLiteralValue() };
+ }
+
+ /**
+ * Returns the values of the given lower case operand at the given row.
+ *
+ * @param operand lower case operand
+ * @param row row
+ * @return values of the operand at the given row
+ * @throws RepositoryException if the operand can't be evaluated
+ */
+ private Value[] getLowerCaseValues(LowerCase operand, Row row)
+ throws RepositoryException {
+ Value[] values = getValues(operand.getOperand(), row);
+ for (int i = 0; i < values.length; i++) {
+ String value = values[i].getString();
+ String lower = value.toLowerCase(ENGLISH);
+ if (!value.equals(lower)) {
+ values[i] = factory.createValue(lower);
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Returns the value of the given local name operand at the given row.
+ *
+ * @param operand local name operand
+ * @param row row
+ * @return value of the operand at the given row
+ * @throws RepositoryException if the operand can't be evaluated
+ */
+ private Value[] getNodeLocalNameValues(NodeLocalName operand, Row row)
+ throws RepositoryException {
+ String name = row.getNode(operand.getSelectorName()).getName();
+ int colon = name.indexOf(':');
+ if (colon != -1) {
+ name = name.substring(colon + 1);
+ }
+ return new Value[] { factory.createValue(name, NAME) };
+ }
+
+ /**
+ * Returns the value of the given node name operand at the given row.
+ *
+ * @param operand node name operand
+ * @param row row
+ * @return value of the operand at the given row
+ * @throws RepositoryException if the operand can't be evaluated
+ */
+ private Value[] getNodeNameValues(NodeName operand, Row row)
+ throws RepositoryException {
+ Node node = row.getNode(operand.getSelectorName());
+ return new Value[] { factory.createValue(node.getName(), NAME) };
+ }
+
+ /**
+ * Returns the values of the given property value operand at the given row.
+ *
+ * @see #getProperty(PropertyValue, Row)
+ * @param operand property value operand
+ * @param row row
+ * @return values of the operand at the given row
+ * @throws RepositoryException if the operand can't be evaluated
+ */
+ private Value[] getPropertyValues(PropertyValue operand, Row row)
+ throws RepositoryException {
+ Property property = getProperty(operand, row);
+ if (property == null) {
+ return new Value[0];
+ } else if (property.isMultiple()) {
+ return property.getValues();
+ } else {
+ return new Value[] { property.getValue() };
+ }
+ }
+
+ /**
+ * Returns the values of the given upper case operand at the given row.
+ *
+ * @param operand upper case operand
+ * @param row row
+ * @return values of the operand at the given row
+ * @throws RepositoryException if the operand can't be evaluated
+ */
+ private Value[] getUpperCaseValues(UpperCase operand, Row row)
+ throws RepositoryException {
+ Value[] values = getValues(operand.getOperand(), row);
+ for (int i = 0; i < values.length; i++) {
+ String value = values[i].getString();
+ String upper = value.toLowerCase(ENGLISH);
+ if (!value.equals(upper)) {
+ values[i] = factory.createValue(upper);
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Returns the identified property from the given row. This method
+ * is used by both the {@link #getValue(Length, Row)} and the
+ * {@link #getValue(PropertyValue, Row)} methods to access properties.
+ *
+ * @param operand property value operand
+ * @param row row
+ * @return the identified property,
+ * or <code>null</code> if the property does not exist
+ * @throws RepositoryException if the property can't be accessed
+ */
+ private Property getProperty(PropertyValue operand, Row row)
+ throws RepositoryException {
+ try {
+ String selector = operand.getSelectorName();
+ String property = operand.getPropertyName();
+ return row.getNode(selector).getProperty(property);
+ } catch (PathNotFoundException e) {
+ return null;
+ }
+ }
+
+}
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/OperandEvaluator.java
------------------------------------------------------------------------------
svn:eol-style = native