/* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.hibernate.hql.internal.classic; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.hibernate.QueryException; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.hql.internal.QuerySplitter; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; /** * Parsers the select clause of a Hibernate query. * * @author Gavin King, David Channon */ public class SelectParser implements Parser { //TODO: arithmetic expressions, multiple new Foo(...) private static final Set COUNT_MODIFIERS = new HashSet(); static { COUNT_MODIFIERS.add( "distinct" ); COUNT_MODIFIERS.add( "all" ); COUNT_MODIFIERS.add( "*" ); } private LinkedList aggregateFuncTokenList = new LinkedList(); private boolean ready; private boolean aggregate; private boolean first; private boolean afterNew; private boolean insideNew; private boolean aggregateAddSelectScalar; private Class holderClass; private final SelectPathExpressionParser pathExpressionParser; private final PathExpressionParser aggregatePathExpressionParser; { pathExpressionParser = new SelectPathExpressionParser(); aggregatePathExpressionParser = new PathExpressionParser(); //TODO: would be nice to use false, but issues with MS SQL pathExpressionParser.setUseThetaStyleJoin( true ); aggregatePathExpressionParser.setUseThetaStyleJoin( true ); } public void token(String token, QueryTranslatorImpl q) throws QueryException { String lctoken = token.toLowerCase(); if ( first ) { first = false; if ( "distinct".equals( lctoken ) ) { q.setDistinct( true ); return; } else if ( "all".equals( lctoken ) ) { q.setDistinct( false ); return; } } if ( afterNew ) { afterNew = false; try { holderClass = ReflectHelper.classForName( QuerySplitter.getImportedClass( token, q.getFactory() ) ); } catch ( ClassNotFoundException cnfe ) { throw new QueryException( cnfe ); } if ( holderClass == null ) throw new QueryException( "class not found: " + token ); q.setHolderClass( holderClass ); insideNew = true; } else if ( token.equals( "," ) ) { if ( !aggregate && ready ) throw new QueryException( "alias or expression expected in SELECT" ); q.appendScalarSelectToken( ", " ); ready = true; } else if ( "new".equals( lctoken ) ) { afterNew = true; ready = false; } else if ( "(".equals( token ) ) { if ( insideNew && !aggregate && !ready ) { //opening paren in new Foo ( ... ) ready = true; } else if ( aggregate ) { q.appendScalarSelectToken( token ); } else { throw new QueryException( "aggregate function expected before ( in SELECT" ); } ready = true; } else if ( ")".equals( token ) ) { if ( insideNew && !aggregate && !ready ) { //if we are inside a new Result(), but not inside a nested function insideNew = false; } else if ( aggregate && ready ) { q.appendScalarSelectToken( token ); aggregateFuncTokenList.removeLast(); if ( aggregateFuncTokenList.size() < 1 ) { aggregate = false; ready = false; } } else { throw new QueryException( "( expected before ) in select" ); } } else if ( COUNT_MODIFIERS.contains( lctoken ) ) { if ( !ready || !aggregate ) { throw new QueryException( token + " only allowed inside aggregate function in SELECT" ); } q.appendScalarSelectToken( token ); if ( "*".equals( token ) ) { // special case q.addSelectScalar( getFunction( "count", q ).getReturnType( StandardBasicTypes.LONG, q.getFactory() ) ); } } else if ( getFunction( lctoken, q ) != null && token.equals( q.unalias( token ) ) ) { // the name of an SQL function if ( !ready ) throw new QueryException( ", expected before aggregate function in SELECT: " + token ); aggregate = true; aggregateAddSelectScalar = true; aggregateFuncTokenList.add( lctoken ); ready = false; q.appendScalarSelectToken( token ); if ( !aggregateHasArgs( lctoken, q ) ) { q.addSelectScalar( aggregateType( aggregateFuncTokenList, null, q ) ); if ( !aggregateFuncNoArgsHasParenthesis( lctoken, q ) ) { aggregateFuncTokenList.removeLast(); if ( aggregateFuncTokenList.size() < 1 ) { aggregate = false; ready = false; } else { ready = true; } } } } else if ( aggregate ) { boolean constantToken = false; if ( !ready ) throw new QueryException( "( expected after aggregate function in SELECT" ); try { ParserHelper.parse( aggregatePathExpressionParser, q.unalias( token ), ParserHelper.PATH_SEPARATORS, q ); } catch ( QueryException qex ) { constantToken = true; } if ( constantToken ) { q.appendScalarSelectToken( token ); } else { if ( aggregatePathExpressionParser.isCollectionValued() ) { q.addCollection( aggregatePathExpressionParser.getCollectionName(), aggregatePathExpressionParser.getCollectionRole() ); } q.appendScalarSelectToken( aggregatePathExpressionParser.getWhereColumn() ); if ( aggregateAddSelectScalar ) { q.addSelectScalar( aggregateType( aggregateFuncTokenList, aggregatePathExpressionParser.getWhereColumnType(), q ) ); aggregateAddSelectScalar = false; } aggregatePathExpressionParser.addAssociation( q ); } } else { if ( !ready ) throw new QueryException( ", expected in SELECT" ); ParserHelper.parse( pathExpressionParser, q.unalias( token ), ParserHelper.PATH_SEPARATORS, q ); if ( pathExpressionParser.isCollectionValued() ) { q.addCollection( pathExpressionParser.getCollectionName(), pathExpressionParser.getCollectionRole() ); } else if ( pathExpressionParser.getWhereColumnType().isEntityType() ) { q.addSelectClass( pathExpressionParser.getSelectName() ); } q.appendScalarSelectTokens( pathExpressionParser.getWhereColumns() ); q.addSelectScalar( pathExpressionParser.getWhereColumnType() ); pathExpressionParser.addAssociation( q ); ready = false; } } public boolean aggregateHasArgs(String funcToken, QueryTranslatorImpl q) { return getFunction( funcToken, q ).hasArguments(); } public boolean aggregateFuncNoArgsHasParenthesis(String funcToken, QueryTranslatorImpl q) { return getFunction( funcToken, q ).hasParenthesesIfNoArguments(); } public Type aggregateType(List funcTokenList, Type type, QueryTranslatorImpl q) throws QueryException { Type retType = type; Type argType; for ( int i = funcTokenList.size() - 1; i >= 0; i-- ) { argType = retType; String funcToken = ( String ) funcTokenList.get( i ); retType = getFunction( funcToken, q ).getReturnType( argType, q.getFactory() ); } return retType; } private SQLFunction getFunction(String name, QueryTranslatorImpl q) { return q.getFactory().getSqlFunctionRegistry().findSQLFunction( name ); } public void start(QueryTranslatorImpl q) { ready = true; first = true; aggregate = false; afterNew = false; insideNew = false; holderClass = null; aggregateFuncTokenList.clear(); } public void end(QueryTranslatorImpl q) { } }