org.modeshape.jcr.query.parse.BasicSqlQueryParser 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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.jcr.RepositoryException;
import org.modeshape.common.CommonI18n;
import org.modeshape.common.text.ParsingException;
import org.modeshape.common.text.Position;
import org.modeshape.common.text.TokenStream;
import org.modeshape.common.text.TokenStream.CharacterStream;
import org.modeshape.common.text.TokenStream.Tokenizer;
import org.modeshape.common.text.TokenStream.Tokens;
import org.modeshape.common.xml.XmlCharacters;
import org.modeshape.jcr.GraphI18n;
import org.modeshape.jcr.api.query.qom.Operator;
import org.modeshape.jcr.query.model.And;
import org.modeshape.jcr.query.model.ArithmeticOperand;
import org.modeshape.jcr.query.model.ArithmeticOperator;
import org.modeshape.jcr.query.model.Between;
import org.modeshape.jcr.query.model.BindVariableName;
import org.modeshape.jcr.query.model.ChildCount;
import org.modeshape.jcr.query.model.ChildNode;
import org.modeshape.jcr.query.model.ChildNodeJoinCondition;
import org.modeshape.jcr.query.model.Column;
import org.modeshape.jcr.query.model.Comparison;
import org.modeshape.jcr.query.model.Constraint;
import org.modeshape.jcr.query.model.DescendantNode;
import org.modeshape.jcr.query.model.DescendantNodeJoinCondition;
import org.modeshape.jcr.query.model.DynamicOperand;
import org.modeshape.jcr.query.model.EquiJoinCondition;
import org.modeshape.jcr.query.model.FullTextSearch;
import org.modeshape.jcr.query.model.FullTextSearch.Term;
import org.modeshape.jcr.query.model.FullTextSearchScore;
import org.modeshape.jcr.query.model.Join;
import org.modeshape.jcr.query.model.JoinCondition;
import org.modeshape.jcr.query.model.JoinType;
import org.modeshape.jcr.query.model.Length;
import org.modeshape.jcr.query.model.Limit;
import org.modeshape.jcr.query.model.Literal;
import org.modeshape.jcr.query.model.LowerCase;
import org.modeshape.jcr.query.model.NamedSelector;
import org.modeshape.jcr.query.model.NodeDepth;
import org.modeshape.jcr.query.model.NodeId;
import org.modeshape.jcr.query.model.NodeLocalName;
import org.modeshape.jcr.query.model.NodeName;
import org.modeshape.jcr.query.model.NodePath;
import org.modeshape.jcr.query.model.Not;
import org.modeshape.jcr.query.model.NullOrder;
import org.modeshape.jcr.query.model.Or;
import org.modeshape.jcr.query.model.Order;
import org.modeshape.jcr.query.model.Ordering;
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.QueryCommand;
import org.modeshape.jcr.query.model.ReferenceValue;
import org.modeshape.jcr.query.model.Relike;
import org.modeshape.jcr.query.model.SameNode;
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.SetQuery;
import org.modeshape.jcr.query.model.SetQuery.Operation;
import org.modeshape.jcr.query.model.Source;
import org.modeshape.jcr.query.model.StaticOperand;
import org.modeshape.jcr.query.model.Subquery;
import org.modeshape.jcr.query.model.TypeSystem;
import org.modeshape.jcr.query.model.TypeSystem.TypeFactory;
import org.modeshape.jcr.query.model.UpperCase;
import org.modeshape.jcr.value.ValueFormatException;
/**
* A {@link QueryParser} implementation that parses a subset of SQL select and set queries.
*
* This grammar is equivalent to the SQL grammar as defined by the JCR 2.0 specification, with some useful additions:
*
* - "
... (UNION|INTERSECT|EXCEPT) [ALL] ...
" to combine and merge results from multiple queries
* - "
SELECT DISTINCT ...
" to remove duplicates
* - "
LIMIT count [OFFSET number]
" clauses to control the number of results returned as well as the number of rows
* that should be skipped
* - Support for additional join types, including "
FULL OUTER JOIN
" and "CROSS JOIN
"
* - Additional dynamic operands "
DEPTH([<selectorName>])
", "PATH([<selectorName>])
", "ID([<selectorName>])
", and "CHILDCOUNT([<selectorName>])
" that
* enables placing constraints on the node depth, path, and identifier, respectively, and which can be used in a manner similar to "
* NAME([<selectorName>])
" and "LOCALNAME([<selectorName>])
. Note in each of these cases, the
* selector name is optional if there is only one selector in the query.
* - Additional dynamic operand "
REFERENCE([<selectorName>.]<propertyName>])
" that
* enables placing constraints on one or all reference properties, and which can be used in a manner similar to "
* PropertyValue([<selectorName>.]<propertyName>)
". Note in each of these cases, the
* selector name is optional if there is only one selector in the query, and that the property name can be excluded
* if the constraint should apply to all reference properties.
* - Support for the IN clause and NOT IN clause to more easily supply a list of valid discrete static operands: "
*
<dynamicOperand> [NOT] IN (<staticOperand> {, <staticOperand>})
"
* - Support for the BETWEEN clause: "
<dynamicOperand> [NOT] BETWEEN <lowerBoundStaticOperand> [EXCLUSIVE] AND
* <upperBoundStaticOperand> [EXCLUSIVE]
"
* - Support for arithmetic operations ('+', '-', '*', '/') between dynamic operands used in
WHERE
criteria and ORDER BY
* clauses: "WHERE <dynamicOperand> + <dynamicOperand> ...
" or "ORDER BY (<dynamicOperand> + <dynamicOperand>) [ASC]
".
* Note that standard operator precedence is used, but grouping by (potentially nested) parentheses is also supported.
*
*
* SQL grammar
*
* This section defines the complete grammar for the SQL dialect supported by this parser.
*
* 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' | 'NOT 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 | NodeId |
* 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 */
*
* Node identifier
*
* NodeId ::= 'ID(' [selectorName] ')'
* /* If only one selector exists in this query, explicit specification of the selectorName
* is optional */
*
* Child count
*
* ChildCount ::= 'CHILDCOUNT(' [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 BasicSqlQueryParser implements QueryParser {
public static final String LANGUAGE = "SQL";
@Override
public String getLanguage() {
return LANGUAGE;
}
@Override
public String toString() {
return getLanguage();
}
@Override
public int hashCode() {
return getLanguage().hashCode();
}
@Override
public boolean equals( Object obj ) {
if (obj == this) return true;
if (obj instanceof QueryParser) {
QueryParser that = (QueryParser)obj;
return this.getLanguage().equals(that.getLanguage());
}
return false;
}
@Override
public QueryCommand parseQuery( String query,
TypeSystem typeSystem ) {
Tokenizer tokenizer = new SqlTokenizer(false);
TokenStream tokens = new TokenStream(query, tokenizer, false);
tokens.start();
return parseQueryCommand(tokens, typeSystem);
}
protected QueryCommand parseQueryCommand( TokenStream tokens,
TypeSystem typeSystem ) {
QueryCommand command = null;
if (tokens.matches("SELECT")) {
command = parseQuery(tokens, typeSystem);
while (tokens.hasNext()) {
if (tokens.matchesAnyOf("UNION", "INTERSECT", "EXCEPT")) {
command = parseSetQuery(tokens, command, typeSystem);
} else if (tokens.matches(')')) {
// There's more in this token stream, but we'll stop reading ...
break;
} else {
Position pos = tokens.previousPosition();
String msg = GraphI18n.unexpectedToken.text(tokens.consume(), pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
}
} else {
// We expected SELECT ...
Position pos = tokens.nextPosition();
String msg = GraphI18n.unexpectedToken.text(tokens.consume(), pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
return command;
}
protected Query parseQuery( TokenStream tokens,
TypeSystem typeSystem ) {
AtomicBoolean isDistinct = new AtomicBoolean(false);
List columnExpressions = parseSelect(tokens, isDistinct, typeSystem);
Source source = parseFrom(tokens, typeSystem);
Constraint constraint = parseWhere(tokens, typeSystem, source);
// Parse the order by and limit (can be in any order) ...
List extends Ordering> orderings = parseOrderBy(tokens, typeSystem, source);
Limit limit = parseLimit(tokens);
if (orderings == null) parseOrderBy(tokens, typeSystem, source);
// Convert the column expressions to columns ...
List columns = new ArrayList(columnExpressions.size());
for (ColumnExpression expression : columnExpressions) {
SelectorName selectorName = expression.getSelectorName();
String propertyName = expression.getPropertyName();
if (selectorName == null) {
if (source instanceof Selector) {
selectorName = ((Selector)source).aliasOrName();
} else {
Position pos = expression.getPosition();
String msg = GraphI18n.mustBeScopedAtLineAndColumn.text(expression, pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
}
columns.add(column(selectorName, propertyName, expression.getColumnName()));
}
// Now create the query ...
return query(source, constraint, orderings, columns, limit, isDistinct.get());
}
protected SetQuery parseSetQuery( TokenStream tokens,
QueryCommand leftHandSide,
TypeSystem typeSystem ) {
Operation operation = null;
if (tokens.canConsume("UNION")) {
operation = Operation.UNION;
} else if (tokens.canConsume("INTERSECT")) {
operation = Operation.INTERSECT;
} else {
tokens.consume("EXCEPT");
operation = Operation.EXCEPT;
}
boolean all = tokens.canConsume("ALL");
// Parse the next select
QueryCommand rightQuery = parseQuery(tokens, typeSystem);
return setQuery(leftHandSide, operation, rightQuery, all);
}
protected List parseSelect( TokenStream tokens,
AtomicBoolean isDistinct,
TypeSystem typeSystem ) {
tokens.consume("SELECT");
if (tokens.canConsume("DISTINCT")) isDistinct.set(true);
if (tokens.canConsume('*')) {
return Collections.emptyList();
}
List columns = new ArrayList();
do {
Position position = tokens.nextPosition();
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 = parseName(tokens, typeSystem);
}
String alias = propertyName;
if (tokens.canConsume("AS")) alias = parseName(tokens, typeSystem);
columns.add(new ColumnExpression(selectorName, propertyName, alias, position));
} while (tokens.canConsume(','));
return columns;
}
protected Source parseFrom( TokenStream tokens,
TypeSystem typeSystem ) {
Source source = null;
tokens.consume("FROM");
source = parseNamedSelector(tokens, typeSystem);
while (tokens.hasNext()) {
JoinType joinType = null;
if (tokens.canConsume("JOIN") || tokens.canConsume("INNER", "JOIN")) {
joinType = JoinType.INNER;
} else if (tokens.canConsume("OUTER", "JOIN") || tokens.canConsume("LEFT", "JOIN")
|| tokens.canConsume("LEFT", "OUTER", "JOIN")) {
joinType = JoinType.LEFT_OUTER;
} else if (tokens.canConsume("RIGHT", "OUTER", "JOIN") || tokens.canConsume("RIGHT", "OUTER")) {
joinType = JoinType.RIGHT_OUTER;
} else if (tokens.canConsume("FULL", "OUTER", "JOIN") || tokens.canConsume("FULL", "OUTER")) {
joinType = JoinType.FULL_OUTER;
} else if (tokens.canConsume("CROSS", "JOIN") || tokens.canConsume("CROSS")) {
joinType = JoinType.CROSS;
}
if (joinType == null) break;
// Read the name of the selector on the right side of the join ...
NamedSelector right = parseNamedSelector(tokens, typeSystem);
// Read the join condition ...
JoinCondition joinCondition = parseJoinCondition(tokens, typeSystem);
// Create the join ...
source = join(source, joinType, right, joinCondition);
}
return source;
}
protected JoinCondition parseJoinCondition( TokenStream tokens,
TypeSystem typeSystem ) {
tokens.consume("ON");
if (tokens.canConsume("ISSAMENODE", "(")) {
SelectorName selector1Name = parseSelectorName(tokens, typeSystem);
tokens.consume(',');
SelectorName selector2Name = parseSelectorName(tokens, typeSystem);
if (tokens.canConsume(',')) {
String path = parsePath(tokens, typeSystem);
tokens.consume(')');
return sameNodeJoinCondition(selector1Name, selector2Name, path);
}
tokens.consume(')');
return sameNodeJoinCondition(selector1Name, selector2Name);
}
if (tokens.canConsume("ISCHILDNODE", "(")) {
SelectorName child = parseSelectorName(tokens, typeSystem);
tokens.consume(',');
SelectorName parent = parseSelectorName(tokens, typeSystem);
tokens.consume(')');
return childNodeJoinCondition(parent, child);
}
if (tokens.canConsume("ISDESCENDANTNODE", "(")) {
SelectorName descendant = parseSelectorName(tokens, typeSystem);
tokens.consume(',');
SelectorName ancestor = parseSelectorName(tokens, typeSystem);
tokens.consume(')');
return descendantNodeJoinCondition(ancestor, descendant);
}
SelectorName selector1 = parseSelectorName(tokens, typeSystem);
tokens.consume('.');
String property1 = parseName(tokens, typeSystem);
tokens.consume('=');
SelectorName selector2 = parseSelectorName(tokens, typeSystem);
tokens.consume('.');
String property2 = parseName(tokens, typeSystem);
return equiJoinCondition(selector1, property1, selector2, property2);
}
protected Constraint parseWhere( TokenStream tokens,
TypeSystem typeSystem,
Source source ) {
if (tokens.canConsume("WHERE")) {
return parseConstraint(tokens, typeSystem, source);
}
return null;
}
protected Constraint parseConstraint( TokenStream tokens,
TypeSystem typeSystem,
Source source ) {
Constraint constraint = null;
Position pos = tokens.nextPosition();
if (tokens.canConsume("(")) {
constraint = parseConstraint(tokens, typeSystem, source);
tokens.consume(")");
} else if (tokens.canConsume("NOT")) {
tokens.canConsume('(');
constraint = not(parseConstraint(tokens, typeSystem, source));
tokens.canConsume(')');
} else if (tokens.canConsume("CONTAINS", "(")) {
// Either 'selectorName.propertyName', or 'selectorName.*' or 'propertyName' ...
// MODE-2027 '.' will be treated as 'selectorName.*'
String first = tokens.consume();
SelectorName selectorName = null;
String propertyName = null;
Position position = tokens.previousPosition();
if (first.equalsIgnoreCase(".")) {
selectorName = ((Selector)source).aliasOrName();
} else if (tokens.canConsume(".", "*")) {
selectorName = new SelectorName(removeBracketsAndQuotes(first, position));
} else if (tokens.canConsume('.')) {
selectorName = new SelectorName(removeBracketsAndQuotes(first, position));
propertyName = parseName(tokens, typeSystem);
} else {
if (!(source instanceof Selector)) {
String msg = GraphI18n.functionIsAmbiguous.text("CONTAINS()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
selectorName = ((Selector)source).aliasOrName();
propertyName = removeBracketsAndQuotes(first, position);
}
tokens.consume(',');
if (tokens.canConsume('$')) {
// The value parameter is a bind variable ...
BindVariableName var = parseBindVariableName(tokens, typeSystem);
try {
constraint = fullTextSearch(selectorName, propertyName, var);
} catch (RepositoryException e) {
String msg = GraphI18n.functionHasInvalidBindVariable.text("CONTAINS()", pos.getLine(), pos.getColumn(), var);
throw new ParsingException(pos, msg);
}
} else {
// It's just a full text search expression (don't remove nested quotes!!!) ...
String expression = removeBracketsAndQuotes(tokens.consume(), false, tokens.previousPosition());
Term term = parseFullTextSearchExpression(expression, tokens.previousPosition());
constraint = fullTextSearch(selectorName, propertyName, expression, term);
}
tokens.consume(")");
} else if (tokens.canConsume("ISSAMENODE", "(")) {
SelectorName selectorName = null;
if (tokens.matches(ANY_VALUE, ")")) {
if (!(source instanceof Selector)) {
String msg = GraphI18n.functionIsAmbiguous.text("ISSAMENODE()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
selectorName = ((Selector)source).name();
} else {
selectorName = parseSelectorName(tokens, typeSystem);
tokens.consume(',');
}
String path = parsePath(tokens, typeSystem);
tokens.consume(')');
constraint = sameNode(selectorName, path);
} else if (tokens.canConsume("ISCHILDNODE", "(")) {
SelectorName selectorName = null;
if (tokens.matches(ANY_VALUE, ")")) {
if (!(source instanceof Selector)) {
String msg = GraphI18n.functionIsAmbiguous.text("ISCHILDNODE()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
selectorName = ((Selector)source).name();
} else {
selectorName = parseSelectorName(tokens, typeSystem);
tokens.consume(',');
}
String path = parsePath(tokens, typeSystem);
tokens.consume(')');
constraint = childNode(selectorName, path);
} else if (tokens.canConsume("ISDESCENDANTNODE", "(")) {
SelectorName selectorName = null;
if (tokens.matches(ANY_VALUE, ")")) {
if (!(source instanceof Selector)) {
String msg = GraphI18n.functionIsAmbiguous.text("ISDESCENDANTNODE()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
selectorName = ((Selector)source).name();
} else {
selectorName = parseSelectorName(tokens, typeSystem);
tokens.consume(',');
}
String path = parsePath(tokens, typeSystem);
tokens.consume(')');
constraint = descendantNode(selectorName, path);
} else if (tokens.canConsume("RELIKE", "(")) {
StaticOperand left = parseStaticOperand(tokens, typeSystem);
tokens.consume(',');
PropertyValue right = parsePropertyValue(tokens, typeSystem, source);
tokens.consume(')');
constraint = new Relike(left, right);
} else {
// First try a property existance ...
Position pos2 = tokens.nextPosition();
constraint = parsePropertyExistance(tokens, typeSystem, source);
if (constraint == null) {
// Try to parse as a dynamic operand ...
DynamicOperand left = parseDynamicOperand(tokens, typeSystem, source);
if (left != null) {
if (tokens.matches('(') && left instanceof PropertyValue) {
// This was probably a bad function that we parsed as the start of a dynamic operation ...
String name = ((PropertyValue)left).getPropertyName(); // this may be the function name
String msg = GraphI18n.expectingConstraintCondition.text(name, pos2.getLine(), pos2.getColumn());
throw new ParsingException(pos, msg);
}
if (tokens.matches("IN", "(") || tokens.matches("NOT", "IN", "(")) {
boolean not = tokens.canConsume("NOT");
Collection staticOperands = parseInClause(tokens, typeSystem);
constraint = setCriteria(left, staticOperands);
if (not) constraint = not(constraint);
} else if (tokens.matches("BETWEEN") || tokens.matches("NOT", "BETWEEN")) {
boolean not = tokens.canConsume("NOT");
tokens.consume("BETWEEN");
StaticOperand lowerBound = parseStaticOperand(tokens, typeSystem);
boolean lowerInclusive = !tokens.canConsume("EXCLUSIVE");
tokens.consume("AND");
StaticOperand upperBound = parseStaticOperand(tokens, typeSystem);
boolean upperInclusive = !tokens.canConsume("EXCLUSIVE");
constraint = between(left, lowerBound, upperBound, lowerInclusive, upperInclusive);
if (not) constraint = not(constraint);
} else if (tokens.matches("NOT", "LIKE")) {
tokens.consume("NOT");
Operator operator = parseComparisonOperator(tokens);
StaticOperand right = parseStaticOperand(tokens, typeSystem);
constraint = comparison(left, operator, right);
constraint = not(constraint);
} else {
Operator operator = parseComparisonOperator(tokens);
StaticOperand right = parseStaticOperand(tokens, typeSystem);
constraint = comparison(left, operator, right);
}
}
// else continue ...
}
}
if (constraint == null) {
String msg = GraphI18n.expectingConstraintCondition.text(tokens.consume(), pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
// 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 = and(constraint, rhs);
}
while (tokens.canConsume("OR")) {
Constraint rhs = parseConstraint(tokens, typeSystem, source);
if (rhs != null) constraint = or(constraint, rhs);
}
return constraint;
}
protected List parseInClause( TokenStream tokens,
TypeSystem typeSystem ) {
List result = new ArrayList();
tokens.consume("IN");
tokens.consume("(");
if (!tokens.canConsume(")")) {
// Not empty, so read the static operands ...
do {
result.add(parseStaticOperand(tokens, typeSystem));
} while (tokens.canConsume(','));
tokens.consume(")");
}
return result;
}
protected Term parseFullTextSearchExpression( String expression,
Position startOfExpression ) {
try {
return new FullTextSearchParser().parse(expression);
} catch (ParsingException e) {
// Convert the position in the exception into a position in the query.
Position queryPos = startOfExpression.add(e.getPosition());
throw new ParsingException(queryPos, e.getMessage());
}
}
protected Operator parseComparisonOperator( TokenStream tokens ) {
if (tokens.canConsume("=")) return Operator.EQUAL_TO;
if (tokens.canConsume("LIKE")) return Operator.LIKE;
if (tokens.canConsume("!", "=")) return Operator.NOT_EQUAL_TO;
if (tokens.canConsume("<", ">")) return Operator.NOT_EQUAL_TO;
if (tokens.canConsume("<", "=")) return Operator.LESS_THAN_OR_EQUAL_TO;
if (tokens.canConsume(">", "=")) return Operator.GREATER_THAN_OR_EQUAL_TO;
if (tokens.canConsume("<")) return Operator.LESS_THAN;
if (tokens.canConsume(">")) return Operator.GREATER_THAN;
Position pos = tokens.nextPosition();
String msg = GraphI18n.expectingComparisonOperator.text(tokens.consume(), pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
protected List parseOrderBy( TokenStream tokens,
TypeSystem typeSystem,
Source source ) {
if (tokens.canConsume("ORDER", "BY")) {
List orderings = new ArrayList();
do {
orderings.add(parseOrdering(tokens, typeSystem, source));
} while (tokens.canConsume(','));
return orderings;
}
return null;
}
protected Ordering parseOrdering( TokenStream tokens,
TypeSystem typeSystem,
Source source ) {
DynamicOperand operand = parseDynamicOperand(tokens, typeSystem, source);
Order order = Order.ASCENDING;
if (tokens.canConsume("DESC")) order = Order.DESCENDING;
if (tokens.canConsume("ASC")) order = Order.ASCENDING;
NullOrder nullOrder = NullOrder.defaultOrder(order);
if (tokens.canConsume("NULLS", "FIRST")) nullOrder = NullOrder.NULLS_FIRST;
if (tokens.canConsume("NULLS", "LAST")) nullOrder = NullOrder.NULLS_LAST;
return ordering(operand, order, nullOrder);
}
protected Constraint parsePropertyExistance( TokenStream tokens,
TypeSystem typeSystem,
Source source ) {
if (tokens.matches(ANY_VALUE, ".", ANY_VALUE, "IS", "NOT", "NULL")
|| tokens.matches(ANY_VALUE, ".", ANY_VALUE, "IS", "NULL") || tokens.matches(ANY_VALUE, "IS", "NOT", "NULL")
|| tokens.matches(ANY_VALUE, "IS", "NULL")) {
Position pos = tokens.nextPosition();
String firstWord = tokens.consume();
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 = parseName(firstWord, typeSystem, pos);
}
if (tokens.canConsume("IS", "NOT", "NULL")) {
return propertyExistence(selectorName, propertyName);
}
tokens.consume("IS", "NULL");
return not(propertyExistence(selectorName, propertyName));
}
return null;
}
protected StaticOperand parseStaticOperand( TokenStream tokens,
TypeSystem typeSystem ) {
if (tokens.canConsume('$')) {
return parseBindVariableName(tokens, typeSystem);
}
if (tokens.canConsume('(')) {
// Sometimes the subqueries are wrapped with parentheses ...
StaticOperand result = parseStaticOperand(tokens, typeSystem);
tokens.consume(')');
return result;
}
if (tokens.matches("SELECT")) {
// This is a subquery. This object is stateless, so we can reuse this object ...
QueryCommand subqueryExpression = parseQueryCommand(tokens, typeSystem);
return subquery(subqueryExpression);
}
return parseLiteral(tokens, typeSystem);
}
protected BindVariableName parseBindVariableName( TokenStream tokens,
TypeSystem typeSystem ) {
// The variable name must conform to a valid prefix, which is defined as a valid NCName ...
String value = tokens.consume();
if (!XmlCharacters.isValidNcName(value)) {
Position pos = tokens.previousPosition();
String msg = GraphI18n.bindVariableMustConformToNcName.text(value, pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
return bindVariableName(value);
}
protected Subquery subquery( QueryCommand queryCommand ) {
return new Subquery(queryCommand);
}
protected Literal parseLiteral( TokenStream tokens,
TypeSystem typeSystem ) {
if (tokens.canConsume("CAST", "(")) {
// Get the value that is to be cast ...
Position pos = tokens.nextPosition();
Object value = parseLiteralValue(tokens, typeSystem);
// Figure out the type we're supposed to cast to ...
tokens.consume("AS");
String typeName = tokens.consume();
TypeFactory> typeFactory = typeSystem.getTypeFactory(typeName);
if (typeFactory == null) {
Position typePos = tokens.previousPosition();
String msg = GraphI18n.invalidPropertyType.text(tokens.consume(), typePos.getLine(), typePos.getColumn());
throw new ParsingException(typePos, msg);
}
// Convert the supplied value to the desired value ...
tokens.consume(')');
try {
Object literal = typeFactory.create(value);
return literal(typeSystem, literal);
} catch (ValueFormatException e) {
String msg = GraphI18n.valueCannotBeCastToSpecifiedType.text(value, pos.getLine(), pos.getColumn(),
typeFactory.getTypeName(), e.getMessage());
throw new ParsingException(pos, msg);
}
}
// Just create a literal out of the supplied value ...
return literal(typeSystem, parseLiteralValue(tokens, typeSystem));
}
protected Object parseLiteralValue( TokenStream tokens,
TypeSystem typeSystem ) {
if (tokens.matches(SqlTokenizer.QUOTED_STRING)) {
return removeBracketsAndQuotes(tokens.consume(), tokens.previousPosition());
}
TypeFactory booleanFactory = typeSystem.getBooleanFactory();
if (booleanFactory != null) {
if (tokens.canConsume("TRUE")) return booleanFactory.create(Boolean.TRUE);
if (tokens.canConsume("FALSE")) return booleanFactory.create(Boolean.FALSE);
}
// Otherwise it is an unquoted literal value ...
Position pos = tokens.nextPosition();
String sign = "";
if (tokens.canConsume('-')) sign = "-";
else if (tokens.canConsume('+')) sign = "";
// Try to parse this value as a number ...
String integral = tokens.consume();
TypeFactory doubleFactory = typeSystem.getDoubleFactory();
if (doubleFactory != null) {
String decimal = null;
if (tokens.canConsume('.')) {
decimal = tokens.consume();
String value = sign + integral + "." + decimal;
if ((decimal.endsWith("e") || decimal.endsWith("E")) && (tokens.matches('+') || tokens.matches('-'))) {
// There's more to the number ...
value = value + tokens.consume() + tokens.consume(); // +/-EXP
}
try {
// Convert to a double and then back to a string to get canonical form ...
return doubleFactory.create(value);
} catch (ValueFormatException e) {
String msg = GraphI18n.expectingLiteralAndUnableToParseAsDouble.text(value, pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
}
}
TypeFactory> dateTimeFactory = typeSystem.getDateTimeFactory();
if (dateTimeFactory != null) {
if (tokens.canConsume('-')) {
// Looks like a date (see Section 3.6.4.3 of the JCR 2.0 specification) ...
// sYYYY-MM-DDThh:mm:ss.sssTZD
String year = integral;
String month = tokens.consume();
tokens.consume('-');
String dateAndHour = tokens.consume();
tokens.consume(':');
String minutes = tokens.consume();
tokens.consume(':');
String seconds = tokens.consume();
tokens.consume('.');
String subSeconds = tokens.consume(); // should contain 'T' separator and possibly the TZ name and (if no +/-)
// hours
String tzSign = "+";
String tzHours = "00";
String tzMinutes = "00";
String tzDelim = ":";
if (tokens.canConsume('+')) {
// the fractionalSeconds did NOT contain the tzHours ...
tzHours = tokens.consume();
if (tokens.canConsume(':')) tzMinutes = tokens.consume();
} else if (tokens.canConsume('-')) {
// the fractionalSeconds did NOT contain the tzHours ...
tzSign = "-";
tzHours = tokens.consume();
if (tokens.canConsume(':')) tzMinutes = tokens.consume();
} else if (tokens.canConsume(':')) {
// fractionalSeconds DID contain the TZ hours (without + or -)
tzHours = tzSign = "";
if (tokens.canConsume(':')) tzMinutes = tokens.consume();
} else if (subSeconds.endsWith("Z")) {
tzSign = tzMinutes = tzDelim = tzHours = "";
} else if (subSeconds.endsWith("UTC")) {
subSeconds = subSeconds.length() > 3 ? subSeconds.substring(0, subSeconds.length() - 3) : subSeconds;
}
String value = sign + year + "-" + month + "-" + dateAndHour + ":" + minutes + ":" + seconds + "." + subSeconds
+ tzSign + tzHours + tzDelim + tzMinutes;
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);
}
}
}
TypeFactory longFactory = typeSystem.getLongFactory();
// try to parse an a long ...
String value = sign + integral;
try {
// Convert to a long and then back to a string to get canonical form ...
return longFactory.create(value);
} catch (ValueFormatException e) {
String msg = GraphI18n.expectingLiteralAndUnableToParseAsLong.text(value, pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
}
protected DynamicOperand parseDynamicOperand( TokenStream tokens,
TypeSystem typeSystem,
Source source ) {
DynamicOperand result = null;
Position pos = tokens.nextPosition();
if (tokens.canConsume('(')) {
result = parseDynamicOperand(tokens, typeSystem, source);
tokens.consume(")");
} else if (tokens.canConsume("LENGTH", "(")) {
result = length(parsePropertyValue(tokens, typeSystem, source));
tokens.consume(")");
} else if (tokens.canConsume("LOWER", "(")) {
result = lowerCase(parseDynamicOperand(tokens, typeSystem, source));
tokens.consume(")");
} else if (tokens.canConsume("UPPER", "(")) {
result = upperCase(parseDynamicOperand(tokens, typeSystem, source));
tokens.consume(")");
} else if (tokens.canConsume("NAME", "(")) {
if (tokens.canConsume(")")) {
if (source instanceof Selector) {
return nodeName(((Selector)source).name());
}
String msg = GraphI18n.functionIsAmbiguous.text("NAME()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
result = nodeName(parseSelectorName(tokens, typeSystem));
tokens.consume(")");
} else if (tokens.canConsume("LOCALNAME", "(")) {
if (tokens.canConsume(")")) {
if (source instanceof Selector) {
return nodeLocalName(((Selector)source).name());
}
String msg = GraphI18n.functionIsAmbiguous.text("LOCALNAME()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
result = nodeLocalName(parseSelectorName(tokens, typeSystem));
tokens.consume(")");
} else if (tokens.canConsume("SCORE", "(")) {
if (tokens.canConsume(")")) {
if (source instanceof Selector) {
return fullTextSearchScore(((Selector)source).name());
}
String msg = GraphI18n.functionIsAmbiguous.text("SCORE()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
result = fullTextSearchScore(parseSelectorName(tokens, typeSystem));
tokens.consume(")");
} else if (tokens.canConsume("DEPTH", "(")) {
if (tokens.canConsume(")")) {
if (source instanceof Selector) {
return nodeDepth(((Selector)source).name());
}
String msg = GraphI18n.functionIsAmbiguous.text("DEPTH()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
result = nodeDepth(parseSelectorName(tokens, typeSystem));
tokens.consume(")");
} else if (tokens.canConsume("ID", "(")) {
if (tokens.canConsume(")")) {
if (source instanceof Selector) {
return nodeId(((Selector)source).name());
}
String msg = GraphI18n.functionIsAmbiguous.text("ID()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
result = nodeId(parseSelectorName(tokens, typeSystem));
tokens.consume(")");
} else if (tokens.canConsume("PATH", "(")) {
if (tokens.canConsume(")")) {
if (source instanceof Selector) {
return nodePath(((Selector)source).name());
}
String msg = GraphI18n.functionIsAmbiguous.text("PATH()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
result = nodePath(parseSelectorName(tokens, typeSystem));
tokens.consume(")");
} else if (tokens.canConsume("CHILDCOUNT", "(")) {
if (tokens.canConsume(")")) {
if (source instanceof Selector) {
return childCount(((Selector)source).name());
}
String msg = GraphI18n.functionIsAmbiguous.text("CHILDCOUNT()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
result = childCount(parseSelectorName(tokens, typeSystem));
tokens.consume(")");
} else if (tokens.canConsume("REFERENCE", "(")) {
result = parseReferenceValue(tokens, typeSystem, source);
} else {
result = parsePropertyValue(tokens, typeSystem, source);
}
// Is this operand followed by an arithmetic operation ...
ArithmeticOperator arithmeticOperator = null;
if (tokens.canConsume('+')) {
arithmeticOperator = ArithmeticOperator.ADD;
} else if (tokens.canConsume('-')) {
arithmeticOperator = ArithmeticOperator.SUBTRACT;
} else if (tokens.canConsume('*')) {
arithmeticOperator = ArithmeticOperator.MULTIPLY;
} else if (tokens.canConsume('/')) {
arithmeticOperator = ArithmeticOperator.DIVIDE;
}
if (arithmeticOperator != null) {
if (tokens.matches('(')) {
// Don't use precendence, but instead use the next DynamicOperand as the RHS ...
DynamicOperand right = parseDynamicOperand(tokens, typeSystem, source);
result = arithmeticOperand(result, arithmeticOperator, right);
} else {
// There is no parenthesis, so use operator precedence ...
DynamicOperand right = parseDynamicOperand(tokens, typeSystem, source);
if (right instanceof ArithmeticOperand) {
// But the RHS is an arithmetic operand, so we need to use operator precedence ...
ArithmeticOperand arithRhs = (ArithmeticOperand)right;
ArithmeticOperator rhsOperator = arithRhs.operator();
if (arithmeticOperator.precedes(rhsOperator)) {
// This operand's operator does take precedence, so this must be computed before working with the RHS ...
DynamicOperand newRhs = arithRhs.getRight();
DynamicOperand newLhs = new ArithmeticOperand(result, arithmeticOperator, arithRhs.getLeft());
result = arithmeticOperand(newLhs, rhsOperator, newRhs);
} else {
result = arithmeticOperand(result, arithmeticOperator, right);
}
} else {
// The RHS is just another DynamicOperand ...
result = arithmeticOperand(result, arithmeticOperator, right);
}
}
}
return result;
}
protected PropertyValue parsePropertyValue( TokenStream tokens,
TypeSystem typeSystem,
Source source ) {
Position pos = tokens.nextPosition();
String firstWord = parseName(tokens, typeSystem);
SelectorName selectorName = null;
if (tokens.canConsume('.')) {
// We actually read the selector name, so now read the property name ...
selectorName = new SelectorName(firstWord);
String propertyName = parseName(tokens, typeSystem);
return propertyValue(selectorName, propertyName);
}
// Otherwise the source should be a single named selector
if (source instanceof Selector) {
selectorName = ((Selector)source).aliasOrName();
return propertyValue(selectorName, firstWord);
}
String msg = GraphI18n.mustBeScopedAtLineAndColumn.text(firstWord, pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
protected ReferenceValue parseReferenceValue( TokenStream tokens,
TypeSystem typeSystem,
Source source ) {
Position pos = tokens.nextPosition();
SelectorName selectorName = null;
if (tokens.canConsume(')')) {
// There should be a single source ...
if (source instanceof Selector) {
selectorName = ((Selector)source).aliasOrName();
return referenceValue(selectorName);
}
String msg = GraphI18n.functionIsAmbiguous.text("REFERENCE()", pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
// Otherwise, there is at least one word inside the parentheses ...
String firstWord = parseName(tokens, typeSystem);
if (tokens.canConsume('.')) {
// We actually read the selector name, so now read the property name ...
selectorName = new SelectorName(firstWord);
String propertyName = parseName(tokens, typeSystem);
return referenceValue(selectorName, propertyName);
}
tokens.consume(")");
// The name may be a selector name, or it may be a property name on the default selector.
// If there is just a single selector ...
if (source instanceof Selector) {
Selector selector = (Selector)source;
// and the selector name matches ...
selectorName = new SelectorName(firstWord);
if (selectorName.equals(selector.name()) || (selector.hasAlias() && selectorName.equals(selector.alias()))) {
// This is a reference value with just the selector name ...
return referenceValue(selectorName);
}
// Otherwise, the reference value is just the property name ...
return referenceValue(selector.aliasOrName(), firstWord);
}
// Otherwise, the first word is the name of a selector ...
selectorName = new SelectorName(firstWord);
return referenceValue(selectorName);
}
protected Limit parseLimit( TokenStream tokens ) {
if (tokens.canConsume("LIMIT")) {
int first = tokens.consumeInteger();
if (tokens.canConsume(',')) {
// This is of the 'from,to' style ...
int to = tokens.consumeInteger();
int offset = to - first;
if (offset < 0) {
Position pos = tokens.previousPosition();
String msg = GraphI18n.secondValueInLimitRangeCannotBeLessThanFirst.text(first, to, pos.getLine(),
pos.getColumn());
throw new ParsingException(pos, msg);
}
return limit(offset, first);
}
if (tokens.canConsume("OFFSET")) {
int offset = tokens.consumeInteger();
return limit(first, offset);
}
// No offset
return limit(first, 0);
}
if (tokens.canConsume("OFFSET")) {
int offset = tokens.consumeInteger();
return limit(Integer.MAX_VALUE, offset);
}
return null;
}
/**
* Remove all leading and trailing single-quotes, double-quotes, or square brackets from the supplied text. If multiple,
* properly-paired quotes or brackets are found, they will all be removed.
*
* @param text the input text; may not be null
* @param position the position of the text; may not be null
* @return the text without leading and trailing brackets and quotes, or text
if there were no square brackets or
* quotes
*/
protected String removeBracketsAndQuotes( String text,
Position position ) {
return removeBracketsAndQuotes(text, true, position);
}
/**
* Remove any leading and trailing single-quotes, double-quotes, or square brackets from the supplied text.
*
* @param text the input text; may not be null
* @param recursive true if more than one pair of quotes, double-quotes, or square brackets should be removed, or false if
* just the first pair should be removed
* @param position the position of the text; may not be null
* @return the text without leading and trailing brackets and quotes, or text
if there were no square brackets or
* quotes
*/
protected String removeBracketsAndQuotes( String text,
boolean recursive,
Position position ) {
if (text.length() > 0) {
char firstChar = text.charAt(0);
switch (firstChar) {
case '\'':
case '"':
if (text.charAt(text.length() - 1) != firstChar) {
String msg = GraphI18n.expectingValidName.text(text, position.getLine(), position.getColumn());
throw new ParsingException(position, msg);
}
String removed = text.substring(1, text.length() - 1);
return recursive ? removeBracketsAndQuotes(removed, recursive, position) : removed;
case '[':
if (text.charAt(text.length() - 1) != ']') {
String msg = GraphI18n.expectingValidName.text(text, position.getLine(), position.getColumn());
throw new ParsingException(position, msg);
}
removed = text.substring(1, text.length() - 1);
return recursive ? removeBracketsAndQuotes(removed, recursive, position) : removed;
}
}
return text;
}
protected NamedSelector parseNamedSelector( TokenStream tokens,
TypeSystem typeSystem ) {
SelectorName name = parseSelectorName(tokens, typeSystem);
SelectorName alias = null;
if (tokens.canConsume("AS")) alias = parseSelectorName(tokens, typeSystem);
return new NamedSelector(name, alias);
}
protected SelectorName parseSelectorName( TokenStream tokens,
TypeSystem typeSystem ) {
return new SelectorName(parseName(tokens, typeSystem));
}
protected String parsePath( TokenStream tokens,
TypeSystem typeSystem ) {
return removeBracketsAndQuotes(tokens.consume(), tokens.previousPosition());
}
protected String parseName( TokenStream tokens,
TypeSystem typeSystem ) {
return removeBracketsAndQuotes(tokens.consume(), tokens.previousPosition());
}
protected String parseName( String token,
TypeSystem typeSystem,
Position position ) {
return removeBracketsAndQuotes(token, position);
}
protected Query query( Source source,
Constraint constraint,
List extends Ordering> orderings,
List extends Column> columns,
Limit limit,
boolean distinct ) {
return new Query(source, constraint, orderings, columns, limit, distinct);
}
protected SetQuery setQuery( QueryCommand leftQuery,
Operation operation,
QueryCommand rightQuery,
boolean all ) {
return new SetQuery(leftQuery, operation, rightQuery, all);
}
protected Length length( PropertyValue propertyValue ) {
return new Length(propertyValue);
}
protected LowerCase lowerCase( DynamicOperand operand ) {
return new LowerCase(operand);
}
protected UpperCase upperCase( DynamicOperand operand ) {
return new UpperCase(operand);
}
protected NodeName nodeName( SelectorName selector ) {
return new NodeName(selector);
}
protected NodeLocalName nodeLocalName( SelectorName selector ) {
return new NodeLocalName(selector);
}
protected NodeDepth nodeDepth( SelectorName selector ) {
return new NodeDepth(selector);
}
protected NodeId nodeId( SelectorName selector ) {
return new NodeId(selector);
}
protected NodePath nodePath( SelectorName selector ) {
return new NodePath(selector);
}
protected ChildCount childCount( SelectorName selector ) {
return new ChildCount(selector);
}
protected EquiJoinCondition equiJoinCondition( SelectorName selector1,
String property1,
SelectorName selector2,
String property2 ) {
return new EquiJoinCondition(selector1, property1, selector2, property2);
}
protected DescendantNodeJoinCondition descendantNodeJoinCondition( SelectorName ancestor,
SelectorName descendant ) {
return new DescendantNodeJoinCondition(ancestor, descendant);
}
protected ChildNodeJoinCondition childNodeJoinCondition( SelectorName parent,
SelectorName child ) {
return new ChildNodeJoinCondition(parent, child);
}
protected SameNodeJoinCondition sameNodeJoinCondition( SelectorName selector1,
SelectorName selector2 ) {
return new SameNodeJoinCondition(selector1, selector2);
}
protected SameNodeJoinCondition sameNodeJoinCondition( SelectorName selector1,
SelectorName selector2,
String path ) {
return new SameNodeJoinCondition(selector1, selector2, path);
}
protected Limit limit( int rowCount,
int offset ) {
return new Limit(rowCount, offset);
}
protected Column column( SelectorName selectorName,
String propertyName,
String columnName ) {
return new Column(selectorName, propertyName, columnName);
}
protected Join join( Source left,
JoinType joinType,
Source right,
JoinCondition joinCondition ) {
return new Join(left, joinType, right, joinCondition);
}
protected Not not( Constraint constraint ) {
return new Not(constraint);
}
protected And and( Constraint constraint1,
Constraint constraint2 ) {
return new And(constraint1, constraint2);
}
protected Or or( Constraint constraint1,
Constraint constraint2 ) {
return new Or(constraint1, constraint2);
}
protected Between between( DynamicOperand operand,
StaticOperand lowerBound,
StaticOperand upperBound,
boolean lowerInclusive,
boolean upperInclusive ) {
return new Between(operand, lowerBound, upperBound, lowerInclusive, upperInclusive);
}
protected SetCriteria setCriteria( DynamicOperand operand,
Collection extends StaticOperand> values ) {
return new SetCriteria(operand, values);
}
protected FullTextSearch fullTextSearch( SelectorName name,
String propertyName,
String expression,
Term term ) {
return new FullTextSearch(name, propertyName, expression, term);
}
protected FullTextSearch fullTextSearch( SelectorName name,
String propertyName,
StaticOperand expression ) throws RepositoryException {
return new FullTextSearch(name, propertyName, expression, null);
}
protected SameNode sameNode( SelectorName name,
String path ) {
return new SameNode(name, path);
}
protected ChildNode childNode( SelectorName name,
String path ) {
return new ChildNode(name, path);
}
protected DescendantNode descendantNode( SelectorName name,
String path ) {
return new DescendantNode(name, path);
}
protected Comparison comparison( DynamicOperand left,
Operator operator,
StaticOperand right ) {
return new Comparison(left, operator, right);
}
protected Ordering ordering( DynamicOperand operand,
Order order,
NullOrder nullOrder ) {
return new Ordering(operand, order, nullOrder);
}
protected PropertyExistence propertyExistence( SelectorName selector,
String propertyName ) {
return new PropertyExistence(selector, propertyName);
}
protected FullTextSearchScore fullTextSearchScore( SelectorName selector ) {
return new FullTextSearchScore(selector);
}
protected ArithmeticOperand arithmeticOperand( DynamicOperand leftOperand,
ArithmeticOperator operator,
DynamicOperand rightOperand ) {
return new ArithmeticOperand(leftOperand, operator, rightOperand);
}
protected PropertyValue propertyValue( SelectorName selector,
String propertyName ) {
return new PropertyValue(selector, propertyName);
}
protected ReferenceValue referenceValue( SelectorName selector ) {
return new ReferenceValue(selector);
}
protected ReferenceValue referenceValue( SelectorName selector,
String propertyName ) {
return new ReferenceValue(selector, propertyName);
}
protected BindVariableName bindVariableName( String variableName ) {
return new BindVariableName(variableName);
}
protected Literal literal( TypeSystem typeSystem,
Object value ) throws ValueFormatException {
return new Literal(value);
}
/**
* A {@link org.modeshape.common.text.TokenStream.Tokenizer} implementation that parses words, quoted phrases, comments, and
* symbols. Words are delimited by whitespace and consist only of alpha-number characters plus the underscore character.
* Quoted phrases are delimited by single-quote and double-quote characters (which may be escaped within the quote). Comments
* are the characters starting with '/*' and ending with '*/', or starting with '--' and ending with the next line
* terminator (or the end of the content).
*/
public static class SqlTokenizer implements TokenStream.Tokenizer {
/**
* The token type for tokens that represent an unquoted string containing a character sequence made up of non-whitespace
* and non-symbol characters.
*/
public static final int WORD = 1;
/**
* The token type for tokens that consist of an individual "symbol" character. The set of characters includes:
* []<>=-+(),
*/
public static final int SYMBOL = 2;
/**
* The token type for tokens that consist of other characters.
*/
public static final int OTHER = 3;
/**
* The token type for tokens that consist of all the characters within single-quotes, double-quotes, or square brackets.
*/
public static final int QUOTED_STRING = 4;
/**
* The token type for tokens that consist of all the characters between "/*" and "*/" or between "--" and the next
* line terminator (e.g., '\n', '\r' or "\r\n")
*/
public static final int COMMENT = 6;
private final boolean useComments;
public SqlTokenizer( boolean useComments ) {
this.useComments = useComments;
}
@Override
public void tokenize( CharacterStream input,
Tokens tokens ) throws ParsingException {
while (input.hasNext()) {
char c = input.next();
switch (c) {
case ' ':
case '\t':
case '\n':
case '\r':
// Just skip these whitespace characters ...
break;
case '(':
case ')':
case '{':
case '}':
case '*':
case '.':
case ',':
case ';':
case '+':
case '%':
case '?':
case '$':
case ']':
case '!':
case '<':
case '>':
case '|':
case '=':
case ':':
tokens.addToken(input.position(input.index()), input.index(), input.index() + 1, SYMBOL);
break;
case '[':
int startIndex = input.index();
char closingChar = ']';
Position pos = input.position(startIndex);
// found one opening character, so we expect to find one closing character ...
int numExpectedClosingQuoteChars = 1;
while (input.hasNext()) {
c = input.next();
if (c == '\\' && input.isNext(closingChar)) {
c = input.next(); // consume the closingChar since it is escaped
} else if (c == '[') {
// Found an opening quote character (within the literal) ...
++numExpectedClosingQuoteChars;
} else if (c == closingChar) {
--numExpectedClosingQuoteChars;
if (numExpectedClosingQuoteChars == 0) break;
}
}
if (numExpectedClosingQuoteChars > 0) {
String msg = GraphI18n.noMatchingBracketFound.text(pos.getLine(), pos.getColumn());
throw new ParsingException(pos, msg);
}
int endIndex = input.index() + 1; // beyond last character read
tokens.addToken(pos, startIndex, endIndex, QUOTED_STRING);
break;
case '\'':
case '\"':
startIndex = input.index();
closingChar = c;
pos = input.position(startIndex);
boolean foundClosingQuote = false;
while (input.hasNext()) {
c = input.next();
if (c == '\\' && input.isNext(closingChar)) {
c = input.next(); // consume the closingChar since it is escaped
} else if (c == closingChar) {
foundClosingQuote = true;
break;
}
}
if (!foundClosingQuote) {
String msg = CommonI18n.noMatchingDoubleQuoteFound.text(pos.getLine(), pos.getColumn());
if (closingChar == '\'') {
msg = CommonI18n.noMatchingSingleQuoteFound.text(pos.getLine(), pos.getColumn());
}
throw new ParsingException(pos, msg);
}
endIndex = input.index() + 1; // beyond last character read
tokens.addToken(pos, startIndex, endIndex, QUOTED_STRING);
break;
case '-':
startIndex = input.index();
pos = input.position(input.index());
if (input.isNext('-')) {
// End-of-line comment ...
boolean foundLineTerminator = false;
while (input.hasNext()) {
c = input.next();
if (c == '\n' || c == '\r') {
foundLineTerminator = true;
break;
}
}
endIndex = input.index(); // the token won't include the '\n' or '\r' character(s)
if (!foundLineTerminator) ++endIndex; // must point beyond last char
if (c == '\r' && input.isNext('\n')) input.next();
if (useComments) {
tokens.addToken(pos, startIndex, endIndex, COMMENT);
}
} else {
tokens.addToken(input.position(input.index()), input.index(), input.index() + 1, SYMBOL);
break;
}
break;
case '/':
startIndex = input.index();
pos = input.position(input.index());
if (input.isNext('*')) {
// Multi-line comment ...
while (input.hasNext() && !input.isNext('*', '/')) {
c = input.next();
}
if (input.hasNext()) input.next(); // consume the '*'
if (input.hasNext()) input.next(); // consume the '/'
if (useComments) {
endIndex = input.index() + 1; // the token will include the quote characters
tokens.addToken(pos, startIndex, endIndex, COMMENT);
}
} else {
tokens.addToken(input.position(input.index()), input.index(), input.index() + 1, SYMBOL);
break;
}
break;
default:
startIndex = input.index();
pos = input.position(input.index());
// Read as long as there is a valid XML character ...
int tokenType = (Character.isLetterOrDigit(c) || c == '_') ? WORD : OTHER;
while (input.isNextLetterOrDigit() || input.isNext('_')) {
c = input.next();
}
endIndex = input.index() + 1; // beyond last character that was included
tokens.addToken(pos, startIndex, endIndex, tokenType);
}
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy