Index: modeshape-graph/src/main/java/org/modeshape/graph/query/parse/SqlQueryParser.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/parse/SqlQueryParser.java (revision 1779) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/parse/SqlQueryParser.java (working copy) @@ -587,15 +587,15 @@ public class SqlQueryParser implements QueryParser { List columns = new ArrayList(); do { Position position = tokens.nextPosition(); - String propertyName = removeBracketsAndQuotes(tokens.consume()); + String propertyName = parseName(tokens, typeSystem); SelectorName selectorName = null; if (tokens.canConsume('.')) { // We actually read the selector name, so now read the property name ... selectorName = new SelectorName(propertyName); - propertyName = removeBracketsAndQuotes(tokens.consume()); + propertyName = parseName(tokens, typeSystem); } String alias = propertyName; - if (tokens.canConsume("AS")) alias = removeBracketsAndQuotes(tokens.consume()); + if (tokens.canConsume("AS")) alias = parseName(tokens, typeSystem); columns.add(new ColumnExpression(selectorName, propertyName, alias, position)); } while (tokens.canConsume(',')); return columns; @@ -605,7 +605,7 @@ public class SqlQueryParser implements QueryParser { TypeSystem typeSystem ) { Source source = null; tokens.consume("FROM"); - source = parseNamedSelector(tokens); + source = parseNamedSelector(tokens, typeSystem); while (tokens.hasNext()) { JoinType joinType = null; if (tokens.canConsume("JOIN") || tokens.canConsume("INNER", "JOIN")) { @@ -622,7 +622,7 @@ public class SqlQueryParser implements QueryParser { } if (joinType == null) break; // Read the name of the selector on the right side of the join ... - NamedSelector right = parseNamedSelector(tokens); + NamedSelector right = parseNamedSelector(tokens, typeSystem); // Read the join condition ... JoinCondition joinCondition = parseJoinCondition(tokens, typeSystem); // Create the join ... @@ -635,9 +635,9 @@ public class SqlQueryParser implements QueryParser { TypeSystem typeSystem ) { tokens.consume("ON"); if (tokens.canConsume("ISSAMENODE", "(")) { - SelectorName selector1Name = parseSelectorName(tokens); + SelectorName selector1Name = parseSelectorName(tokens, typeSystem); tokens.consume(','); - SelectorName selector2Name = parseSelectorName(tokens); + SelectorName selector2Name = parseSelectorName(tokens, typeSystem); if (tokens.canConsume('.')) { String path = parsePath(tokens, typeSystem); tokens.consume(')'); @@ -647,24 +647,24 @@ public class SqlQueryParser implements QueryParser { return new SameNodeJoinCondition(selector1Name, selector2Name); } if (tokens.canConsume("ISCHILDNODE", "(")) { - SelectorName child = parseSelectorName(tokens); + SelectorName child = parseSelectorName(tokens, typeSystem); tokens.consume(','); - SelectorName parent = parseSelectorName(tokens); + SelectorName parent = parseSelectorName(tokens, typeSystem); tokens.consume(')'); return new ChildNodeJoinCondition(parent, child); } if (tokens.canConsume("ISDESCENDANTNODE", "(")) { - SelectorName descendant = parseSelectorName(tokens); + SelectorName descendant = parseSelectorName(tokens, typeSystem); tokens.consume(','); - SelectorName ancestor = parseSelectorName(tokens); + SelectorName ancestor = parseSelectorName(tokens, typeSystem); tokens.consume(')'); return new DescendantNodeJoinCondition(ancestor, descendant); } - SelectorName selector1 = parseSelectorName(tokens); + SelectorName selector1 = parseSelectorName(tokens, typeSystem); tokens.consume('.'); String property1 = parseName(tokens, typeSystem); tokens.consume('='); - SelectorName selector2 = parseSelectorName(tokens); + SelectorName selector2 = parseSelectorName(tokens, typeSystem); tokens.consume('.'); String property2 = parseName(tokens, typeSystem); return new EquiJoinCondition(selector1, property1, selector2, property2); @@ -725,7 +725,7 @@ public class SqlQueryParser implements QueryParser { } selectorName = ((Selector)source).getName(); } else { - selectorName = parseSelectorName(tokens); + selectorName = parseSelectorName(tokens, typeSystem); tokens.consume(','); } String path = parsePath(tokens, typeSystem); @@ -740,7 +740,7 @@ public class SqlQueryParser implements QueryParser { } selectorName = ((Selector)source).getName(); } else { - selectorName = parseSelectorName(tokens); + selectorName = parseSelectorName(tokens, typeSystem); tokens.consume(','); } String path = parsePath(tokens, typeSystem); @@ -755,7 +755,7 @@ public class SqlQueryParser implements QueryParser { } selectorName = ((Selector)source).getName(); } else { - selectorName = parseSelectorName(tokens); + selectorName = parseSelectorName(tokens, typeSystem); tokens.consume(','); } String path = parsePath(tokens, typeSystem); @@ -1081,7 +1081,7 @@ public class SqlQueryParser implements QueryParser { String msg = GraphI18n.functionIsAmbiguous.text("NAME()", pos.getLine(), pos.getColumn()); throw new ParsingException(pos, msg); } - result = new NodeName(parseSelectorName(tokens)); + result = new NodeName(parseSelectorName(tokens, typeSystem)); tokens.consume(")"); } else if (tokens.canConsume("LOCALNAME", "(")) { if (tokens.canConsume(")")) { @@ -1091,7 +1091,7 @@ public class SqlQueryParser implements QueryParser { String msg = GraphI18n.functionIsAmbiguous.text("LOCALNAME()", pos.getLine(), pos.getColumn()); throw new ParsingException(pos, msg); } - result = new NodeLocalName(parseSelectorName(tokens)); + result = new NodeLocalName(parseSelectorName(tokens, typeSystem)); tokens.consume(")"); } else if (tokens.canConsume("SCORE", "(")) { if (tokens.canConsume(")")) { @@ -1101,7 +1101,7 @@ public class SqlQueryParser implements QueryParser { String msg = GraphI18n.functionIsAmbiguous.text("SCORE()", pos.getLine(), pos.getColumn()); throw new ParsingException(pos, msg); } - result = new FullTextSearchScore(parseSelectorName(tokens)); + result = new FullTextSearchScore(parseSelectorName(tokens, typeSystem)); tokens.consume(")"); } else if (tokens.canConsume("DEPTH", "(")) { if (tokens.canConsume(")")) { @@ -1111,7 +1111,7 @@ public class SqlQueryParser implements QueryParser { String msg = GraphI18n.functionIsAmbiguous.text("DEPTH()", pos.getLine(), pos.getColumn()); throw new ParsingException(pos, msg); } - result = new NodeDepth(parseSelectorName(tokens)); + result = new NodeDepth(parseSelectorName(tokens, typeSystem)); tokens.consume(")"); } else if (tokens.canConsume("PATH", "(")) { if (tokens.canConsume(")")) { @@ -1121,7 +1121,7 @@ public class SqlQueryParser implements QueryParser { String msg = GraphI18n.functionIsAmbiguous.text("PATH()", pos.getLine(), pos.getColumn()); throw new ParsingException(pos, msg); } - result = new NodePath(parseSelectorName(tokens)); + result = new NodePath(parseSelectorName(tokens, typeSystem)); tokens.consume(")"); } else if (tokens.canConsume("REFERENCE", "(")) { result = parseReferenceValue(tokens, typeSystem, source); @@ -1173,7 +1173,7 @@ public class SqlQueryParser implements QueryParser { TypeSystem typeSystem, Source source ) { Position pos = tokens.nextPosition(); - String firstWord = removeBracketsAndQuotes(tokens.consume()); + String firstWord = parseName(tokens, typeSystem); SelectorName selectorName = null; if (tokens.canConsume('.')) { // We actually read the selector name, so now read the property name ... @@ -1205,7 +1205,7 @@ public class SqlQueryParser implements QueryParser { throw new ParsingException(pos, msg); } // Otherwise, there is at least one word inside the parentheses ... - String firstWord = removeBracketsAndQuotes(tokens.consume()); + String firstWord = parseName(tokens, typeSystem); if (tokens.canConsume('.')) { // We actually read the selector name, so now read the property name ... selectorName = new SelectorName(firstWord); @@ -1281,15 +1281,17 @@ public class SqlQueryParser implements QueryParser { return text; } - protected NamedSelector parseNamedSelector( TokenStream tokens ) { - SelectorName name = parseSelectorName(tokens); + protected NamedSelector parseNamedSelector( TokenStream tokens, + TypeSystem typeSystem ) { + SelectorName name = parseSelectorName(tokens, typeSystem); SelectorName alias = null; - if (tokens.canConsume("AS")) alias = parseSelectorName(tokens); + if (tokens.canConsume("AS")) alias = parseSelectorName(tokens, typeSystem); return new NamedSelector(name, alias); } - protected SelectorName parseSelectorName( TokenStream tokens ) { - return new SelectorName(removeBracketsAndQuotes(tokens.consume())); + protected SelectorName parseSelectorName( TokenStream tokens, + TypeSystem typeSystem ) { + return new SelectorName(parseName(tokens, typeSystem)); } protected String parsePath( TokenStream tokens, Index: modeshape-jcr/src/main/java/org/modeshape/jcr/sql/JcrSqlQueryParser.java new file mode 100644 =================================================================== --- /dev/null (revision 1779) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/sql/JcrSqlQueryParser.java (working copy) @@ -0,0 +1,499 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.jcr.sql; + +import org.modeshape.common.text.ParsingException; +import org.modeshape.common.text.Position; +import org.modeshape.common.text.TokenStream; +import org.modeshape.graph.GraphI18n; +import org.modeshape.graph.property.ValueFormatException; +import org.modeshape.graph.query.model.Comparison; +import org.modeshape.graph.query.model.Constraint; +import org.modeshape.graph.query.model.DynamicOperand; +import org.modeshape.graph.query.model.FullTextSearchScore; +import org.modeshape.graph.query.model.NodePath; +import org.modeshape.graph.query.model.PropertyValue; +import org.modeshape.graph.query.model.Source; +import org.modeshape.graph.query.model.TypeSystem; +import org.modeshape.graph.query.model.TypeSystem.TypeFactory; +import org.modeshape.graph.query.parse.FullTextSearchParser; +import org.modeshape.graph.query.parse.SqlQueryParser; + +/** + * Parser for JCR-SQL queries that produces {@link org.modeshape.graph.query.model abstract query model (AQM)} objects. + *

+ *

JCR-SQL grammar

+ *

+ * This section defines the complete grammar for the JCR-SQL dialect supported by this parser, as defined by the + * JCR 1.0.1 specification. This parser actually extends the {@link SqlQueryParser (extended) JCR-SQL2 parser}, + * and thus allows many of the JCR-SQL2 standard and extended features, although there are several key differences: + *

    + *
  1. Names are not enclosed by square brackets.
  2. + *
  3. Criteria on scores use
    jcr:score
    as a pseudo-column.
  4. + *
  5. Criteria on path use
    jcr:path
    as a pseudo-column.
  6. + *
  7. Joins are specified with comma-separated table names in the FROM clause and join criteria in the WHERE clause.
  8. + *
+ *

+ *

Queries

+ * + *
+ * QueryCommand ::= Query | SetQuery
+ * 
+ * SetQuery ::= Query ('UNION'|'INTERSECT'|'EXCEPT') [ALL] Query
+ *                  { ('UNION'|'INTERSECT'|'EXCEPT') [ALL] Query }
+ * 
+ * Query ::= 'SELECT' ['DISINCT'] columns
+ *           'FROM' Source
+ *           ['WHERE' Constraint]
+ *           ['ORDER BY' orderings]
+ *           [Limit]
+ * 
+ * + *

Sources

+ * + *
+ * Source ::= Selector | Join
+ * 
+ * Selector ::= nodeTypeName ['AS' selectorName]
+ * 
+ * nodeTypeName ::= Name
+ * 
+ * + *

Joins

+ * + *
+ * Join ::= left [JoinType] 'JOIN' right 'ON' JoinCondition
+ *          // If JoinType is omitted INNER is assumed.
+ *          
+ * left ::= Source
+ * right ::= Source
+ * 
+ * JoinType ::= Inner | LeftOuter | RightOuter | FullOuter | Cross
+ * 
+ * Inner ::= 'INNER' ['JOIN']
+ * 
+ * LeftOuter ::= 'LEFT JOIN' | 'OUTER JOIN' | 'LEFT OUTER JOIN'
+ * 
+ * RightOuter ::= 'RIGHT OUTER' ['JOIN']
+ * 
+ * RightOuter ::= 'FULL OUTER' ['JOIN']
+ * 
+ * RightOuter ::= 'CROSS' ['JOIN']
+ * 
+ * JoinCondition ::= EquiJoinCondition | SameNodeJoinCondition | ChildNodeJoinCondition | DescendantNodeJoinCondition
+ * 
+ * + *
Equi-join conditions
+ * + *
+ * EquiJoinCondition ::= selector1Name'.'property1Name '=' selector2Name'.'property2Name
+ * 
+ * selector1Name ::= selectorName
+ * selector2Name ::= selectorName
+ * property1Name ::= propertyName
+ * property2Name ::= propertyName
+ * 
+ * + *
Same-node join condition
+ * + *
+ * SameNodeJoinCondition ::= 'ISSAMENODE(' selector1Name ',' selector2Name [',' selector2Path] ')'
+ * 
+ * selector2Path ::= Path
+ * 
+ * + *
Child-node join condition
+ * + *
+ * ChildNodeJoinCondition ::= 'ISCHILDNODE(' childSelectorName ',' parentSelectorName ')'
+ * 
+ * childSelectorName ::= selectorName
+ * parentSelectorName ::= selectorName
+ * 
+ * + *
Descendant-node join condition
+ * + *
+ * DescendantNodeJoinCondition ::= 'ISDESCENDANTNODE(' descendantSelectorName ',' ancestorSelectorName ')'
+ * descendantSelectorName ::= selectorName
+ * ancestorSelectorName ::= selectorName
+ * 
+ * + *

Constraints

+ * + *
+ * Constraint ::= ConstraintItem | '(' ConstraintItem ')'
+ * 
+ * ConstraintItem ::= And | Or | Not | Comparison | Between | PropertyExistence | SetConstraint | FullTextSearch | 
+ *                    SameNode | ChildNode | DescendantNode
+ * 
+ * + *
And constraint
+ * + *
+ * And ::= constraint1 'AND' constraint2
+ * 
+ * constraint1 ::= Constraint
+ * constraint2 ::= Constraint
+ * 
+ * + *
Or constraint
+ * + *
+ * Or ::= constraint1 'OR' constraint2
+ * 
+ * + *
Not constraint
+ * + *
+ * Not ::= 'NOT' Constraint
+ * 
+ * + *
Comparison constraint
+ * + *
+ * Comparison ::= DynamicOperand Operator StaticOperand
+ * 
+ * Operator ::= '=' | '!=' | '<' | '<=' | '>' | '>=' | 'LIKE'
+ * 
+ * + *
Between constraint
+ * + *
+ * Between ::= DynamicOperand ['NOT'] 'BETWEEN' lowerBound ['EXCLUSIVE'] 'AND' upperBound ['EXCLUSIVE']
+ * 
+ * lowerBound ::= StaticOperand
+ * upperBound ::= StaticOperand
+ * 
+ * + *
Property existence constraint
+ * + *
+ * PropertyExistence ::= selectorName'.'propertyName 'IS' ['NOT'] 'NULL' | 
+ *                       propertyName 'IS' ['NOT'] 'NULL' /* If only one selector exists in this query */
+ * 
+ * 
+ * + *
Set constraint
+ * + *
+ * SetConstraint ::= selectorName'.'propertyName ['NOT'] 'IN' | 
+ *                       propertyName ['NOT'] 'IN' /* If only one selector exists in this query */
+ *                       '(' firstStaticOperand {',' additionalStaticOperand } ')'
+ * firstStaticOperand ::= StaticOperand
+ * additionalStaticOperand ::= StaticOperand
+ * 
+ * + *
Full-text search constraint
+ * + *
+ * FullTextSearch ::= 'CONTAINS(' ([selectorName'.']propertyName | selectorName'.*') 
+ *                            ',' ''' fullTextSearchExpression''' ')'
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       preceding the propertyName is optional */
+ * fullTextSearchExpression ::= /* a full-text search expression, see {@link FullTextSearchParser} */
+ * 
+ * + *
Same-node constraint
+ * + *
+ * SameNode ::= 'ISSAMENODE(' [selectorName ','] Path ')' 
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       preceding the propertyName is optional */
+ * 
+ * + *
Child-node constraint
+ * + *
+ * ChildNode ::= 'ISCHILDNODE(' [selectorName ','] Path ')' 
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       preceding the propertyName is optional */
+ * 
+ * + *
Descendant-node constraint
+ * + *
+ * DescendantNode ::= 'ISDESCENDANTNODE(' [selectorName ','] Path ')' 
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       preceding the propertyName is optional */
+ * 
+ * + *
Paths and names
+ * + *
+ * 
+ * Name ::= '[' quotedName ']' | '[' simpleName ']' | simpleName
+ * 
+ * quotedName ::= /* A JCR Name (see the JCR specification) */
+ * simpleName ::= /* A JCR Name that contains only SQL-legal characters (namely letters, digits, and underscore) */
+ *
+ * Path ::= '[' quotedPath ']' | '[' simplePath ']' | simplePath
+ *
+ * quotedPath ::= /* A JCR Path that contains non-SQL-legal characters */
+ * simplePath ::= /* A JCR Path (rather Name) that contains only SQL-legal characters (namely letters, digits, and underscore) */
+ * 
+ * + *

Static operands

+ * + *
+ * StaticOperand ::= Literal | BindVariableValue
+ * 
+ * + *
Literal
+ * + *
+ * Literal ::= CastLiteral | UncastLiteral
+ * 
+ * CastLiteral ::= 'CAST(' UncastLiteral ' AS ' PropertyType ')'
+ * 
+ * PropertyType ::= 'STRING' | 'BINARY' | 'DATE' | 'LONG' | 'DOUBLE' | 'DECIMAL' | 'BOOLEAN' | 'NAME' | 'PATH' | 
+ *                  'REFERENCE' | 'WEAKREFERENCE' | 'URI'
+ *                  /* 'WEAKREFERENCE' is not currently supported in JCR 1.0 */
+ *                  
+ * UncastLiteral ::= UnquotedLiteral | ''' UnquotedLiteral ''' | '"' UnquotedLiteral '"'
+ * 
+ * UnquotedLiteral ::= /* String form of a JCR Value, as defined in the JCR specification */
+ * 
+ * + *
Bind variables
+ * + *
+ * BindVariableValue ::= '$'bindVariableName
+ * 
+ * bindVariableName ::= /* A string that conforms to the JCR Name syntax, though the prefix does not need to be
+ *                         a registered namespace prefix. */
+ * 
+ * + *

Dynamic operands

+ * + *
+ * DynamicOperand ::= PropertyValue | ReferenceValue | Length | NodeName | NodeLocalName | NodePath | NodeDepth | 
+ *                    FullTextSearchScore | LowerCase | UpperCase | Arithmetic |
+ *                    '(' DynamicOperand ')'
+ * 
+ *
Property value
+ *
+ * PropertyValue ::= [selectorName'.'] propertyName
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       preceding the propertyName is optional */
+ * 
+ *
Reference value
+ *
+ * ReferenceValue ::= 'REFERENCE(' selectorName '.' propertyName ')' |
+ *                    'REFERENCE(' selectorName ')' |
+ *                    'REFERENCE()' |
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       preceding the propertyName is optional. Also, the property name may be excluded 
+ *                       if the constraint should apply to any reference property. */
+ * 
+ *
Property length
+ *
+ * Length ::= 'LENGTH(' PropertyValue ')'
+ * 
+ *
Node name
+ *
+ * NodeName ::= 'NAME(' [selectorName] ')'
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       is optional */
+ * 
+ *
Node local name
+ *
+ * NodeLocalName ::= 'LOCALNAME(' [selectorName] ')'
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       is optional */
+ * 
+ *
Node path
+ *
+ * NodePath ::= 'PATH(' [selectorName] ')'
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       is optional */
+ * 
+ *
Node depth
+ *
+ * NodeDepth ::= 'DEPTH(' [selectorName] ')'
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       is optional */
+ * 
+ *
Full-text search score
+ *
+ * FullTextSearchScore ::= 'SCORE(' [selectorName] ')'
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       is optional */
+ * 
+ *
Lowercase
+ *
+ * LowerCase ::= 'LOWER(' DynamicOperand ')'
+ * 
+ *
Uppercase
+ *
+ * UpperCase ::= 'UPPER(' DynamicOperand ')'
+ * 
+ *
Arithmetic
+ *
+ * Arithmetic ::= DynamicOperand ('+'|'-'|'*'|'/') DynamicOperand
+ * 
+ * + *

Ordering

+ * + *
+ * orderings ::= Ordering {',' Ordering}
+ * 
+ * Ordering ::= DynamicOperand [Order]
+ * 
+ * Order ::= 'ASC' | 'DESC'
+ * 
+ * + *

Columns

+ * + *
+ * columns ::= (Column ',' {Column}) | '*'
+ * 
+ * Column ::= ([selectorName'.']propertyName ['AS' columnName]) | (selectorName'.*')
+ *                    /* If only one selector exists in this query, explicit specification of the selectorName
+ *                       preceding the propertyName is optional */
+ * selectorName ::= Name
+ * propertyName ::= Name
+ * columnName ::= Name
+ * 
+ * + *

Limit

+ * + *
+ * Limit ::= 'LIMIT' count [ 'OFFSET' offset ]
+ * count ::= /* Positive integer value */
+ * offset ::= /* Non-negative integer value */
+ * 
+ */ +public class JcrSqlQueryParser extends SqlQueryParser { + + /** + * + */ + public JcrSqlQueryParser() { + } + + /** + * Parse a constraint clause. This method inherits all of the functionality from JCR-SQL2, except that JCR-SQL allows + * constraints that use "jcr:path" and "jcr:score" pseudo-columns. In these special cases, the + * resulting {@link Comparison comparison} will have a {@link NodePath} or {@link FullTextSearchScore} dynamic operand. + * + * @see org.modeshape.graph.query.parse.SqlQueryParser#parseConstraint(org.modeshape.common.text.TokenStream, + * org.modeshape.graph.query.model.TypeSystem, org.modeshape.graph.query.model.Source) + */ + @Override + protected Constraint parseConstraint( TokenStream tokens, + TypeSystem typeSystem, + Source source ) { + Constraint constraint = super.parseConstraint(tokens, typeSystem, source); + if (constraint instanceof Comparison) { + Comparison comparison = (Comparison)constraint; + DynamicOperand left = comparison.getOperand1(); + if (left instanceof PropertyValue) { + PropertyValue propValue = (PropertyValue)left; + if ("jcr:path".equals(propValue.getPropertyName())) { + // Rewrite this constraint as a PATH criteria ... + NodePath path = new NodePath(propValue.getSelectorName()); + return new Comparison(path, comparison.getOperator(), comparison.getOperand2()); + } + if ("jcr:score".equals(propValue.getPropertyName())) { + // Rewrite this constraint as a SCORE criteria ... + FullTextSearchScore score = new FullTextSearchScore(propValue.getSelectorName()); + return new Comparison(score, comparison.getOperator(), comparison.getOperand2()); + } + } + } + + return constraint; + } + + /** + * {@inheritDoc} + *

+ * Parsing behavior is overridden to that JCR-SQL style (unquoted prefixed) names are allowed. This method parses the selector + * name, which may be of the form "unprefixedName" (consisting of a single token) or "prefix:name" + * (consisting of three tokens). + *

+ * + * @see org.modeshape.graph.query.parse.SqlQueryParser#parseName(org.modeshape.common.text.TokenStream, + * org.modeshape.graph.query.model.TypeSystem) + */ + @Override + protected String parseName( TokenStream tokens, + TypeSystem typeSystem ) { + String token1 = tokens.consume(); + token1 = removeBracketsAndQuotes(token1); + if (tokens.canConsume(':')) { + String token2 = tokens.consume(); + token2 = removeBracketsAndQuotes(token2); + return token1 + ':' + token2; + } + return token1; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.query.parse.SqlQueryParser#parseLiteralValue(org.modeshape.common.text.TokenStream, + * org.modeshape.graph.query.model.TypeSystem) + */ + @Override + protected String parseLiteralValue( TokenStream tokens, + TypeSystem typeSystem ) { + if (tokens.canConsume("TIMESTAMP")) { + Position pos = tokens.previousPosition(); + // This should be a timestamp represented as a single-quoted string ... + String value = removeBracketsAndQuotes(tokens.consume()); + TypeFactory dateTimeFactory = typeSystem.getDateTimeFactory(); + try { + // Convert to a date and then back to a string to get canonical form ... + Object dateTime = dateTimeFactory.create(value); + return dateTimeFactory.asString(dateTime); + } catch (ValueFormatException e) { + String msg = GraphI18n.expectingLiteralAndUnableToParseAsDate.text(value, pos.getLine(), pos.getColumn()); + throw new ParsingException(pos, msg); + } + } + return super.parseLiteralValue(tokens, typeSystem); + } + + /** + * Remove any leading and trailing single-quotes. + * + * @param text the input text; may not be null + * @return the text without leading and trailing quotes, or text if there were no quotes + */ + @Override + protected String removeBracketsAndQuotes( String text ) { + if (text.length() > 0) { + char firstChar = text.charAt(0); + switch (firstChar) { + case '\'': + assert text.charAt(text.length() - 1) == firstChar; + return removeBracketsAndQuotes(text.substring(1, text.length() - 1)); + } + } + return text; + } + +}