Index: docs/reference/src/main/docbook/en-US/content/jcr/query_and_search.xml
===================================================================
--- docs/reference/src/main/docbook/en-US/content/jcr/query_and_search.xml (revision 2216)
+++ docs/reference/src/main/docbook/en-US/content/jcr/query_and_search.xml (working copy)
@@ -621,13 +621,24 @@ ORDER BY nodeSet1.title,
Support for the IN and NOT IN clauses to more easily and concisely supply multiple
of discrete static operands.
- For example, "WHERE ... [my:type].[prop1] IN (3,5,7,10,11,50) ...".
+ For example, "WHERE ... prop1 IN (3,5,7,10,11,50) ...".
Support for the BETWEEN clause to more easily and concisely supply a range of discrete operands.
- For example, "WHERE ... [my:type].[prop1] BETWEEN 3 EXCLUSIVE AND 10 ...".
+ For example, "WHERE ... prop1 BETWEEN 3 EXCLUSIVE AND 10 ...".
+
+
+
+
+ Support for (non-correlated) subqueries in the WHERE clause, wherever a static operand can be used.
+ Subqueries can even be used within another subquery. All subqueries must return a single column, and each row's single
+ value will be treated as a literal value. If the subquery is used in a clause that expects a single value
+ (e.g., in a comparison), only the subquery's first row will be used. If the subquery is used in a clause that
+ allows multiple values (e.g., IN (...)), then all of the subquery's rows will be used.
+ For example, this query "WHERE ... prop1 IN ( SELECT my:prop2 FROM my:type2 WHERE my:prop3 < '1000' ) AND ..."
+ will use the results of the subquery as the literal values in the IN clause.
@@ -694,9 +705,13 @@ ntname ::= quotedntname | unquotedntname
quotedntname ::= ''' unquotedntname '''
unquotedntname ::= /* A node type name */
-value ::= ''' literalvalue ''' | literalvalue
+value ::= literal | subquery
+
+literal ::= ''' literalvalue ''' | literalvalue
literalvalue ::= /* A property value (in standard string form) */
+subquery ::= '(' QueryCommand ')' | QueryCommand
+
like ::= propname 'LIKE' likepattern [ escape ]
likepattern ::= ''' likechar { likepattern } '''
likechar ::= char | '%' | '_'
@@ -812,6 +827,17 @@ offset ::= /* Non-negative integer value */
For detail, see the grammar for order-by clauses.
+
+
+ Support for (non-correlated) subqueries in the WHERE clause, wherever a static operand can be used.
+ Subqueries can even be used within another subquery. All subqueries must return a single column, and each row's single
+ value will be treated as a literal value. If the subquery is used in a clause that expects a single value
+ (e.g., in a comparison), only the subquery's first row will be used. If the subquery is used in a clause that
+ allows multiple values (e.g., IN (...)), then all of the subquery's rows will be used.
+ For example, this query "WHERE ... [my:type].[prop1] IN ( SELECT [my:prop2] FROM [my:type2] WHERE [my:prop3] < '1000' ) AND ..."
+ will use the results of the subquery as the literal values in the IN clause.
+
+
@@ -1095,7 +1121,7 @@ simplePath ::= /* A JCR Path (rather Name) that contains only SQL-legal
Static Operands
+
+ Subqueries
+
+ Dynamic Operands 0;
if (numRightOperands == 1) {
- return createQuery(left, Operator.EQUAL_TO, setCriteria.rightOperands().iterator().next());
+ StaticOperand rightOperand = setCriteria.rightOperands().iterator().next();
+ if (rightOperand instanceof Literal) {
+ return createQuery(left, Operator.EQUAL_TO, setCriteria.rightOperands().iterator().next());
+ }
}
BooleanQuery setQuery = new BooleanQuery();
for (StaticOperand right : setCriteria.rightOperands()) {
- Query rightQuery = createQuery(left, Operator.EQUAL_TO, right);
- if (rightQuery == null) return null;
- setQuery.add(rightQuery, Occur.SHOULD);
+ if (right instanceof BindVariableName) {
+ // This single value is a variable name, which may evaluate to a single value or multiple values ...
+ BindVariableName var = (BindVariableName)right;
+ Object value = variables.get(var.variableName());
+ if (value instanceof Iterable>) {
+ Iterator> iter = ((Iterable>)value).iterator();
+ while (iter.hasNext()) {
+ Object resolvedValue = iter.next();
+ if (resolvedValue == null) continue;
+ StaticOperand elementInRight = null;
+ if (resolvedValue instanceof Literal) {
+ elementInRight = (Literal)resolvedValue;
+ } else {
+ elementInRight = new Literal(resolvedValue);
+ }
+ Query rightQuery = createQuery(left, Operator.EQUAL_TO, elementInRight);
+ if (rightQuery == null) continue;
+ setQuery.add(rightQuery, Occur.SHOULD);
+ }
+ }
+ if (value == null) {
+ throw new LuceneException(LuceneI18n.missingVariableValue.text(var.variableName()));
+ }
+ } else {
+ Query rightQuery = createQuery(left, Operator.EQUAL_TO, right);
+ if (rightQuery == null) return null;
+ setQuery.add(rightQuery, Occur.SHOULD);
+ }
}
return setQuery;
}
@@ -683,6 +711,15 @@ public abstract class AbstractLuceneSearchEngine) {
+ // We can only return one value ...
+ Iterator> iter = ((Iterable>)value).iterator();
+ if (iter.hasNext()) return iter.next();
+ value = null;
+ }
+ if (value == null) {
+ throw new LuceneException(LuceneI18n.missingVariableValue.text(variableName));
+ }
if (!caseSensitive) value = lowerCase(value);
} else {
assert false;
Index: extensions/modeshape-search-lucene/src/main/java/org/modeshape/search/lucene/LuceneI18n.java
===================================================================
--- extensions/modeshape-search-lucene/src/main/java/org/modeshape/search/lucene/LuceneI18n.java (revision 2216)
+++ extensions/modeshape-search-lucene/src/main/java/org/modeshape/search/lucene/LuceneI18n.java (working copy)
@@ -35,6 +35,7 @@ public class LuceneI18n {
public static I18n locationForIndexesCannotBeWritten;
public static I18n errorWhileCommittingIndexChanges;
public static I18n errorWhileRollingBackIndexChanges;
+ public static I18n missingVariableValue;
static {
try {
Index: extensions/modeshape-search-lucene/src/main/resources/org/modeshape/search/lucene/LuceneI18n.properties
===================================================================
--- extensions/modeshape-search-lucene/src/main/resources/org/modeshape/search/lucene/LuceneI18n.properties (revision 2216)
+++ extensions/modeshape-search-lucene/src/main/resources/org/modeshape/search/lucene/LuceneI18n.properties (working copy)
@@ -28,3 +28,5 @@ locationForIndexesCannotBeWritten = Location "{0}" cannot be used for search ind
errorWhileCommittingIndexChanges = Error while committing changes to the indexes for the "{0}" workspace of the "{1}" source: {2}
errorWhileRollingBackIndexChanges = Error while rolling back changes to the indexes for the "{0}" workspace of the "{1}" source: {2}
+
+missingVariableValue = Variable "{0}" has no value
\ No newline at end of file
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/QueryBuilder.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/query/QueryBuilder.java (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/QueryBuilder.java (working copy)
@@ -86,6 +86,7 @@ import org.modeshape.graph.query.model.SetCriteria;
import org.modeshape.graph.query.model.SetQuery;
import org.modeshape.graph.query.model.Source;
import org.modeshape.graph.query.model.StaticOperand;
+import org.modeshape.graph.query.model.Subquery;
import org.modeshape.graph.query.model.TypeSystem;
import org.modeshape.graph.query.model.UpperCase;
import org.modeshape.graph.query.model.Visitors;
@@ -1908,6 +1909,16 @@ public class QueryBuilder {
/**
* Define the right-hand side of a comparison.
*
+ * @param subquery the subquery
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder literal( QueryCommand subquery ) {
+ return comparisonBuilder.is(operator, subquery);
+ }
+
+ /**
+ * Define the right-hand side of a comparison.
+ *
* @param variableName the name of the variable
* @return the constraint builder; never null
*/
@@ -2180,6 +2191,26 @@ public class QueryBuilder {
/**
* Define the upper boundary value of a range.
*
+ * @param subquery the subquery
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder subquery( Subquery subquery ) {
+ return comparisonBuilder.constraintBuilder.setConstraint(new Between(comparisonBuilder.left, lowerBound, subquery));
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
+ * @param subquery the subquery
+ * @return the constraint builder; never null
+ */
+ public ConstraintBuilder subquery( QueryCommand subquery ) {
+ return subquery(new Subquery(subquery));
+ }
+
+ /**
+ * Define the upper boundary value of a range.
+ *
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
@@ -2438,6 +2469,26 @@ public class QueryBuilder {
/**
* Define the lower boundary value of a range.
*
+ * @param subquery the subquery
+ * @return the constraint builder; never null
+ */
+ public AndBuilder subquery( Subquery subquery ) {
+ return new AndBuilder(new UpperBoundary(comparisonBuilder, subquery));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
+ * @param subquery the subquery
+ * @return the constraint builder; never null
+ */
+ public AndBuilder subquery( QueryCommand subquery ) {
+ return subquery(new Subquery(subquery));
+ }
+
+ /**
+ * Define the lower boundary value of a range.
+ *
* @param literal the literal value that is to be cast
* @return the constraint builder; never null
*/
@@ -2703,6 +2754,16 @@ public class QueryBuilder {
this.constraintBuilder = constraintBuilder;
}
+ public ConstraintBuilder isInSubquery( QueryCommand subquery ) {
+ CheckArg.isNotNull(subquery, "subquery");
+ return this.constraintBuilder.setConstraint(new SetCriteria(left, new Subquery(subquery)));
+ }
+
+ public ConstraintBuilder isInSubquery( Subquery subquery ) {
+ CheckArg.isNotNull(subquery, "subquery");
+ return this.constraintBuilder.setConstraint(new SetCriteria(left, subquery));
+ }
+
public ConstraintBuilder isIn( Object... literals ) {
CheckArg.isNotNull(literals, "literals");
Collection right = new ArrayList();
@@ -2841,22 +2902,63 @@ public class QueryBuilder {
* Define the right-hand-side of the constraint using the supplied operator.
*
* @param operator the operator; may not be null
- * @param literal the literal value
+ * @param subquery the subquery
+ * @return the builder used to create the constraint clause, ready to be used to create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder is( Operator operator,
+ QueryCommand subquery ) {
+ assert operator != null;
+ return is(operator, subquery);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint using the supplied operator.
+ *
+ * @param operator the operator; may not be null
+ * @param subquery the subquery
* @return the builder used to create the constraint clause, ready to be used to create other constraints clauses or
* complete already-started clauses; never null
*/
public ConstraintBuilder is( Operator operator,
- Object literal ) {
+ Subquery subquery ) {
assert operator != null;
- Literal value = literal instanceof Literal ? (Literal)literal : new Literal(literal);
- return this.constraintBuilder.setConstraint(new Comparison(left, operator, value));
+ return is(operator, subquery);
+ }
+
+ /**
+ * Define the right-hand-side of the constraint using the supplied operator.
+ *
+ * @param operator the operator; may not be null
+ * @param literalOrSubquery the literal value or subquery
+ * @return the builder used to create the constraint clause, ready to be used to create other constraints clauses or
+ * complete already-started clauses; never null
+ */
+ public ConstraintBuilder is( Operator operator,
+ Object literalOrSubquery ) {
+ assert operator != null;
+ return this.constraintBuilder.setConstraint(new Comparison(left, operator, adapt(literalOrSubquery)));
+ }
+
+ protected StaticOperand adapt( Object literalOrSubquery ) {
+ if (literalOrSubquery instanceof QueryCommand) {
+ // Wrap the query in a subquery ...
+ return new Subquery((QueryCommand)literalOrSubquery);
+ }
+ if (literalOrSubquery instanceof Subquery) {
+ return (Subquery)literalOrSubquery;
+ }
+ if (literalOrSubquery instanceof Literal) {
+ return (Literal)literalOrSubquery;
+ }
+ return new Literal(literalOrSubquery);
}
/**
* Define the right-hand-side of the constraint using the supplied operator.
*
- * @param lowerBoundLiteral the literal value that represents the lower bound of the range (inclusive)
- * @param upperBoundLiteral the literal value that represents the upper bound of the range (inclusive)
+ * @param lowerBoundLiteral the literal value that represents the lower bound of the range (inclusive); may be a subquery
+ * @param upperBoundLiteral the literal value that represents the upper bound of the range (inclusive); may be a subquery
* @return the builder used to create the constraint clause, ready to be used to create other constraints clauses or
* complete already-started clauses; never null
*/
@@ -2864,9 +2966,7 @@ public class QueryBuilder {
Object upperBoundLiteral ) {
assert lowerBoundLiteral != null;
assert upperBoundLiteral != null;
- Literal lower = lowerBoundLiteral instanceof Literal ? (Literal)lowerBoundLiteral : new Literal(lowerBoundLiteral);
- Literal upper = upperBoundLiteral instanceof Literal ? (Literal)upperBoundLiteral : new Literal(upperBoundLiteral);
- return this.constraintBuilder.setConstraint(new Between(left, lower, upper));
+ return this.constraintBuilder.setConstraint(new Between(left, adapt(lowerBoundLiteral), adapt(upperBoundLiteral)));
}
/**
@@ -2949,78 +3049,78 @@ public class QueryBuilder {
/**
* Define the right-hand-side of the constraint to be equivalent to the supplied literal value.
*
- * @param literal the literal value
+ * @param literalOrSubquery the literal value or a subquery
* @return the builder used to create the constraint clause, ready to be used to create other constraints clauses or
* complete already-started clauses; never null
*/
- public ConstraintBuilder isEqualTo( Object literal ) {
- return is(Operator.EQUAL_TO, literal);
+ public ConstraintBuilder isEqualTo( Object literalOrSubquery ) {
+ return is(Operator.EQUAL_TO, literalOrSubquery);
}
/**
* Define the right-hand-side of the constraint to be greater than the supplied literal value.
*
- * @param literal the literal value
+ * @param literalOrSubquery the literal value or a subquery
* @return the builder used to create the constraint clause, ready to be used to create other constraints clauses or
* complete already-started clauses; never null
*/
- public ConstraintBuilder isGreaterThan( Object literal ) {
- return is(Operator.GREATER_THAN, literal);
+ public ConstraintBuilder isGreaterThan( Object literalOrSubquery ) {
+ return is(Operator.GREATER_THAN, literalOrSubquery);
}
/**
* Define the right-hand-side of the constraint to be greater than or equal to the supplied literal value.
*
- * @param literal the literal value
+ * @param literalOrSubquery the literal value or a subquery
* @return the builder used to create the constraint clause, ready to be used to create other constraints clauses or
* complete already-started clauses; never null
*/
- public ConstraintBuilder isGreaterThanOrEqualTo( Object literal ) {
- return is(Operator.GREATER_THAN_OR_EQUAL_TO, literal);
+ public ConstraintBuilder isGreaterThanOrEqualTo( Object literalOrSubquery ) {
+ return is(Operator.GREATER_THAN_OR_EQUAL_TO, literalOrSubquery);
}
/**
* Define the right-hand-side of the constraint to be less than the supplied literal value.
*
- * @param literal the literal value
+ * @param literalOrSubquery the literal value or a subquery
* @return the builder used to create the constraint clause, ready to be used to create other constraints clauses or
* complete already-started clauses; never null
*/
- public ConstraintBuilder isLessThan( Object literal ) {
- return is(Operator.LESS_THAN, literal);
+ public ConstraintBuilder isLessThan( Object literalOrSubquery ) {
+ return is(Operator.LESS_THAN, literalOrSubquery);
}
/**
* Define the right-hand-side of the constraint to be less than or equal to the supplied literal value.
*
- * @param literal the literal value
+ * @param literalOrSubquery the literal value or a subquery
* @return the builder used to create the constraint clause, ready to be used to create other constraints clauses or
* complete already-started clauses; never null
*/
- public ConstraintBuilder isLessThanOrEqualTo( Object literal ) {
- return is(Operator.LESS_THAN_OR_EQUAL_TO, literal);
+ public ConstraintBuilder isLessThanOrEqualTo( Object literalOrSubquery ) {
+ return is(Operator.LESS_THAN_OR_EQUAL_TO, literalOrSubquery);
}
/**
* Define the right-hand-side of the constraint to be LIKE the supplied literal value.
*
- * @param literal the literal value
+ * @param literalOrSubquery the literal value or a subquery
* @return the builder used to create the constraint clause, ready to be used to create other constraints clauses or
* complete already-started clauses; never null
*/
- public ConstraintBuilder isLike( Object literal ) {
- return is(Operator.LIKE, literal);
+ public ConstraintBuilder isLike( Object literalOrSubquery ) {
+ return is(Operator.LIKE, literalOrSubquery);
}
/**
* Define the right-hand-side of the constraint to be not equal to the supplied literal value.
*
- * @param literal the literal value
+ * @param literalOrSubquery the literal value or a subquery
* @return the builder used to create the constraint clause, ready to be used to create other constraints clauses or
* complete already-started clauses; never null
*/
- public ConstraintBuilder isNotEqualTo( Object literal ) {
- return is(Operator.NOT_EQUAL_TO, literal);
+ public ConstraintBuilder isNotEqualTo( Object literalOrSubquery ) {
+ return is(Operator.NOT_EQUAL_TO, literalOrSubquery);
}
/**
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/QueryContext.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/query/QueryContext.java (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/QueryContext.java (working copy)
@@ -23,7 +23,6 @@
*/
package org.modeshape.graph.query;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import net.jcip.annotations.Immutable;
@@ -68,7 +67,7 @@ public class QueryContext {
this.hints = hints != null ? hints : new PlanHints();
this.schemata = schemata;
this.problems = problems != null ? problems : new SimpleProblems();
- this.variables = variables != null ? Collections.unmodifiableMap(new HashMap(variables)) : Collections.emptyMap();
+ this.variables = variables != null ? new HashMap(variables) : new HashMap();
assert this.typeSystem != null;
assert this.hints != null;
assert this.schemata != null;
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/model/Subquery.java
new file mode 100644
===================================================================
--- /dev/null (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/model/Subquery.java (working copy)
@@ -0,0 +1,97 @@
+/*
+ * ModeShape (http://www.modeshape.org)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * ModeShape is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.modeshape.graph.query.model;
+
+/**
+ * A representation of a non-correlated subquery. This component uses composition to hold the various types of QueryCommand
+ * objects, rather than inheriting from StaticOperand and QueryCommand.
+ */
+public class Subquery implements StaticOperand {
+
+ private static final long serialVersionUID = 1L;
+
+ private final QueryCommand query;
+
+ /**
+ * Create a new subquery component that uses the supplied query as the subquery expression.
+ *
+ * @param query the Command representing the subquery.
+ */
+ public Subquery( QueryCommand query ) {
+ this.query = query;
+ }
+
+ /**
+ * Get the query representing the subquery.
+ *
+ * @return the subquery
+ */
+ public QueryCommand query() {
+ return query;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.query.model.Visitable#accept(org.modeshape.graph.query.model.Visitor)
+ */
+ public void accept( Visitor visitor ) {
+ visitor.visit(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return Visitors.readable(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return query.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof Subquery) {
+ Subquery that = (Subquery)obj;
+ return this.query.equals(that.query) || this.query.toString().equals(that.query.toString());
+ }
+ return false;
+ }
+}
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/model/Visitor.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/query/model/Visitor.java (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/model/Visitor.java (working copy)
@@ -86,6 +86,8 @@ public interface Visitor {
void visit( Query obj );
+ void visit( Subquery obj );
+
void visit( ReferenceValue obj );
void visit( SameNode obj );
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/model/Visitors.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/query/model/Visitors.java (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/model/Visitors.java (working copy)
@@ -23,6 +23,7 @@
*/
package org.modeshape.graph.query.model;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -83,6 +84,31 @@ public class Visitors {
}
/**
+ * Using a visitor, obtain the {@link Subquery} objects that are contained within the supplied {@link Visitable object}. This
+ * method does find Subquery objets nested in other Subquery objects.
+ *
+ * @param visitable the visitable
+ * @param includeNestedSubqueries true if any Subquery objects within other Subquery objects should be included, or false if
+ * only the top-level Subquery objects should be included
+ * @return the collection of subqueries; never null but possibly empty if no subqueries were found
+ */
+ public static Collection subqueries( Visitable visitable,
+ final boolean includeNestedSubqueries ) {
+ final Collection subqueries = new LinkedList();
+ Visitors.visitAll(visitable, new Visitors.AbstractVisitor() {
+ @Override
+ public void visit( Subquery subquery ) {
+ subqueries.add(subquery);
+ if (includeNestedSubqueries) {
+ // Now look for any subqueries in the subquery ...
+ subquery.query().accept(this);
+ }
+ }
+ });
+ return subqueries;
+ }
+
+ /**
* Get a map of the selector names keyed by their aliases.
*
* @param visitable the object to be visited
@@ -243,6 +269,11 @@ public class Visitors {
}
@Override
+ public void visit( Subquery obj ) {
+ // do nothing ...
+ }
+
+ @Override
public void visit( ReferenceValue ref ) {
symbols.add(ref.selectorName());
}
@@ -517,6 +548,14 @@ public class Visitors {
/**
* {@inheritDoc}
*
+ * @see org.modeshape.graph.query.model.Visitor#visit(org.modeshape.graph.query.model.Subquery)
+ */
+ public void visit( Subquery obj ) {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.modeshape.graph.query.model.Visitor#visit(org.modeshape.graph.query.model.ReferenceValue)
*/
public void visit( ReferenceValue obj ) {
@@ -588,17 +627,15 @@ public class Visitors {
this.strategy = strategy;
}
- protected final void enqueue( Visitable objectToBeVisited ) {
+ protected void enqueue( Visitable objectToBeVisited ) {
if (objectToBeVisited != null) {
itemQueue.add(objectToBeVisited);
}
}
- protected final void enqueue( Iterable extends Visitable> objectsToBeVisited ) {
+ protected void enqueue( Iterable extends Visitable> objectsToBeVisited ) {
for (Visitable objectToBeVisited : objectsToBeVisited) {
- if (objectToBeVisited != null) {
- itemQueue.add(objectToBeVisited);
- }
+ enqueue(objectToBeVisited);
}
}
@@ -622,7 +659,7 @@ public class Visitors {
*
* @param strategy the visitor that should be called at every node.
*/
- protected WalkAllVisitor( Visitor strategy ) {
+ public WalkAllVisitor( Visitor strategy ) {
super(strategy);
}
@@ -950,6 +987,17 @@ public class Visitors {
/**
* {@inheritDoc}
*
+ * @see org.modeshape.graph.query.model.Visitor#visit(org.modeshape.graph.query.model.Subquery)
+ */
+ public void visit( Subquery subquery ) {
+ strategy.visit(subquery);
+ enqueue(subquery.query());
+ visitNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.modeshape.graph.query.model.Visitor#visit(org.modeshape.graph.query.model.ReferenceValue)
*/
public void visit( ReferenceValue referenceValue ) {
@@ -1491,6 +1539,17 @@ public class Visitors {
/**
* {@inheritDoc}
*
+ * @see org.modeshape.graph.query.model.Visitor#visit(org.modeshape.graph.query.model.Subquery)
+ */
+ public void visit( Subquery subquery ) {
+ append('(');
+ subquery.query().accept(this);
+ append(')');
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.modeshape.graph.query.model.Visitor#visit(org.modeshape.graph.query.model.SameNode)
*/
public void visit( SameNode sameNode ) {
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/RaiseVariableName.java
new file mode 100644
===================================================================
--- /dev/null (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/RaiseVariableName.java (working copy)
@@ -0,0 +1,83 @@
+/*
+ * ModeShape (http://www.modeshape.org)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * ModeShape is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.modeshape.graph.query.optimize;
+
+import java.util.LinkedList;
+import net.jcip.annotations.Immutable;
+import org.modeshape.graph.query.QueryContext;
+import org.modeshape.graph.query.plan.PlanNode;
+import org.modeshape.graph.query.plan.PlanNode.Property;
+import org.modeshape.graph.query.plan.PlanNode.Traversal;
+import org.modeshape.graph.query.plan.PlanNode.Type;
+
+/**
+ * An {@link OptimizerRule optimizer rule} that moves up higher in the plan any {@link Property#VARIABLE_NAME variable name}
+ * property to the node immediately under a {@link Type#DEPENDENT_QUERY dependent query} node.
+ */
+@Immutable
+public class RaiseVariableName implements OptimizerRule {
+
+ public static final RaiseVariableName INSTANCE = new RaiseVariableName();
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.query.optimize.OptimizerRule#execute(org.modeshape.graph.query.QueryContext,
+ * org.modeshape.graph.query.plan.PlanNode, java.util.LinkedList)
+ */
+ public PlanNode execute( QueryContext context,
+ PlanNode plan,
+ LinkedList ruleStack ) {
+ for (PlanNode depQuery : plan.findAllAtOrBelow(Traversal.PRE_ORDER, Type.DEPENDENT_QUERY)) {
+ // Check the left ...
+ PlanNode left = depQuery.getFirstChild();
+ raiseVariableName(left);
+
+ // Check the right ...
+ PlanNode right = depQuery.getLastChild();
+ raiseVariableName(right);
+ }
+ return plan;
+ }
+
+ protected void raiseVariableName( PlanNode node ) {
+ if (node.getType() != Type.DEPENDENT_QUERY) {
+ String variableName = removeVariableName(node);
+ if (variableName != null) {
+ node.setProperty(Property.VARIABLE_NAME, variableName);
+ }
+ }
+ }
+
+ protected String removeVariableName( PlanNode node ) {
+ if (node == null) return null;
+ String variableName = node.getProperty(Property.VARIABLE_NAME, String.class);
+ if (variableName != null) {
+ node.removeProperty(Property.VARIABLE_NAME);
+ return variableName;
+ }
+ // Look for it in the left side ...
+ return removeVariableName(node.getFirstChild());
+ }
+}
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/RuleBasedOptimizer.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/RuleBasedOptimizer.java (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/RuleBasedOptimizer.java (working copy)
@@ -69,6 +69,9 @@ public class RuleBasedOptimizer implements Optimizer {
*/
protected void populateRuleStack( LinkedList ruleStack,
PlanHints hints ) {
+ if (hints.hasSubqueries) {
+ ruleStack.addFirst(RaiseVariableName.INSTANCE);
+ }
ruleStack.addFirst(RewriteAsRangeCriteria.INSTANCE);
if (hints.hasJoin) {
ruleStack.addFirst(ChooseJoinAlgorithm.USE_ONLY_NESTED_JOIN_ALGORITHM);
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/parse/SqlQueryParser.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/query/parse/SqlQueryParser.java (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/parse/SqlQueryParser.java (working copy)
@@ -85,6 +85,7 @@ import org.modeshape.graph.query.model.SetCriteria;
import org.modeshape.graph.query.model.SetQuery;
import org.modeshape.graph.query.model.Source;
import org.modeshape.graph.query.model.StaticOperand;
+import org.modeshape.graph.query.model.Subquery;
import org.modeshape.graph.query.model.TypeSystem;
import org.modeshape.graph.query.model.UpperCase;
import org.modeshape.graph.query.model.FullTextSearch.Term;
@@ -511,6 +512,9 @@ public class SqlQueryParser implements QueryParser {
while (tokens.hasNext()) {
if (tokens.matchesAnyOf("UNION", "INTERSECT", "EXCEPT")) {
command = parseSetQuery(tokens, command, typeSystem);
+ } else if (tokens.matches(')')) {
+ // There's more in this token stream, but we'll stop reading ...
+ break;
} else {
Position pos = tokens.previousPosition();
String msg = GraphI18n.unexpectedToken.text(tokens.consume(), pos.getLine(), pos.getColumn());
@@ -921,6 +925,17 @@ public class SqlQueryParser implements QueryParser {
}
return bindVariableName(value);
}
+ if (tokens.canConsume('(')) {
+ // Sometimes the subqueries are wrapped with parentheses ...
+ StaticOperand result = parseStaticOperand(tokens, typeSystem);
+ tokens.consume(')');
+ return result;
+ }
+ if (tokens.matches("SELECT")) {
+ // This is a subquery. This object is stateless, so we can reuse this object ...
+ QueryCommand subqueryExpression = parseQueryCommand(tokens, typeSystem);
+ return new Subquery(subqueryExpression);
+ }
return parseLiteral(tokens, typeSystem);
}
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/plan/CanonicalPlanner.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/query/plan/CanonicalPlanner.java (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/plan/CanonicalPlanner.java (working copy)
@@ -24,6 +24,7 @@
package org.modeshape.graph.query.plan;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -46,7 +47,10 @@ import org.modeshape.graph.query.model.Selector;
import org.modeshape.graph.query.model.SelectorName;
import org.modeshape.graph.query.model.SetQuery;
import org.modeshape.graph.query.model.Source;
+import org.modeshape.graph.query.model.Subquery;
+import org.modeshape.graph.query.model.Visitable;
import org.modeshape.graph.query.model.Visitors;
+import org.modeshape.graph.query.model.Visitors.WalkAllVisitor;
import org.modeshape.graph.query.plan.PlanNode.Property;
import org.modeshape.graph.query.plan.PlanNode.Type;
import org.modeshape.graph.query.validate.Schemata;
@@ -125,7 +129,8 @@ public class CanonicalPlanner implements Planner {
plan = createPlanNode(context, query.source(), usedSources);
// Attach criteria (on top) ...
- plan = attachCriteria(context, plan, query.constraint());
+ Map subqueriesByVariableName = new HashMap();
+ plan = attachCriteria(context, plan, query.constraint(), subqueriesByVariableName);
// Attach groupbys (on top) ...
// plan = attachGrouping(context,plan,query.getGroupBy());
@@ -142,9 +147,18 @@ public class CanonicalPlanner implements Planner {
plan = attachSorting(context, plan, query.orderings());
plan = attachLimits(context, plan, query.limits());
+ // Now add in the subqueries as dependent joins, in reverse order ...
+ plan = attachSubqueries(context, plan, subqueriesByVariableName);
+
// Validate that all the parts of the query are resolvable ...
validate(context, query, usedSources);
+ // Now we need to validate all of the subqueries ...
+ for (Subquery subquery : Visitors.subqueries(query, false)) {
+ // Just do it by creating a plan, even though we aren't doing anything with these plans ...
+ createPlan(context, subquery.query());
+ }
+
return plan;
}
@@ -158,8 +172,17 @@ public class CanonicalPlanner implements Planner {
protected void validate( QueryContext context,
QueryCommand query,
Map usedSelectors ) {
- // Resolve everything ...
- Visitors.visitAll(query, new Validator(context, usedSelectors));
+ // // Resolve everything ...
+ // Visitors.visitAll(query, new Validator(context, usedSelectors));
+ // Resolve everything (except subqueries) ...
+ Validator validator = new Validator(context, usedSelectors);
+ query.accept(new WalkAllVisitor(validator) {
+ @Override
+ protected void enqueue( Visitable objectToBeVisited ) {
+ if (objectToBeVisited instanceof Subquery) return;
+ super.enqueue(objectToBeVisited);
+ }
+ });
}
/**
@@ -261,11 +284,13 @@ public class CanonicalPlanner implements Planner {
* @param context the context in which the query is being planned
* @param plan the existing plan, which joins all source groups
* @param constraint the criteria or constraint from the query
+ * @param subqueriesByVariableName the subqueries by variable name
* @return the updated plan, or the existing plan if there were no constraints; never null
*/
protected PlanNode attachCriteria( final QueryContext context,
PlanNode plan,
- Constraint constraint ) {
+ Constraint constraint,
+ Map subqueriesByVariableName ) {
if (constraint == null) return plan;
context.getHints().hasCriteria = true;
@@ -278,6 +303,10 @@ public class CanonicalPlanner implements Planner {
// Do this in reverse order so that the top-most SELECT node corresponds to the first constraint.
while (!andableConstraints.isEmpty()) {
Constraint criteria = andableConstraints.removeLast();
+
+ // Replace any subqueries with bind variables ...
+ criteria = PlanUtil.replaceSubqueriesWithBindVariables(context, criteria, subqueriesByVariableName);
+
// Create the select node ...
PlanNode criteriaNode = new PlanNode(Type.SELECT);
criteriaNode.setProperty(Property.SELECT_CRITERIA, criteria);
@@ -285,7 +314,7 @@ public class CanonicalPlanner implements Planner {
// Add selectors to the criteria node ...
criteriaNode.addSelectors(Visitors.getSelectorsReferencedBy(criteria));
- // Is a full-text search of some kind included ...
+ // Is there at least one full-text search or subquery ...
Visitors.visitAll(criteria, new Visitors.AbstractVisitor() {
@Override
public void visit( FullTextSearch obj ) {
@@ -296,6 +325,11 @@ public class CanonicalPlanner implements Planner {
criteriaNode.addFirstChild(plan);
plan = criteriaNode;
}
+
+ if (!subqueriesByVariableName.isEmpty()) {
+ context.getHints().hasSubqueries = true;
+
+ }
return plan;
}
@@ -471,4 +505,45 @@ public class CanonicalPlanner implements Planner {
return dupNode;
}
+ /**
+ * Attach plan nodes for each subquery, resulting with the first subquery at the top of the plan tree.
+ *
+ * @param context the context in which the query is being planned
+ * @param plan the existing plan
+ * @param subqueriesByVariableName the queries by the variable name used in substitution
+ * @return the updated plan, or the existing plan if there were no limits
+ */
+ protected PlanNode attachSubqueries( QueryContext context,
+ PlanNode plan,
+ Map subqueriesByVariableName ) {
+ // Order the variable names in reverse order ...
+ List varNames = new ArrayList(subqueriesByVariableName.keySet());
+ Collections.sort(varNames);
+ Collections.reverse(varNames);
+
+ for (String varName : varNames) {
+ Subquery subquery = subqueriesByVariableName.get(varName);
+ // Plan out the subquery ...
+ PlanNode subqueryNode = createPlan(context, subquery.query());
+ setSubqueryVariableName(subqueryNode, varName);
+
+ // Create a DEPENDENT_QUERY node, with the subquery on the LHS (so it is executed first) ...
+ PlanNode depQuery = new PlanNode(Type.DEPENDENT_QUERY);
+ depQuery.addChildren(subqueryNode, plan);
+ depQuery.addSelectors(subqueryNode.getSelectors());
+ depQuery.addSelectors(plan.getSelectors());
+ plan = depQuery;
+ }
+ return plan;
+ }
+
+ protected void setSubqueryVariableName( PlanNode subqueryPlan,
+ String varName ) {
+ if (subqueryPlan.getType() != Type.DEPENDENT_QUERY) {
+ subqueryPlan.setProperty(Property.VARIABLE_NAME, varName);
+ return;
+ }
+ // Otherwise, this is a dependent query, and our subquery should be on the right (last child) ...
+ setSubqueryVariableName(subqueryPlan.getLastChild(), varName);
+ }
}
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanHints.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanHints.java (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanHints.java (working copy)
@@ -59,6 +59,8 @@ public final class PlanHints implements Serializable, Cloneable {
public boolean hasFullTextSearch = false;
+ public boolean hasSubqueries = false;
+
/** Flag indicates that the plan has at least one view somewhere */
public boolean hasView = false;
@@ -82,6 +84,7 @@ public final class PlanHints implements Serializable, Cloneable {
sb.append(", hasLimit=").append(hasLimit);
sb.append(", hasOptionalJoin=").append(hasOptionalJoin);
sb.append(", hasFullTextSearch=").append(hasFullTextSearch);
+ sb.append(", hasSubqueries=").append(hasSubqueries);
sb.append(", showPlan=").append(showPlan);
sb.append(", validateColumnExistance=").append(validateColumnExistance);
sb.append('}');
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanNode.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanNode.java (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanNode.java (working copy)
@@ -83,7 +83,9 @@ public final class PlanNode implements Iterable, Readable, Cloneable,
/** A node that limits the number of tuples returned */
LIMIT("Limit"),
/** A node the performs set operations on two sets of tuples, including UNION */
- SET_OPERATION("SetOperation");
+ SET_OPERATION("SetOperation"),
+ /** A node that contains two nodes, where the left side must be done before the right */
+ DEPENDENT_QUERY("DependentQuery");
private static final Map TYPE_BY_SYMBOL;
static {
@@ -198,7 +200,10 @@ public final class PlanNode implements Iterable, Readable, Cloneable,
* For ACESS nodes, this signifies that the node will never return results. Value is a {@link Boolean} object, though the
* mere presence of this property signifies that it is no longer needed.
*/
- ACCESS_NO_RESULTS
+ ACCESS_NO_RESULTS,
+
+ /** For dependenty queries, defines the variable where the results will be placed. */
+ VARIABLE_NAME
}
private Type type;
@@ -1152,7 +1157,7 @@ public final class PlanNode implements Iterable, Readable, Cloneable,
}
/**
- * Find all of the nodes of the specified type that are at or below this node.
+ * Find all of the nodes of the specified type that are at or below this node, using pre-order traversal.
*
* @param typeToFind the type of node to find; may not be null
* @return the collection of nodes that are at or below this node that all have the supplied type; never null but possibly
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanUtil.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanUtil.java (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanUtil.java (working copy)
@@ -39,6 +39,7 @@ import org.modeshape.graph.query.model.And;
import org.modeshape.graph.query.model.ArithmeticOperand;
import org.modeshape.graph.query.model.ArithmeticOperator;
import org.modeshape.graph.query.model.Between;
+import org.modeshape.graph.query.model.BindVariableName;
import org.modeshape.graph.query.model.ChildNode;
import org.modeshape.graph.query.model.ChildNodeJoinCondition;
import org.modeshape.graph.query.model.Column;
@@ -68,6 +69,7 @@ import org.modeshape.graph.query.model.SameNodeJoinCondition;
import org.modeshape.graph.query.model.SelectorName;
import org.modeshape.graph.query.model.SetCriteria;
import org.modeshape.graph.query.model.StaticOperand;
+import org.modeshape.graph.query.model.Subquery;
import org.modeshape.graph.query.model.UpperCase;
import org.modeshape.graph.query.model.Visitors;
import org.modeshape.graph.query.model.Visitors.AbstractVisitor;
@@ -416,6 +418,7 @@ public class PlanUtil {
case DUP_REMOVE:
case LIMIT:
case NULL:
+ case DEPENDENT_QUERY:
case ACCESS:
// None of these have to be changed ...
break;
@@ -758,6 +761,7 @@ public class PlanUtil {
case GROUP:
// Don't yet use GROUP BY
case SET_OPERATION:
+ case DEPENDENT_QUERY:
case DUP_REMOVE:
case LIMIT:
case NULL:
@@ -1155,4 +1159,97 @@ public class PlanUtil {
}
}
+ public static Constraint replaceSubqueriesWithBindVariables( QueryContext context,
+ Constraint constraint,
+ Map subqueriesByVariableName ) {
+ if (constraint instanceof And) {
+ And and = (And)constraint;
+ Constraint left = replaceSubqueriesWithBindVariables(context, and.left(), subqueriesByVariableName);
+ Constraint right = replaceSubqueriesWithBindVariables(context, and.right(), subqueriesByVariableName);
+ if (left == and.left() && right == and.right()) return and;
+ return new And(left, right);
+ }
+ if (constraint instanceof Or) {
+ Or or = (Or)constraint;
+ Constraint left = replaceSubqueriesWithBindVariables(context, or.left(), subqueriesByVariableName);
+ Constraint right = replaceSubqueriesWithBindVariables(context, or.right(), subqueriesByVariableName);
+ if (left == or.left() && right == or.right()) return or;
+ return new Or(left, right);
+ }
+ if (constraint instanceof Not) {
+ Not not = (Not)constraint;
+ Constraint wrapped = replaceSubqueriesWithBindVariables(context, not.constraint(), subqueriesByVariableName);
+ if (wrapped == not.constraint()) return not;
+ return new Not(wrapped);
+ }
+ if (constraint instanceof SameNode) {
+ return constraint;
+ }
+ if (constraint instanceof ChildNode) {
+ return constraint;
+ }
+ if (constraint instanceof DescendantNode) {
+ return constraint;
+ }
+ if (constraint instanceof PropertyExistence) {
+ return constraint;
+ }
+ if (constraint instanceof FullTextSearch) {
+ return constraint;
+ }
+ if (constraint instanceof Between) {
+ Between between = (Between)constraint;
+ DynamicOperand lhs = between.operand();
+ StaticOperand lower = between.lowerBound(); // Current only a literal; therefore, no reference to selector
+ StaticOperand upper = between.upperBound(); // Current only a literal; therefore, no reference to selector
+ StaticOperand newLower = replaceSubqueriesWithBindVariables(context, lower, subqueriesByVariableName);
+ StaticOperand newUpper = replaceSubqueriesWithBindVariables(context, upper, subqueriesByVariableName);
+ if (lower == newLower && upper == newUpper) return between;
+ return new Between(lhs, newLower, newUpper, between.isLowerBoundIncluded(), between.isUpperBoundIncluded());
+ }
+ if (constraint instanceof Comparison) {
+ Comparison comparison = (Comparison)constraint;
+ DynamicOperand lhs = comparison.operand1();
+ StaticOperand rhs = comparison.operand2(); // Current only a literal; therefore, no reference to selector
+ StaticOperand newRhs = replaceSubqueriesWithBindVariables(context, rhs, subqueriesByVariableName);
+ if (rhs == newRhs) return comparison;
+ return new Comparison(lhs, comparison.operator(), newRhs);
+ }
+ if (constraint instanceof SetCriteria) {
+ SetCriteria criteria = (SetCriteria)constraint;
+ DynamicOperand lhs = criteria.leftOperand();
+ boolean foundSubquery = false;
+ List newStaticOperands = new LinkedList();
+ for (StaticOperand rhs : criteria.rightOperands()) {
+ StaticOperand newRhs = replaceSubqueriesWithBindVariables(context, rhs, subqueriesByVariableName);
+ newStaticOperands.add(newRhs);
+ if (rhs != newRhs) {
+ foundSubquery = true;
+ }
+ }
+ if (!foundSubquery) return criteria;
+ return new SetCriteria(lhs, newStaticOperands);
+ }
+ return constraint;
+ }
+
+ public static StaticOperand replaceSubqueriesWithBindVariables( QueryContext context,
+ StaticOperand staticOperand,
+ Map subqueriesByVariableName ) {
+ if (staticOperand instanceof Subquery) {
+ Subquery subquery = (Subquery)staticOperand;
+ // Create a variable name ...
+ int i = 1;
+ String variableName = "__subquery";
+ while (context.getVariables().containsKey(variableName + i)) {
+ ++i;
+ }
+ variableName = variableName + i;
+ subqueriesByVariableName.put(variableName, subquery);
+ context.getVariables().put(variableName, null);
+ // Replace with a variable substitution ...
+ return new BindVariableName(variableName);
+ }
+ return staticOperand;
+ }
}
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/process/DependentQueryComponent.java
new file mode 100644
===================================================================
--- /dev/null (revision 2216)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/DependentQueryComponent.java (working copy)
@@ -0,0 +1,130 @@
+/*
+ * ModeShape (http://www.modeshape.org)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * ModeShape is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.modeshape.graph.query.process;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.modeshape.graph.query.QueryContext;
+import org.modeshape.graph.query.QueryResults.Columns;
+import org.modeshape.graph.query.plan.PlanNode.Type;
+
+/**
+ * A {@link ProcessingComponent} that executes a {@link Type#DEPENDENT_QUERY dependent query} node by first executing the left
+ * component and then executing the right component. If a variable name is specified, this component will save the query results
+ * from the the corresponding component into the {@link QueryContext#getVariables() variables} in the {@link QueryContext}.
+ */
+public class DependentQueryComponent extends ProcessingComponent {
+
+ private final ProcessingComponent left;
+ private final ProcessingComponent right;
+ private final String leftVariableName;
+ private final String rightVariableName;
+
+ public DependentQueryComponent( QueryContext context,
+ ProcessingComponent left,
+ ProcessingComponent right,
+ String leftVariableName,
+ String rightVariableName ) {
+ super(context, right.getColumns());
+ this.left = left;
+ this.right = right;
+ this.leftVariableName = leftVariableName;
+ this.rightVariableName = rightVariableName;
+ }
+
+ /**
+ * Get the processing component that serves as the left side of the join.
+ *
+ * @return the left-side processing component; never null
+ */
+ protected final ProcessingComponent left() {
+ return left;
+ }
+
+ /**
+ * Get the processing component that serves as the right side of the join.
+ *
+ * @return the right-side processing component; never null
+ */
+ protected final ProcessingComponent right() {
+ return right;
+ }
+
+ /**
+ * Get the columns definition for the results from the left, independent query that is processed first.
+ *
+ * @return the left-side columns; never null
+ */
+ protected final Columns colunnsOfIndependentQuery() {
+ return left.getColumns();
+ }
+
+ /**
+ * Get the columns definition for the results from the right component that is dependent upon the left.
+ *
+ * @return the right-side columns; never null
+ */
+ protected final Columns colunnsOfDependentQuery() {
+ return right.getColumns();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.query.process.ProcessingComponent#execute()
+ */
+ @Override
+ public List