org.modeshape.jcr.query.parse.JcrSqlQueryParser Maven / Gradle / Ivy
/*
* 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:
*
* - Names are not enclosed by square brackets.
* - Criteria on scores use
jcr:score
as a pseudo-column.
* - Criteria on path use
jcr:path
as a pseudo-column.
* - Joins are specified with comma-separated table names in the FROM clause and join criteria in the WHERE clause.
*
*
* 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