All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.modeshape.jcr.query.parse.JcrSqlQueryParser Maven / Gradle / Ivy

There is a newer version: 5.4.1.Final
Show newest version
/*
 * ModeShape (http://www.modeshape.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.modeshape.jcr.query.parse;

import static org.modeshape.common.text.TokenStream.ANY_VALUE;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.modeshape.common.text.ParsingException;
import org.modeshape.common.text.Position;
import org.modeshape.common.text.TokenStream;
import org.modeshape.jcr.GraphI18n;
import org.modeshape.jcr.JcrI18n;
import org.modeshape.jcr.api.query.qom.Operator;
import org.modeshape.jcr.query.model.And;
import org.modeshape.jcr.query.model.Comparison;
import org.modeshape.jcr.query.model.Constraint;
import org.modeshape.jcr.query.model.DynamicOperand;
import org.modeshape.jcr.query.model.FullTextSearch;
import org.modeshape.jcr.query.model.FullTextSearchScore;
import org.modeshape.jcr.query.model.Join;
import org.modeshape.jcr.query.model.JoinType;
import org.modeshape.jcr.query.model.Literal;
import org.modeshape.jcr.query.model.NamedSelector;
import org.modeshape.jcr.query.model.NodePath;
import org.modeshape.jcr.query.model.Not;
import org.modeshape.jcr.query.model.Or;
import org.modeshape.jcr.query.model.PropertyExistence;
import org.modeshape.jcr.query.model.PropertyValue;
import org.modeshape.jcr.query.model.Query;
import org.modeshape.jcr.query.model.SameNodeJoinCondition;
import org.modeshape.jcr.query.model.Selector;
import org.modeshape.jcr.query.model.SelectorName;
import org.modeshape.jcr.query.model.SetCriteria;
import org.modeshape.jcr.query.model.Source;
import org.modeshape.jcr.query.model.StaticOperand;
import org.modeshape.jcr.query.model.TypeSystem;
import org.modeshape.jcr.query.model.TypeSystem.TypeFactory;
import org.modeshape.jcr.query.model.Visitor;
import org.modeshape.jcr.value.ValueFormatException;

/**
 * Parser for JCR-SQL queries that produces {@link org.modeshape.jcr.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 BasicSqlQueryParser (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'
 *                  
 * 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 BasicSqlQueryParser { @SuppressWarnings( "deprecation" ) public static final String LANGUAGE = javax.jcr.query.Query.SQL; /** * */ public JcrSqlQueryParser() { } /** * {@inheritDoc} * * @see org.modeshape.jcr.query.parse.QueryParser#getLanguage() */ @Override public String getLanguage() { return LANGUAGE; } /** * {@inheritDoc} * * @see org.modeshape.jcr.query.parse.BasicSqlQueryParser#parseQuery(org.modeshape.common.text.TokenStream, * org.modeshape.jcr.query.model.TypeSystem) */ @Override protected Query parseQuery( TokenStream tokens, TypeSystem typeSystem ) { Query query = super.parseQuery(tokens, typeSystem); // See if we have to rewrite the JCR-SQL-style join ... if (query.source() instanceof JoinableSources) { JoinableSources joinableSources = (JoinableSources)query.source(); // Rewrite the joins ... Source newSource = rewrite(joinableSources); query = new Query(newSource, query.constraint(), query.orderings(), query.columns(), query.getLimits(), query.isDistinct()); } return query; } /** * {@inheritDoc} * * @see org.modeshape.jcr.query.parse.BasicSqlQueryParser#parseFrom(org.modeshape.common.text.TokenStream, * org.modeshape.jcr.query.model.TypeSystem) */ @Override protected Source parseFrom( TokenStream tokens, TypeSystem typeSystem ) { Position firstSourcePosition = tokens.nextPosition(); Source source = super.parseFrom(tokens, typeSystem); if (tokens.matches(',') && source instanceof NamedSelector) { NamedSelector selector = (NamedSelector)source; JoinableSources joinedSources = new JoinableSources(selector, firstSourcePosition); while (tokens.canConsume(',')) { // This is a JCR-SQL-style JOIN ... Position nextSourcePosition = tokens.nextPosition(); NamedSelector nextSource = parseNamedSelector(tokens, typeSystem); joinedSources.add(nextSource, nextSourcePosition); } source = joinedSources; } return source; } /** * 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.jcr.query.parse.BasicSqlQueryParser#parseConstraint(org.modeshape.common.text.TokenStream, * org.modeshape.jcr.query.model.TypeSystem, org.modeshape.jcr.query.model.Source) */ @Override protected Constraint parseConstraint( TokenStream tokens, TypeSystem typeSystem, Source source ) { Constraint constraint = null; if (tokens.canConsume("JCR", ":", "PATH")) { // It is a property constraint on "jcr:path" ... SelectorName selector = getSelectorNameFor(source); PropertyValue value = new PropertyValue(selector, "jcr:path"); Operator operator = parseComparisonOperator(tokens); StaticOperand right = parseStaticOperand(tokens, typeSystem); constraint = rewriteConstraint(new Comparison(value, operator, right)); } else if (tokens.matches(ANY_VALUE, "IN")) { // This is a "... 'value' IN prop ..." pattern used in the JCR TCK tests but not in the JCR 1.0.1 specification // ... Literal value = parseLiteral(tokens, typeSystem); tokens.consume("IN"); PropertyValue propertyValue = parsePropertyValue(tokens, typeSystem, source); constraint = new SetCriteria(propertyValue, value); } else if (source instanceof JoinableSources && !(tokens.matches("(") || tokens.matches("NOT") || tokens.matches("CONTAINS", "(") || tokens.matches("ISSAMENODE", "(") || tokens.matches("ISCHILDNODE", "(") || tokens.matches("ISDESCENDANTNODE", "("))) { JoinableSources joinableSources = (JoinableSources)source; // See if this is a join condition ... if (tokens.matches(ANY_VALUE, ":", ANY_VALUE, ".", "JCR", ":", "PATH", "=") || tokens.matches(ANY_VALUE, ".", "JCR", ":", "PATH", "=")) { Position position = tokens.nextPosition(); SelectorName selector1 = parseSelectorName(tokens, typeSystem); tokens.consume('.'); parseName(tokens, typeSystem); // jcr:path tokens.consume('='); SelectorName selector2 = parseSelectorName(tokens, typeSystem); tokens.consume('.'); parseName(tokens, typeSystem); // jcr:path joinableSources.add(new SameNodeJoinCondition(selector1, selector2), position); // AND has higher precedence than OR, so we need to evaluate it first ... while (tokens.canConsume("AND")) { Constraint rhs = parseConstraint(tokens, typeSystem, source); if (rhs != null) constraint = constraint != null ? new And(constraint, rhs) : rhs; } while (tokens.canConsume("OR")) { Constraint rhs = parseConstraint(tokens, typeSystem, source); if (rhs != null) constraint = constraint != null ? new And(constraint, rhs) : rhs; } return constraint; } } if (constraint != null) { // AND has higher precedence than OR, so we need to evaluate it first ... while (tokens.canConsume("AND")) { Constraint rhs = parseConstraint(tokens, typeSystem, source); if (rhs != null) constraint = new And(constraint, rhs); } while (tokens.canConsume("OR")) { Constraint rhs = parseConstraint(tokens, typeSystem, source); if (rhs != null) constraint = new Or(constraint, rhs); } return constraint; } constraint = super.parseConstraint(tokens, typeSystem, source); constraint = rewriteConstraint(constraint); return constraint; } @Override protected Constraint parsePropertyExistance( TokenStream tokens, TypeSystem typeSystem, Source source ) { if (tokens.matches(ANY_VALUE, "IS", "NOT", "NULL") || tokens.matches(ANY_VALUE, "IS", "NULL") || tokens.matches(ANY_VALUE, ".", ANY_VALUE, "IS", "NOT", "NULL") || tokens.matches(ANY_VALUE, ".", ANY_VALUE, ":", ANY_VALUE, "IS", "NOT", "NULL") || tokens.matches(ANY_VALUE, ".", ANY_VALUE, "IS", "NULL") || tokens.matches(ANY_VALUE, ".", ANY_VALUE, ":", ANY_VALUE, "IS", "NULL") || tokens.matches(ANY_VALUE, ":", ANY_VALUE, "IS", "NOT", "NULL") || tokens.matches(ANY_VALUE, ":", ANY_VALUE, ".", ANY_VALUE, "IS", "NOT", "NULL") || tokens.matches(ANY_VALUE, ":", ANY_VALUE, ".", ANY_VALUE, ":", ANY_VALUE, "IS", "NOT", "NULL") || tokens.matches(ANY_VALUE, ":", ANY_VALUE, ".", ANY_VALUE, "IS", "NULL") || tokens.matches(ANY_VALUE, ":", ANY_VALUE, ".", ANY_VALUE, ":", ANY_VALUE, "IS", "NULL")) { Position pos = tokens.nextPosition(); String firstWord = parseName(tokens, typeSystem); SelectorName selectorName = null; String propertyName = null; if (tokens.canConsume('.')) { // We actually read the selector name, so now read the property name ... selectorName = new SelectorName(firstWord); propertyName = parseName(tokens, typeSystem); } else { // Otherwise the source should be a single named selector if (!(source instanceof Selector)) { String msg = GraphI18n.mustBeScopedAtLineAndColumn.text(firstWord, pos.getLine(), pos.getColumn()); throw new ParsingException(pos, msg); } selectorName = ((Selector)source).name(); propertyName = firstWord; } if (tokens.canConsume("IS", "NOT", "NULL")) { return new PropertyExistence(selectorName, propertyName); } tokens.consume("IS", "NULL"); return new Not(new PropertyExistence(selectorName, propertyName)); } return null; } protected SelectorName getSelectorNameFor( Source source ) { // Since JCR-SQL only allows ISSAMENODE join constraints, it doesn't matter which source we select ... if (source instanceof JoinableSources) { return ((JoinableSources)source).getSelectors().values().iterator().next().aliasOrName(); } if (source instanceof Selector) { return ((Selector)source).aliasOrName(); } assert false; return null; } protected Constraint rewriteConstraint( Constraint constraint ) { 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.selectorName()); return new Comparison(path, comparison.operator(), comparison.getOperand2()); } if ("jcr:score".equals(propValue.getPropertyName())) { // Rewrite this constraint as a SCORE criteria ... FullTextSearchScore score = new FullTextSearchScore(propValue.selectorName()); return new Comparison(score, comparison.operator(), comparison.getOperand2()); } } } else if (constraint instanceof FullTextSearch) { FullTextSearch search = (FullTextSearch)constraint; if (".".equals(search.getPropertyName())) { // JCR-SQL's use of CONTAINS allows a '.' to be used to represent the search is to be // performed on all properties of the node(s). However, JCR-SQL2 and our AQM // expect a '*' to be used instead ... return new FullTextSearch(search.selectorName(), search.fullTextSearchExpression()); } } else if (constraint instanceof And) { And and = (And)constraint; constraint = new And(rewriteConstraint(and.left()), rewriteConstraint(and.right())); } else if (constraint instanceof Or) { Or or = (Or)constraint; constraint = new Or(rewriteConstraint(or.left()), rewriteConstraint(or.right())); } 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.jcr.query.parse.BasicSqlQueryParser#parseName(org.modeshape.common.text.TokenStream, * org.modeshape.jcr.query.model.TypeSystem) */ @Override protected String parseName( TokenStream tokens, TypeSystem typeSystem ) { String token1 = tokens.consume(); token1 = removeBracketsAndQuotes(token1, tokens.previousPosition()); if (tokens.canConsume(':')) { String token2 = tokens.consume(); token2 = removeBracketsAndQuotes(token2, tokens.previousPosition()); return token1 + ':' + token2; } return token1; } /** * {@inheritDoc} * * @see org.modeshape.jcr.query.parse.BasicSqlQueryParser#parseLiteralValue(org.modeshape.common.text.TokenStream, * org.modeshape.jcr.query.model.TypeSystem) */ @Override protected Object 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(), tokens.previousPosition()); 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 dateTime; // 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); } @Override protected String removeBracketsAndQuotes( String text, Position position ) { 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), position); } } return text; } protected Source rewrite( JoinableSources joinableSources ) { // Find the order of the joins ... List joins = new LinkedList(); for (SameNodeJoinCondition joinCondition : joinableSources.getJoinConditions()) { SelectorName selector1 = joinCondition.selector1Name(); SelectorName selector2 = joinCondition.selector2Name(); boolean found = false; ListIterator iter = joins.listIterator(); while (iter.hasNext()) { Join next = iter.next(); Join replacement = null; if (usesSelector(next, selector1)) { Source right = joinableSources.getSelectors().get(selector2.name()); replacement = new Join(next, JoinType.INNER, right, joinCondition); } else if (usesSelector(next, selector2)) { Source left = joinableSources.getSelectors().get(selector1.name()); replacement = new Join(left, JoinType.INNER, next, joinCondition); } if (replacement != null) { iter.previous(); iter.remove(); joins.add(replacement); found = true; break; } } if (!found) { // Nothing matched, so add a new join ... Source left = joinableSources.getSelectors().get(selector1.name()); Source right = joinableSources.getSelectors().get(selector2.name()); if (left == null) { Position pos = joinableSources.getJoinCriteriaPosition(); String msg = JcrI18n.selectorUsedInEquiJoinCriteriaDoesNotExistInQuery.text(selector1.name(), pos.getLine(), pos.getColumn()); throw new ParsingException(pos, msg); } if (right == null) { Position pos = joinableSources.getJoinCriteriaPosition(); String msg = JcrI18n.selectorUsedInEquiJoinCriteriaDoesNotExistInQuery.text(selector2.name(), pos.getLine(), pos.getColumn()); throw new ParsingException(pos, msg); } joins.add(new Join(left, JoinType.INNER, right, joinCondition)); } } if (joins.size() == 1) { return joins.get(0); } // Otherwise the join conditions were not sufficient return null; } protected boolean usesSelector( Join join, SelectorName selector ) { Source left = join.getLeft(); if (left instanceof Selector && selector.equals(((Selector)left).aliasOrName())) return true; if (left instanceof Join && usesSelector((Join)left, selector)) return true; Source right = join.getRight(); if (right instanceof Selector && selector.equals(((Selector)right).aliasOrName())) return true; if (right instanceof Join && usesSelector((Join)right, selector)) return true; return false; } protected static class JoinableSources implements Source { private static final long serialVersionUID = 1L; private transient Map selectors = new LinkedHashMap(); private transient List joinConditions = new ArrayList(); private transient List selectorPositions = new ArrayList(); private transient Position joinCriteriaPosition; protected JoinableSources( Selector firstSelector, Position position ) { add(firstSelector, position); } public void add( Selector selector, Position position ) { selectors.put(selector.aliasOrName().name(), selector); selectorPositions.add(position); } public void add( SameNodeJoinCondition joinCondition, Position position ) { joinConditions.add(joinCondition); joinCriteriaPosition = position; } public Iterable selectorNames() { return selectors.keySet(); } /** * @return joinConditions */ public List getJoinConditions() { return joinConditions; } /** * @return selectors */ public Map getSelectors() { return selectors; } /** * @return joinCriteriaPosition */ public Position getJoinCriteriaPosition() { return joinCriteriaPosition; } public Position getPositionForSelector( String selector ) { int index = 0; for (Map.Entry entry : selectors.entrySet()) { if (entry.getKey().equals(selector)) return selectorPositions.get(index); ++index; } return null; } @Override public void accept( Visitor visitor ) { } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy