Index: modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java (revision 2609) +++ modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java (working copy) @@ -210,6 +210,8 @@ public final class GraphI18n { public static I18n expectingLiteralAndUnableToParseAsDate; public static I18n unexpectedClosingParenthesis; public static I18n leftAndRightQueriesInSetQueryMustHaveUnionableColumns; + public static I18n operatorIsNotValidAgainstColumnInTable; + public static I18n columnInTableIsNotOrderable; /* Search */ public static I18n interruptedWhileClosingChannel; Index: modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableColumn.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableColumn.java (revision 2609) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableColumn.java (working copy) @@ -23,31 +23,62 @@ */ package org.modeshape.graph.query.validate; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Set; import net.jcip.annotations.Immutable; +import org.modeshape.common.collection.Collections; +import org.modeshape.graph.query.model.Operator; import org.modeshape.graph.query.validate.Schemata.Column; @Immutable class ImmutableColumn implements Column { + public static final Set ALL_OPERATORS = Collections.unmodifiableSet(EnumSet.allOf(Operator.class)); + public static final Set NO_OPERATORS = Collections.unmodifiableSet(EnumSet.noneOf(Operator.class)); + public static final boolean DEFAULT_FULL_TEXT_SEARCHABLE = false; + public static final boolean DEFAULT_ORDERABLE = true; private final boolean fullTextSearchable; + private final boolean orderable; private final String name; private final String type; + private final Set operators; protected ImmutableColumn( String name, String type ) { - this(name, type, DEFAULT_FULL_TEXT_SEARCHABLE); + this(name, type, DEFAULT_FULL_TEXT_SEARCHABLE, DEFAULT_ORDERABLE, ALL_OPERATORS); } protected ImmutableColumn( String name, String type, boolean fullTextSearchable ) { + this(name, type, fullTextSearchable, DEFAULT_ORDERABLE, ALL_OPERATORS); + } + + protected ImmutableColumn( String name, + String type, + boolean fullTextSearchable, + boolean orderable, + Operator... operators ) { + this(name, type, fullTextSearchable, orderable, + operators != null && operators.length != 0 ? EnumSet.copyOf(Arrays.asList(operators)) : null); + } + + protected ImmutableColumn( String name, + String type, + boolean fullTextSearchable, + boolean orderable, + Set operators ) { this.name = name; this.type = type; this.fullTextSearchable = fullTextSearchable; + this.orderable = orderable; + this.operators = operators == null || operators.isEmpty() ? ALL_OPERATORS : Collections.unmodifiableSet(EnumSet.copyOf(operators)); assert this.name != null; assert this.type != null; + assert this.operators != null; } /** @@ -80,6 +111,24 @@ class ImmutableColumn implements Column { /** * {@inheritDoc} * + * @see org.modeshape.graph.query.validate.Schemata.Column#isOrderable() + */ + public boolean isOrderable() { + return orderable; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.query.validate.Schemata.Column#getOperators() + */ + public Set getOperators() { + return operators; + } + + /** + * {@inheritDoc} + * * @see java.lang.Object#toString() */ @Override 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 2609) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableSchemata.java (working copy) @@ -26,6 +26,7 @@ package org.modeshape.graph.query.validate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -38,6 +39,7 @@ import org.modeshape.common.text.ParsingException; import org.modeshape.common.util.CheckArg; import org.modeshape.graph.GraphI18n; import org.modeshape.graph.query.QueryContext; +import org.modeshape.graph.query.model.Operator; import org.modeshape.graph.query.model.QueryCommand; import org.modeshape.graph.query.model.SelectorName; import org.modeshape.graph.query.model.TypeSystem; @@ -78,6 +80,8 @@ public class ImmutableSchemata implements Schemata { private final Map tables = new HashMap(); private final Map viewDefinitions = new HashMap(); private final Set tablesOrViewsWithExtraColumns = new HashSet(); + private final Map> orderableColumnsByTableName = new HashMap>(); + private final Map>> operatorsForColumnsByTableName = new HashMap>>(); protected Builder( TypeSystem typeSystem ) { this.typeSystem = typeSystem; @@ -192,7 +196,12 @@ public class ImmutableSchemata implements Schemata { CheckArg.isNotEmpty(tableName, "tableName"); CheckArg.isNotEmpty(columnName, "columnName"); CheckArg.isNotNull(type, "type"); - return addColumn(tableName, columnName, type, ImmutableColumn.DEFAULT_FULL_TEXT_SEARCHABLE); + return addColumn(tableName, + columnName, + type, + ImmutableColumn.DEFAULT_FULL_TEXT_SEARCHABLE, + ImmutableColumn.DEFAULT_ORDERABLE, + ImmutableColumn.ALL_OPERATORS); } /** @@ -203,6 +212,9 @@ public class ImmutableSchemata implements Schemata { * @param columnName the names of the column * @param type the type for the column * @param fullTextSearchable true if the column should be full-text searchable, or false if not + * @param orderable true if the column can be used in order clauses, or false if not + * @param operations the set operations that can be applied to this column within comparisons; may be empty or null if all + * operations apply * @return this builder, for convenience in method chaining; never null * @throws IllegalArgumentException if the table name is null or empty, the column name is null or empty, or if the * property type is null @@ -210,12 +222,14 @@ public class ImmutableSchemata implements Schemata { public Builder addColumn( String tableName, String columnName, String type, - boolean fullTextSearchable ) { + boolean fullTextSearchable, + boolean orderable, + Set operations ) { CheckArg.isNotEmpty(tableName, "tableName"); CheckArg.isNotEmpty(columnName, "columnName"); CheckArg.isNotNull(type, "type"); MutableTable existing = tables.get(tableName); - Column column = new ImmutableColumn(columnName, type, fullTextSearchable); + Column column = new ImmutableColumn(columnName, type, fullTextSearchable, orderable, operations); if (existing == null) { List columns = new ArrayList(); columns.add(column); @@ -242,13 +256,16 @@ public class ImmutableSchemata implements Schemata { MutableTable existing = tables.get(tableName); if (existing == null) { List columns = new ArrayList(); - columns.add(new ImmutableColumn(columnName, typeSystem.getDefaultType(), true)); + columns.add(new ImmutableColumn(columnName, typeSystem.getDefaultType(), true, ImmutableColumn.DEFAULT_ORDERABLE, + ImmutableColumn.ALL_OPERATORS)); existing = new MutableTable(tableName, columns, false); tables.put(tableName, existing); } else { Column column = existing.getColumn(columnName); if (column != null && !column.isFullTextSearchable()) { - column = new ImmutableColumn(columnName, column.getPropertyType(), true); + boolean orderable = column.isOrderable(); + Set operators = column.getOperators(); + column = new ImmutableColumn(columnName, column.getPropertyType(), true, orderable, operators); } existing.addColumn(column); } @@ -256,6 +273,82 @@ public class ImmutableSchemata implements Schemata { } /** + * Record whether the column on the named table should be orderable. + * + * @param tableName the name of the new table + * @param columnName the names of the column + * @param orderable true if the column should be orderable, or false otherwise + * @return this builder, for convenience in method chaining; never null + */ + public Builder markOrderable( String tableName, + String columnName, + boolean orderable ) { + CheckArg.isNotEmpty(tableName, "tableName"); + Map byColumnNames = orderableColumnsByTableName.get(tableName); + if (byColumnNames == null) { + byColumnNames = new HashMap(); + orderableColumnsByTableName.put(tableName, byColumnNames); + } + byColumnNames.put(columnName, orderable); + return this; + } + + protected boolean orderable( SelectorName tableName, + String columnName, + boolean defaultValue ) { + Map byColumnNames = orderableColumnsByTableName.get(tableName.getString()); + if (byColumnNames != null) { + Boolean value = byColumnNames.get(columnName); + if (value != null) return value.booleanValue(); + } + return defaultValue; + } + + /** + * Record the operators that are allowed for the named column on the named table. + * + * @param tableName the name of the new table + * @param columnName the names of the column + * @param operators the set of operators, or null or empty if the default operators should be used + * @return this builder, for convenience in method chaining; never null + */ + public Builder markOperators( String tableName, + String columnName, + Set operators ) { + CheckArg.isNotEmpty(tableName, "tableName"); + boolean useDefaults = operators == null || operators.isEmpty(); + Map> byColumnNames = operatorsForColumnsByTableName.get(tableName); + if (byColumnNames == null) { + if (useDefaults) return this; + byColumnNames = new HashMap>(); + operatorsForColumnsByTableName.put(tableName, byColumnNames); + } + if (useDefaults) { + byColumnNames.remove(columnName); + if (byColumnNames.isEmpty()) { + // Nothing more for any of the columns, so remove the table from the map ... + operatorsForColumnsByTableName.remove(tableName); + } + } else { + Set opSet = EnumSet.copyOf(operators); + byColumnNames.put(columnName, opSet); + } + return this; + } + + protected Set operators( SelectorName tableName, + String columnName, + Set defaultOperators ) { + Map> byColumnNames = operatorsForColumnsByTableName.get(tableName.getString()); + if (byColumnNames != null) { + Set ops = byColumnNames.get(columnName); + if (ops != null) return ops; + } + return defaultOperators; + + } + + /** * Make sure the column on the named table has extra columns that can be used without validation error. * * @param tableName the name of the table @@ -283,7 +376,8 @@ public class ImmutableSchemata implements Schemata { MutableTable existing = tables.get(tableName); if (existing == null) { List columns = new ArrayList(); - columns.add(new ImmutableColumn(columnName, typeSystem.getDefaultType(), true)); + columns.add(new ImmutableColumn(columnName, typeSystem.getDefaultType(), true, ImmutableColumn.DEFAULT_ORDERABLE, + ImmutableColumn.ALL_OPERATORS)); existing = new MutableTable(tableName, columns, false); tables.put(tableName, existing); } @@ -393,8 +487,10 @@ public class ImmutableSchemata implements Schemata { "The view references a non-existant column '" + column.columnName() + "' in '" + source.getName() + "'"); } + Set operators = operators(name, viewColumnName, sourceColumn.getOperators()); + boolean orderable = orderable(name, viewColumnName, sourceColumn.isOrderable()); Column newColumn = new ImmutableColumn(viewColumnName, sourceColumn.getPropertyType(), - sourceColumn.isFullTextSearchable()); + sourceColumn.isFullTextSearchable(), orderable, operators); viewColumns.add(newColumn); if (source.getSelectAllColumnsByName().containsKey(sourceColumnName)) { viewColumnsInSelectStar.add(newColumn); Index: modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Schemata.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Schemata.java (revision 2609) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Schemata.java (working copy) @@ -28,6 +28,8 @@ import java.util.List; import java.util.Map; import java.util.Set; import net.jcip.annotations.Immutable; +import org.modeshape.graph.query.model.Operator; +import org.modeshape.graph.query.model.Ordering; import org.modeshape.graph.query.model.QueryCommand; import org.modeshape.graph.query.model.SelectorName; @@ -189,6 +191,20 @@ public interface Schemata { * @return true if the column is full-text searchable, or false otherwise */ boolean isFullTextSearchable(); + + /** + * Get the set of operators that can be used in a comparison involving this column. + * + * @return the operators; never null but possibly empty + */ + Set getOperators(); + + /** + * Get whether this column can be used within an {@link Ordering ORDER BY clause}. + * + * @return true if this column can be used in an order specification, or false otherwise + */ + boolean isOrderable(); } /** Index: modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Validator.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Validator.java (revision 2609) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Validator.java (working copy) @@ -34,6 +34,7 @@ import org.modeshape.graph.query.model.ArithmeticOperand; import org.modeshape.graph.query.model.ChildNode; import org.modeshape.graph.query.model.ChildNodeJoinCondition; import org.modeshape.graph.query.model.Column; +import org.modeshape.graph.query.model.Comparison; import org.modeshape.graph.query.model.DescendantNode; import org.modeshape.graph.query.model.DescendantNodeJoinCondition; import org.modeshape.graph.query.model.DynamicOperand; @@ -47,6 +48,8 @@ import org.modeshape.graph.query.model.NodeDepth; import org.modeshape.graph.query.model.NodeLocalName; import org.modeshape.graph.query.model.NodeName; import org.modeshape.graph.query.model.NodePath; +import org.modeshape.graph.query.model.Operator; +import org.modeshape.graph.query.model.Ordering; import org.modeshape.graph.query.model.PropertyExistence; import org.modeshape.graph.query.model.PropertyValue; import org.modeshape.graph.query.model.Query; @@ -56,6 +59,7 @@ import org.modeshape.graph.query.model.SameNodeJoinCondition; import org.modeshape.graph.query.model.SelectorName; import org.modeshape.graph.query.model.Subquery; import org.modeshape.graph.query.model.TypeSystem; +import org.modeshape.graph.query.model.UpperCase; import org.modeshape.graph.query.model.Visitor; import org.modeshape.graph.query.model.Visitors.AbstractVisitor; import org.modeshape.graph.query.validate.Schemata.Table; @@ -181,6 +185,18 @@ public class Validator extends AbstractVisitor { /** * {@inheritDoc} * + * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.Comparison) + */ + @Override + public void visit( Comparison obj ) { + // The dynamic operand itself will be visited by the validator as it walks the comparison object. + // All we need to do here is check the operator ... + verifyOperator(obj.operand1(), obj.operator()); + } + + /** + * {@inheritDoc} + * * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.DescendantNode) */ @Override @@ -328,6 +344,16 @@ public class Validator extends AbstractVisitor { /** * {@inheritDoc} * + * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.Ordering) + */ + @Override + public void visit( Ordering obj ) { + verifyOrdering(obj.operand()); + } + + /** + * {@inheritDoc} + * * @see org.modeshape.graph.query.model.Visitors.AbstractVisitor#visit(org.modeshape.graph.query.model.PropertyExistence) */ @Override @@ -413,13 +439,112 @@ public class Validator extends AbstractVisitor { verify(obj.selector2Name()); } + protected void verifyOrdering( DynamicOperand operand ) { + if (operand instanceof PropertyValue) { + PropertyValue propValue = (PropertyValue)operand; + verifyOrdering(propValue.selectorName(), propValue.propertyName()); + } else if (operand instanceof ReferenceValue) { + ReferenceValue value = (ReferenceValue)operand; + verifyOrdering(value.selectorName(), value.propertyName()); + } else if (operand instanceof Length) { + Length length = (Length)operand; + verifyOrdering(length.propertyValue()); + } else if (operand instanceof LowerCase) { + verifyOrdering(((LowerCase)operand).operand()); + } else if (operand instanceof UpperCase) { + verifyOrdering(((UpperCase)operand).operand()); + // } else if (operand instanceof NodeDepth) { + // NodeDepth depth = (NodeDepth)operand; + // verifyOrdering(depth.selectorName(), "mode:depth"); + // } else if (operand instanceof NodePath) { + // NodePath depth = (NodePath)operand; + // verifyOrdering(depth.selectorName(), "jcr:path"); + // } else if (operand instanceof NodeLocalName) { + // NodeLocalName depth = (NodeLocalName)operand; + // verifyOrdering(depth.selectorName(), "mode:localName"); + // } else if (operand instanceof NodeName) { + // NodeName depth = (NodeName)operand; + // verifyOrdering(depth.selectorName(), "jcr:name"); + } else if (operand instanceof ArithmeticOperand) { + // The LEFT and RIGHT dynamic operands must both work with this operator ... + ArithmeticOperand arith = (ArithmeticOperand)operand; + verifyOrdering(arith.left()); + verifyOrdering(arith.right()); + } + } + + protected void verifyOrdering( SelectorName selectorName, + String propertyName ) { + Schemata.Column column = verify(selectorName, propertyName, false); + if (column != null && !column.isOrderable()) { + problems.addError(GraphI18n.columnInTableIsNotOrderable, propertyName, selectorName.getString()); + } + } + + protected void verifyOperator( DynamicOperand operand, + Operator op ) { + if (operand instanceof PropertyValue) { + PropertyValue propValue = (PropertyValue)operand; + verifyOperator(propValue.selectorName(), propValue.propertyName(), op); + } else if (operand instanceof ReferenceValue) { + ReferenceValue value = (ReferenceValue)operand; + verifyOperator(value.selectorName(), value.propertyName(), op); + } else if (operand instanceof Length) { + Length length = (Length)operand; + verifyOperator(length.propertyValue(), op); + } else if (operand instanceof LowerCase) { + verifyOperator(((LowerCase)operand).operand(), op); + } else if (operand instanceof UpperCase) { + verifyOperator(((UpperCase)operand).operand(), op); + // } else if (operand instanceof NodeDepth) { + // NodeDepth depth = (NodeDepth)operand; + // verifyOperator(depth.selectorName(), "mode:depth", op); + // } else if (operand instanceof NodePath) { + // NodePath depth = (NodePath)operand; + // verifyOperator(depth.selectorName(), "jcr:path", op); + // } else if (operand instanceof NodeLocalName) { + // NodeLocalName depth = (NodeLocalName)operand; + // verifyOperator(depth.selectorName(), "mode:localName", op); + // } else if (operand instanceof NodeName) { + // NodeName depth = (NodeName)operand; + // verifyOperator(depth.selectorName(), "jcr:name", op); + } else if (operand instanceof ArithmeticOperand) { + // The LEFT and RIGHT dynamic operands must both work with this operator ... + ArithmeticOperand arith = (ArithmeticOperand)operand; + verifyOperator(arith.left(), op); + verifyOperator(arith.right(), op); + } + } + + protected void verifyOperator( SelectorName selectorName, + String propertyName, + Operator op ) { + Schemata.Column column = verify(selectorName, propertyName, false); + if (column != null) { + if (!column.getOperators().contains(op)) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Operator allowed : column.getOperators()) { + if (first) first = false; + else sb.append(", "); + sb.append(allowed.symbol()); + } + problems.addError(GraphI18n.operatorIsNotValidAgainstColumnInTable, + op.symbol(), + propertyName, + selectorName.getString(), + sb); + } + } + } + protected Table tableWithNameOrAlias( SelectorName tableName ) { Table table = selectorsByNameOrAlias.get(tableName); if (table == null) { // Try looking up the table by it's real name (if an alias were used) ... table = selectorsByName.get(tableName); } - return table; + return table; // may be null } protected Table verify( SelectorName selectorName ) { @@ -427,7 +552,7 @@ public class Validator extends AbstractVisitor { if (table == null) { problems.addError(GraphI18n.tableDoesNotExist, selectorName.name()); } - return table; + return table; // may be null } protected Table verifyTable( SelectorName tableName ) { @@ -435,7 +560,7 @@ public class Validator extends AbstractVisitor { if (table == null) { problems.addError(GraphI18n.tableDoesNotExist, tableName.name()); } - return table; + return table; // may be null } protected Schemata.Column verify( SelectorName selectorName, Index: modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties =================================================================== --- modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties (revision 2609) +++ modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties (working copy) @@ -199,6 +199,8 @@ expectingLiteralAndUnableToParseAsDouble = Expecting literal and unable to parse expectingLiteralAndUnableToParseAsDate = Expecting literal and unable to parse '{0}' at line {1}, column {2} as a date unexpectedClosingParenthesis = Unexpected closing parenthesis without a matching opening parenthesis leftAndRightQueriesInSetQueryMustHaveUnionableColumns = The left and right queries in a set query must have unionable columns: {0} vs {1} +operatorIsNotValidAgainstColumnInTable = The '{0}' operator is not valid against the '{1}' column in the '{2}' table. Only these operators are allowed: {3} +columnInTableIsNotOrderable = The '{0}' column in the '{1}' table cannot be used in the ordering clause of a query. # Search interruptedWhileClosingChannel = Thread was interrupted while closing request processing channel for source "{0}" Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrPropertyDefinition.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrPropertyDefinition.java (revision 2609) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrPropertyDefinition.java (working copy) @@ -45,6 +45,7 @@ import org.modeshape.graph.property.ValueFactories; import org.modeshape.graph.property.ValueFactory; import org.modeshape.graph.property.ValueFormatException; import org.modeshape.graph.property.basic.JodaDateTime; +import org.modeshape.jcr.api.query.qom.QueryObjectModelConstants; /** * ModeShape implementation of the {@link PropertyDefinition} interface. This implementation is immutable and has all fields @@ -86,7 +87,11 @@ class JcrPropertyDefinition extends JcrItemDefinition implements PropertyDefinit this.multiple = multiple; this.fullTextSearchable = fullTextSearchable; this.queryOrderable = queryOrderable; - this.queryOperators = queryOperators; + this.queryOperators = queryOperators != null ? queryOperators : new String[] { + QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO, QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN, + QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO, QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN, + QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO, QueryObjectModelConstants.JCR_OPERATOR_LIKE, + QueryObjectModelConstants.JCR_OPERATOR_NOT_EQUAL_TO}; this.isPrivate = name != null && ModeShapeIntLexicon.Namespace.URI.equals(name.getNamespaceUri()); assert this.valueConstraints != null; } @@ -149,18 +154,40 @@ class JcrPropertyDefinition extends JcrItemDefinition implements PropertyDefinit return isPrivate; } + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.PropertyDefinition#isFullTextSearchable() + */ public boolean isFullTextSearchable() { return fullTextSearchable; } + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.PropertyDefinition#isQueryOrderable() + */ public boolean isQueryOrderable() { return queryOrderable; } + /** + * Same as {@link #getAvailableQueryOperators()}. + * + * @return the query operators + * @deprecated Use the standard JCR {@link #getAvailableQueryOperators()} method instead + */ + @Deprecated public String[] getQueryOperators() { return queryOperators; } + @Override + public String[] getAvailableQueryOperators() { + return queryOperators; + } + /** * Creates a new JcrPropertyDefinition that is identical to the current object, but with the given * declaringNodeType. Provided to support immutable pattern for this class. @@ -173,7 +200,7 @@ class JcrPropertyDefinition extends JcrItemDefinition implements PropertyDefinit return new JcrPropertyDefinition(this.context, declaringNodeType, this.name, this.getOnParentVersion(), this.isAutoCreated(), this.isMandatory(), this.isProtected(), this.getDefaultValues(), this.getRequiredType(), this.getValueConstraints(), this.isMultiple(), - this.isFullTextSearchable(), this.isQueryOrderable(), this.getQueryOperators()); + this.isFullTextSearchable(), this.isQueryOrderable(), this.getAvailableQueryOperators()); } /** @@ -188,7 +215,7 @@ class JcrPropertyDefinition extends JcrItemDefinition implements PropertyDefinit return new JcrPropertyDefinition(context, this.declaringNodeType, this.name, this.getOnParentVersion(), this.isAutoCreated(), this.isMandatory(), this.isProtected(), this.getDefaultValues(), this.getRequiredType(), this.getValueConstraints(), this.isMultiple(), - this.isFullTextSearchable(), this.isQueryOrderable(), this.getQueryOperators()); + this.isFullTextSearchable(), this.isQueryOrderable(), this.getAvailableQueryOperators()); } /** @@ -923,10 +950,4 @@ class JcrPropertyDefinition extends JcrItemDefinition implements PropertyDefinit } } - @Override - public String[] getAvailableQueryOperators() { - // TODO Auto-generated method stub - return null; - } - } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/NodeTypeSchemata.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/NodeTypeSchemata.java (revision 2609) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/NodeTypeSchemata.java (working copy) @@ -25,6 +25,8 @@ package org.modeshape.jcr; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -47,10 +49,12 @@ import org.modeshape.graph.property.NamespaceRegistry; import org.modeshape.graph.property.NamespaceRegistry.Namespace; import org.modeshape.graph.property.basic.LocalNamespaceRegistry; import org.modeshape.graph.query.model.AllNodes; +import org.modeshape.graph.query.model.Operator; import org.modeshape.graph.query.model.SelectorName; import org.modeshape.graph.query.model.TypeSystem; import org.modeshape.graph.query.validate.ImmutableSchemata; import org.modeshape.graph.query.validate.Schemata; +import org.modeshape.jcr.api.query.qom.QueryObjectModelConstants; import org.modeshape.search.lucene.IndexRules; import org.modeshape.search.lucene.LuceneSearchEngine; import com.google.common.collect.LinkedHashMultimap; @@ -66,6 +70,19 @@ class NodeTypeSchemata implements Schemata { protected static final boolean DEFAULT_CAN_CONTAIN_REFERENCES = true; protected static final boolean DEFAULT_FULL_TEXT_SEARCHABLE = true; + protected static final Map OPERATORS_BY_JCR_NAME; + + static { + Map map = new HashMap(); + map.put(QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO, Operator.EQUAL_TO); + map.put(QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN, Operator.GREATER_THAN); + map.put(QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO, Operator.GREATER_THAN_OR_EQUAL_TO); + map.put(QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN, Operator.LESS_THAN); + map.put(QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO, Operator.LESS_THAN_OR_EQUAL_TO); + map.put(QueryObjectModelConstants.JCR_OPERATOR_LIKE, Operator.LIKE); + map.put(QueryObjectModelConstants.JCR_OPERATOR_NOT_EQUAL_TO, Operator.NOT_EQUAL_TO); + OPERATORS_BY_JCR_NAME = Collections.unmodifiableMap(map); + } private final Schemata schemata; private final Map types; @@ -221,7 +238,9 @@ class NodeTypeSchemata implements Schemata { boolean fullTextSearchable = fullTextSearchableNames.contains(columnName) || defn.isFullTextSearchable(); if (fullTextSearchable) fullTextSearchableNames.add(columnName); // Add (or overwrite) the column ... - builder.addColumn(tableName, columnName, type, fullTextSearchable); + boolean orderable = defn.isQueryOrderable(); + Set operators = operatorsFor(defn); + builder.addColumn(tableName, columnName, type, fullTextSearchable, orderable, operators); // And build an indexing rule for this type ... if (indexRuleBuilder != null) addIndexRule(indexRuleBuilder, @@ -249,7 +268,9 @@ class NodeTypeSchemata implements Schemata { type = typeSystem.getCompatibleType(previousType, type); } // Add (or overwrite) the column ... - builder.addColumn(tableName, columnName, type, fullTextSearchable); + boolean orderable = defn.isQueryOrderable(); + Set operators = operatorsFor(defn); + builder.addColumn(tableName, columnName, type, fullTextSearchable, orderable, operators); if (!includePseudoColumnsInSelectStar) { builder.excludeFromSelectStar(tableName, columnName); } @@ -265,6 +286,19 @@ class NodeTypeSchemata implements Schemata { } } + protected Set operatorsFor( JcrPropertyDefinition defn ) { + String[] ops = defn.getAvailableQueryOperators(); + if (ops == null || ops.length == 0) return EnumSet.allOf(Operator.class); + Set result = new HashSet(); + for (String symbol : ops) { + Operator op = OPERATORS_BY_JCR_NAME.get(symbol); + if (op == null) op = Operator.forSymbol(symbol); + assert op != null; + result.add(op); + } + return result; + } + /** * Add an index rule for the given property definition and the type in the {@link TypeSystem}. * @@ -351,6 +385,10 @@ class NodeTypeSchemata implements Schemata { if (first) first = false; else viewDefinition.append(','); viewDefinition.append('[').append(columnName).append(']'); + if (!defn.isQueryOrderable()) { + builder.markOrderable(tableName, columnName, false); + } + builder.markOperators(tableName, columnName, operatorsFor(defn)); } // Add the pseudo-properties ... for (JcrPropertyDefinition defn : pseudoProperties) { @@ -359,6 +397,7 @@ class NodeTypeSchemata implements Schemata { if (first) first = false; else viewDefinition.append(','); viewDefinition.append('[').append(columnName).append(']'); + builder.markOperators(tableName, columnName, operatorsFor(defn)); } if (first) { // All the properties were skipped ... Index: modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryNodeTypeManager.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryNodeTypeManager.java (revision 2609) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryNodeTypeManager.java (working copy) @@ -1744,7 +1744,6 @@ class RepositoryNodeTypeManager implements JcrSystemObserver { String[] valueConstraints = propDefn.getValueConstraints(); String[] queryOperators = propDefn.getAvailableQueryOperators(); if (valueConstraints == null) valueConstraints = new String[0]; - if (queryOperators == null) queryOperators = new String[0]; return new JcrPropertyDefinition(this.context, null, propertyName, onParentVersionBehavior, autoCreated, mandatory, isProtected, defaultValues, requiredType, valueConstraints, multiple, fullTextSearchable, queryOrderable, queryOperators); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java (revision 2609) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java (working copy) @@ -429,6 +429,34 @@ public class JcrQueryManagerTest { assertRow(result, 10).has("car:model", "DB9").and("car:msrp", "$171,600").and("car:mpgCity", 12); } + @FixFor( "MODE-1057" ) + @Test + public void shouldAllowEqualityCriteriaOnPropertyDefinedWithNumericPropertyDefinition() throws RepositoryException { + Query query = session.getWorkspace() + .getQueryManager() + .createQuery("SELECT [car:maker], [car:model], [car:year], [car:userRating] FROM [car:Car] AS car WHERE [car:userRating] = 4", + Query.JCR_SQL2); + assertThat(query, is(notNullValue())); + QueryResult result = query.execute(); + assertThat(result, is(notNullValue())); + assertResults(query, result, 4L); + assertResultsHaveColumns(result, "car:maker", "car:model", "car:year", "car:userRating"); + } + + @FixFor( "MODE-1057" ) + @Test + public void shouldAllowLikeCriteriaOnPropertyDefinedWithNumericPropertyDefinition() throws RepositoryException { + Query query = session.getWorkspace() + .getQueryManager() + .createQuery("SELECT [car:maker], [car:model], [car:year], [car:userRating] FROM [car:Car] AS car WHERE [car:userRating] LIKE 4", + Query.JCR_SQL2); + assertThat(query, is(notNullValue())); + QueryResult result = query.execute(); + assertThat(result, is(notNullValue())); + assertResults(query, result, 4L); + assertResultsHaveColumns(result, "car:maker", "car:model", "car:year", "car:userRating"); + } + @Test public void shouldBeAbleToCreateAndExecuteJcrSql2QueryToFindAllCarsUnderHybrid() throws RepositoryException { Query query = session.getWorkspace() Index: modeshape-jcr/src/test/java/org/modeshape/jcr/NodeTypeAssertion.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/NodeTypeAssertion.java (revision 2609) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/NodeTypeAssertion.java (working copy) @@ -11,6 +11,7 @@ import javax.jcr.nodetype.NodeTypeDefinition; import javax.jcr.nodetype.NodeTypeTemplate; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.nodetype.PropertyDefinitionTemplate; +import org.modeshape.jcr.api.query.qom.QueryObjectModelConstants; /** * Helper class to compare various manifestions of node type definitions. Ideally, this should be converted to a Hamcrest matcher @@ -81,7 +82,8 @@ public class NodeTypeAssertion { String ntName = childNodeTemplate.getName() == null ? JcrNodeType.RESIDUAL_ITEM_NAME : childNodeTemplate.getName(); if (nd.getName().equals(ntName) && nd.allowsSameNameSiblings() == childNodeTemplate.allowsSameNameSiblings()) { boolean onlyBaseTypeRequired = nd.getRequiredPrimaryTypes().length == 0 - || (nd.getRequiredPrimaryTypes().length == 1 && nd.getRequiredPrimaryTypes()[0].getName().equals("nt:base")); + || (nd.getRequiredPrimaryTypes().length == 1 && nd.getRequiredPrimaryTypes()[0].getName() + .equals("nt:base")); boolean onlyHasBase = childNodeTemplate.getRequiredPrimaryTypeNames().length == 0 || (childNodeTemplate.getRequiredPrimaryTypeNames().length == 1 && childNodeTemplate.getRequiredPrimaryTypeNames()[0].equals("nt:base")); @@ -117,9 +119,8 @@ public class NodeTypeAssertion { } } - private static void comparePropertyTemplateToPropertyDefinition( JcrPropertyDefinitionTemplate template, - JcrPropertyDefinition definition ) { + JcrPropertyDefinition definition ) { assertThat(definition, is(notNullValue())); assertThat(definition.getDeclaringNodeType(), is(notNullValue())); @@ -136,11 +137,19 @@ public class NodeTypeAssertion { assertThat(template.isFullTextSearchable(), is(definition.isFullTextSearchable())); assertThat(template.isQueryOrderable(), is(definition.isQueryOrderable())); - assertThat(template.getAvailableQueryOperators(), is(definition.getAvailableQueryOperators())); + String[] tempOps = template.getAvailableQueryOperators(); + if (tempOps == null) { + tempOps = new String[] {QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO, + QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN, + QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO, + QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN, QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO, + QueryObjectModelConstants.JCR_OPERATOR_LIKE, QueryObjectModelConstants.JCR_OPERATOR_NOT_EQUAL_TO}; + } + assertThat(tempOps, is(definition.getAvailableQueryOperators())); } private static void compareNodeTemplateToNodeDefinition( JcrNodeDefinitionTemplate template, - JcrNodeDefinition definition ) { + JcrNodeDefinition definition ) { assertThat(definition, is(notNullValue())); assertThat(definition.getDeclaringNodeType(), is(notNullValue())); // Had to match on name to even get to the definition