Index: modeshape-graph/src/main/java/org/modeshape/graph/query/QueryResults.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/QueryResults.java (revision 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/QueryResults.java (working copy) @@ -274,15 +274,6 @@ public interface QueryResults extends Serializable { public String getPropertyNameForColumn( int columnIndex ); /** - * Get the name of the property that corresponds to the supplied column in each tuple. - * - * @param columnName the column name - * @return the property name; never null - * @throws NoSuchElementException if the column name is invalid or doesn't match an existing column - */ - public String getPropertyNameForColumn( String columnName ); - - /** * Get the index of the column given the column name. * * @param columnName the column name @@ -355,6 +346,16 @@ public interface QueryResults extends Serializable { * @return the new columns definition; never null */ public Columns subSelect( Column... columns ); + + /** + * Obtain a new definition for the query results that is a combination of the these columns and the supplied columns, + * where the columns from this object appear first, followed by columns from the supplied set. This is useful in a JOIN + * operation. + * + * @param columns the new columns, which must be a subset of the columns in this definition; may not be null + * @return the new columns definition; never null + */ + public Columns joinWith( Columns columns ); } @Immutable Index: modeshape-graph/src/main/java/org/modeshape/graph/query/model/SetCriteria.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/model/SetCriteria.java (revision 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/model/SetCriteria.java (working copy) @@ -37,10 +37,10 @@ public class SetCriteria implements Constraint { private static final long serialVersionUID = 1L; private final DynamicOperand left; - private final Collection setOperands; + private final Collection setOperands; public SetCriteria( DynamicOperand left, - Collection setOperands ) { + Collection setOperands ) { CheckArg.isNotNull(left, "left"); CheckArg.isNotNull(setOperands, "setOperands"); CheckArg.isNotEmpty(setOperands, "setOperands"); @@ -71,7 +71,7 @@ public class SetCriteria implements Constraint { * * @return the right-hand-side static operands; never null and never empty */ - public final Collection rightOperands() { + public final Collection rightOperands() { return setOperands; } 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 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/model/Visitors.java (working copy) @@ -23,9 +23,11 @@ */ package org.modeshape.graph.query.model; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; +import java.util.Map; import java.util.Set; import org.modeshape.common.util.CheckArg; import org.modeshape.graph.ExecutionContext; @@ -81,6 +83,60 @@ public class Visitors { } /** + * Get a map of the selector names keyed by their aliases. + * + * @param visitable the object to be visited + * @return the map from the aliases to the aliased selector name; never null but possibly empty + */ + public static Map getSelectorNamesByAlias( Visitable visitable ) { + // Find all of the selectors that have aliases ... + final Map result = new HashMap(); + Visitors.visitAll(visitable, new Visitors.AbstractVisitor() { + @Override + public void visit( AllNodes allNodes ) { + if (allNodes.hasAlias()) { + result.put(allNodes.alias(), allNodes.name()); + } + } + + @Override + public void visit( NamedSelector selector ) { + if (selector.hasAlias()) { + result.put(selector.alias(), selector.name()); + } + } + }); + return result; + } + + /** + * Get a map of the selector aliases keyed by their names. + * + * @param visitable the object to be visited + * @return the map from the selector names to their alias (or name if there is no alias); never null but possibly empty + */ + public static Map getSelectorAliasesByName( Visitable visitable ) { + // Find all of the selectors that have aliases ... + final Map result = new HashMap(); + Visitors.visitAll(visitable, new Visitors.AbstractVisitor() { + @Override + public void visit( AllNodes allNodes ) { + if (allNodes.hasAlias()) { + result.put(allNodes.name(), allNodes.aliasOrName()); + } + } + + @Override + public void visit( NamedSelector selector ) { + if (selector.hasAlias()) { + result.put(selector.name(), selector.aliasOrName()); + } + } + }); + return result; + } + + /** * Get the names of the selectors referenced by the visitable object. * * @param visitable the object to be visited @@ -1462,7 +1518,7 @@ public class Visitors { public void visit( SetCriteria criteria ) { criteria.leftOperand().accept(this); append(" IN ("); - Iterator iter = criteria.rightOperands().iterator(); + Iterator iter = criteria.rightOperands().iterator(); if (iter.hasNext()) { iter.next().accept(this); while (iter.hasNext()) { Index: modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/PushProjects.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/PushProjects.java (revision 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/PushProjects.java (working copy) @@ -88,7 +88,7 @@ public class PushProjects implements OptimizerRule { project.extractFromParent(); } child.insertAsParent(project); - if (plan == access) return plan; + if (plan == access) break; // We need to make sure we have all of the columns needed for any ancestor ... List requiredColumns = PlanUtil.findRequiredColumns(context, project); Index: modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/ReplaceViews.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/ReplaceViews.java (revision 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/ReplaceViews.java (working copy) @@ -23,6 +23,7 @@ */ package org.modeshape.graph.query.optimize; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -114,17 +115,32 @@ public class ReplaceViews implements OptimizerRule { if (viewPlan == null) continue; // there were likely errors when creating the plan viewPlan = viewPlan.clone(); + // If the view doesn't have an alias, or if the view's alias doesn't match the table's name/alias ... + PlanNode viewProjectNode = viewPlan.findAtOrBelow(Type.PROJECT); + if (viewProjectNode.getSelectors().size() == 1) { + SelectorName tableAliasOrName = tableAlias != null ? tableAlias : tableName; + SelectorName viewAlias = viewProjectNode.getSelectors().iterator().next(); + // Replace the view's alias ... + Map replacements = Collections.singletonMap(viewAlias, tableAliasOrName); + PlanUtil.replaceReferencesToRemovedSource(context, viewPlan, replacements); + } + // Insert the view plan under the parent SOURCE node ... sourceNode.addLastChild(viewPlan); - // Update the plan above this node to replace references to the view columns with references to the - // tables/views used by the view ... - PlanUtil.ColumnMapping viewMappings = PlanUtil.createMappingFor(view, viewPlan); - PlanUtil.replaceViewReferences(context, viewPlan, viewMappings); - if (tableAlias != null) { - // We also need to replace references to the alias for the view ... - PlanUtil.ColumnMapping aliasMappings = PlanUtil.createMappingForAliased(tableAlias, view, viewPlan); - PlanUtil.replaceViewReferences(context, viewPlan, aliasMappings); + // Remove the source node ... + sourceNode.extractFromParent(); + + // // Replace the original view's name with the name/alias ... + PlanNode parent = viewPlan.getParent(); + if (parent != null) { + PlanUtil.ColumnMapping aliasMappings = null; + if (tableAlias != null) { + aliasMappings = PlanUtil.createMappingForAliased(tableAlias, view, viewPlan); + PlanUtil.replaceViewReferences(context, parent, aliasMappings); + } + PlanUtil.ColumnMapping viewMappings = PlanUtil.createMappingFor(view, viewPlan); + PlanUtil.replaceViewReferences(context, parent, viewMappings); } if (viewPlan.is(Type.PROJECT)) { Index: modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/RewriteIdentityJoins.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/RewriteIdentityJoins.java (revision 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/RewriteIdentityJoins.java (working copy) @@ -138,8 +138,7 @@ public class RewriteIdentityJoins implements OptimizerRule { } } else if (condition instanceof SameNodeJoinCondition) { SameNodeJoinCondition sameNodeCondition = (SameNodeJoinCondition)condition; - if (sameNodeCondition.selector1Name().equals(sameNodeCondition.selector2Name()) - && sameNodeCondition.selector2Path() == null) { + if (sameNodeCondition.selector2Path() == null) { // It meets all the criteria, so rewrite this join node ... if (rewrittenSelectors == null) rewrittenSelectors = new HashMap(); rewriteJoinNode(context, joinNode, rewrittenSelectors); 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 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/RuleBasedOptimizer.java (working copy) @@ -69,7 +69,7 @@ public class RuleBasedOptimizer implements Optimizer { */ protected void populateRuleStack( LinkedList ruleStack, PlanHints hints ) { - ruleStack.addFirst(ReplaceAliases.INSTANCE); + // ruleStack.addFirst(ReplaceAliases.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 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/parse/SqlQueryParser.java (working copy) @@ -545,7 +545,7 @@ public class SqlQueryParser implements QueryParser { String propertyName = expression.getPropertyName(); if (selectorName == null) { if (source instanceof Selector) { - selectorName = ((Selector)source).name(); + selectorName = ((Selector)source).aliasOrName(); } else { Position pos = expression.getPosition(); String msg = GraphI18n.mustBeScopedAtLineAndColumn.text(expression, pos.getLine(), pos.getColumn()); @@ -778,7 +778,7 @@ public class SqlQueryParser implements QueryParser { if (tokens.matches("IN", "(") || tokens.matches("NOT", "IN", "(")) { boolean not = tokens.canConsume("NOT"); Collection staticOperands = parseInClause(tokens, typeSystem); - constraint = new SetCriteria(left, staticOperands); + constraint = setCriteria(left, staticOperands); if (not) constraint = not(constraint); } else if (tokens.matches("BETWEEN") || tokens.matches("NOT", "BETWEEN")) { boolean not = tokens.canConsume("NOT"); @@ -788,7 +788,7 @@ public class SqlQueryParser implements QueryParser { tokens.consume("AND"); StaticOperand upperBound = parseStaticOperand(tokens, typeSystem); boolean upperInclusive = !tokens.canConsume("EXCLUSIVE"); - constraint = new Between(left, lowerBound, upperBound, lowerInclusive, upperInclusive); + constraint = between(left, lowerBound, upperBound, lowerInclusive, upperInclusive); if (not) constraint = not(constraint); } else { Operator operator = parseComparisonOperator(tokens); @@ -1427,6 +1427,19 @@ public class SqlQueryParser implements QueryParser { return new Or(constraint1, constraint2); } + protected Between between( DynamicOperand operand, + StaticOperand lowerBound, + StaticOperand upperBound, + boolean lowerInclusive, + boolean upperInclusive ) { + return new Between(operand, lowerBound, upperBound, lowerInclusive, upperInclusive); + } + + protected SetCriteria setCriteria( DynamicOperand operand, + Collection values ) { + return new SetCriteria(operand, values); + } + protected FullTextSearch fullTextSearch( SelectorName name, String propertyName, String expression, 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 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/plan/CanonicalPlanner.java (working copy) @@ -23,7 +23,6 @@ */ package org.modeshape.graph.query.plan; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -388,11 +387,10 @@ public class CanonicalPlanner implements Planner { PlanNode plan, List columns, Map selectors ) { - if (columns == null) columns = Collections.emptyList(); PlanNode projectNode = new PlanNode(Type.PROJECT); - if (columns.isEmpty()) { - List newColumns = new LinkedList(); + List newColumns = new LinkedList(); + if (columns == null || columns.isEmpty()) { // SELECT *, so find all of the columns that are available from all the sources ... for (Map.Entry entry : selectors.entrySet()) { SelectorName tableName = entry.getKey(); @@ -400,14 +398,8 @@ public class CanonicalPlanner implements Planner { // Add the selector that is being used ... projectNode.addSelector(tableName); // Compute the columns from this selector ... - for (Schemata.Column column : table.getColumns()) { - String columnName = column.getName(); - String propertyName = columnName; - Column newColumn = new Column(tableName, propertyName, columnName); - newColumns.add(newColumn); - } + allColumnsFor(table, tableName, newColumns); } - columns = newColumns; } else { // Add the selector used by each column ... for (Column column : columns) { @@ -422,18 +414,36 @@ public class CanonicalPlanner implements Planner { } else { // Make sure that the column is in the table ... String columnName = column.propertyName(); - String name = columnName; - if (table.getColumn(name) == null && context.getHints().validateColumnExistance) { - context.getProblems().addError(GraphI18n.columnDoesNotExistOnTable, name, tableName); + if ("*".equals(columnName)) { + // This is a 'SELECT *' on this source, but this source is one of multiple sources ... + allColumnsFor(table, tableName, newColumns); + } else { + // This is a particular column, so add it ... + newColumns.add(column); + } + if (table.getColumn(columnName) == null && context.getHints().validateColumnExistance) { + context.getProblems().addError(GraphI18n.columnDoesNotExistOnTable, columnName, tableName); } } } } - projectNode.setProperty(Property.PROJECT_COLUMNS, columns); + projectNode.setProperty(Property.PROJECT_COLUMNS, newColumns); projectNode.addLastChild(plan); return projectNode; } + protected void allColumnsFor( Table table, + SelectorName tableName, + List columns ) { + // Compute the columns from this selector ... + for (Schemata.Column column : table.getColumns()) { + String columnName = column.getName(); + String propertyName = columnName; + Column newColumn = new Column(tableName, propertyName, columnName); + columns.add(newColumn); + } + } + /** * Attach DUP_REMOVE node at top of tree. The DUP_REMOVE may be pushed down to a source (or sources) if possible by the * optimizer. 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 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanUtil.java (working copy) @@ -326,12 +326,26 @@ public class PlanUtil { } } break; + case SOURCE: + // Check the source alias ... + SelectorName sourceAlias = planNode.getProperty(Property.SOURCE_ALIAS, SelectorName.class); + SelectorName replacement = rewrittenSelectors.get(sourceAlias); + if (replacement == null) { + // Try the source name ... + SelectorName sourceName = planNode.getProperty(Property.SOURCE_NAME, SelectorName.class); + replacement = rewrittenSelectors.get(sourceName); + } + if (replacement != null) { + planNode.setProperty(Property.SOURCE_ALIAS, replacement); + planNode.getSelectors().remove(sourceAlias); + planNode.getSelectors().add(replacement); + } + break; case GROUP: case SET_OPERATION: case DUP_REMOVE: case LIMIT: case NULL: - case SOURCE: case ACCESS: // None of these have to be changed ... break; @@ -492,6 +506,13 @@ public class PlanUtil { if (lhs == newLhs) return comparison; return new Comparison(newLhs, comparison.operator(), rhs); } + if (constraint instanceof SetCriteria) { + SetCriteria criteria = (SetCriteria)constraint; + DynamicOperand lhs = criteria.leftOperand(); + DynamicOperand newLhs = replaceReferencesToRemovedSource(context, lhs, rewrittenSelectors); + if (lhs == newLhs) return constraint; + return new SetCriteria(newLhs, criteria.rightOperands()); + } return constraint; } @@ -539,110 +560,8 @@ public class PlanUtil { PlanNode node = topOfViewInPlan; List potentiallyRemovableSources = new LinkedList(); do { - // Remove the view from the selectors ... - if (node.getSelectors().remove(viewName)) { - switch (node.getType()) { - case PROJECT: - // Adjust the columns ... - List columns = node.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); - if (columns != null) { - for (int i = 0; i != columns.size(); ++i) { - Column column = columns.get(i); - if (column.selectorName().equals(viewName)) { - // This column references the view ... - String columnName = column.propertyName(); - String columnAlias = column.columnName(); - // Find the source column that this view column corresponds to ... - Column sourceColumn = mappings.getMappedColumn(columnName); - if (sourceColumn != null) { - SelectorName sourceName = sourceColumn.selectorName(); - // Replace the view column with one that uses the same alias but that references the - // source - // column ... - columns.set(i, new Column(sourceName, sourceColumn.propertyName(), columnAlias)); - node.addSelector(sourceName); - } else { - if (mappings.getMappedSelectorNames().size() == 1) { - SelectorName sourceName = mappings.getSingleMappedSelectorName(); - if (sourceName != null) { - columns.set(i, new Column(sourceName, columnName, columnAlias)); - node.addSelector(sourceName); - } else { - node.addSelector(column.selectorName()); - } - } else { - node.addSelector(column.selectorName()); - } - } - } else { - node.addSelector(column.selectorName()); - } - } - } - break; - case SELECT: - Constraint constraint = node.getProperty(Property.SELECT_CRITERIA, Constraint.class); - Constraint newConstraint = replaceReferences(context, constraint, mappings, node); - if (constraint != newConstraint) { - node.getSelectors().clear(); - node.addSelectors(Visitors.getSelectorsReferencedBy(newConstraint)); - node.setProperty(Property.SELECT_CRITERIA, newConstraint); - } - break; - case SOURCE: - SelectorName sourceName = node.getProperty(Property.SOURCE_NAME, SelectorName.class); - assert sourceName.equals(sourceName); // selector name already matches - potentiallyRemovableSources.add(node); - break; - case JOIN: - JoinCondition joinCondition = node.getProperty(Property.JOIN_CONDITION, JoinCondition.class); - JoinCondition newJoinCondition = replaceViewReferences(context, joinCondition, mappings, node); - node.getSelectors().clear(); - node.setProperty(Property.JOIN_CONDITION, newJoinCondition); - node.addSelectors(Visitors.getSelectorsReferencedBy(newJoinCondition)); - List joinConstraints = node.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class); - if (joinConstraints != null && !joinConstraints.isEmpty()) { - List newConstraints = new ArrayList(joinConstraints.size()); - for (Constraint joinConstraint : joinConstraints) { - newConstraint = replaceReferences(context, joinConstraint, mappings, node); - newConstraints.add(newConstraint); - node.addSelectors(Visitors.getSelectorsReferencedBy(newConstraint)); - } - node.setProperty(Property.JOIN_CONSTRAINTS, newConstraints); - } - break; - case ACCESS: - // Add all the selectors used by the subnodes ... - for (PlanNode child : node) { - node.addSelectors(child.getSelectors()); - } - break; - case SORT: - // The selector names and Ordering objects needs to be changed ... - List orderings = node.getPropertyAsList(Property.SORT_ORDER_BY, Ordering.class); - List newOrderings = new ArrayList(orderings.size()); - node.getSelectors().clear(); - for (Ordering ordering : orderings) { - DynamicOperand operand = ordering.operand(); - DynamicOperand newOperand = replaceViewReferences(context, operand, mappings, node); - if (newOperand != operand) { - ordering = new Ordering(newOperand, ordering.order()); - } - node.addSelectors(Visitors.getSelectorsReferencedBy(ordering)); - newOrderings.add(ordering); - } - node.setProperty(Property.SORT_ORDER_BY, newOrderings); - break; - case GROUP: - // Don't yet use GROUP BY - case SET_OPERATION: - case DUP_REMOVE: - case LIMIT: - case NULL: - break; - } - } // Move to the parent ... + replaceViewReferences(context, node, mappings, viewName, potentiallyRemovableSources); node = node.getParent(); } while (node != null); @@ -664,6 +583,119 @@ public class PlanUtil { } } + protected static void replaceViewReferences( QueryContext context, + PlanNode node, + ColumnMapping mappings, + SelectorName viewName, + List potentiallyRemovableSources ) { + assert node != null; + assert viewName != null; + + // Remove the view from the selectors ... + if (node.getSelectors().remove(viewName)) { + switch (node.getType()) { + case PROJECT: + // Adjust the columns ... + List columns = node.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); + if (columns != null) { + for (int i = 0; i != columns.size(); ++i) { + Column column = columns.get(i); + if (column.selectorName().equals(viewName)) { + // This column references the view ... + String columnName = column.propertyName(); + String columnAlias = column.columnName(); + // Find the source column that this view column corresponds to ... + Column sourceColumn = mappings.getMappedColumn(columnName); + if (sourceColumn != null) { + SelectorName sourceName = sourceColumn.selectorName(); + // Replace the view column with one that uses the same alias but that references the + // source + // column ... + columns.set(i, new Column(sourceName, sourceColumn.propertyName(), columnAlias)); + node.addSelector(sourceName); + } else { + if (mappings.getMappedSelectorNames().size() == 1) { + SelectorName sourceName = mappings.getSingleMappedSelectorName(); + if (sourceName != null) { + columns.set(i, new Column(sourceName, columnName, columnAlias)); + node.addSelector(sourceName); + } else { + node.addSelector(column.selectorName()); + } + } else { + node.addSelector(column.selectorName()); + } + } + } else { + node.addSelector(column.selectorName()); + } + } + } + break; + case SELECT: + Constraint constraint = node.getProperty(Property.SELECT_CRITERIA, Constraint.class); + Constraint newConstraint = replaceReferences(context, constraint, mappings, node); + if (constraint != newConstraint) { + node.getSelectors().clear(); + node.addSelectors(Visitors.getSelectorsReferencedBy(newConstraint)); + node.setProperty(Property.SELECT_CRITERIA, newConstraint); + } + break; + case SOURCE: + SelectorName sourceName = node.getProperty(Property.SOURCE_NAME, SelectorName.class); + assert sourceName.equals(sourceName); // selector name already matches + potentiallyRemovableSources.add(node); + break; + case JOIN: + JoinCondition joinCondition = node.getProperty(Property.JOIN_CONDITION, JoinCondition.class); + JoinCondition newJoinCondition = replaceViewReferences(context, joinCondition, mappings, node); + node.getSelectors().clear(); + node.setProperty(Property.JOIN_CONDITION, newJoinCondition); + node.addSelectors(Visitors.getSelectorsReferencedBy(newJoinCondition)); + List joinConstraints = node.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class); + if (joinConstraints != null && !joinConstraints.isEmpty()) { + List newConstraints = new ArrayList(joinConstraints.size()); + for (Constraint joinConstraint : joinConstraints) { + newConstraint = replaceReferences(context, joinConstraint, mappings, node); + newConstraints.add(newConstraint); + node.addSelectors(Visitors.getSelectorsReferencedBy(newConstraint)); + } + node.setProperty(Property.JOIN_CONSTRAINTS, newConstraints); + } + break; + case ACCESS: + // Add all the selectors used by the subnodes ... + for (PlanNode child : node) { + node.addSelectors(child.getSelectors()); + } + break; + case SORT: + // The selector names and Ordering objects needs to be changed ... + List orderings = node.getPropertyAsList(Property.SORT_ORDER_BY, Ordering.class); + List newOrderings = new ArrayList(orderings.size()); + node.getSelectors().clear(); + for (Ordering ordering : orderings) { + DynamicOperand operand = ordering.operand(); + DynamicOperand newOperand = replaceViewReferences(context, operand, mappings, node); + if (newOperand != operand) { + ordering = new Ordering(newOperand, ordering.order()); + } + node.addSelectors(Visitors.getSelectorsReferencedBy(ordering)); + newOrderings.add(ordering); + } + node.setProperty(Property.SORT_ORDER_BY, newOrderings); + break; + case GROUP: + // Don't yet use GROUP BY + case SET_OPERATION: + case DUP_REMOVE: + case LIMIT: + case NULL: + break; + } + } + } + public static Constraint replaceReferences( QueryContext context, Constraint constraint, ColumnMapping mapping, @@ -1002,6 +1034,15 @@ public class PlanUtil { return mapping; } + public static void setSelectorsOnSubplan( PlanNode subplan, + Set selectors ) { + subplan.getSelectors().clear(); + subplan.getSelectors().addAll(selectors); + for (PlanNode child : subplan.getChildren()) { + setSelectorsOnSubplan(child, selectors); + } + } + /** * Defines how the view columns are mapped (or resolved) into the columns from the source tables. */ Index: modeshape-graph/src/main/java/org/modeshape/graph/query/process/JoinComponent.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/process/JoinComponent.java (revision 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/JoinComponent.java (working copy) @@ -23,16 +23,13 @@ */ package org.modeshape.graph.query.process; -import java.util.ArrayList; import java.util.Comparator; -import java.util.List; import org.modeshape.graph.Location; import org.modeshape.graph.property.Path; import org.modeshape.graph.property.ValueComparators; import org.modeshape.graph.query.QueryContext; import org.modeshape.graph.query.QueryResults.Columns; import org.modeshape.graph.query.model.ChildNodeJoinCondition; -import org.modeshape.graph.query.model.Column; import org.modeshape.graph.query.model.DescendantNodeJoinCondition; import org.modeshape.graph.query.model.EquiJoinCondition; import org.modeshape.graph.query.model.JoinCondition; @@ -60,7 +57,7 @@ public abstract class JoinComponent extends ProcessingComponent { ProcessingComponent right, JoinCondition condition, JoinType joinType ) { - super(context, computeJoinedColumns(left.getColumns(), right.getColumns())); + super(context, left.getColumns().joinWith(right.getColumns())); this.left = left; this.right = right; this.joinType = joinType; @@ -124,16 +121,26 @@ public abstract class JoinComponent extends ProcessingComponent { return right.getColumns(); } - protected static Columns computeJoinedColumns( Columns leftColumns, - Columns rightColumns ) { - if (leftColumns == rightColumns) return leftColumns; - List columns = new ArrayList(leftColumns.getColumnCount() + rightColumns.getColumnCount()); - columns.addAll(leftColumns.getColumns()); - columns.addAll(rightColumns.getColumns()); - boolean includeFullTextScores = leftColumns.hasFullTextSearchScores() || rightColumns.hasFullTextSearchScores(); - return new QueryResultColumns(columns, includeFullTextScores); - } - + /** + * Create a {@link TupleMerger} implementation that will combine a tuple fitting the left columns with a tuple fitting the + * right columns. This merger will properly place all of the values, locations, and scores such that the tuples always have + * this arrangement: + * + *
+     *    [ v1, v2, ..., vM, loc1, loc2, ..., locN, s1, s2, ..., sN ]
+     * 
+ * + * where M is the number of values in the tuple, and N is the number of sources in the tuple. + *

+ * Note that this merger does not actually reduce or combine values. That is done with a particular {@link JoinComponent} + * subclass. + *

+ * + * @param joinColumns the Columns specification for the joined/merged tuples; may not be null + * @param leftColumns the Columns specification for the tuple on the left side of the join; may not be null + * @param rightColumns the Columns specification for the tuple on the right side of the join; may not be null + * @return the merger implementation that will combine tuples from the left and right to form the merged tuples; never null + */ protected static TupleMerger createMerger( Columns joinColumns, Columns leftColumns, Columns rightColumns ) { @@ -159,8 +166,8 @@ public abstract class JoinComponent extends ProcessingComponent { final int rightScoreCount = rightTupleSize - rightColumnCount - rightLocationCount; final int startLeftScores = startRightLocations + rightLocationCount; final int startRightScores = startLeftScores + leftScoreCount; - final boolean leftScores = leftScoreCount > 0; - final boolean rightScores = rightScoreCount > 0; + final int leftScoreIndex = leftTupleSize - leftScoreCount; + final int rightScoreIndex = rightTupleSize - rightScoreCount; return new TupleMerger() { /** @@ -175,19 +182,19 @@ public abstract class JoinComponent extends ProcessingComponent { // initialized to null values. if (leftTuple != null) { // Copy the left tuple values ... - System.arraycopy(result, 0, leftTuple, 0, leftColumnCount); - System.arraycopy(result, startLeftLocations, leftTuple, leftColumnCount, leftLocationCount); - if (leftScores) { - System.arraycopy(result, startLeftScores, leftTuple, leftLocationCount, leftScoreCount); - } + System.arraycopy(leftTuple, 0, result, 0, leftColumnCount); + // Copy the left tuple locations ... + System.arraycopy(leftTuple, leftColumnCount, result, startLeftLocations, leftLocationCount); + // Copy the left tuple scores ... + System.arraycopy(leftTuple, leftScoreIndex, result, startLeftScores, leftScoreCount); } if (rightTuple != null) { // Copy the right tuple values ... - System.arraycopy(result, leftColumnCount, rightTuple, 0, rightColumnCount); - System.arraycopy(result, startRightLocations, rightTuple, rightColumnCount, rightLocationCount); - if (rightScores) { - System.arraycopy(result, startRightScores, rightTuple, rightLocationCount, rightScoreCount); - } + System.arraycopy(rightTuple, 0, result, leftColumnCount, rightColumnCount); + // Copy the right tuple locations ... + System.arraycopy(rightTuple, rightColumnCount, result, startRightLocations, rightLocationCount); + // Copy the right tuple scores ... + System.arraycopy(rightTuple, rightScoreIndex, result, startRightScores, rightScoreCount); } return result; } @@ -207,19 +214,23 @@ public abstract class JoinComponent extends ProcessingComponent { // initialized to null values. if (leftTuple != null) { // Copy the left tuple values ... - System.arraycopy(result, 0, leftTuple, 0, leftColumnCount); - System.arraycopy(result, startLeftLocations, leftTuple, leftColumnCount, leftLocationCount); + System.arraycopy(leftTuple, 0, result, 0, leftColumnCount); + System.arraycopy(leftTuple, leftColumnCount, result, startLeftLocations, leftLocationCount); } if (rightTuple != null) { // Copy the right tuple values ... - System.arraycopy(result, leftColumnCount, rightTuple, 0, rightColumnCount); - System.arraycopy(result, startRightLocations, rightTuple, rightColumnCount, rightLocationCount); + System.arraycopy(rightTuple, 0, result, leftColumnCount, rightColumnCount); + System.arraycopy(rightTuple, rightColumnCount, result, startRightLocations, rightLocationCount); } return result; } }; } + /** + * A component that will merge the supplied tuple on the left side of a join with the supplied tuple on the right side of the + * join, to produce a single tuple with all components from the left and right tuples. + */ protected static interface TupleMerger { Object[] merge( Object[] leftTuple, Object[] rightTuple ); @@ -290,8 +301,7 @@ public abstract class JoinComponent extends ProcessingComponent { final int index = component.getColumns().getLocationIndex(selectorName); return new ValueSelector() { public Object evaluate( Object[] tuple ) { - Location location = (Location)tuple[index]; - return location != null ? location.getPath() : null; + return tuple[index]; // Location } }; } Index: modeshape-graph/src/main/java/org/modeshape/graph/query/process/ProcessingComponent.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/process/ProcessingComponent.java (revision 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/ProcessingComponent.java (working copy) @@ -153,7 +153,6 @@ public abstract class ProcessingComponent { DynamicOperand operand ) { assert operand != null; assert columns != null; - assert context != null; if (operand instanceof PropertyValue) { PropertyValue propValue = (PropertyValue)operand; String propertyName = propValue.propertyName(); @@ -202,7 +201,7 @@ public abstract class ProcessingComponent { String selectorName = value.selectorName().name(); final int index = columns.getColumnIndexForProperty(selectorName, propertyName); // Find the expected property type of the value ... - Table table = context.getSchemata().getTable(value.selectorName()); + Table table = schemata.getTable(value.selectorName()); Column schemaColumn = table.getColumn(propertyName); final String expectedType = schemaColumn.getPropertyType(); final TypeFactory typeFactory = typeSystem.getTypeFactory(expectedType); Index: modeshape-graph/src/main/java/org/modeshape/graph/query/process/QueryProcessor.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/process/QueryProcessor.java (revision 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/QueryProcessor.java (working copy) @@ -25,8 +25,10 @@ package org.modeshape.graph.query.process; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import org.modeshape.graph.query.QueryContext; import org.modeshape.graph.query.QueryResults; import org.modeshape.graph.query.QueryResults.Columns; @@ -41,6 +43,7 @@ import org.modeshape.graph.query.model.Limit; import org.modeshape.graph.query.model.Ordering; import org.modeshape.graph.query.model.QueryCommand; import org.modeshape.graph.query.model.SameNodeJoinCondition; +import org.modeshape.graph.query.model.SelectorName; import org.modeshape.graph.query.model.SetQuery.Operation; import org.modeshape.graph.query.plan.JoinAlgorithm; import org.modeshape.graph.query.plan.PlanNode; @@ -89,6 +92,7 @@ public abstract class QueryProcessor implements Processor { if (component != null) { // Now execute the component ... try { + columns = component.getColumns(); preExecute(context); tuples = component.execute(); } finally { @@ -213,8 +217,15 @@ public abstract class QueryProcessor implements Processor { case JOIN: // Create the components under the JOIN ... assert node.getChildCount() == 2; - ProcessingComponent left = createComponent(originalQuery, context, node.getFirstChild(), columns, analyzer); - ProcessingComponent right = createComponent(originalQuery, context, node.getLastChild(), columns, analyzer); + PlanNode leftPlan = node.getFirstChild(); + PlanNode rightPlan = node.getLastChild(); + + // Define the columns for each side, taken from the supplied columns ... + Columns leftColumns = createColumnsFor(leftPlan, columns); + Columns rightColumns = createColumnsFor(rightPlan, columns); + + ProcessingComponent left = createComponent(originalQuery, context, leftPlan, leftColumns, analyzer); + ProcessingComponent right = createComponent(originalQuery, context, rightPlan, rightColumns, analyzer); // Create the join component ... JoinAlgorithm algorithm = node.getProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.class); JoinType joinType = node.getProperty(Property.JOIN_TYPE, JoinType.class); @@ -330,7 +341,15 @@ public abstract class QueryProcessor implements Processor { for (Object orderBy : orderBys) { orderings.add((Ordering)orderBy); } - component = new SortValuesComponent(sortDelegate, orderings); + // Determine the alias-to-name mappings for the selectors in the orderings ... + Map sourceNamesByAlias = new HashMap(); + for (PlanNode source : node.findAllAtOrBelow(Type.SOURCE)) { + SelectorName name = source.getProperty(Property.SOURCE_NAME, SelectorName.class); + SelectorName alias = source.getProperty(Property.SOURCE_ALIAS, SelectorName.class); + if (alias != null) sourceNamesByAlias.put(alias, name); + } + // Now create the sorting component ... + component = new SortValuesComponent(sortDelegate, orderings, sourceNamesByAlias); } else { // Order by the location(s) because it's before a merge-join ... component = new SortLocationsComponent(sortDelegate); @@ -344,4 +363,14 @@ public abstract class QueryProcessor implements Processor { assert component != null; return component; } + + protected Columns createColumnsFor( PlanNode node, + Columns projectedColumns ) { + PlanNode project = node.findAtOrBelow(Type.PROJECT); + assert project != null; + List columns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); + assert columns != null; + assert !columns.isEmpty(); + return new QueryResultColumns(columns, projectedColumns.hasFullTextSearchScores()); + } } Index: modeshape-graph/src/main/java/org/modeshape/graph/query/process/QueryResultColumns.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/process/QueryResultColumns.java (revision 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/QueryResultColumns.java (working copy) @@ -70,7 +70,6 @@ public class QueryResultColumns implements Columns { private final List columnNames; private final List selectorNames; private List tupleValueNames; - private final Map columnsByName; private final Map columnIndexByColumnName; private final Map locationIndexBySelectorName; private final Map locationIndexByColumnName; @@ -101,7 +100,6 @@ public class QueryResultColumns implements Columns { protected QueryResultColumns( boolean includeFullTextSearchScores, List columns ) { this.columns = columns != null ? Collections.unmodifiableList(columns) : NO_COLUMNS; - this.columnsByName = new HashMap(); this.columnIndexByColumnName = new HashMap(); Set selectors = new HashSet(); final int columnCount = this.columns.size(); @@ -115,19 +113,15 @@ public class QueryResultColumns implements Columns { for (int i = 0, max = this.columns.size(); i != max; ++i) { Column column = this.columns.get(i); assert column != null; - String columnName = column.columnName(); - assert columnName != null; - if (columnsByName.put(columnName, column) != null) { - assert false : "Column names must be unique"; - } - names.add(columnName); - columnIndexByColumnName.put(columnName, new Integer(i)); String selectorName = column.selectorName().name(); if (selectors.add(selectorName)) { selectorNames.add(selectorName); selectorIndex = new Integer(selectorIndex.intValue() + 1); locationIndexBySelectorName.put(selectorName, selectorIndex); } + String columnName = columnNameFor(column, names); + assert columnName != null; + columnIndexByColumnName.put(columnName, new Integer(i)); locationIndexByColumnIndex.put(new Integer(i), selectorIndex); locationIndexByColumnName.put(columnName, selectorIndex); // Insert the entry by selector name and property name ... @@ -158,49 +152,10 @@ public class QueryResultColumns implements Columns { } } - public static boolean includeFullTextScores( Iterable constraints ) { - for (Constraint constraint : constraints) { - if (includeFullTextScores(constraint)) return true; - } - return false; - } - - public static boolean includeFullTextScores( Constraint constraint ) { - final AtomicBoolean includeFullTextScores = new AtomicBoolean(false); - if (constraint != null) { - Visitors.visitAll(constraint, new Visitors.AbstractVisitor() { - @Override - public void visit( FullTextSearch obj ) { - includeFullTextScores.set(true); - } - }); - } - return includeFullTextScores.get(); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.query.QueryResults.Columns#subSelect(java.util.List) - */ - public Columns subSelect( List columns ) { - return new QueryResultColumns(columns, this); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.query.QueryResults.Columns#subSelect(org.modeshape.graph.query.model.Column[]) - */ - public Columns subSelect( Column... columns ) { - return new QueryResultColumns(Arrays.asList(columns), this); - } - private QueryResultColumns( List columns, QueryResultColumns wrappedAround ) { assert columns != null; this.columns = Collections.unmodifiableList(columns); - this.columnsByName = new HashMap(); this.columnIndexByColumnName = new HashMap(); this.locationIndexBySelectorName = new HashMap(); this.locationIndexByColumnIndex = new HashMap(); @@ -211,19 +166,15 @@ public class QueryResultColumns implements Columns { for (int i = 0, max = this.columns.size(); i != max; ++i) { Column column = this.columns.get(i); assert column != null; - String columnName = column.columnName(); + String selectorName = column.selectorName().name(); + if (!selectorNames.contains(selectorName)) selectorNames.add(selectorName); + String columnName = columnNameFor(column, names); assert columnName != null; - if (columnsByName.put(columnName, column) != null) { - assert false : "Column names must be unique"; - } - names.add(columnName); Integer columnIndex = new Integer(wrappedAround.getColumnIndexForName(columnName)); columnIndexByColumnName.put(columnName, columnIndex); - String selectorName = column.selectorName().name(); - if (!selectorNames.contains(selectorName)) selectorNames.add(selectorName); Integer selectorIndex = new Integer(wrappedAround.getLocationIndex(selectorName)); locationIndexBySelectorName.put(selectorName, selectorIndex); - locationIndexByColumnIndex.put(new Integer(0), selectorIndex); + locationIndexByColumnIndex.put(columnIndex, selectorIndex); locationIndexByColumnName.put(columnName, selectorIndex); // Insert the entry by selector name and property name ... Map byPropertyName = columnIndexByPropertyNameBySelectorName.get(selectorName); @@ -252,6 +203,58 @@ public class QueryResultColumns implements Columns { } } + public static boolean includeFullTextScores( Iterable constraints ) { + for (Constraint constraint : constraints) { + if (includeFullTextScores(constraint)) return true; + } + return false; + } + + public static boolean includeFullTextScores( Constraint constraint ) { + final AtomicBoolean includeFullTextScores = new AtomicBoolean(false); + if (constraint != null) { + Visitors.visitAll(constraint, new Visitors.AbstractVisitor() { + @Override + public void visit( FullTextSearch obj ) { + includeFullTextScores.set(true); + } + }); + } + return includeFullTextScores.get(); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.query.QueryResults.Columns#subSelect(java.util.List) + */ + public Columns subSelect( List columns ) { + return new QueryResultColumns(columns, this); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.query.QueryResults.Columns#subSelect(org.modeshape.graph.query.model.Column[]) + */ + public Columns subSelect( Column... columns ) { + return new QueryResultColumns(Arrays.asList(columns), this); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.query.QueryResults.Columns#joinWith(org.modeshape.graph.query.QueryResults.Columns) + */ + public Columns joinWith( Columns rightColumns ) { + if (this == rightColumns) return this; + List columns = new ArrayList(this.getColumnCount() + rightColumns.getColumnCount()); + columns.addAll(this.getColumns()); + columns.addAll(rightColumns.getColumns()); + boolean includeFullTextScores = this.hasFullTextSearchScores() || rightColumns.hasFullTextSearchScores(); + return new QueryResultColumns(columns, includeFullTextScores); + } + /** * {@inheritDoc} * @@ -406,19 +409,6 @@ public class QueryResultColumns implements Columns { /** * {@inheritDoc} * - * @see org.modeshape.graph.query.QueryResults.Columns#getPropertyNameForColumn(java.lang.String) - */ - public String getPropertyNameForColumn( String columnName ) { - Column result = columnsByName.get(columnName); - if (result == null) { - throw new NoSuchElementException(GraphI18n.columnDoesNotExistInQuery.text(columnName)); - } - return result.propertyName(); - } - - /** - * {@inheritDoc} - * * @see org.modeshape.graph.query.QueryResults.Columns#getColumnIndexForName(java.lang.String) */ public int getColumnIndexForName( String columnName ) { @@ -509,6 +499,16 @@ public class QueryResultColumns implements Columns { return false; } + protected static String columnNameFor( Column column, + List columnNames ) { + String columnName = column.columnName() != null ? column.columnName() : column.propertyName(); + if (columnNames.contains(columnName)) { + columnName = column.selectorName() + "." + columnName; + } + columnNames.add(columnName); + return columnName; + } + /** * {@inheritDoc} * @@ -517,12 +517,14 @@ public class QueryResultColumns implements Columns { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(" ["); + sb.append('['); boolean first = true; + Iterator nameIter = this.columnNames.iterator(); for (Column column : getColumns()) { if (first) first = false; else sb.append(", "); sb.append(column); + sb.append('(').append(getColumnIndexForName(nameIter.next())).append(')'); } sb.append("] => Locations["); first = true; Index: modeshape-graph/src/main/java/org/modeshape/graph/query/process/SortValuesComponent.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/process/SortValuesComponent.java (revision 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/SortValuesComponent.java (working copy) @@ -27,13 +27,16 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Map; import org.modeshape.graph.query.QueryContext; import org.modeshape.graph.query.QueryResults.Columns; import org.modeshape.graph.query.model.Order; import org.modeshape.graph.query.model.Ordering; +import org.modeshape.graph.query.model.SelectorName; import org.modeshape.graph.query.model.TypeSystem; import org.modeshape.graph.query.model.TypeSystem.TypeFactory; import org.modeshape.graph.query.plan.PlanNode.Type; +import org.modeshape.graph.query.validate.Schemata; /** * A {@link ProcessingComponent} implementation that performs a {@link Type#PROJECT PROJECT} operation to reduce the columns that @@ -44,9 +47,10 @@ public class SortValuesComponent extends DelegatingComponent { private final Comparator sortingComparator; public SortValuesComponent( ProcessingComponent delegate, - List orderings ) { + List orderings, + Map sourceNamesByAlias ) { super(delegate); - this.sortingComparator = createSortComparator(delegate.getContext(), delegate.getColumns(), orderings); + this.sortingComparator = createSortComparator(delegate.getContext(), delegate.getColumns(), orderings, sourceNamesByAlias); } /** @@ -73,19 +77,20 @@ public class SortValuesComponent extends DelegatingComponent { protected Comparator createSortComparator( QueryContext context, Columns columns, - List orderings ) { + List orderings, + Map sourceNamesByAlias ) { assert context != null; assert orderings != null; if (orderings.isEmpty()) { return null; } if (orderings.size() == 1) { - return createSortComparator(context, columns, orderings.get(0)); + return createSortComparator(context, columns, orderings.get(0), sourceNamesByAlias); } // Create a comparator that uses an ordered list of comparators ... final List> comparators = new ArrayList>(orderings.size()); for (Ordering ordering : orderings) { - comparators.add(createSortComparator(context, columns, ordering)); + comparators.add(createSortComparator(context, columns, ordering, sourceNamesByAlias)); } return new Comparator() { public int compare( Object[] tuple1, @@ -97,17 +102,33 @@ public class SortValuesComponent extends DelegatingComponent { return 0; } }; - } @SuppressWarnings( "unchecked" ) protected Comparator createSortComparator( QueryContext context, Columns columns, - Ordering ordering ) { + Ordering ordering, + final Map sourceNamesByAlias ) { assert context != null; assert ordering != null; + final Schemata originalSchemata = context.getSchemata(); + final Schemata schemataWithAliases = sourceNamesByAlias == null ? originalSchemata : new Schemata() { + public Table getTable( SelectorName name ) { + // First assume that the name is an alias, so try resolving it first ... + Table result = null; + SelectorName unaliasedName = sourceNamesByAlias.get(name); + if (unaliasedName != null) { + result = originalSchemata.getTable(unaliasedName); + } + if (result == null) { + // The name was not an alias, so use it to look up the table ... + result = originalSchemata.getTable(name); + } + return result; + } + }; final DynamicOperation operation = createDynamicOperation(context.getTypeSystem(), - context.getSchemata(), + schemataWithAliases, columns, ordering.operand()); final TypeSystem typeSystem = context.getTypeSystem(); Index: modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableSchemata.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableSchemata.java (revision 1916) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableSchemata.java (working copy) @@ -323,18 +323,29 @@ public class ImmutableSchemata implements Schemata { assert !columns.isEmpty(); // Go through all the columns and look up the types ... + Map tableNameByAlias = null; List viewColumns = new ArrayList(columns.size()); for (org.modeshape.graph.query.model.Column column : columns) { // Find the table that the column came from ... Table source = schemata.getTable(column.selectorName()); - if (source == null) break; + if (source == null) { + // The column may be referring to the alias of the table ... + if (tableNameByAlias == null) { + tableNameByAlias = Visitors.getSelectorNamesByAlias(command); + } + SelectorName tableName = tableNameByAlias.get(column.selectorName()); + if (tableName != null) source = schemata.getTable(tableName); + if (source == null) { + continue; + } + } String viewColumnName = column.columnName(); String sourceColumnName = column.propertyName(); // getColumnName() returns alias Column sourceColumn = source.getColumn(sourceColumnName); if (sourceColumn == null) { throw new InvalidQueryException(Visitors.readable(command), - "The view references a non-existant column '" - + column.columnName() + "' in '" + source.getName() + "'"); + "The view references a non-existant column '" + column.columnName() + + "' in '" + source.getName() + "'"); } viewColumns.add(new ImmutableColumn(viewColumnName, sourceColumn.getPropertyType(), sourceColumn.isFullTextSearchable())); Index: modeshape-graph/src/test/java/org/modeshape/graph/query/optimize/RuleBasedOptimizerTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/query/optimize/RuleBasedOptimizerTest.java (revision 1916) +++ modeshape-graph/src/test/java/org/modeshape/graph/query/optimize/RuleBasedOptimizerTest.java (working copy) @@ -127,10 +127,6 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { print = false; } - // ---------------------------------------------------------------------------------------------------------------- - // Test the rule stack logic - // ---------------------------------------------------------------------------------------------------------------- - @Test public void shouldExecuteEachRuleInSequence() { optimizer.optimize(context, node); @@ -173,7 +169,7 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { source.setProperty(Property.SOURCE_NAME, selector("t1")); source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(expected), is(true)); + assertPlanMatches(expected); } @Test @@ -187,78 +183,82 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { source.setProperty(Property.SOURCE_NAME, selector("t1")); source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(expected), is(true)); + assertPlanMatches(expected); } @Test public void shouldOptimizePlanForSimpleQueryWithSelectStarWithAlias() { node = optimize("SELECT * FROM t1 AS x1"); // Create the expected plan ... - PlanNode expected = new PlanNode(Type.ACCESS, selector("t1")); - PlanNode project = new PlanNode(Type.PROJECT, expected, selector("t1")); - project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12"), column("t1", "c13"))); - PlanNode source = new PlanNode(Type.SOURCE, project, selector("t1")); + PlanNode expected = new PlanNode(Type.ACCESS, selector("x1")); + PlanNode project = new PlanNode(Type.PROJECT, expected, selector("x1")); + project.setProperty(Property.PROJECT_COLUMNS, columns(column("x1", "c11"), column("x1", "c12"), column("x1", "c13"))); + PlanNode source = new PlanNode(Type.SOURCE, project, selector("x1")); source.setProperty(Property.SOURCE_NAME, selector("t1")); + source.setProperty(Property.SOURCE_ALIAS, selector("x1")); source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(expected), is(true)); + assertPlanMatches(expected); } @Test public void shouldOptimizePlanForSimpleQueryWithSelectStarFromTableWithAliasAndValueCriteria() { node = optimize("SELECT * FROM t1 AS x1 WHERE c13 < CAST('3' AS LONG)"); // Create the expected plan ... - PlanNode expected = new PlanNode(Type.ACCESS, selector("t1")); - PlanNode project = new PlanNode(Type.PROJECT, expected, selector("t1")); - project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12"), column("t1", "c13"))); - PlanNode select = new PlanNode(Type.SELECT, project, selector("t1")); - select.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c13"), Operator.LESS_THAN, + PlanNode expected = new PlanNode(Type.ACCESS, selector("x1")); + PlanNode project = new PlanNode(Type.PROJECT, expected, selector("x1")); + project.setProperty(Property.PROJECT_COLUMNS, columns(column("x1", "c11"), column("x1", "c12"), column("x1", "c13"))); + PlanNode select = new PlanNode(Type.SELECT, project, selector("x1")); + select.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("x1"), "c13"), Operator.LESS_THAN, new Literal(3L))); - PlanNode source = new PlanNode(Type.SOURCE, select, selector("t1")); + PlanNode source = new PlanNode(Type.SOURCE, select, selector("x1")); source.setProperty(Property.SOURCE_NAME, selector("t1")); + source.setProperty(Property.SOURCE_ALIAS, selector("x1")); source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(expected), is(true)); + assertPlanMatches(expected); } @Test public void shouldOptimizePlanForSimpleQueryWithSelectStarFromViewWithNoAliasAndValueCriteria() { node = optimize("SELECT * FROM v1 WHERE c11 = 'value'"); // Create the expected plan ... - PlanNode expected = new PlanNode(Type.ACCESS, selector("t1")); - PlanNode project = new PlanNode(Type.PROJECT, expected, selector("t1")); - project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12", "c2"))); - PlanNode select1 = new PlanNode(Type.SELECT, project, selector("t1")); - select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c11"), Operator.EQUAL_TO, + PlanNode expected = new PlanNode(Type.ACCESS, selector("v1")); + PlanNode project = new PlanNode(Type.PROJECT, expected, selector("v1")); + project.setProperty(Property.PROJECT_COLUMNS, columns(column("v1", "c11"), column("v1", "c12", "c2"))); + PlanNode select1 = new PlanNode(Type.SELECT, project, selector("v1")); + select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("v1"), "c11"), Operator.EQUAL_TO, new Literal("value"))); - PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("t1")); - select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c13"), + PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("v1")); + select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("v1"), "c13"), Operator.LESS_THAN, new Literal(3L))); - PlanNode source = new PlanNode(Type.SOURCE, select2, selector("t1")); + PlanNode source = new PlanNode(Type.SOURCE, select2, selector("v1")); source.setProperty(Property.SOURCE_NAME, selector("t1")); + source.setProperty(Property.SOURCE_ALIAS, selector("v1")); source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(expected), is(true)); + assertPlanMatches(expected); } @Test public void shouldOptimizePlanForSimpleQueryWithSelectStarFromViewWithAliasAndValueCriteria() { node = optimize("SELECT * FROM v1 AS x1 WHERE c11 = 'value'"); // Create the expected plan ... - PlanNode expected = new PlanNode(Type.ACCESS, selector("t1")); - PlanNode project = new PlanNode(Type.PROJECT, expected, selector("t1")); - project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12", "c2"))); - PlanNode select1 = new PlanNode(Type.SELECT, project, selector("t1")); - select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c11"), Operator.EQUAL_TO, + PlanNode expected = new PlanNode(Type.ACCESS, selector("x1")); + PlanNode project = new PlanNode(Type.PROJECT, expected, selector("x1")); + project.setProperty(Property.PROJECT_COLUMNS, columns(column("x1", "c11"), column("x1", "c12", "c2"))); + PlanNode select1 = new PlanNode(Type.SELECT, project, selector("x1")); + select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("x1"), "c11"), Operator.EQUAL_TO, new Literal("value"))); - PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("t1")); - select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c13"), + PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("x1")); + select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("x1"), "c13"), Operator.LESS_THAN, new Literal(3L))); - PlanNode source = new PlanNode(Type.SOURCE, select2, selector("t1")); + PlanNode source = new PlanNode(Type.SOURCE, select2, selector("x1")); source.setProperty(Property.SOURCE_NAME, selector("t1")); + source.setProperty(Property.SOURCE_ALIAS, selector("x1")); source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(expected), is(true)); + assertPlanMatches(expected); } @Test @@ -275,7 +275,7 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { source.setProperty(Property.SOURCE_NAME, selector("t1")); source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(expected), is(true)); + assertPlanMatches(expected); } @Test @@ -305,7 +305,7 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t2")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(project), is(true)); + assertPlanMatches(project); } @Test @@ -313,24 +313,25 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { node = optimize("SELECT v1.c11 AS c1 FROM v1 WHERE v1.c11 = 'x' AND v1.c2 = 'y'"); // Create the expected plan ... - PlanNode access = new PlanNode(Type.ACCESS, selector("t1")); - PlanNode project = new PlanNode(Type.PROJECT, access, selector("t1")); - project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11", "c1"))); - PlanNode select1 = new PlanNode(Type.SELECT, project, selector("t1")); - select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c11"), Operator.EQUAL_TO, - new Literal('x'))); - PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("t1")); - select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c12"), Operator.EQUAL_TO, - new Literal('y'))); - PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("t1")); - select3.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c13"), + PlanNode access = new PlanNode(Type.ACCESS, selector("v1")); + PlanNode project = new PlanNode(Type.PROJECT, access, selector("v1")); + project.setProperty(Property.PROJECT_COLUMNS, columns(column("v1", "c11", "c1"))); + PlanNode select1 = new PlanNode(Type.SELECT, project, selector("v1")); + select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("v1"), "c11"), Operator.EQUAL_TO, + new Literal("x"))); + PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("v1")); + select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("v1"), "c12"), Operator.EQUAL_TO, + new Literal("y"))); + PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("v1")); + select3.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("v1"), "c13"), Operator.LESS_THAN, new Literal(3L))); - PlanNode source = new PlanNode(Type.SOURCE, select3, selector("t1")); + PlanNode source = new PlanNode(Type.SOURCE, select3, selector("v1")); source.setProperty(Property.SOURCE_NAME, selector("t1")); + source.setProperty(Property.SOURCE_ALIAS, selector("v1")); source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(access), is(true)); + assertPlanMatches(access); } @Test @@ -369,7 +370,7 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t2")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(project), is(true)); + assertPlanMatches(project); } @Test @@ -377,23 +378,24 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { node = optimize("SELECT type1.a1 AS a, type1.a2 AS b FROM type1 WHERE CONTAINS(type1.a2,'something')"); // Create the expected plan ... - PlanNode access = new PlanNode(Type.ACCESS, selector("all")); - PlanNode project = new PlanNode(Type.PROJECT, access, selector("all")); - project.setProperty(Property.PROJECT_COLUMNS, columns(column("all", "a1", "a"), column("all", "a2", "b"))); - PlanNode select1 = new PlanNode(Type.SELECT, project, selector("all")); - select1.setProperty(Property.SELECT_CRITERIA, new FullTextSearch(selector("all"), "a2", "something")); - PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("all")); - select2.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "primaryType"), + PlanNode access = new PlanNode(Type.ACCESS, selector("type1")); + PlanNode project = new PlanNode(Type.PROJECT, access, selector("type1")); + project.setProperty(Property.PROJECT_COLUMNS, columns(column("type1", "a1", "a"), column("type1", "a2", "b"))); + PlanNode select1 = new PlanNode(Type.SELECT, project, selector("type1")); + select1.setProperty(Property.SELECT_CRITERIA, new FullTextSearch(selector("type1"), "a2", "something")); + PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("type1")); + select2.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "primaryType"), new Literal("t1"), new Literal("t0"))); - PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("all")); - select3.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "mixins"), + PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("type1")); + select3.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "mixins"), new Literal("t3"), new Literal("t4"))); - PlanNode source = new PlanNode(Type.SOURCE, select3, selector("all")); + PlanNode source = new PlanNode(Type.SOURCE, select3, selector("type1")); source.setProperty(Property.SOURCE_NAME, selector("all")); + source.setProperty(Property.SOURCE_ALIAS, selector("type1")); source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("all")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(access), is(true)); + assertPlanMatches(access); } @Test @@ -402,32 +404,33 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { + "FROM type1 JOIN type2 ON type1.a1 = type2.a3 WHERE CONTAINS(type1.a2,'something')"); // Create the expected plan ... - PlanNode access = new PlanNode(Type.ACCESS, selector("all")); - PlanNode project = new PlanNode(Type.PROJECT, access, selector("all")); - project.setProperty(Property.PROJECT_COLUMNS, columns(column("all", "a1", "a"), - column("all", "a2", "b"), - column("all", "a3", "c"), - column("all", "a4", "d"))); - PlanNode select1 = new PlanNode(Type.SELECT, project, selector("all")); - select1.setProperty(Property.SELECT_CRITERIA, new FullTextSearch(selector("all"), "a2", "something")); - PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("all")); - select2.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "primaryType"), + PlanNode access = new PlanNode(Type.ACCESS, selector("type1")); + PlanNode project = new PlanNode(Type.PROJECT, access, selector("type1")); + project.setProperty(Property.PROJECT_COLUMNS, columns(column("type1", "a1", "a"), + column("type1", "a2", "b"), + column("type1", "a3", "c"), + column("type1", "a4", "d"))); + PlanNode select1 = new PlanNode(Type.SELECT, project, selector("type1")); + select1.setProperty(Property.SELECT_CRITERIA, new FullTextSearch(selector("type1"), "a2", "something")); + PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("type1")); + select2.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "primaryType"), new Literal("t1"), new Literal("t0"))); - PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("all")); - select3.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "mixins"), + PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("type1")); + select3.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "mixins"), new Literal("t3"), new Literal("t4"))); - PlanNode select4 = new PlanNode(Type.SELECT, select3, selector("all")); - select4.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "primaryType"), + PlanNode select4 = new PlanNode(Type.SELECT, select3, selector("type1")); + select4.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "primaryType"), new Literal("t2"), new Literal("t0"))); - PlanNode select5 = new PlanNode(Type.SELECT, select4, selector("all")); - select5.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "mixins"), + PlanNode select5 = new PlanNode(Type.SELECT, select4, selector("type1")); + select5.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "mixins"), new Literal("t4"), new Literal("t5"))); - PlanNode source = new PlanNode(Type.SOURCE, select5, selector("all")); + PlanNode source = new PlanNode(Type.SOURCE, select5, selector("type1")); source.setProperty(Property.SOURCE_NAME, selector("all")); + source.setProperty(Property.SOURCE_ALIAS, selector("type1")); source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("all")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(access), is(true)); + assertPlanMatches(access); } @Test @@ -436,46 +439,48 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { + "FROM type1 JOIN type2 ON type1.a2 = type2.a3 WHERE CONTAINS(type1.a1,'something')"); // Create the expected plan ... - PlanNode project = new PlanNode(Type.PROJECT, selector("all")); - project.setProperty(Property.PROJECT_COLUMNS, columns(column("all", "a1", "a"), - column("all", "a2", "b"), - column("all", "a3", "c"), - column("all", "a4", "d"))); - PlanNode select1 = new PlanNode(Type.SELECT, project, selector("all")); - select1.setProperty(Property.SELECT_CRITERIA, new FullTextSearch(selector("all"), "a1", "something")); - PlanNode join = new PlanNode(Type.JOIN, select1, selector("all")); + PlanNode project = new PlanNode(Type.PROJECT, selector("type1"), selector("type2")); + project.setProperty(Property.PROJECT_COLUMNS, columns(column("type1", "a1", "a"), + column("type1", "a2", "b"), + column("type2", "a3", "c"), + column("type2", "a4", "d"))); + PlanNode join = new PlanNode(Type.JOIN, project, selector("type1"), selector("type2")); join.setProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.NESTED_LOOP); join.setProperty(Property.JOIN_TYPE, JoinType.INNER); - join.setProperty(Property.JOIN_CONDITION, new EquiJoinCondition(selector("all"), "a2", selector("all"), "a3")); - - PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("all")); - PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess, selector("all")); - leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("all", "a1"), column("all", "a2"), column("all", "a3"))); - PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject, selector("all")); - leftSelect1.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "primaryType"), + join.setProperty(Property.JOIN_CONDITION, new EquiJoinCondition(selector("type1"), "a2", selector("type2"), "a3")); + + PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("type1")); + PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess, selector("type1")); + leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("type1", "a1"), column("type1", "a2"))); + PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject, selector("type1")); + leftSelect1.setProperty(Property.SELECT_CRITERIA, new FullTextSearch(selector("type1"), "a1", "something")); + PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1, selector("type1")); + leftSelect2.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "primaryType"), new Literal("t1"), new Literal("t0"))); - PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1, selector("all")); - leftSelect2.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "mixins"), + PlanNode leftSelect3 = new PlanNode(Type.SELECT, leftSelect2, selector("type1")); + leftSelect3.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "mixins"), new Literal("t3"), new Literal("t4"))); - PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2, selector("all")); + PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect3, selector("type1")); leftSource.setProperty(Property.SOURCE_NAME, selector("all")); + leftSource.setProperty(Property.SOURCE_ALIAS, selector("type1")); leftSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("all")).getColumns()); - PlanNode rightAccess = new PlanNode(Type.ACCESS, join, selector("all")); - PlanNode rightProject = new PlanNode(Type.PROJECT, rightAccess, selector("all")); - rightProject.setProperty(Property.PROJECT_COLUMNS, columns(column("all", "a3"), column("all", "a4"), column("all", "a2"))); - PlanNode rightSelect1 = new PlanNode(Type.SELECT, rightProject, selector("all")); - rightSelect1.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "primaryType"), + PlanNode rightAccess = new PlanNode(Type.ACCESS, join, selector("type2")); + PlanNode rightProject = new PlanNode(Type.PROJECT, rightAccess, selector("type2")); + rightProject.setProperty(Property.PROJECT_COLUMNS, columns(column("type2", "a3"), column("type2", "a4"))); + PlanNode rightSelect1 = new PlanNode(Type.SELECT, rightProject, selector("type2")); + rightSelect1.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type2"), "primaryType"), new Literal("t2"), new Literal("t0"))); - PlanNode rightSelect2 = new PlanNode(Type.SELECT, rightSelect1, selector("all")); - rightSelect2.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "mixins"), + PlanNode rightSelect2 = new PlanNode(Type.SELECT, rightSelect1, selector("type2")); + rightSelect2.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type2"), "mixins"), new Literal("t4"), new Literal("t5"))); - PlanNode rightSource = new PlanNode(Type.SOURCE, rightSelect2, selector("all")); + PlanNode rightSource = new PlanNode(Type.SOURCE, rightSelect2, selector("type2")); rightSource.setProperty(Property.SOURCE_NAME, selector("all")); + rightSource.setProperty(Property.SOURCE_ALIAS, selector("type2")); rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("all")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(project), is(true)); + assertPlanMatches(project); } @Test @@ -484,32 +489,33 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { + "FROM type1 JOIN type2 ON ISSAMENODE(type1,type2) WHERE CONTAINS(type1.a2,'something')"); // Create the expected plan ... - PlanNode access = new PlanNode(Type.ACCESS, selector("all")); - PlanNode project = new PlanNode(Type.PROJECT, access, selector("all")); - project.setProperty(Property.PROJECT_COLUMNS, columns(column("all", "a1", "a"), - column("all", "a2", "b"), - column("all", "a3", "c"), - column("all", "a4", "d"))); - PlanNode select1 = new PlanNode(Type.SELECT, project, selector("all")); - select1.setProperty(Property.SELECT_CRITERIA, new FullTextSearch(selector("all"), "a2", "something")); - PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("all")); - select2.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "primaryType"), + PlanNode access = new PlanNode(Type.ACCESS, selector("type1")); + PlanNode project = new PlanNode(Type.PROJECT, access, selector("type1")); + project.setProperty(Property.PROJECT_COLUMNS, columns(column("type1", "a1", "a"), + column("type1", "a2", "b"), + column("type1", "a3", "c"), + column("type1", "a4", "d"))); + PlanNode select1 = new PlanNode(Type.SELECT, project, selector("type1")); + select1.setProperty(Property.SELECT_CRITERIA, new FullTextSearch(selector("type1"), "a2", "something")); + PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("type1")); + select2.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "primaryType"), new Literal("t1"), new Literal("t0"))); - PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("all")); - select3.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "mixins"), + PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("type1")); + select3.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "mixins"), new Literal("t3"), new Literal("t4"))); - PlanNode select4 = new PlanNode(Type.SELECT, select3, selector("all")); - select4.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "primaryType"), + PlanNode select4 = new PlanNode(Type.SELECT, select3, selector("type1")); + select4.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "primaryType"), new Literal("t2"), new Literal("t0"))); - PlanNode select5 = new PlanNode(Type.SELECT, select4, selector("all")); - select5.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("all"), "mixins"), + PlanNode select5 = new PlanNode(Type.SELECT, select4, selector("type1")); + select5.setProperty(Property.SELECT_CRITERIA, new SetCriteria(new PropertyValue(selector("type1"), "mixins"), new Literal("t4"), new Literal("t5"))); - PlanNode source = new PlanNode(Type.SOURCE, select5, selector("all")); + PlanNode source = new PlanNode(Type.SOURCE, select5, selector("type1")); source.setProperty(Property.SOURCE_NAME, selector("all")); + source.setProperty(Property.SOURCE_ALIAS, selector("type1")); source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("all")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(access), is(true)); + assertPlanMatches(access); } @Test @@ -534,7 +540,7 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { leftSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(sort), is(true)); + assertPlanMatches(sort); } @Test @@ -543,23 +549,24 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { node = optimize("SELECT X.c11 AS c1 FROM t1 AS X WHERE X.c11 = 'x' AND X.c12 = 'y' ORDER BY X.c11, X.c12 DESC"); // Create the expected plan ... - PlanNode sort = new PlanNode(Type.SORT, selector("t1")); - sort.setProperty(Property.SORT_ORDER_BY, orderings(ascending("t1", "c11"), descending("t1", "c12"))); - PlanNode leftAccess = new PlanNode(Type.ACCESS, sort, selector("t1")); - PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess, selector("t1")); - leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11", "c1"), column("t1", "c12"))); - PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject, selector("t1")); - leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c11"), + PlanNode sort = new PlanNode(Type.SORT, selector("X")); + sort.setProperty(Property.SORT_ORDER_BY, orderings(ascending("X", "c11"), descending("X", "c12"))); + PlanNode leftAccess = new PlanNode(Type.ACCESS, sort, selector("X")); + PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess, selector("X")); + leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("X", "c11", "c1"), column("X", "c12"))); + PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject, selector("X")); + leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("X"), "c11"), Operator.EQUAL_TO, new Literal("x"))); - PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1, selector("t1")); - leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c12"), + PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1, selector("X")); + leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("X"), "c12"), Operator.EQUAL_TO, new Literal("y"))); - PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2, selector("t1")); + PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2, selector("X")); leftSource.setProperty(Property.SOURCE_NAME, selector("t1")); + leftSource.setProperty(Property.SOURCE_ALIAS, selector("X")); leftSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(sort), is(true)); + assertPlanMatches(sort); } @Test @@ -568,23 +575,24 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { node = optimize("SELECT X.c11 AS c1 FROM t1 AS X WHERE X.c11 = 'x' AND X.c12 = 'y' ORDER BY X.c1, X.c12 DESC"); // Create the expected plan ... - PlanNode sort = new PlanNode(Type.SORT, selector("t1")); - sort.setProperty(Property.SORT_ORDER_BY, orderings(ascending("t1", "c11"), descending("t1", "c12"))); - PlanNode leftAccess = new PlanNode(Type.ACCESS, sort, selector("t1")); - PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess, selector("t1")); - leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11", "c1"), column("t1", "c12"))); - PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject, selector("t1")); - leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c11"), + PlanNode sort = new PlanNode(Type.SORT, selector("X")); + sort.setProperty(Property.SORT_ORDER_BY, orderings(ascending("X", "c1"), descending("X", "c12"))); + PlanNode leftAccess = new PlanNode(Type.ACCESS, sort, selector("X")); + PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess, selector("X")); + leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("X", "c11", "c1"), column("X", "c12"))); + PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject, selector("X")); + leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("X"), "c11"), Operator.EQUAL_TO, new Literal("x"))); - PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1, selector("t1")); - leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c12"), + PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1, selector("X")); + leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("X"), "c12"), Operator.EQUAL_TO, new Literal("y"))); - PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2, selector("t1")); + PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2, selector("X")); leftSource.setProperty(Property.SOURCE_NAME, selector("t1")); + leftSource.setProperty(Property.SOURCE_ALIAS, selector("X")); leftSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(sort), is(true)); + assertPlanMatches(sort); } @Test @@ -626,7 +634,7 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t2")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(sort), is(true)); + assertPlanMatches(sort); } @Test @@ -668,7 +676,7 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t2")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(sort), is(true)); + assertPlanMatches(sort); } @Test @@ -709,7 +717,7 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t2")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(sort), is(true)); + assertPlanMatches(sort); } @Test @@ -750,7 +758,7 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t2")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(sort), is(true)); + assertPlanMatches(sort); } @Test @@ -788,13 +796,20 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t2")).getColumns()); // Compare the expected and actual plan ... - assertThat(node.isSameAs(sort), is(true)); + assertPlanMatches(sort); } // ---------------------------------------------------------------------------------------------------------------- // Utility methods ... // ---------------------------------------------------------------------------------------------------------------- + protected void assertPlanMatches( PlanNode expected ) { + if (!node.isSameAs(expected)) { + String message = "Plan was\n " + node.getString() + "\n but was expecting\n " + expected.getString(); + assertThat(message, node.isSameAs(expected), is(true)); + } + } + protected List columns( Column... columns ) { return Arrays.asList(columns); } Index: modeshape-graph/src/test/java/org/modeshape/graph/query/process/JoinComponentTest.java new file mode 100644 =================================================================== --- /dev/null (revision 1916) +++ modeshape-graph/src/test/java/org/modeshape/graph/query/process/JoinComponentTest.java (working copy) @@ -0,0 +1,158 @@ +/* + * 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 static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.junit.Assert.assertThat; +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.Location; +import org.modeshape.graph.property.Path; +import org.modeshape.graph.query.QueryResults.Columns; +import org.modeshape.graph.query.model.Column; +import org.modeshape.graph.query.model.SelectorName; +import org.modeshape.graph.query.process.JoinComponent.TupleMerger; + +/** + * + */ +public class JoinComponentTest { + + private ExecutionContext context; + private Columns mergedColumns; + private Columns leftColumns; + private Columns rightColumns; + private boolean print = false; + + @Before + public void beforeEach() { + context = new ExecutionContext(); + } + + protected Columns columns( String tableName, + String... columnNames ) { + return new QueryResultColumns(columnList(tableName, columnNames), false); + } + + protected Columns columnsWithScores( String tableName, + String... columnNames ) { + return new QueryResultColumns(columnList(tableName, columnNames), true); + } + + protected List columnList( String tableName, + String... columnNames ) { + List columns = new ArrayList(); + SelectorName selectorName = new SelectorName(tableName); + for (String columnName : columnNames) { + columns.add(new Column(selectorName, columnName, columnName)); + } + return columns; + } + + protected Path path( String path ) { + return context.getValueFactories().getPathFactory().create(path); + } + + protected Location location( String path ) { + return Location.create(path(path)); + } + + protected String toString( Object[] tuple ) { + StringBuilder sb = new StringBuilder(); + sb.append('['); + boolean first = true; + for (Object value : tuple) { + if (first) first = false; + else sb.append(", "); + sb.append(value); + } + sb.append(']'); + return sb.toString(); + } + + protected void print( Object object ) { + if (print) { + if (object instanceof Object[]) { + System.out.println(toString((Object[])object)); + } else { + System.out.println(object); + } + } + } + + @Test + public void shouldCreateMergerAndThenMergeTuplesForColumnsWithoutFullTextScore() { + List columns = columnList("t1", "c11", "c12", "c13"); + columns.addAll(columnList("t2", "c21", "c22", "c23")); + mergedColumns = new QueryResultColumns(columns, false); + + leftColumns = columns("t1", "c11", "c12", "c13"); + rightColumns = columns("t2", "c21", "c22", "c23"); + print(mergedColumns); + print(leftColumns); + print(rightColumns); + + TupleMerger merger = JoinComponent.createMerger(mergedColumns, leftColumns, rightColumns); + assertThat(merger, is(notNullValue())); + + Location leftLocation = location("/a/b/c"); + Location rightLocation = location("/a/b/c/d"); + Object[] leftTuple = {"vc11", "vc12", "vc3", leftLocation}; + Object[] rightTuple = {"vc21", "vc22", "vc3", rightLocation}; + Object[] merged = merger.merge(leftTuple, rightTuple); + print(merged); + Object[] expectedTuple = {"vc11", "vc12", "vc3", "vc21", "vc22", "vc3", leftLocation, rightLocation}; + assertThat(merged, is(expectedTuple)); + } + + @Test + public void shouldCreateMergerAndThenMergeTuplesForColumnsWithFullTextScore() { + List columns = columnList("t1", "c11", "c12", "c13"); + columns.addAll(columnList("t2", "c21", "c22", "c23")); + mergedColumns = new QueryResultColumns(columns, true); + + leftColumns = columnsWithScores("t1", "c11", "c12", "c13"); + rightColumns = columnsWithScores("t2", "c21", "c22", "c23"); + print(mergedColumns); + print(leftColumns); + print(rightColumns); + + TupleMerger merger = JoinComponent.createMerger(mergedColumns, leftColumns, rightColumns); + assertThat(merger, is(notNullValue())); + + Location leftLocation = location("/a/b/c"); + Location rightLocation = location("/a/b/c/d"); + Object[] leftTuple = {"vc11", "vc12", "vc3", leftLocation, 1.0}; + Object[] rightTuple = {"vc21", "vc22", "vc3", rightLocation, 2.0}; + Object[] merged = merger.merge(leftTuple, rightTuple); + print(merged); + Object[] expectedTuple = {"vc11", "vc12", "vc3", "vc21", "vc22", "vc3", leftLocation, rightLocation, 1.0, 2.0}; + assertThat(merged, is(expectedTuple)); + } + +} Index: modeshape-graph/src/test/java/org/modeshape/graph/query/process/SortValuesComponentTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/query/process/SortValuesComponentTest.java (revision 1916) +++ modeshape-graph/src/test/java/org/modeshape/graph/query/process/SortValuesComponentTest.java (working copy) @@ -27,13 +27,13 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.List; +import org.junit.Before; +import org.junit.Test; import org.modeshape.graph.property.PropertyType; import org.modeshape.graph.query.QueryContext; import org.modeshape.graph.query.QueryResults.Columns; import org.modeshape.graph.query.model.Ordering; import org.modeshape.graph.query.validate.Schemata; -import org.junit.Before; -import org.junit.Test; /** * @@ -66,13 +66,13 @@ public class SortValuesComponentTest extends AbstractQueryResultsTest { }; // Create the component we're testing ... orderings = new ArrayList(); - component = new SortValuesComponent(delegate, orderings); + component = new SortValuesComponent(delegate, orderings, null); } @Test public void shouldReturnAllResultsOrderedByNodeName() { orderings.add(orderByNodeName("Selector1")); - component = new SortValuesComponent(delegate, orderings); + component = new SortValuesComponent(delegate, orderings, null); inputTuples.add(tuple(columns, "/a/b1/c1", "v1", 100, "v4")); inputTuples.add(tuple(columns, "/a/b2/c4", "v4", 100, "v3")); inputTuples.add(tuple(columns, "/a/b3/c2", 100, 100, "v2")); @@ -88,7 +88,7 @@ public class SortValuesComponentTest extends AbstractQueryResultsTest { @Test public void shouldReturnAllResultsOrderedByNodeNameWhenThereAreDuplicateTuples() { orderings.add(orderByNodeName("Selector1")); - component = new SortValuesComponent(delegate, orderings); + component = new SortValuesComponent(delegate, orderings, null); inputTuples.add(tuple(columns, "/a/b1/c1", "v1", 100, "v3")); inputTuples.add(tuple(columns, "/a/b2/c4", "v1", 100, "v3")); inputTuples.add(tuple(columns, "/a/b3/c2", "v1", 100, "v3")); @@ -110,7 +110,7 @@ public class SortValuesComponentTest extends AbstractQueryResultsTest { @Test public void shouldReturnAllResultsOrderedByNodeDepth() { orderings.add(orderByNodeDepth("Selector1")); - component = new SortValuesComponent(delegate, orderings); + component = new SortValuesComponent(delegate, orderings, null); inputTuples.add(tuple(columns, "/a/b1", "v1", 100, "v4")); inputTuples.add(tuple(columns, "/a/b2/c4", "v4", 100, "v3")); inputTuples.add(tuple(columns, "/a", 100, 100, "v2")); @@ -126,7 +126,7 @@ public class SortValuesComponentTest extends AbstractQueryResultsTest { @Test public void shouldReturnAllResultsOrderedByNodePath() { orderings.add(orderByNodePath("Selector1")); - component = new SortValuesComponent(delegate, orderings); + component = new SortValuesComponent(delegate, orderings, null); inputTuples.add(tuple(columns, "/a/b1", "v1", 100, "v4")); inputTuples.add(tuple(columns, "/a/b1/c4[2]", "v4", 100, "v3")); inputTuples.add(tuple(columns, "/a/b1/c2", 100, 100, "v2")); @@ -142,7 +142,7 @@ public class SortValuesComponentTest extends AbstractQueryResultsTest { @Test public void shouldReturnAllResultsOrderedByNodeLocalName() { orderings.add(orderByNodeLocalName("Selector1")); - component = new SortValuesComponent(delegate, orderings); + component = new SortValuesComponent(delegate, orderings, null); inputTuples.add(tuple(columns, "/a/b1/c1", "v1", 100, "v4")); inputTuples.add(tuple(columns, "/a/b2/c4", "v4", 100, "v3")); inputTuples.add(tuple(columns, "/a/b3/c2", 100, 100, "v2")); @@ -158,7 +158,7 @@ public class SortValuesComponentTest extends AbstractQueryResultsTest { @Test public void shouldReturnAllResultsOrderedByNodeLocalNameWhenThereAreDuplicateTuples() { orderings.add(orderByNodeName("Selector1")); - component = new SortValuesComponent(delegate, orderings); + component = new SortValuesComponent(delegate, orderings, null); inputTuples.add(tuple(columns, "/a/b1/mode:c1", "v1", 100, "v3")); inputTuples.add(tuple(columns, "/a/b2/mode:c4", "v1", 100, "v3")); inputTuples.add(tuple(columns, "/a/b3/mode:c2", "v1", 100, "v3")); @@ -180,7 +180,7 @@ public class SortValuesComponentTest extends AbstractQueryResultsTest { @Test public void shouldReturnAllResultsOrderedByValueLengthOfLong() { orderings.add(orderByPropertyLength(columns.getColumns().get(1))); - component = new SortValuesComponent(delegate, orderings); + component = new SortValuesComponent(delegate, orderings, null); inputTuples.add(tuple(columns, "/a/b/c1", "v1", 1L, "v4")); inputTuples.add(tuple(columns, "/a/b/c4", "v1", 1114L, "v3")); inputTuples.add(tuple(columns, "/a/b/c2", "v1", 113L, "v2")); @@ -196,7 +196,7 @@ public class SortValuesComponentTest extends AbstractQueryResultsTest { @Test public void shouldReturnAllResultsOrderedByValueLengthOfString() { orderings.add(orderByPropertyLength(columns.getColumns().get(0))); - component = new SortValuesComponent(delegate, orderings); + component = new SortValuesComponent(delegate, orderings, null); inputTuples.add(tuple(columns, "/a/b/c1", "v1", 100L, "v4")); inputTuples.add(tuple(columns, "/a/b/c4", "v1111", 100L, "v3")); inputTuples.add(tuple(columns, "/a/b/c2", "v111", 100L, "v2")); @@ -212,7 +212,7 @@ public class SortValuesComponentTest extends AbstractQueryResultsTest { @Test public void shouldReturnAllResultsInSuppliedOrderWhenThereAreNoOrderings() { orderings.clear(); - component = new SortValuesComponent(delegate, orderings); + component = new SortValuesComponent(delegate, orderings, null); inputTuples.add(tuple(columns, "/a/b/c1", "v1", 100L, "v3")); inputTuples.add(tuple(columns, "/a/b/c4", "v1", 100L, "v3")); inputTuples.add(tuple(columns, "/a/b/c2", "v1", 100L, "v3")); Index: modeshape-jcr-api/src/main/java/org/modeshape/jcr/api/query/qom/QueryObjectModelFactory.java =================================================================== --- modeshape-jcr-api/src/main/java/org/modeshape/jcr/api/query/qom/QueryObjectModelFactory.java (revision 1916) +++ modeshape-jcr-api/src/main/java/org/modeshape/jcr/api/query/qom/QueryObjectModelFactory.java (working copy) @@ -193,6 +193,19 @@ public interface QueryObjectModelFactory extends javax.jcr.query.qom.QueryObject boolean includeUpperBound ) throws InvalidQueryException, RepositoryException; /** + * Tests that the value (or values) defined by the supplied dynamic operand are found within the specified set of values. + * + * @param operand the dynamic operand describing the values that are to be constrained + * @param values the static operand values; may not be null or empty + * @return the constraint; non-null + * @throws InvalidQueryException if a particular validity test is possible on this method, the implemention chooses to perform + * that test (and not leave it until later, on {@link #createQuery}), and the parameters given fail that test + * @throws RepositoryException if the operation otherwise fails + */ + public SetCriteria in( DynamicOperand operand, + StaticOperand... values ) throws InvalidQueryException, RepositoryException; + + /** * Create an arithmetic dynamic operand that adds the numeric value of the two supplied operand(s). * * @param left the left-hand-side operand; not null Index: modeshape-jcr-api/src/main/java/org/modeshape/jcr/api/query/qom/SetCriteria.java new file mode 100644 =================================================================== --- /dev/null (revision 1916) +++ modeshape-jcr-api/src/main/java/org/modeshape/jcr/api/query/qom/SetCriteria.java (working copy) @@ -0,0 +1,50 @@ +/* + * 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.jcr.api.query.qom; + +import java.util.Collection; +import javax.jcr.query.qom.Constraint; +import javax.jcr.query.qom.DynamicOperand; +import javax.jcr.query.qom.StaticOperand; + +/** + * A constraint that evaluates to true when the value defined by the dynamic operand evaluates to be within the set of values + * specified by the collection of values. + */ +public interface SetCriteria extends Constraint { + + /** + * Get the dynamic operand specification for the left-hand side of the set criteria. + * + * @return the dynamic operand; never null + */ + public DynamicOperand getOperand(); + + /** + * Get the static operands for this set criteria. + * + * @return the static operand; never null and never empty + */ + public Collection getValues(); +} Index: modeshape-jcr/src/main/java/org/modeshape/jcr/NodeTypeSchemata.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/NodeTypeSchemata.java (revision 1916) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/NodeTypeSchemata.java (working copy) @@ -273,7 +273,7 @@ class NodeTypeSchemata implements Schemata { // All the properties were skipped ... return; } - viewDefinition.append(" FROM ").append(AllNodes.ALL_NODES_NAME); + viewDefinition.append(" FROM ").append(AllNodes.ALL_NODES_NAME).append(" AS [").append(tableName).append(']'); // The 'nt:base' node type will have every single object in it, so we don't need to add the type criteria ... if (!JcrNtLexicon.BASE.equals(nodeType.getInternalName())) { Index: modeshape-jcr/src/main/java/org/modeshape/jcr/query/JcrSql2QueryParser.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/query/JcrSql2QueryParser.java (revision 1916) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/query/JcrSql2QueryParser.java (working copy) @@ -27,6 +27,7 @@ import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collection; import java.util.List; import javax.jcr.Binary; import javax.jcr.Node; @@ -37,6 +38,7 @@ import javax.jcr.query.Query; import org.modeshape.common.text.TokenStream; import org.modeshape.graph.property.DateTime; import org.modeshape.graph.query.model.ArithmeticOperator; +import org.modeshape.graph.query.model.Between; import org.modeshape.graph.query.model.Column; import org.modeshape.graph.query.model.Constraint; import org.modeshape.graph.query.model.DynamicOperand; @@ -50,6 +52,7 @@ import org.modeshape.graph.query.model.Ordering; import org.modeshape.graph.query.model.PropertyValue; import org.modeshape.graph.query.model.QueryCommand; import org.modeshape.graph.query.model.SelectorName; +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; @@ -59,6 +62,7 @@ import org.modeshape.graph.query.model.SetQuery.Operation; import org.modeshape.graph.query.parse.SqlQueryParser; import org.modeshape.jcr.query.qom.JcrAnd; import org.modeshape.jcr.query.qom.JcrArithmeticOperand; +import org.modeshape.jcr.query.qom.JcrBetween; import org.modeshape.jcr.query.qom.JcrBindVariableName; import org.modeshape.jcr.query.qom.JcrChildNode; import org.modeshape.jcr.query.qom.JcrChildNodeJoinCondition; @@ -92,6 +96,7 @@ import org.modeshape.jcr.query.qom.JcrReferenceValue; import org.modeshape.jcr.query.qom.JcrSameNode; import org.modeshape.jcr.query.qom.JcrSameNodeJoinCondition; import org.modeshape.jcr.query.qom.JcrSelectQuery; +import org.modeshape.jcr.query.qom.JcrSetCriteria; import org.modeshape.jcr.query.qom.JcrSetQuery; import org.modeshape.jcr.query.qom.JcrSource; import org.modeshape.jcr.query.qom.JcrStaticOperand; @@ -491,6 +496,35 @@ public class JcrSql2QueryParser extends SqlQueryParser { /** * {@inheritDoc} * + * @see org.modeshape.graph.query.parse.SqlQueryParser#between(org.modeshape.graph.query.model.DynamicOperand, + * org.modeshape.graph.query.model.StaticOperand, org.modeshape.graph.query.model.StaticOperand, boolean, boolean) + */ + @Override + protected Between between( DynamicOperand operand, + StaticOperand lowerBound, + StaticOperand upperBound, + boolean lowerInclusive, + boolean upperInclusive ) { + return new JcrBetween((JcrDynamicOperand)operand, (JcrStaticOperand)lowerBound, (JcrStaticOperand)upperBound, + lowerInclusive, upperInclusive); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.query.parse.SqlQueryParser#setCriteria(org.modeshape.graph.query.model.DynamicOperand, + * java.util.Collection) + */ + @SuppressWarnings( "unchecked" ) + @Override + protected SetCriteria setCriteria( DynamicOperand operand, + Collection values ) { + return new JcrSetCriteria((JcrDynamicOperand)operand, (Collection)values); + } + + /** + * {@inheritDoc} + * * @see org.modeshape.graph.query.parse.SqlQueryParser#arithmeticOperand(org.modeshape.graph.query.model.DynamicOperand, * org.modeshape.graph.query.model.ArithmeticOperator, org.modeshape.graph.query.model.DynamicOperand) */ Index: modeshape-jcr/src/main/java/org/modeshape/jcr/query/qom/JcrJoin.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/query/qom/JcrJoin.java (revision 1916) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/query/qom/JcrJoin.java (working copy) @@ -31,7 +31,7 @@ import org.modeshape.jcr.api.query.qom.QueryObjectModelConstants; /** * Implementation of the join for the JCR Query Object Model and the Graph API. */ -public class JcrJoin extends Join implements javax.jcr.query.qom.Join { +public class JcrJoin extends Join implements javax.jcr.query.qom.Join, JcrSource { private static final long serialVersionUID = 1L; Index: modeshape-jcr/src/main/java/org/modeshape/jcr/query/qom/JcrQueryObjectModelFactory.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/query/qom/JcrQueryObjectModelFactory.java (revision 1916) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/query/qom/JcrQueryObjectModelFactory.java (working copy) @@ -74,6 +74,7 @@ import org.modeshape.jcr.api.query.qom.NodeDepth; import org.modeshape.jcr.api.query.qom.NodePath; import org.modeshape.jcr.api.query.qom.QueryCommand; import org.modeshape.jcr.api.query.qom.QueryObjectModelConstants; +import org.modeshape.jcr.api.query.qom.SetCriteria; import org.modeshape.jcr.api.query.qom.SetQuery; import org.modeshape.jcr.query.JcrQueryContext; @@ -645,6 +646,24 @@ public class JcrQueryObjectModelFactory /** * {@inheritDoc} * + * @see org.modeshape.jcr.api.query.qom.QueryObjectModelFactory#in(javax.jcr.query.qom.DynamicOperand, + * javax.jcr.query.qom.StaticOperand[]) + */ + @Override + public SetCriteria in( DynamicOperand operand, + StaticOperand... values ) { + JcrDynamicOperand jcrOperand = CheckArg.getInstanceOf(operand, JcrDynamicOperand.class, "operand"); + List jcrValues = new ArrayList(); + for (StaticOperand value : values) { + JcrStaticOperand jcrValue = CheckArg.getInstanceOf(value, JcrStaticOperand.class, "values"); + jcrValues.add(jcrValue); + } + return new JcrSetCriteria(jcrOperand, jcrValues); + } + + /** + * {@inheritDoc} + * * @see org.modeshape.jcr.api.query.qom.QueryObjectModelFactory#add(javax.jcr.query.qom.DynamicOperand, * javax.jcr.query.qom.DynamicOperand) */ Index: modeshape-jcr/src/main/java/org/modeshape/jcr/query/qom/JcrSetCriteria.java new file mode 100644 =================================================================== --- /dev/null (revision 1916) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/query/qom/JcrSetCriteria.java (working copy) @@ -0,0 +1,75 @@ +/* + * 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.jcr.query.qom; + +import java.util.Collection; +import org.modeshape.graph.query.model.SetCriteria; + +/** + * Implementation of the same node constraint for the JCR Query Object Model and the Graph API. + */ +public class JcrSetCriteria extends SetCriteria implements JcrConstraint, org.modeshape.jcr.api.query.qom.SetCriteria { + + private static final long serialVersionUID = 1L; + + /** + * @param left + * @param setOperands + */ + public JcrSetCriteria( JcrDynamicOperand left, + Collection setOperands ) { + super(left, setOperands); + } + + /** + * @param left + * @param setOperands + */ + public JcrSetCriteria( JcrDynamicOperand left, + JcrStaticOperand... setOperands ) { + super(left, setOperands); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.jcr.api.query.qom.SetCriteria#getOperand() + */ + @Override + public javax.jcr.query.qom.DynamicOperand getOperand() { + return (JcrDynamicOperand)super.leftOperand(); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.jcr.api.query.qom.SetCriteria#getValues() + */ + @SuppressWarnings( "unchecked" ) + @Override + public Collection getValues() { + return (Collection)super.rightOperands(); + } + +} Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java (revision 1916) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java (working copy) @@ -383,6 +383,32 @@ public class JcrQueryManagerTest { assertResultsHaveColumns(result, "jcr:primaryType"); } + @Test + public void shouldBeAbleToCreateAndExecuteSqlQueryWithChildNodeJoin() throws RepositoryException { + String sql = "SELECT car.* from [car:Car] as car JOIN [nt:unstructured] as category ON ISCHILDNODE(car,category) WHERE NAME(category) LIKE 'Utility'"; + Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); + assertThat(query, is(notNullValue())); + QueryResult result = query.execute(); + assertThat(result, is(notNullValue())); + assertResults(query, result, 4); + assertResultsHaveColumns(result, carColumnNames()); + } + + @Test + public void shouldBeAbleToCreateAndExecuteSqlQueryWithChildNodeJoinAndColumnsFromBothSidesOfJoin() throws RepositoryException { + String sql = "SELECT car.*, category.[jcr:primaryType] from [car:Car] as car JOIN [nt:unstructured] as category ON ISCHILDNODE(car,category) WHERE NAME(category) LIKE 'Utility'"; + Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); + assertThat(query, is(notNullValue())); + QueryResult result = query.execute(); + assertThat(result, is(notNullValue())); + print = true; + assertResults(query, result, 4L); + String[] expectedColumnNames = {"car:mpgCity", "car:lengthInInches", "car:maker", "car:userRating", "car:engine", + "car:mpgHighway", "car:valueRating", "jcr:primaryType", "car:wheelbaseInInches", "car:year", "car:model", "car:msrp", + "jcr:created", "jcr:createdBy", "category.jcr:primaryType"}; + assertResultsHaveColumns(result, expectedColumnNames); + } + // ---------------------------------------------------------------------------------------------------------------- // JCR-SQL Queries // ---------------------------------------------------------------------------------------------------------------- @@ -401,6 +427,20 @@ public class JcrQueryManagerTest { assertResultsHaveColumns(result, "jcr:path", "jcr:score", "car:model"); } + @SuppressWarnings( "deprecation" ) + @Test + public void shouldBeAbleToCreateAndExecuteSqlQueryWithPathCriteriaAndOrderByClause() throws RepositoryException { + Query query = session.getWorkspace() + .getQueryManager() + .createQuery("SELECT car:model FROM car:Car WHERE jcr:path LIKE '/Cars/%' ORDER BY car:model ASC", + Query.SQL); + assertThat(query, is(notNullValue())); + QueryResult result = query.execute(); + assertThat(result, is(notNullValue())); + assertResults(query, result, 12); + assertResultsHaveColumns(result, "jcr:path", "jcr:score", "car:model"); + } + /** * Tests that the child nodes (but no grandchild nodes) are returned. *