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

org.modeshape.jcr.query.parse.BasicSqlQueryParser 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.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 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 orderings, List 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 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