Index: extensions/modeshape-search-lucene/src/test/java/org/modeshape/search/lucene/LuceneSearchEngineObservationTest.java =================================================================== --- extensions/modeshape-search-lucene/src/test/java/org/modeshape/search/lucene/LuceneSearchEngineObservationTest.java (revision 1917) +++ extensions/modeshape-search-lucene/src/test/java/org/modeshape/search/lucene/LuceneSearchEngineObservationTest.java (working copy) @@ -53,6 +53,7 @@ import org.modeshape.graph.observe.Changes; import org.modeshape.graph.observe.Observer; import org.modeshape.graph.property.Name; import org.modeshape.graph.property.Path; +import org.modeshape.graph.property.PropertyType; import org.modeshape.graph.query.QueryResults; import org.modeshape.graph.query.QueryResults.Columns; import org.modeshape.graph.query.model.And; @@ -268,7 +269,12 @@ public class LuceneSearchEngineObservationTest { assertThat(source, is(instanceOf(Selector.class))); SelectorName tableName = ((Selector)source).name(); Constraint constraint = query.constraint(); - Columns resultColumns = new QueryResultColumns(query.columns(), QueryResultColumns.includeFullTextScores(constraint)); + List types = new ArrayList(); + for (int i = 0; i != query.columns().size(); ++i) { + types.add(PropertyType.STRING.getName()); + } + Columns resultColumns = new QueryResultColumns(query.columns(), types, + QueryResultColumns.includeFullTextScores(constraint)); List andedConstraints = getAndedConstraint(constraint, new ArrayList()); Limit limit = query.limits(); RequestProcessor processor = searchEngine.createProcessor(context, null, true); Index: extensions/modeshape-search-lucene/src/test/java/org/modeshape/search/lucene/LuceneSearchEngineTest.java =================================================================== --- extensions/modeshape-search-lucene/src/test/java/org/modeshape/search/lucene/LuceneSearchEngineTest.java (revision 1917) +++ extensions/modeshape-search-lucene/src/test/java/org/modeshape/search/lucene/LuceneSearchEngineTest.java (working copy) @@ -46,6 +46,7 @@ import org.modeshape.graph.connector.RepositorySourceException; import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; import org.modeshape.graph.property.Name; import org.modeshape.graph.property.Path; +import org.modeshape.graph.property.PropertyType; import org.modeshape.graph.query.QueryResults; import org.modeshape.graph.query.QueryResults.Columns; import org.modeshape.graph.query.model.And; @@ -220,7 +221,12 @@ public class LuceneSearchEngineTest { assertThat(source, is(instanceOf(Selector.class))); SelectorName tableName = ((Selector)source).name(); Constraint constraint = query.constraint(); - Columns resultColumns = new QueryResultColumns(query.columns(), QueryResultColumns.includeFullTextScores(constraint)); + List types = new ArrayList(); + for (int i = 0; i != query.columns().size(); ++i) { + types.add(PropertyType.STRING.getName()); + } + Columns resultColumns = new QueryResultColumns(query.columns(), types, + QueryResultColumns.includeFullTextScores(constraint)); List andedConstraints = getAndedConstraint(constraint, new ArrayList()); Limit limit = query.limits(); RequestProcessor processor = engine.createProcessor(context, null, true); Index: modeshape-graph/src/main/java/org/modeshape/graph/query/QueryEngine.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/QueryEngine.java (revision 1917) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/QueryEngine.java (working copy) @@ -125,6 +125,7 @@ public class QueryEngine implements Queryable { PlanNode project = optimizedPlan.findAtOrBelow(Traversal.LEVEL_ORDER, Type.PROJECT); if (project != null) { List columns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); + List columnTypes = project.getPropertyAsList(Property.PROJECT_COLUMN_TYPES, String.class); // Determine whether to include the full-text search scores in the results ... boolean includeFullTextSearchScores = hints.hasFullTextSearch; if (!includeFullTextSearchScores) { @@ -136,7 +137,7 @@ public class QueryEngine implements Queryable { } } } - return new QueryResultColumns(columns, includeFullTextSearchScores); + return new QueryResultColumns(columns, columnTypes, includeFullTextSearchScores); } return QueryResultColumns.empty(); } Index: modeshape-graph/src/main/java/org/modeshape/graph/query/QueryResults.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/QueryResults.java (revision 1917) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/QueryResults.java (working copy) @@ -33,6 +33,7 @@ import org.modeshape.common.collection.Problems; import org.modeshape.common.util.CheckArg; import org.modeshape.graph.Location; import org.modeshape.graph.query.model.Column; +import org.modeshape.graph.query.model.TypeSystem.TypeFactory; /** * The resulting output of a query. @@ -194,6 +195,13 @@ public interface QueryResults extends Serializable { public List getColumnNames(); /** + * Get the {@link TypeFactory#getTypeName() type name} for each column. + * + * @return the immutable list of type names, with size equal to {@link #getColumnCount()}; never null + */ + public List getColumnTypes(); + + /** * Get the number of columns in each tuple. * * @return the number of columns; always positive 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 1917) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/PushProjects.java (working copy) @@ -92,17 +92,21 @@ public class PushProjects implements OptimizerRule { // We need to make sure we have all of the columns needed for any ancestor ... List requiredColumns = PlanUtil.findRequiredColumns(context, project); + List requiredTypes = PlanUtil.findRequiredColumnTypes(context, requiredColumns, child); project.setProperty(Property.PROJECT_COLUMNS, requiredColumns); + project.setProperty(Property.PROJECT_COLUMN_TYPES, requiredTypes); project.addSelectors(getSelectorsFor(requiredColumns)); continue; } // There is no PROJECT, so find the columns that are required by the plan above this point ... List requiredColumns = PlanUtil.findRequiredColumns(context, child); + List requiredTypes = PlanUtil.findRequiredColumnTypes(context, requiredColumns, child); // And insert the PROJECT ... PlanNode projectNode = new PlanNode(Type.PROJECT); projectNode.setProperty(Property.PROJECT_COLUMNS, requiredColumns); + projectNode.setProperty(Property.PROJECT_COLUMN_TYPES, requiredTypes); projectNode.addSelectors(getSelectorsFor(requiredColumns)); child.insertAsParent(projectNode); } Index: modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/ReplaceAliases.java deleted file mode 100644 =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/ReplaceAliases.java (revision 1917) +++ /dev/null (working copy) @@ -1,88 +0,0 @@ -/* - * ModeShape (http://www.modeshape.org) - * See the COPYRIGHT.txt file distributed with this work for information - * regarding copyright ownership. Some portions may be licensed - * to Red Hat, Inc. under one or more contributor license agreements. - * See the AUTHORS.txt file in the distribution for a full listing of - * individual contributors. - * - * ModeShape is free software. Unless otherwise indicated, all code in ModeShape - * is licensed to you under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * ModeShape is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ -package org.modeshape.graph.query.optimize; - -import java.util.LinkedList; -import net.jcip.annotations.Immutable; -import org.modeshape.graph.query.QueryContext; -import org.modeshape.graph.query.model.SelectorName; -import org.modeshape.graph.query.plan.PlanNode; -import org.modeshape.graph.query.plan.PlanUtil; -import org.modeshape.graph.query.plan.PlanNode.Property; -import org.modeshape.graph.query.plan.PlanNode.Type; -import org.modeshape.graph.query.validate.Schemata; -import org.modeshape.graph.query.validate.Schemata.Table; - -/** - * An {@link OptimizerRule optimizer rule} that changes any nodes that make use of an alias for a SOURCE, including columns, - * including criteria, project nodes, etc. This behavior is similar to what {@link ReplaceViews} does with views that are given - * aliases. - */ -@Immutable -public class ReplaceAliases implements OptimizerRule { - - public static final ReplaceAliases INSTANCE = new ReplaceAliases(); - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.query.optimize.OptimizerRule#execute(org.modeshape.graph.query.QueryContext, - * org.modeshape.graph.query.plan.PlanNode, java.util.LinkedList) - */ - public PlanNode execute( QueryContext context, - PlanNode plan, - LinkedList ruleStack ) { - // For each of the SOURCE nodes ... - Schemata schemata = context.getSchemata(); - for (PlanNode sourceNode : plan.findAllAtOrBelow(Type.SOURCE)) { - - // Resolve the node to find the definition in the schemata ... - SelectorName tableName = sourceNode.getProperty(Property.SOURCE_NAME, SelectorName.class); - SelectorName tableAlias = sourceNode.getProperty(Property.SOURCE_ALIAS, SelectorName.class); - if (tableAlias != null) { - Table table = schemata.getTable(tableName); - // We also need to replace references to the alias for the view ... - PlanUtil.ColumnMapping aliasMappings = PlanUtil.createMappingForAliased(tableAlias, table, sourceNode); - // Adjust the plan nodes above the SOURCE node ... - PlanUtil.replaceViewReferences(context, sourceNode.getParent(), aliasMappings); - // And adjust the SOURCE node ... - sourceNode.removeProperty(Property.SOURCE_ALIAS); - sourceNode.getSelectors().remove(tableAlias); - sourceNode.addSelector(tableName); - } - } - return plan; - } - - /** - * {@inheritDoc} - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return getClass().getSimpleName(); - } - -} 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 1917) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/RuleBasedOptimizer.java (working copy) @@ -69,7 +69,6 @@ public class RuleBasedOptimizer implements Optimizer { */ protected void populateRuleStack( LinkedList ruleStack, PlanHints hints ) { - // 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/plan/CanonicalPlanner.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/plan/CanonicalPlanner.java (revision 1917) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/plan/CanonicalPlanner.java (working copy) @@ -23,6 +23,7 @@ */ package org.modeshape.graph.query.plan; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -390,6 +391,7 @@ public class CanonicalPlanner implements Planner { PlanNode projectNode = new PlanNode(Type.PROJECT); List newColumns = new LinkedList(); + List newTypes = new ArrayList(); 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()) { @@ -398,7 +400,7 @@ public class CanonicalPlanner implements Planner { // Add the selector that is being used ... projectNode.addSelector(tableName); // Compute the columns from this selector ... - allColumnsFor(table, tableName, newColumns); + allColumnsFor(table, tableName, newColumns, newTypes); } } else { // Add the selector used by each column ... @@ -416,10 +418,16 @@ public class CanonicalPlanner implements Planner { String columnName = column.propertyName(); if ("*".equals(columnName)) { // This is a 'SELECT *' on this source, but this source is one of multiple sources ... - allColumnsFor(table, tableName, newColumns); + allColumnsFor(table, tableName, newColumns, newTypes); } else { // This is a particular column, so add it ... newColumns.add(column); + org.modeshape.graph.query.validate.Schemata.Column schemaColumn = table.getColumn(columnName); + if (schemaColumn != null) { + newTypes.add(schemaColumn.getPropertyType()); + } else { + newTypes.add(context.getTypeSystem().getStringFactory().getTypeName()); + } } if (table.getColumn(columnName) == null && context.getHints().validateColumnExistance) { context.getProblems().addError(GraphI18n.columnDoesNotExistOnTable, columnName, tableName); @@ -428,19 +436,22 @@ public class CanonicalPlanner implements Planner { } } projectNode.setProperty(Property.PROJECT_COLUMNS, newColumns); + projectNode.setProperty(Property.PROJECT_COLUMN_TYPES, newTypes); projectNode.addLastChild(plan); return projectNode; } protected void allColumnsFor( Table table, SelectorName tableName, - List columns ) { + List columns, + List columnTypes ) { // 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); + columnTypes.add(column.getPropertyType()); } } Index: modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanNode.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanNode.java (revision 1917) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanNode.java (working copy) @@ -171,6 +171,11 @@ public final class PlanNode implements Iterable, Readable, Cloneable, /** For PROJECT nodes, the ordered collection of columns being projected. Value is a Collection of {@link Column} objects. */ PROJECT_COLUMNS, + /** + * For PROJECT nodes, the ordered collection of the type names for the columns being projected. Value is a Collection of + * {@link String} objects. + */ + PROJECT_COLUMN_TYPES, /** * For GROUP nodes, the ordered collection of columns used to group the result tuples. Value is a Collection of @@ -1084,6 +1089,29 @@ public final class PlanNode implements Iterable, Readable, Cloneable, return null; } + /** + * Look at nodes below this node, searching for nodes that have the supplied type. As soon as a node with a matching type is + * found, then no other nodes below it are searched. + * + * @param typeToFind the type of node to find; may not be null + * @return the collection of nodes that are at or below this node that all have the supplied type; never null but possibly + * empty + */ + public List findAllFirstNodesAtOrBelow( Type typeToFind ) { + List results = new LinkedList(); + LinkedList queue = new LinkedList(); + queue.add(this); + while (!queue.isEmpty()) { + PlanNode aNode = queue.poll(); + if (aNode.getType() == Type.PROJECT) { + results.add(aNode); + } else { + queue.addAll(0, aNode.getChildren()); + } + } + return results; + } + public static enum Traversal { LEVEL_ORDER, PRE_ORDER; 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 1917) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanUtil.java (working copy) @@ -71,6 +71,7 @@ import org.modeshape.graph.query.model.Visitors; import org.modeshape.graph.query.model.Visitors.AbstractVisitor; import org.modeshape.graph.query.plan.PlanNode.Property; import org.modeshape.graph.query.plan.PlanNode.Type; +import org.modeshape.graph.query.validate.Schemata; import org.modeshape.graph.query.validate.Schemata.Table; import org.modeshape.graph.query.validate.Schemata.View; @@ -169,6 +170,62 @@ public class PlanUtil { return collectionVisitor.getRequiredColumns(); } + public static List findRequiredColumnTypes( QueryContext context, + List columns, + PlanNode node ) { + if (node.getType() == Type.PROJECT) { + assert node.getChildCount() == 1; + node = node.getFirstChild(); + } + // See if there are any PROJECT nodes below this node ... + List projects = node.findAllFirstNodesAtOrBelow(Type.PROJECT); + if (!projects.isEmpty()) { + List types = new ArrayList(columns.size()); + for (Column column : columns) { + boolean added = false; + for (PlanNode project : projects) { + List projectedColumns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); + List projectedTypes = project.getPropertyAsList(Property.PROJECT_COLUMN_TYPES, String.class); + if (projectedTypes == null) continue; + for (int i = 0; i != projectedColumns.size(); ++i) { + Column projectedColumn = projectedColumns.get(i); + if (column.equals(projectedColumn)) { + types.add(projectedTypes.get(i)); + added = true; + break; + } + } + if (added) break; + } + } + if (types.size() == columns.size()) return types; + } + + // Otherwise, look for the sources ... + List types = new ArrayList(columns.size()); + List sources = node.findAllAtOrBelow(Type.SOURCE); + for (Column column : columns) { + boolean added = false; + for (PlanNode source : sources) { + SelectorName alias = source.getProperty(Property.SOURCE_ALIAS, SelectorName.class); + SelectorName name = source.getProperty(Property.SOURCE_NAME, SelectorName.class); + if ((alias != null && alias.equals(column.selectorName())) || name.equals(column.selectorName())) { + List sourceColumns = source.getPropertyAsList(Property.SOURCE_COLUMNS, Schemata.Column.class); + for (Schemata.Column sourceColumn : sourceColumns) { + if (sourceColumn.getName().equals(column.columnName()) + || sourceColumn.getName().equals(column.propertyName())) { + types.add(sourceColumn.getPropertyType()); + added = true; + break; + } + } + if (added) break; + } + } + } + return types; + } + protected static class RequiredColumnVisitor extends AbstractVisitor { private final Set names; private final List columns = new LinkedList(); Index: modeshape-graph/src/main/java/org/modeshape/graph/query/process/FullTextSearchResultColumns.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/process/FullTextSearchResultColumns.java (revision 1917) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/FullTextSearchResultColumns.java (working copy) @@ -42,16 +42,18 @@ public class FullTextSearchResultColumns extends QueryResultColumns { * Create a new definition for the query results containing just the locations and the full-text search scores. */ public FullTextSearchResultColumns() { - super(true, NO_COLUMNS); + super(true, NO_COLUMNS, NO_TYPES); } /** * Create a new definition for the query results given the supplied columns. * * @param columns the columns that define the results; should never be modified directly + * @param columnTypes the type name for each of the Column objects in columns */ - public FullTextSearchResultColumns( List columns ) { - super(true, columns != null ? columns : NO_COLUMNS); + public FullTextSearchResultColumns( List columns, + List columnTypes ) { + super(true, columns != null ? columns : NO_COLUMNS, columnTypes != null ? columnTypes : NO_TYPES); CheckArg.isNotEmpty(columns, "columns"); } 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 1917) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/ProcessingComponent.java (working copy) @@ -47,8 +47,6 @@ import org.modeshape.graph.query.model.TypeSystem; import org.modeshape.graph.query.model.UpperCase; import org.modeshape.graph.query.model.TypeSystem.TypeFactory; import org.modeshape.graph.query.validate.Schemata; -import org.modeshape.graph.query.validate.Schemata.Column; -import org.modeshape.graph.query.validate.Schemata.Table; /** * A component that performs (some) portion of the query processing by {@link #execute() returning the tuples} that result from @@ -159,9 +157,7 @@ public abstract class ProcessingComponent { String selectorName = propValue.selectorName().name(); final int index = columns.getColumnIndexForProperty(selectorName, propertyName); // Find the expected property type of the value ... - Table table = schemata.getTable(propValue.selectorName()); - Column schemaColumn = table.getColumn(propertyName); - final String expectedType = schemaColumn.getPropertyType(); + final String expectedType = columns.getColumnTypes().get(index); final TypeFactory typeFactory = typeSystem.getTypeFactory(expectedType); return new DynamicOperation() { public String getExpectedType() { @@ -179,9 +175,7 @@ public abstract class ProcessingComponent { String selectorName = refValue.selectorName().name(); final int index = columns.getColumnIndexForProperty(selectorName, propertyName); // Find the expected property type of the value ... - Table table = schemata.getTable(refValue.selectorName()); - Column schemaColumn = table.getColumn(propertyName); - final String expectedType = schemaColumn.getPropertyType(); + final String expectedType = columns.getColumnTypes().get(index); final TypeFactory typeFactory = typeSystem.getTypeFactory(expectedType); return new DynamicOperation() { public String getExpectedType() { @@ -201,9 +195,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 = schemata.getTable(value.selectorName()); - Column schemaColumn = table.getColumn(propertyName); - final String expectedType = schemaColumn.getPropertyType(); + final String expectedType = columns.getColumnTypes().get(index); final TypeFactory typeFactory = typeSystem.getTypeFactory(expectedType); final TypeFactory longFactory = typeSystem.getLongFactory(); return new DynamicOperation() { 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 1917) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/QueryProcessor.java (working copy) @@ -80,7 +80,10 @@ public abstract class QueryProcessor implements Processor { List projectedColumns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); assert projectedColumns != null; assert !projectedColumns.isEmpty(); - columns = new QueryResultColumns(projectedColumns, context.getHints().hasFullTextSearch); + List columnTypes = project.getPropertyAsList(Property.PROJECT_COLUMN_TYPES, String.class); + assert columnTypes != null; + assert columnTypes.size() == projectedColumns.size(); + columns = new QueryResultColumns(projectedColumns, columnTypes, context.getHints().hasFullTextSearch); // Go through the plan and create the corresponding ProcessingComponents ... Analyzer analyzer = createAnalyzer(context); @@ -369,8 +372,11 @@ public abstract class QueryProcessor implements Processor { PlanNode project = node.findAtOrBelow(Type.PROJECT); assert project != null; List columns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); + List columnTypes = project.getPropertyAsList(Property.PROJECT_COLUMN_TYPES, String.class); assert columns != null; assert !columns.isEmpty(); - return new QueryResultColumns(columns, projectedColumns.hasFullTextSearchScores()); + assert columnTypes != null; + assert columnTypes.size() == columns.size(); + return new QueryResultColumns(columns, columnTypes, 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 1917) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/QueryResultColumns.java (working copy) @@ -52,7 +52,8 @@ public class QueryResultColumns implements Columns { private static final long serialVersionUID = 1L; protected static final List NO_COLUMNS = Collections.emptyList(); - protected static final QueryResultColumns EMPTY = new QueryResultColumns(false, null); + protected static final List NO_TYPES = Collections.emptyList(); + protected static final QueryResultColumns EMPTY = new QueryResultColumns(false, null, null); protected static final String DEFAULT_SELECTOR_NAME = "Results"; @@ -68,6 +69,7 @@ public class QueryResultColumns implements Columns { private final int tupleSize; private final List columns; private final List columnNames; + private final List columnTypes; private final List selectorNames; private List tupleValueNames; private final Map columnIndexByColumnName; @@ -81,13 +83,16 @@ public class QueryResultColumns implements Columns { * Create a new definition for the query results given the supplied columns. * * @param columns the columns that define the results; should never be modified directly + * @param columnTypes the names of the types for each column in columns * @param includeFullTextSearchScores true if room should be made in the tuples for the full-text search scores for each * {@link Location}, or false otherwise */ public QueryResultColumns( List columns, + List columnTypes, boolean includeFullTextSearchScores ) { - this(includeFullTextSearchScores, columns); + this(includeFullTextSearchScores, columns, columnTypes); CheckArg.isNotEmpty(columns, "columns"); + CheckArg.isNotEmpty(columnTypes, "columnTypes"); } /** @@ -96,10 +101,13 @@ public class QueryResultColumns implements Columns { * @param includeFullTextSearchScores true if room should be made in the tuples for the full-text search scores for each * {@link Location}, or false otherwise * @param columns the columns that define the results; should never be modified directly + * @param columnTypes the names of the types for each column in columns */ protected QueryResultColumns( boolean includeFullTextSearchScores, - List columns ) { + List columns, + List columnTypes ) { this.columns = columns != null ? Collections.unmodifiableList(columns) : NO_COLUMNS; + this.columnTypes = columnTypes != null ? Collections.unmodifiableList(columnTypes) : NO_TYPES; this.columnIndexByColumnName = new HashMap(); Set selectors = new HashSet(); final int columnCount = this.columns.size(); @@ -162,6 +170,7 @@ public class QueryResultColumns implements Columns { this.locationIndexByColumnName = new HashMap(); this.columnIndexByPropertyNameBySelectorName = new HashMap>(); this.selectorNames = new ArrayList(columns.size()); + List types = new ArrayList(columns.size()); List names = new ArrayList(columns.size()); for (int i = 0, max = this.columns.size(); i != max; ++i) { Column column = this.columns.get(i); @@ -170,8 +179,10 @@ public class QueryResultColumns implements Columns { if (!selectorNames.contains(selectorName)) selectorNames.add(selectorName); String columnName = columnNameFor(column, names); assert columnName != null; - Integer columnIndex = new Integer(wrappedAround.getColumnIndexForName(columnName)); + int columnIndexInt = wrappedAround.getColumnIndexForName(columnName); + Integer columnIndex = new Integer(columnIndexInt); columnIndexByColumnName.put(columnName, columnIndex); + types.add(wrappedAround.getColumnTypes().get(columnIndexInt)); Integer selectorIndex = new Integer(wrappedAround.getLocationIndex(selectorName)); locationIndexBySelectorName.put(selectorName, selectorIndex); locationIndexByColumnIndex.put(columnIndex, selectorIndex); @@ -190,6 +201,7 @@ public class QueryResultColumns implements Columns { locationIndexBySelectorName.put(selectorName, 0); } this.columnNames = Collections.unmodifiableList(names); + this.columnTypes = Collections.unmodifiableList(types); if (wrappedAround.fullTextSearchScoreIndexBySelectorName != null) { this.fullTextSearchScoreIndexBySelectorName = new HashMap(); int index = columnNames.size() + selectorNames.size(); @@ -251,8 +263,11 @@ public class QueryResultColumns implements Columns { List columns = new ArrayList(this.getColumnCount() + rightColumns.getColumnCount()); columns.addAll(this.getColumns()); columns.addAll(rightColumns.getColumns()); + List types = new ArrayList(this.getColumnCount() + rightColumns.getColumnCount()); + types.addAll(this.getColumnTypes()); + types.addAll(rightColumns.getColumnTypes()); boolean includeFullTextScores = this.hasFullTextSearchScores() || rightColumns.hasFullTextSearchScores(); - return new QueryResultColumns(columns, includeFullTextScores); + return new QueryResultColumns(columns, types, includeFullTextScores); } /** @@ -286,6 +301,15 @@ public class QueryResultColumns implements Columns { /** * {@inheritDoc} * + * @see org.modeshape.graph.query.QueryResults.Columns#getColumnTypes() + */ + public List getColumnTypes() { + return columnTypes; + } + + /** + * {@inheritDoc} + * * @see org.modeshape.graph.query.QueryResults.Columns#getColumnCount() */ public int getColumnCount() { Index: modeshape-graph/src/test/java/org/modeshape/graph/GraphTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/GraphTest.java (revision 1917) +++ modeshape-graph/src/test/java/org/modeshape/graph/GraphTest.java (working copy) @@ -61,6 +61,7 @@ import org.modeshape.graph.property.InvalidPathException; import org.modeshape.graph.property.Name; import org.modeshape.graph.property.Path; import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.PropertyType; import org.modeshape.graph.query.QueryResults; import org.modeshape.graph.query.QueryResults.Columns; import org.modeshape.graph.query.QueryResults.Statistics; @@ -1321,12 +1322,24 @@ public class GraphTest { protected Columns columns( String tableName, String... columnNames ) { - return new QueryResultColumns(columnList(tableName, columnNames), false); + List columnList = columnList(tableName, columnNames); + List columnTypes = typesFor(columnList); + return new QueryResultColumns(columnList, columnTypes, false); } protected Columns columnsWithScores( String tableName, String... columnNames ) { - return new QueryResultColumns(columnList(tableName, columnNames), true); + List columnList = columnList(tableName, columnNames); + List columnTypes = typesFor(columnList); + return new QueryResultColumns(columnList, columnTypes, true); + } + + protected List typesFor( List columns ) { + List types = new ArrayList(); + for (int i = 0; i != columns.size(); ++i) { + types.add(PropertyType.STRING.getName()); + } + return types; } protected List columnList( String tableName, 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 1917) +++ modeshape-graph/src/test/java/org/modeshape/graph/query/optimize/RuleBasedOptimizerTest.java (working copy) @@ -59,6 +59,7 @@ import org.modeshape.graph.query.plan.CanonicalPlanner; import org.modeshape.graph.query.plan.JoinAlgorithm; import org.modeshape.graph.query.plan.PlanHints; import org.modeshape.graph.query.plan.PlanNode; +import org.modeshape.graph.query.plan.PlanUtil; import org.modeshape.graph.query.plan.PlanNode.Property; import org.modeshape.graph.query.plan.PlanNode.Type; import org.modeshape.graph.query.validate.ImmutableSchemata; @@ -804,6 +805,9 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { // ---------------------------------------------------------------------------------------------------------------- protected void assertPlanMatches( PlanNode expected ) { + // Make sure the projected types are there ... + ensureProjectTypesOn(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)); @@ -873,4 +877,13 @@ public class RuleBasedOptimizerTest extends AbstractQueryTest { } return optimized; } + + protected void ensureProjectTypesOn( PlanNode node ) { + for (PlanNode project : node.findAllAtOrBelow(Type.PROJECT)) { + List columns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); + List types = PlanUtil.findRequiredColumnTypes(context, columns, project); + assertThat(columns.size(), is(types.size())); + project.setProperty(Property.PROJECT_COLUMN_TYPES, types); + } + } } Index: modeshape-graph/src/test/java/org/modeshape/graph/query/process/AbstractQueryResultsTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/query/process/AbstractQueryResultsTest.java (revision 1917) +++ modeshape-graph/src/test/java/org/modeshape/graph/query/process/AbstractQueryResultsTest.java (working copy) @@ -83,25 +83,31 @@ public abstract class AbstractQueryResultsTest extends AbstractQueryTest { } protected Columns resultColumns( String selectorName, - String... columnNames ) { + String[] columnNames, + PropertyType... columnTypes ) { // Define the columns ... List columnObj = new ArrayList(); + List types = new ArrayList(); SelectorName selector = selector(selectorName); + int i = 0; for (String columnName : columnNames) { columnObj.add(new Column(selector, columnName, columnName)); + types.add(columnTypes[i++].getName()); } - return new QueryResultColumns(columnObj, false); + return new QueryResultColumns(columnObj, types, false); } protected Columns resultColumnsWithSearchResults( String selectorName, String... columnNames ) { // Define the columns ... List columnObj = new ArrayList(); + List types = new ArrayList(); SelectorName selector = selector(selectorName); for (String columnName : columnNames) { columnObj.add(new Column(selector, columnName, columnName)); + types.add(PropertyType.STRING.getName()); } - return new QueryResultColumns(columnObj, true); + return new QueryResultColumns(columnObj, types, true); } protected Object[] tuple( Columns columns, Index: modeshape-graph/src/test/java/org/modeshape/graph/query/process/DistinctComponentTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/query/process/DistinctComponentTest.java (revision 1917) +++ modeshape-graph/src/test/java/org/modeshape/graph/query/process/DistinctComponentTest.java (working copy) @@ -28,11 +28,12 @@ import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; 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.validate.Schemata; -import org.junit.Before; -import org.junit.Test; /** * @@ -49,7 +50,11 @@ public class DistinctComponentTest extends AbstractQueryResultsTest { context = new QueryContext(mock(Schemata.class), typeSystem); inputTuples = new ArrayList(); // Define the columns for the results ... - columns = resultColumns("Selector1", "ColA", "ColB", "ColC"); + columns = resultColumns("Selector1", + new String[] {"ColA", "ColB", "ColC"}, + PropertyType.STRING, + PropertyType.STRING, + PropertyType.STRING); // And define the delegating component ... ProcessingComponent delegate = new ProcessingComponent(context, columns) { @SuppressWarnings( "synthetic-access" ) Index: modeshape-graph/src/test/java/org/modeshape/graph/query/process/JoinComponentTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/query/process/JoinComponentTest.java (revision 1917) +++ modeshape-graph/src/test/java/org/modeshape/graph/query/process/JoinComponentTest.java (working copy) @@ -33,6 +33,7 @@ import org.junit.Test; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.Location; import org.modeshape.graph.property.Path; +import org.modeshape.graph.property.PropertyType; import org.modeshape.graph.query.QueryResults.Columns; import org.modeshape.graph.query.model.Column; import org.modeshape.graph.query.model.SelectorName; @@ -56,12 +57,24 @@ public class JoinComponentTest { protected Columns columns( String tableName, String... columnNames ) { - return new QueryResultColumns(columnList(tableName, columnNames), false); + List columnList = columnList(tableName, columnNames); + List columnTypes = typesFor(columnList); + return new QueryResultColumns(columnList, columnTypes, false); } protected Columns columnsWithScores( String tableName, String... columnNames ) { - return new QueryResultColumns(columnList(tableName, columnNames), true); + List columnList = columnList(tableName, columnNames); + List columnTypes = typesFor(columnList); + return new QueryResultColumns(columnList, columnTypes, true); + } + + protected List typesFor( List columns ) { + List types = new ArrayList(); + for (int i = 0; i != columns.size(); ++i) { + types.add(PropertyType.STRING.getName()); + } + return types; } protected List columnList( String tableName, @@ -109,7 +122,7 @@ public class JoinComponentTest { public void shouldCreateMergerAndThenMergeTuplesForColumnsWithoutFullTextScore() { List columns = columnList("t1", "c11", "c12", "c13"); columns.addAll(columnList("t2", "c21", "c22", "c23")); - mergedColumns = new QueryResultColumns(columns, false); + mergedColumns = new QueryResultColumns(columns, typesFor(columns), false); leftColumns = columns("t1", "c11", "c12", "c13"); rightColumns = columns("t2", "c21", "c22", "c23"); @@ -134,7 +147,7 @@ public class JoinComponentTest { public void shouldCreateMergerAndThenMergeTuplesForColumnsWithFullTextScore() { List columns = columnList("t1", "c11", "c12", "c13"); columns.addAll(columnList("t2", "c21", "c22", "c23")); - mergedColumns = new QueryResultColumns(columns, true); + mergedColumns = new QueryResultColumns(columns, typesFor(columns), true); leftColumns = columnsWithScores("t1", "c11", "c12", "c13"); rightColumns = columnsWithScores("t2", "c21", "c22", "c23"); Index: modeshape-graph/src/test/java/org/modeshape/graph/query/process/QueryResultColumnsTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/query/process/QueryResultColumnsTest.java (revision 1917) +++ modeshape-graph/src/test/java/org/modeshape/graph/query/process/QueryResultColumnsTest.java (working copy) @@ -35,6 +35,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.modeshape.common.util.StringUtil; import org.modeshape.graph.Location; +import org.modeshape.graph.property.PropertyType; import org.modeshape.graph.query.QueryContext; import org.modeshape.graph.query.QueryResults.Columns; import org.modeshape.graph.query.QueryResults.Cursor; @@ -49,6 +50,7 @@ public class QueryResultColumnsTest extends AbstractQueryResultsTest { private QueryContext context; private List columnList; + private List columnTypes; private Columns columns; private QueryResults results; private List tuples; @@ -68,7 +70,11 @@ public class QueryResultColumnsTest extends AbstractQueryResultsTest { columnList.add(new Column(selector("table2"), "colA", "colA2")); columnList.add(new Column(selector("table2"), "colB", "colB2")); columnList.add(new Column(selector("table2"), "colX", "colX")); - columns = new QueryResultColumns(columnList, false); + columnTypes = new ArrayList(); + for (int i = 0; i != columnList.size(); ++i) { + columnTypes.add(PropertyType.STRING.getName()); + } + columns = new QueryResultColumns(columnList, columnTypes, false); tuples = new ArrayList(); tuples.add(tuple(columns, new String[] {"/a/b/c", "/a/x/y"}, 1, 2, 3, "2a", "2b", "x")); tuples.add(tuple(columns, new String[] {"/a/b/d", "/a/x/y"}, 4, 5, 6, "2a", "2b", "x")); Index: modeshape-graph/src/test/java/org/modeshape/graph/query/process/QueryResultsTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/query/process/QueryResultsTest.java (revision 1917) +++ modeshape-graph/src/test/java/org/modeshape/graph/query/process/QueryResultsTest.java (working copy) @@ -30,6 +30,7 @@ import java.util.List; import java.util.NoSuchElementException; import org.junit.Before; import org.junit.Test; +import org.modeshape.graph.property.PropertyType; import org.modeshape.graph.query.QueryResults.Columns; import org.modeshape.graph.query.model.Column; @@ -39,6 +40,7 @@ import org.modeshape.graph.query.model.Column; public class QueryResultsTest extends AbstractQueryResultsTest { private List columnList; + private List columnTypes; private QueryResultColumns columnsWithoutScores; private QueryResultColumns columnsWithScores; @@ -51,8 +53,12 @@ public class QueryResultsTest extends AbstractQueryResultsTest { columnList.add(new Column(selector("table2"), "colA", "colA2")); columnList.add(new Column(selector("table2"), "colB", "colB2")); columnList.add(new Column(selector("table2"), "colX", "colX")); - columnsWithoutScores = new QueryResultColumns(columnList, false); - columnsWithScores = new QueryResultColumns(columnList, true); + columnTypes = new ArrayList(); + for (int i = 0; i != columnList.size(); ++i) { + columnTypes.add(PropertyType.STRING.getName()); + } + columnsWithoutScores = new QueryResultColumns(columnList, columnTypes, false); + columnsWithScores = new QueryResultColumns(columnList, columnTypes, true); } @Test @@ -287,7 +293,11 @@ public class QueryResultsTest extends AbstractQueryResultsTest { subset.add(columnList.get(0)); subset.add(columnList.get(1)); subset.add(columnList.get(4)); - Columns other = new QueryResultColumns(subset, false); + List subsetTypes = new ArrayList(); + subsetTypes.add(PropertyType.STRING.getName()); + subsetTypes.add(PropertyType.STRING.getName()); + subsetTypes.add(PropertyType.STRING.getName()); + Columns other = new QueryResultColumns(subset, subsetTypes, false); assertThat(columnsWithScores.includes(other), is(true)); assertThat(columnsWithoutScores.includes(other), is(true)); assertThat(columnsWithoutScores.includes(columnsWithScores), is(true)); @@ -306,51 +316,62 @@ public class QueryResultsTest extends AbstractQueryResultsTest { @Test public void shouldNotBeUnionCompatibleUnlessBothHaveFullTextSearchScores() { - Columns other = new QueryResultColumns(columnsWithoutScores.getColumns(), !columnsWithoutScores.hasFullTextSearchScores()); + Columns other = new QueryResultColumns(columnsWithoutScores.getColumns(), columnsWithoutScores.getColumnTypes(), + !columnsWithoutScores.hasFullTextSearchScores()); assertThat(columnsWithoutScores.isUnionCompatible(other), is(false)); } @Test public void shouldNotBeUnionCompatibleUnlessBothDoNotHaveFullTextSearchScores() { - Columns other = new QueryResultColumns(columnsWithScores.getColumns(), !columnsWithScores.hasFullTextSearchScores()); + Columns other = new QueryResultColumns(columnsWithScores.getColumns(), columnsWithoutScores.getColumnTypes(), + !columnsWithScores.hasFullTextSearchScores()); assertThat(columnsWithScores.isUnionCompatible(other), is(false)); } @Test public void shouldBeUnionCompatibleWithEquivalentColumns() { List columnListCopy = new ArrayList(); + List columnTypeCopy = new ArrayList(); for (Column column : columnsWithScores.getColumns()) { columnListCopy.add(new Column(column.selectorName(), column.propertyName(), column.columnName())); + columnTypeCopy.add(PropertyType.STRING.getName()); } - Columns other = new QueryResultColumns(columnListCopy, columnsWithScores.hasFullTextSearchScores()); + Columns other = new QueryResultColumns(columnListCopy, columnTypeCopy, columnsWithScores.hasFullTextSearchScores()); assertThat(columnsWithScores.isUnionCompatible(other), is(true)); } @Test public void shouldNotBeUnionCompatibleWithSubsetOfColumns() { List columnListCopy = new ArrayList(); + List columnTypeCopy = new ArrayList(); for (Column column : columnsWithScores.getColumns()) { columnListCopy.add(new Column(column.selectorName(), column.propertyName(), column.columnName())); + columnTypeCopy.add(PropertyType.STRING.getName()); } columnListCopy.remove(3); - Columns other = new QueryResultColumns(columnListCopy, columnsWithScores.hasFullTextSearchScores()); + columnTypeCopy.remove(3); + Columns other = new QueryResultColumns(columnListCopy, columnTypeCopy, columnsWithScores.hasFullTextSearchScores()); assertThat(columnsWithScores.isUnionCompatible(other), is(false)); } @Test public void shouldNotBeUnionCompatibleWithExtraColumns() { List columnListCopy = new ArrayList(); + List columnTypeCopy = new ArrayList(); for (Column column : columnsWithScores.getColumns()) { columnListCopy.add(new Column(column.selectorName(), column.propertyName(), column.columnName())); + columnTypeCopy.add(PropertyType.STRING.getName()); } columnListCopy.add(new Column(selector("table2"), "colZ", "colZ")); - Columns other = new QueryResultColumns(columnListCopy, columnsWithScores.hasFullTextSearchScores()); + columnTypeCopy.add(PropertyType.STRING.getName()); + Columns other = new QueryResultColumns(columnListCopy, columnTypeCopy, columnsWithScores.hasFullTextSearchScores()); assertThat(columnsWithScores.isUnionCompatible(other), is(false)); } @Test public void shouldBeUnionCompatibleWithSameColumns() { - Columns other = new QueryResultColumns(columnsWithScores.getColumns(), columnsWithScores.hasFullTextSearchScores()); + Columns other = new QueryResultColumns(columnsWithScores.getColumns(), columnsWithScores.getColumnTypes(), + columnsWithScores.hasFullTextSearchScores()); assertThat(columnsWithScores.isUnionCompatible(other), is(true)); } Index: modeshape-graph/src/test/java/org/modeshape/graph/query/process/SortLocationsComponentTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/query/process/SortLocationsComponentTest.java (revision 1917) +++ modeshape-graph/src/test/java/org/modeshape/graph/query/process/SortLocationsComponentTest.java (working copy) @@ -28,11 +28,12 @@ import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; 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.validate.Schemata; -import org.junit.Before; -import org.junit.Test; /** * @@ -49,7 +50,11 @@ public class SortLocationsComponentTest extends AbstractQueryResultsTest { context = new QueryContext(mock(Schemata.class), typeSystem); inputTuples = new ArrayList(); // Define the columns for the results ... - columns = resultColumns("Selector1", "ColA", "ColB", "ColC"); + columns = resultColumns("Selector1", + new String[] {"ColA", "ColB", "ColC"}, + PropertyType.STRING, + PropertyType.STRING, + PropertyType.STRING); // And define the delegating component ... ProcessingComponent delegate = new ProcessingComponent(context, columns) { @SuppressWarnings( "synthetic-access" ) 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 1917) +++ modeshape-graph/src/test/java/org/modeshape/graph/query/process/SortValuesComponentTest.java (working copy) @@ -51,7 +51,11 @@ public class SortValuesComponentTest extends AbstractQueryResultsTest { @Before public void beforeEach() { // Define the columns for the results ... - columns = resultColumns("Selector1", "ColA", "ColB", "ColC"); + columns = resultColumns("Selector1", + new String[] {"ColA", "ColB", "ColC"}, + PropertyType.STRING, + PropertyType.LONG, + PropertyType.STRING); schemata = schemataFor(columns, PropertyType.STRING, PropertyType.LONG, PropertyType.STRING); // Define the context ... context = new QueryContext(schemata, typeSystem); Index: modeshape-jcr/src/main/java/org/modeshape/jcr/query/JcrQueryResult.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/query/JcrQueryResult.java (revision 1917) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/query/JcrQueryResult.java (working copy) @@ -33,7 +33,6 @@ import javax.jcr.AccessDeniedException; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; -import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; @@ -62,7 +61,6 @@ public class JcrQueryResult implements QueryResult, org.modeshape.jcr.api.query. protected final QueryResults results; protected final Schemata schemata; protected final String queryStatement; - private List columnTypes; private List columnTables; protected JcrQueryResult( JcrQueryContext context, @@ -87,6 +85,10 @@ public class JcrQueryResult implements QueryResult, org.modeshape.jcr.api.query. return results.getColumns().getColumnNames(); } + public List getColumnTypeList() { + return results.getColumns().getColumnTypes(); + } + /** * {@inheritDoc} * @@ -104,30 +106,8 @@ public class JcrQueryResult implements QueryResult, org.modeshape.jcr.api.query. */ @Override public String[] getColumnTypes() { - if (columnTypes == null) { - // Discover the types ... - columnTypes = loadColumnTypes(results.getColumns()); - } - return columnTypes.toArray(new String[columnTypes.size()]); - } - - protected List loadColumnTypes( Columns columns ) { - List types = new ArrayList(columns.getColumnCount()); - for (Column column : columns) { - String typeName = null; - Table table = schemata.getTable(column.selectorName()); - if (table != null) { - Schemata.Column typedColumn = table.getColumn(column.propertyName()); - typeName = typedColumn.getPropertyType(); - } - if (typeName == null) { - // Might be fabricated column, so just assume string ... - typeName = PropertyType.nameFromValue(PropertyType.STRING); - } - types.add(typeName); - } - - return types; + List types = getColumnTypeList(); + return types.toArray(new String[types.size()]); // make a defensive copy ... } /** Index: modeshape-jcr/src/main/java/org/modeshape/jcr/query/JcrSqlQueryResult.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/query/JcrSqlQueryResult.java (revision 1917) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/query/JcrSqlQueryResult.java (working copy) @@ -23,7 +23,6 @@ */ package org.modeshape.jcr.query; -import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; @@ -38,10 +37,7 @@ import javax.jcr.query.RowIterator; import org.modeshape.graph.Location; import org.modeshape.graph.property.Path; import org.modeshape.graph.query.QueryResults; -import org.modeshape.graph.query.QueryResults.Columns; -import org.modeshape.graph.query.model.Column; import org.modeshape.graph.query.validate.Schemata; -import org.modeshape.graph.query.validate.Schemata.Table; /** * @@ -50,10 +46,12 @@ public class JcrSqlQueryResult extends JcrQueryResult { public static final String JCR_SCORE_COLUMN_NAME = "jcr:score"; public static final String JCR_PATH_COLUMN_NAME = "jcr:path"; + /* The TypeFactory.getTypeName() always returns an uppercased type */ + public static final String JCR_SCORE_COLUMN_TYPE = PropertyType.nameFromValue(PropertyType.DOUBLE).toUpperCase(); + public static final String JCR_PATH_COLUMN_TYPE = PropertyType.nameFromValue(PropertyType.STRING).toUpperCase(); private final List columnNames; - private boolean addedScoreColumn; - private boolean addedPathColumn; + private final List columnTypes; public JcrSqlQueryResult( JcrQueryContext context, String query, @@ -61,15 +59,17 @@ public class JcrSqlQueryResult extends JcrQueryResult { Schemata schemata ) { super(context, query, graphResults, schemata); List columnNames = new LinkedList(graphResults.getColumns().getColumnNames()); + List columnTypes = new LinkedList(graphResults.getColumns().getColumnTypes()); if (!columnNames.contains(JCR_SCORE_COLUMN_NAME)) { columnNames.add(0, JCR_SCORE_COLUMN_NAME); - addedScoreColumn = true; + columnTypes.add(0, JCR_SCORE_COLUMN_TYPE); } if (!columnNames.contains(JCR_PATH_COLUMN_NAME)) { columnNames.add(0, JCR_PATH_COLUMN_NAME); - addedPathColumn = true; + columnTypes.add(0, JCR_PATH_COLUMN_TYPE); } this.columnNames = Collections.unmodifiableList(columnNames); + this.columnTypes = Collections.unmodifiableList(columnTypes); } /** @@ -82,29 +82,14 @@ public class JcrSqlQueryResult extends JcrQueryResult { return columnNames; } + /** + * {@inheritDoc} + * + * @see org.modeshape.jcr.query.JcrQueryResult#getColumnTypeList() + */ @Override - protected List loadColumnTypes( Columns columns ) { - List types = new ArrayList(columns.getColumnCount() + (addedScoreColumn ? 1 : 0) - + (addedPathColumn ? 1 : 0)); - String stringtype = PropertyType.nameFromValue(PropertyType.STRING); - if (addedScoreColumn) types.add(0, stringtype); - if (addedPathColumn) types.add(0, stringtype); - - for (Column column : columns) { - String typeName = null; - Table table = schemata.getTable(column.selectorName()); - if (table != null) { - Schemata.Column typedColumn = table.getColumn(column.propertyName()); - typeName = typedColumn.getPropertyType(); - } - if (typeName == null) { - // Might be fabricated column, so just assume string ... - typeName = stringtype; - } - types.add(typeName); - } - - return types; + public java.util.List getColumnTypeList() { + return columnTypes; } /** Index: utils/modeshape-jdbc/src/test/java/org/modeshape/jdbc/JcrDriverIntegrationTest.java =================================================================== --- utils/modeshape-jdbc/src/test/java/org/modeshape/jdbc/JcrDriverIntegrationTest.java (revision 1917) +++ utils/modeshape-jdbc/src/test/java/org/modeshape/jdbc/JcrDriverIntegrationTest.java (working copy) @@ -46,10 +46,10 @@ import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.modeshape.common.FixFor; import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; import org.modeshape.jcr.JcrConfiguration; import org.modeshape.jcr.JcrEngine; @@ -335,24 +335,17 @@ public class JcrDriverIntegrationTest { } - /** - * JIRA: https://jira.jboss.org/browse/MODE-772 - * - * @throws SQLException - */ - @Ignore + @FixFor( "MODE-722" ) @Test public void shouldBeAbleToExecuteSqlQueryUsingJoinToFindAllCarsUnderHybrid() throws SQLException { - String[] expected = { - "jcr:path[String] jcr:score[String] jcr:primaryType[STRING]", - "/Cars/Utility 1.0 nt:unstructured", - "/Cars/Hybrid 1.0 nt:unstructured", - "/Cars/Sports 1.0 nt:unstructured", - "/Cars/Luxury 1.0 nt:unstructured" - }; - + String[] expected = {"car:maker[STRING] car:model[STRING] car:year[STRING] car:msrp[STRING]", + "Nissan Altima 2008 $18,260", "Toyota Prius 2008 $21,500", + "Toyota Highlander 2008 $34,200"}; + DriverTestUtil.executeTest(this.connection, - "SELECT car.[car:maker], car.[car:model], car.[car:year], car.[car:msrp] FROM [car:Car] AS car JOIN [nt:unstructured] AS hybrid ON ISCHILDNODE(car,hybrid) WHERE NAME(hybrid) = 'Hybrid'", expected, 4); + "SELECT car.[car:maker], car.[car:model], car.[car:year], car.[car:msrp] FROM [car:Car] AS car JOIN [nt:unstructured] AS hybrid ON ISCHILDNODE(car,hybrid) WHERE NAME(hybrid) = 'Hybrid'", + expected, + 3); } @@ -374,7 +367,7 @@ public class JcrDriverIntegrationTest { */ @Test public void shouldBeAbleToExecuteSqlQueryWithChildAxisCriteria() throws SQLException { - String[] expected = {"jcr:path[String] jcr:score[String] jcr:primaryType[STRING]", + String[] expected = {"jcr:path[STRING] jcr:score[DOUBLE] jcr:primaryType[STRING]", "/Cars/Utility 1.0 nt:unstructured", "/Cars/Hybrid 1.0 nt:unstructured", "/Cars/Sports 1.0 nt:unstructured", "/Cars/Luxury 1.0 nt:unstructured"}; DriverTestUtil.executeTest(this.connection, @@ -392,7 +385,7 @@ public class JcrDriverIntegrationTest { */ @Test public void shouldBeAbleToExecuteSqlQueryWithContainsCriteria() throws SQLException { - String[] expected = {"jcr:path[String] jcr:score[String] jcr:primaryType[STRING]", + String[] expected = {"jcr:path[STRING] jcr:score[DOUBLE] jcr:primaryType[STRING]", "/Cars/Utility 1.0 nt:unstructured", "/Cars/Hybrid 1.0 nt:unstructured", "/Cars/Sports 1.0 nt:unstructured", "/Cars/Luxury 1.0 nt:unstructured"};