From c35efa67808ec7d6bbd6010b2df64633dbed0d44 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 13 Aug 2009 19:30:57 +0000 Subject: [PATCH] HHH-4081 - Support for JPA 2.0 "qualified identification variables" (KEY, VALUE and ENTRY) git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@17300 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- core/pom.xml | 2 + core/src/main/antlr/hql-sql.g | 22 +- core/src/main/antlr/hql.g | 14 +- core/src/main/antlr/sql-gen.g | 7 + .../java/org/hibernate/hql/ast/HqlParser.java | 23 ++ .../org/hibernate/hql/ast/HqlSqlWalker.java | 55 ++- .../hql/ast/QueryTranslatorImpl.java | 1 - .../org/hibernate/hql/ast/SqlASTFactory.java | 12 + .../org/hibernate/hql/ast/SqlGenerator.java | 43 ++- .../ast/tree/AbstractMapComponentNode.java | 106 ++++++ .../ast/tree/AggregatedSelectExpression.java | 55 +++ .../hql/ast/tree/ConstructorNode.java | 39 +- .../org/hibernate/hql/ast/tree/DotNode.java | 3 +- .../hibernate/hql/ast/tree/MapEntryNode.java | 273 ++++++++++++++ .../hibernate/hql/ast/tree/MapKeyNode.java | 44 +++ .../hibernate/hql/ast/tree/MapValueNode.java | 44 +++ .../hibernate/hql/ast/tree/SelectClause.java | 64 ++-- .../hibernate/hql/ast/util/ASTPrinter.java | 190 ++++------ .../org/hibernate/hql/ast/util/ASTUtil.java | 343 +++++++++++------- .../org/hibernate/loader/hql/QueryLoader.java | 45 ++- .../entity/AbstractEntityPersister.java | 19 +- .../hibernate/persister/entity/Queryable.java | 3 + .../org/hibernate/sql/AliasGenerator.java | 31 ++ .../org/hibernate/sql/SelectExpression.java | 32 ++ .../org/hibernate/sql/SelectFragment.java | 10 +- .../ordering/antlr/OrderByFragmentParser.java | 23 ++ .../antlr/OrderByFragmentRenderer.java | 37 ++ .../java/org/hibernate/util/ArrayHelper.java | 30 +- .../java/org/hibernate/util/StringHelper.java | 7 + .../test/hql/ASTParserLoadingTest.java | 43 +++ testsuite/src/test/resources/log4j.properties | 4 + 31 files changed, 1266 insertions(+), 358 deletions(-) create mode 100644 core/src/main/java/org/hibernate/hql/ast/tree/AbstractMapComponentNode.java create mode 100644 core/src/main/java/org/hibernate/hql/ast/tree/AggregatedSelectExpression.java create mode 100644 core/src/main/java/org/hibernate/hql/ast/tree/MapEntryNode.java create mode 100644 core/src/main/java/org/hibernate/hql/ast/tree/MapKeyNode.java create mode 100644 core/src/main/java/org/hibernate/hql/ast/tree/MapValueNode.java create mode 100644 core/src/main/java/org/hibernate/sql/AliasGenerator.java create mode 100644 core/src/main/java/org/hibernate/sql/SelectExpression.java diff --git a/core/pom.xml b/core/pom.xml index c6b0a539b5..f1696542e9 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -76,6 +76,8 @@ ${antlrPluginVersion} hql.g,hql-sql.g,sql-gen.g,order-by.g,order-by-render.g + true + true diff --git a/core/src/main/antlr/hql-sql.g b/core/src/main/antlr/hql-sql.g index 03314bc470..40cee752e4 100644 --- a/core/src/main/antlr/hql-sql.g +++ b/core/src/main/antlr/hql-sql.g @@ -230,6 +230,10 @@ tokens protected void prepareLogicOperator(AST operator) throws SemanticException { } protected void prepareArithmeticOperator(AST operator) throws SemanticException { } + + protected void processMapComponentReference(AST node) throws SemanticException { } + + protected void validateMapPropertyExpression(AST node) throws SemanticException { } } // The main statement rule. @@ -640,7 +644,11 @@ propertyName ; propertyRef! - : #(d:DOT lhs:propertyRefLhs rhs:propertyName ) { + : mcr:mapComponentReference { + resolve( #mcr ); + #propertyRef = #mcr; + } + | #(d:DOT lhs:propertyRefLhs rhs:propertyName ) { // This gives lookupProperty() a chance to transform the tree to process collection properties (.elements, etc). #propertyRef = #(#d, #lhs, #rhs); #propertyRef = lookupProperty(#propertyRef,false,true); @@ -672,6 +680,18 @@ aliasRef! } ; +mapComponentReference + : #( KEY mapPropertyExpression ) + | #( VALUE mapPropertyExpression ) + | #( ENTRY mapPropertyExpression ) + ; + +mapPropertyExpression + : e:expr { + validateMapPropertyExpression( #e ); + } + ; + parameter! : #(c:COLON a:identifier) { // Create a NAMED_PARAM node instead of (COLON IDENT). diff --git a/core/src/main/antlr/hql.g b/core/src/main/antlr/hql.g index 3e3cd581fe..06996541ec 100644 --- a/core/src/main/antlr/hql.g +++ b/core/src/main/antlr/hql.g @@ -99,6 +99,9 @@ tokens OBJECT="object"; OF="of"; TRAILING="trailing"; + KEY="key"; + VALUE="value"; + ENTRY="entry"; // -- Synthetic token types -- AGGREGATE; // One of the aggregate functions (e.g. min, max, avg) @@ -633,7 +636,8 @@ vectorExpr // NOTE: handleDotIdent() is called immediately after the first IDENT is recognized because // the method looks a head to find keywords after DOT and turns them into identifiers. identPrimary - : identifier { handleDotIdent(); } + : mapComponentReference + | identifier { handleDotIdent(); } ( options { greedy=true; } : DOT^ ( identifier | ELEMENTS | o:OBJECT { #o.setType(IDENT); } ) )* ( options { greedy=true; } : ( op:OPEN^ { #op.setType(METHOD_CALL);} exprList CLOSE! ) @@ -642,11 +646,9 @@ identPrimary | aggregate ; -//## aggregate: -//## ( aggregateFunction OPEN path CLOSE ) | ( COUNT OPEN STAR CLOSE ) | ( COUNT OPEN (DISTINCT | ALL) path CLOSE ); - -//## aggregateFunction: -//## COUNT | 'sum' | 'avg' | 'max' | 'min'; +mapComponentReference + : ( KEY^ | VALUE^ | ENTRY^ ) OPEN! path CLOSE! + ; aggregate : ( SUM^ | AVG^ | MAX^ | MIN^ ) OPEN! additiveExpression CLOSE! { #aggregate.setType(AGGREGATE); } diff --git a/core/src/main/antlr/sql-gen.g b/core/src/main/antlr/sql-gen.g index 9e860b574a..a617a4077a 100644 --- a/core/src/main/antlr/sql-gen.g +++ b/core/src/main/antlr/sql-gen.g @@ -207,6 +207,7 @@ selectColumn selectExpr : e:selectAtom { out(e); } + | mcr:mapComponentReference { out(mcr); } | count | #(CONSTRUCTOR (DOT | IDENT) ( selectColumn )+ ) | methodCall @@ -240,6 +241,12 @@ selectAtom | SELECT_EXPR ; +mapComponentReference + : KEY + | VALUE + | ENTRY + ; + // The from-clause piece is all goofed up. Currently, nodes of type FROM_FRAGMENT // and JOIN_FRAGMENT can occur at any level in the FromClause sub-tree. We really // should come back and clean this up at some point; which I think will require diff --git a/core/src/main/java/org/hibernate/hql/ast/HqlParser.java b/core/src/main/java/org/hibernate/hql/ast/HqlParser.java index 80f0734ce0..a7bea13c04 100644 --- a/core/src/main/java/org/hibernate/hql/ast/HqlParser.java +++ b/core/src/main/java/org/hibernate/hql/ast/HqlParser.java @@ -42,6 +42,7 @@ import org.hibernate.hql.antlr.HqlTokenTypes; import org.hibernate.hql.ast.util.ASTPrinter; import org.hibernate.hql.ast.util.ASTUtil; import org.hibernate.QueryException; +import org.hibernate.util.StringHelper; /** * Implements the semantic action methods defined in the HQL base parser to keep the grammar @@ -73,6 +74,28 @@ public final class HqlParser extends HqlBaseParser { initialize(); } + + // handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private int traceDepth = 0; + + + public void traceIn(String ruleName) { + if ( inputState.guessing > 0 ) { + return; + } + String prefix = StringHelper.repeat( '-', (traceDepth++ * 2) ) + "-> "; + log.trace( prefix + ruleName ); + } + + public void traceOut(String ruleName) { + if ( inputState.guessing > 0 ) { + return; + } + String prefix = "<-" + StringHelper.repeat( '-', (--traceDepth * 2) ) + " "; + log.trace( prefix + ruleName ); + } + public void reportError(RecognitionException e) { parseErrorHandler.reportError( e ); // Use the delegate. } diff --git a/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java b/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java index 4dc1456c80..e88094a393 100644 --- a/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java +++ b/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java @@ -94,6 +94,7 @@ import org.hibernate.type.VersionType; import org.hibernate.type.DbTimestampType; import org.hibernate.usertype.UserVersionType; import org.hibernate.util.ArrayHelper; +import org.hibernate.util.StringHelper; import antlr.ASTFactory; import antlr.RecognitionException; @@ -169,6 +170,34 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par } + // handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private int traceDepth = 0; + + public void traceIn(String ruleName, AST tree) { + if ( inputState.guessing > 0 ) { + return; + } + String prefix = StringHelper.repeat( '-', (traceDepth++ * 2) ) + "-> "; + String traceText = ruleName + " (" + buildTraceNodeName(tree) + ")"; + log.trace( prefix + traceText ); + } + + private String buildTraceNodeName(AST tree) { + return tree == null + ? "???" + : tree.getText() + " [" + printer.getTokenTypeName( tree.getType() ) + "]"; + } + + public void traceOut(String ruleName, AST tree) { + if ( inputState.guessing > 0 ) { + return; + } + String prefix = "<-" + StringHelper.repeat( '-', (--traceDepth * 2) ) + " "; + log.trace( prefix + ruleName ); + } + + protected void prepareFromClauseInputTree(AST fromClauseInput) { if ( !isSubQuery() ) { // // inject param specifications to account for dynamic filter param values @@ -826,11 +855,12 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par // This is called when it's time to fully resolve a path expression. int type = node.getType(); switch ( type ) { - case DOT: + case DOT: { DotNode dot = ( DotNode ) node; dot.resolveSelectExpression(); break; - case ALIAS_REF: + } + case ALIAS_REF: { // Notify the FROM element that it is being referenced by the select. FromReferenceNode aliasRefNode = ( FromReferenceNode ) node; //aliasRefNode.resolve( false, false, aliasRefNode.getText() ); //TODO: is it kosher to do it here? @@ -839,8 +869,11 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par if ( fromElement != null ) { fromElement.setIncludeSubclasses( true ); } - default: break; + } + default: { + break; + } } } @@ -1101,6 +1134,22 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par ( ( OperatorNode ) operator ).initialize(); } + protected void validateMapPropertyExpression(AST node) throws SemanticException { + try { + FromReferenceNode fromReferenceNode = (FromReferenceNode) node; + QueryableCollection collectionPersister = fromReferenceNode.getFromElement().getQueryableCollection(); + if ( ! Map.class.isAssignableFrom( collectionPersister.getCollectionType().getReturnedClass() ) ) { + throw new SemanticException( "node did not reference a map" ); + } + } + catch ( SemanticException se ) { + throw se; + } + catch ( Throwable t ) { + throw new SemanticException( "node did not reference a map" ); + } + } + public static void panic() { throw new QueryException( "TreeWalker: panic" ); } diff --git a/core/src/main/java/org/hibernate/hql/ast/QueryTranslatorImpl.java b/core/src/main/java/org/hibernate/hql/ast/QueryTranslatorImpl.java index 9b503463a0..311820ff2a 100644 --- a/core/src/main/java/org/hibernate/hql/ast/QueryTranslatorImpl.java +++ b/core/src/main/java/org/hibernate/hql/ast/QueryTranslatorImpl.java @@ -288,7 +288,6 @@ public class QueryTranslatorImpl implements FilterTranslator { void showHqlAst(AST hqlAst) { if ( AST_LOG.isDebugEnabled() ) { ASTPrinter printer = new ASTPrinter( HqlTokenTypes.class ); - printer.setShowClassNames( false ); // The class names aren't interesting in the first tree. AST_LOG.debug( printer.showAsString( hqlAst, "--- HQL AST ---" ) ); } } diff --git a/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java b/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java index 2e357f0fa0..2fc9a7365e 100644 --- a/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java +++ b/core/src/main/java/org/hibernate/hql/ast/SqlASTFactory.java @@ -65,6 +65,9 @@ import org.hibernate.hql.ast.tree.SessionFactoryAwareNode; import org.hibernate.hql.ast.tree.BooleanLiteralNode; import org.hibernate.hql.ast.tree.IsNullLogicOperatorNode; import org.hibernate.hql.ast.tree.IsNotNullLogicOperatorNode; +import org.hibernate.hql.ast.tree.MapKeyNode; +import org.hibernate.hql.ast.tree.MapValueNode; +import org.hibernate.hql.ast.tree.MapEntryNode; import java.lang.reflect.Constructor; @@ -187,6 +190,15 @@ public class SqlASTFactory extends ASTFactory implements HqlSqlTokenTypes { return IsNotNullLogicOperatorNode.class; case EXISTS: return UnaryLogicOperatorNode.class; + case KEY: { + return MapKeyNode.class; + } + case VALUE: { + return MapValueNode.class; + } + case ENTRY: { + return MapEntryNode.class; + } default: return SqlNode.class; } // switch diff --git a/core/src/main/java/org/hibernate/hql/ast/SqlGenerator.java b/core/src/main/java/org/hibernate/hql/ast/SqlGenerator.java index 3dfbd6aebd..22174d08cd 100644 --- a/core/src/main/java/org/hibernate/hql/ast/SqlGenerator.java +++ b/core/src/main/java/org/hibernate/hql/ast/SqlGenerator.java @@ -32,15 +32,21 @@ import java.util.Arrays; import antlr.RecognitionException; import antlr.collections.AST; import org.hibernate.QueryException; +import org.hibernate.util.StringHelper; import org.hibernate.param.ParameterSpecification; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.hql.antlr.SqlGeneratorBase; +import org.hibernate.hql.antlr.SqlTokenTypes; import org.hibernate.hql.ast.tree.MethodNode; import org.hibernate.hql.ast.tree.FromElement; import org.hibernate.hql.ast.tree.Node; import org.hibernate.hql.ast.tree.ParameterNode; import org.hibernate.hql.ast.tree.ParameterContainer; +import org.hibernate.hql.ast.util.ASTPrinter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Generates SQL by overriding callback methods in the base class, which does @@ -50,10 +56,7 @@ import org.hibernate.hql.ast.tree.ParameterContainer; * @author Steve Ebersole */ public class SqlGenerator extends SqlGeneratorBase implements ErrorReporter { - /** - * Handles parser errors. - */ - private ParseErrorHandler parseErrorHandler; + private static final Logger log = LoggerFactory.getLogger( SqlGenerator.class ); /** * all append invocations on the buf should go through this Output instance variable. @@ -64,12 +67,40 @@ public class SqlGenerator extends SqlGeneratorBase implements ErrorReporter { */ private SqlWriter writer = new DefaultWriter(); + private ParseErrorHandler parseErrorHandler; private SessionFactoryImplementor sessionFactory; - private LinkedList outputStack = new LinkedList(); - + private final ASTPrinter printer = new ASTPrinter( SqlTokenTypes.class ); private List collectedParameters = new ArrayList(); + + // handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private int traceDepth = 0; + + public void traceIn(String ruleName, AST tree) { + if ( inputState.guessing > 0 ) { + return; + } + String prefix = StringHelper.repeat( '-', (traceDepth++ * 2) ) + "-> "; + String traceText = ruleName + " (" + buildTraceNodeName(tree) + ")"; + log.trace( prefix + traceText ); + } + + private String buildTraceNodeName(AST tree) { + return tree == null + ? "???" + : tree.getText() + " [" + printer.getTokenTypeName( tree.getType() ) + "]"; + } + + public void traceOut(String ruleName, AST tree) { + if ( inputState.guessing > 0 ) { + return; + } + String prefix = "<-" + StringHelper.repeat( '-', (--traceDepth * 2) ) + " "; + log.trace( prefix + ruleName ); + } + public List getCollectedParameters() { return collectedParameters; } diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/AbstractMapComponentNode.java b/core/src/main/java/org/hibernate/hql/ast/tree/AbstractMapComponentNode.java new file mode 100644 index 0000000000..74e7098e27 --- /dev/null +++ b/core/src/main/java/org/hibernate/hql/ast/tree/AbstractMapComponentNode.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC. + * + * 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.ast.tree; + +import java.util.Map; + +import antlr.SemanticException; +import antlr.collections.AST; + +import org.hibernate.hql.antlr.HqlSqlTokenTypes; +import org.hibernate.hql.ast.util.ColumnHelper; +import org.hibernate.type.CollectionType; +import org.hibernate.type.Type; +import org.hibernate.persister.collection.QueryableCollection; +import org.hibernate.util.StringHelper; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public abstract class AbstractMapComponentNode extends FromReferenceNode implements HqlSqlTokenTypes { + private String[] columns; + + public FromReferenceNode getMapReference() { + return ( FromReferenceNode ) getFirstChild(); + } + + public String[] getColumns() { + return columns; + } + + public void setScalarColumnText(int i) throws SemanticException { + ColumnHelper.generateScalarColumns( this, getColumns(), i ); + } + + public void resolve( + boolean generateJoin, + boolean implicitJoin, + String classAlias, + AST parent) throws SemanticException { + if ( parent != null ) { + throw attemptedDereference(); + } + + FromReferenceNode mapReference = getMapReference(); + mapReference.resolve( true, true ); + if ( mapReference.getDataType().isCollectionType() ) { + CollectionType collectionType = (CollectionType) mapReference.getDataType(); + if ( Map.class.isAssignableFrom( collectionType.getReturnedClass() ) ) { + FromElement sourceFromElement = mapReference.getFromElement(); + setFromElement( sourceFromElement ); + setDataType( resolveType( sourceFromElement.getQueryableCollection() ) ); + this.columns = resolveColumns( sourceFromElement.getQueryableCollection() ); + initText( this.columns ); + setFirstChild( null ); + return; + } + } + + throw nonMap(); + } + + private void initText(String[] columns) { + String text = StringHelper.join( ", ", columns ); + if ( columns.length > 1 && getWalker().isComparativeExpressionClause() ) { + text = "(" + text + ")"; + } + setText( text ); + } + + protected abstract String expressionDescription(); + protected abstract String[] resolveColumns(QueryableCollection collectionPersister); + protected abstract Type resolveType(QueryableCollection collectionPersister); + + protected SemanticException attemptedDereference() { + return new SemanticException( expressionDescription() + " expression cannot be further de-referenced" ); + } + + protected SemanticException nonMap() { + return new SemanticException( expressionDescription() + " expression did not reference map property" ); + } + + public void resolveIndex(AST parent) throws SemanticException { + throw new UnsupportedOperationException( expressionDescription() + " expression cannot be the source for an index operation" ); + } +} diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/AggregatedSelectExpression.java b/core/src/main/java/org/hibernate/hql/ast/tree/AggregatedSelectExpression.java new file mode 100644 index 0000000000..8ce21f94ec --- /dev/null +++ b/core/src/main/java/org/hibernate/hql/ast/tree/AggregatedSelectExpression.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC. + * + * 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.ast.tree; + +import java.util.List; + +import org.hibernate.transform.ResultTransformer; + +/** + * Contract for a select expression which aggregates other select expressions together into a single return + * + * @author Steve Ebersole + */ +public interface AggregatedSelectExpression extends SelectExpression { + /** + * Retrieves a list of the selection {@link org.hibernate.type.Type types} being aggregated + * + * @return The list of types. + */ + public List getAggregatedSelectionTypeList(); + + /** + * Retrieve the aliases for the columns aggregated here. + * + * @return The column aliases. + */ + public String[] getAggregatedAliases(); + + /** + * Retrieve the {@link ResultTransformer} responsible for building aggregated select expression results into their + * aggregated form. + * + * @return The appropriate transformer + */ + public ResultTransformer getResultTransformer(); +} diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/ConstructorNode.java b/core/src/main/java/org/hibernate/hql/ast/tree/ConstructorNode.java index cd60514ce5..5547904a9a 100644 --- a/core/src/main/java/org/hibernate/hql/ast/tree/ConstructorNode.java +++ b/core/src/main/java/org/hibernate/hql/ast/tree/ConstructorNode.java @@ -29,6 +29,10 @@ import java.util.Arrays; import java.util.List; import org.hibernate.PropertyNotFoundException; +import org.hibernate.QueryException; +import org.hibernate.transform.ResultTransformer; +import org.hibernate.transform.AliasToBeanConstructorResultTransformer; +import org.hibernate.transform.Transformers; import org.hibernate.hql.ast.DetailedSemanticException; import org.hibernate.type.Type; import org.hibernate.util.ReflectHelper; @@ -42,13 +46,25 @@ import antlr.collections.AST; * * @author josh */ -public class ConstructorNode extends SelectExpressionList implements SelectExpression { - +public class ConstructorNode extends SelectExpressionList implements AggregatedSelectExpression { private Constructor constructor; private Type[] constructorArgumentTypes; private boolean isMap; private boolean isList; - + + public ResultTransformer getResultTransformer() { + if ( constructor != null ) { + return new AliasToBeanConstructorResultTransformer( constructor ); + } + else if ( isMap ) { + return Transformers.ALIAS_TO_ENTITY_MAP; + } + else if ( isList ) { + return Transformers.TO_LIST; + } + throw new QueryException( "Unable to determine proper dynamic-instantiation tranformer to use." ); + } + public boolean isMap() { return isMap; } @@ -56,8 +72,17 @@ public class ConstructorNode extends SelectExpressionList implements SelectExpre public boolean isList() { return isList; } - - public String[] getAliases() { + + private String[] aggregatedAliases; + + public String[] getAggregatedAliases() { + if ( aggregatedAliases == null ) { + aggregatedAliases = buildAggregatedAliases(); + } + return aggregatedAliases; + } + + private String[] buildAggregatedAliases() { SelectExpression[] selectExpressions = collectSelectExpressions(); String[] aliases = new String[selectExpressions.length] ; for ( int i=0; i + * Delegates to {@link #ASTPrinter(Class, boolean)} with {@link #isShowClassNames showClassNames} as true * - * @param tokenTypeConstants The class with token type constants in it. + * @param tokenTypeConstants The token types to use during printing; typically the {vocabulary}TokenTypes.java + * interface generated by ANTLR. */ public ASTPrinter(Class tokenTypeConstants) { - this.tokenTypeConstants = tokenTypeConstants; + this( ASTUtil.generateTokenNameCache( tokenTypeConstants ), true ); + } + + public ASTPrinter(boolean showClassNames) { + this( ( Map ) null, showClassNames ); } /** - * Returns true if the node class names will be displayed. + * Constructs a printer. * - * @return true if the node class names will be displayed. + * @param tokenTypeConstants The token types to use during printing; typically the {vocabulary}TokenTypes.java + * interface generated by ANTLR. + * @param showClassNames Should the AST class names be shown. + */ + public ASTPrinter(Class tokenTypeConstants, boolean showClassNames) { + this( ASTUtil.generateTokenNameCache( tokenTypeConstants ), showClassNames ); + } + + private ASTPrinter(Map tokenTypeNameCache, boolean showClassNames) { + this.tokenTypeNameCache = tokenTypeNameCache; + this.showClassNames = showClassNames; + } + + /** + * Getter for property 'showClassNames'. + * + * @return Value for property 'showClassNames'. */ public boolean isShowClassNames() { return showClassNames; } /** - * Enables or disables AST node class name display. + * Renders the AST into 'ASCII art' form and returns that string representation. * - * @param showClassNames true to enable class name display, false to disable - */ - public void setShowClassNames(boolean showClassNames) { - this.showClassNames = showClassNames; - } - - /** - * Prints the AST in 'ASCII art' tree form to the specified print stream. - * - * @param ast The AST to print. - * @param out The print stream. - */ - private void showAst(AST ast, PrintStream out) { - showAst( ast, new PrintWriter( out ) ); - } - - /** - * Prints the AST in 'ASCII art' tree form to the specified print writer. - * - * @param ast The AST to print. - * @param pw The print writer. - */ - public void showAst(AST ast, PrintWriter pw) { - ArrayList parents = new ArrayList(); - showAst( parents, pw, ast ); - pw.flush(); - } - - /** - * Prints the AST in 'ASCII art' tree form into a string. - * - * @param ast The AST to display. + * @param ast The AST to display. * @param header The header for the display. + * * @return The AST in 'ASCII art' form, as a string. */ public String showAsString(AST ast, String header) { @@ -116,53 +104,25 @@ public class ASTPrinter { } /** - * Get a single token type name in the specified set of token type constants (interface). + * Prints the AST in 'ASCII art' form to the specified print stream. * - * @param tokenTypeConstants Token type constants interface (e.g. HqlSqlTokenTypes.class). - * @param type The token type ( typically from ast.getType() ). - * @return The token type name, *or* the integer value if the name could not be found for some reason. + * @param ast The AST to print. + * @param out The print stream to which the AST should be printed. */ - public static String getConstantName(Class tokenTypeConstants, int type) { - String tokenTypeName = null; - if ( tokenTypeConstants != null ) { - Field[] fields = tokenTypeConstants.getFields(); - for ( int i = 0; i < fields.length; i++ ) { - Field field = fields[i]; - tokenTypeName = getTokenTypeName( field, type, true ); - if ( tokenTypeName != null ) { - break; // Stop if found. - } - } // for - } // if type constants were provided - - // Use the integer value if no token type name was found - if ( tokenTypeName == null ) { - tokenTypeName = Integer.toString( type ); - } - - return tokenTypeName; + public void showAst(AST ast, PrintStream out) { + showAst( ast, new PrintWriter( out ) ); } - private static String getTokenTypeName(Field field, int type, boolean checkType) { - if ( Modifier.isStatic( field.getModifiers() ) ) { - try { - Object value = field.get( null ); - if ( !checkType ) { - return field.getName(); - } - else if ( value instanceof Integer ) { - Integer integer = ( Integer ) value; - if ( integer.intValue() == type ) { - return field.getName(); - } - } // if value is an integer - } // try - catch ( IllegalArgumentException ignore ) { - } - catch ( IllegalAccessException ignore ) { - } - } // if the field is static - return null; + /** + * Prints the AST in 'ASCII art' tree form to the specified print writer. + * + * @param ast The AST to print. + * @param pw The print writer to which the AST should be written. + */ + public void showAst(AST ast, PrintWriter pw) { + ArrayList parents = new ArrayList(); + showAst( parents, pw, ast ); + pw.flush(); } /** @@ -172,33 +132,16 @@ public class ASTPrinter { * @return String - The token type name from the token type constant class, * or just the integer as a string if none exists. */ - private String getTokenTypeName(int type) { - // If the class with the constants in it was not supplied, just - // use the integer token type as the token type name. - if ( tokenTypeConstants == null ) { - return Integer.toString( type ); + public String getTokenTypeName(int type) { + final Integer typeInteger = new Integer( type ); + String value = null; + if ( tokenTypeNameCache != null ) { + value = ( String ) tokenTypeNameCache.get( typeInteger ); } - - // Otherwise, create a type id -> name map from the class if it - // hasn't already been created. - if ( tokenTypeNamesByTokenType == null ) { - Field[] fields = tokenTypeConstants.getFields(); - tokenTypeNamesByTokenType = new HashMap(); - String tokenTypeName = null; - for ( int i = 0; i < fields.length; i++ ) { - Field field = fields[i]; - tokenTypeName = getTokenTypeName( field, type, false ); - if ( tokenTypeName != null ) { - try { - tokenTypeNamesByTokenType.put( field.get( null ), field.getName() ); - } - catch ( IllegalAccessException ignore ) { - } - } - } // for - } // if the map hasn't been created. - - return ( String ) tokenTypeNamesByTokenType.get( new Integer( type ) ); + if ( value == null ) { + value = typeInteger.toString(); + } + return value; } private void showAst(ArrayList parents, PrintWriter pw, AST ast) { @@ -242,7 +185,7 @@ public class ASTPrinter { public String nodeToString(AST ast, boolean showClassName) { if ( ast == null ) { - return "{null}"; + return "{node:null}"; } StringBuffer buf = new StringBuffer(); buf.append( "[" ).append( getTokenTypeName( ast.getType() ) ).append( "] " ); @@ -252,15 +195,17 @@ public class ASTPrinter { buf.append( "'" ); String text = ast.getText(); - appendEscapedMultibyteChars(text, buf); + if ( text == null ) { + text = "{text:null}"; + } + appendEscapedMultibyteChars(text, buf); buf.append( "'" ); if ( ast instanceof DisplayableNode ) { DisplayableNode displayableNode = ( DisplayableNode ) ast; // Add a space before the display text. buf.append( " " ).append( displayableNode.getDisplayText() ); } - String s = buf.toString(); - return s; + return buf.toString(); } public static void appendEscapedMultibyteChars(String text, StringBuffer buf) { @@ -276,10 +221,9 @@ public class ASTPrinter { } } - public static String escapeMultibyteChars(String text) - { + public static String escapeMultibyteChars(String text) { StringBuffer buf = new StringBuffer(); appendEscapedMultibyteChars(text,buf); return buf.toString(); } -} +} \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/hql/ast/util/ASTUtil.java b/core/src/main/java/org/hibernate/hql/ast/util/ASTUtil.java index cf60c11614..8febf2b3dd 100644 --- a/core/src/main/java/org/hibernate/hql/ast/util/ASTUtil.java +++ b/core/src/main/java/org/hibernate/hql/ast/util/ASTUtil.java @@ -26,6 +26,10 @@ package org.hibernate.hql.ast.util; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import antlr.ASTFactory; import antlr.collections.AST; @@ -35,46 +39,62 @@ import antlr.collections.impl.ASTArray; * Provides utility methods for AST traversal and manipulation. * * @author Joshua Davis + * @author Steve Ebersole */ public final class ASTUtil { /** - * Private empty constructor. - * (or else checkstyle says: 'warning: Utility classes should not have a public or default constructor.') + * Disallow instantiation. * - * @deprecated (tell clover to ignore this) + * @deprecated (tellclovertoignorethis) */ private ASTUtil() { } /** * Creates a single node AST. + *

+ * TODO : this is silly, remove it... * * @param astFactory The factory. - * @param type The node type. - * @param text The node text. + * @param type The node type. + * @param text The node text. + * * @return AST - A single node tree. + * + * @deprecated silly */ public static AST create(ASTFactory astFactory, int type, String text) { - AST node = astFactory.create( type, text ); - return node; + return astFactory.create( type, text ); } /** - * Creates a single node AST as a sibling. + * Creates a single node AST as a sibling of the passed prevSibling, + * taking care to reorganize the tree correctly to account for this + * newly created node. * - * @param astFactory The factory. - * @param type The node type. - * @param text The node text. + * @param astFactory The factory. + * @param type The node type. + * @param text The node text. * @param prevSibling The previous sibling. - * @return AST - A single node tree. + * + * @return The created AST node. */ public static AST createSibling(ASTFactory astFactory, int type, String text, AST prevSibling) { AST node = astFactory.create( type, text ); - node.setNextSibling( prevSibling.getNextSibling() ); - prevSibling.setNextSibling( node ); - return node; + return insertSibling( node, prevSibling ); } + /** + * Inserts a node into a child subtree as a particularly positioned + * sibling taking care to properly reorganize the tree to account for this + * new addition. + * + * @param node The node to insert + * @param prevSibling The previous node at the sibling position + * where we want this node inserted. + * + * @return The return is the same as the node parameter passed in. + */ public static AST insertSibling(AST node, AST prevSibling) { node.setNextSibling( prevSibling.getNextSibling() ); prevSibling.setNextSibling( node ); @@ -85,11 +105,12 @@ public final class ASTUtil { * Creates a 'binary operator' subtree, given the information about the * parent and the two child nodex. * - * @param factory The AST factory. + * @param factory The AST factory. * @param parentType The type of the parent node. * @param parentText The text of the parent node. - * @param child1 The first child. - * @param child2 The second child. + * @param child1 The first child. + * @param child2 The second child. + * * @return AST - A new sub-tree of the form "(parent child1 child2)" */ public static AST createBinarySubtree(ASTFactory factory, int parentType, String parentText, AST child1, AST child2) { @@ -102,10 +123,11 @@ public final class ASTUtil { * Creates a single parent of the specified child (i.e. a 'unary operator' * subtree). * - * @param factory The AST factory. + * @param factory The AST factory. * @param parentType The type of the parent node. * @param parentText The text of the parent node. - * @param child The child. + * @param child The child. + * * @return AST - A new sub-tree of the form "(parent child)" */ public static AST createParent(ASTFactory factory, int parentType, String parentText, AST child) { @@ -126,100 +148,6 @@ public final class ASTUtil { return array[0]; } - /** - * Finds the first node of the specified type in the chain of children. - * - * @param parent The parent - * @param type The type to find. - * @return The first node of the specified type, or null if not found. - */ - public static AST findTypeInChildren(AST parent, int type) { - AST n = parent.getFirstChild(); - while ( n != null && n.getType() != type ) { - n = n.getNextSibling(); - } - return n; - } - - /** - * Returns the last direct child of 'n'. - * - * @param n The parent - * @return The last direct child of 'n'. - */ - public static AST getLastChild(AST n) { - return getLastSibling( n.getFirstChild() ); - } - - /** - * Returns the last sibling of 'a'. - * - * @param a The sibling. - * @return The last sibling of 'a'. - */ - private static AST getLastSibling(AST a) { - AST last = null; - while ( a != null ) { - last = a; - a = a.getNextSibling(); - } - return last; - } - - /** - * Returns the 'list' representation with some brackets around it for debugging. - * - * @param n The tree. - * @return The list representation of the tree. - */ - public static String getDebugString(AST n) { - StringBuffer buf = new StringBuffer(); - buf.append( "[ " ); - buf.append( ( n == null ) ? "{null}" : n.toStringTree() ); - buf.append( " ]" ); - return buf.toString(); - } - - /** - * Find the previous sibling in the parent for the given child. - * - * @param parent the parent node - * @param child the child to find the previous sibling of - * @return the previous sibling of the child - */ - public static AST findPreviousSibling(AST parent, AST child) { - AST prev = null; - AST n = parent.getFirstChild(); - while ( n != null ) { - if ( n == child ) { - return prev; - } - prev = n; - n = n.getNextSibling(); - } - throw new IllegalArgumentException( "Child not found in parent!" ); - } - - /** - * Determine if a given node (test) is a direct (throtle to one level down) - * child of another given node (fixture). - * - * @param fixture The node against which to testto be checked for children. - * @param test The node to be tested as being a child of the parent. - * @return True if test is contained in the fixtures's direct children; - * false otherwise. - */ - public static boolean isDirectChild(AST fixture, AST test) { - AST n = fixture.getFirstChild(); - while ( n != null ) { - if ( n == test ) { - return true; - } - n = n.getNextSibling(); - } - return false; - } - /** * Determine if a given node (test) is contained anywhere in the subtree * of another given node (fixture). @@ -242,11 +170,90 @@ public final class ASTUtil { return false; } + /** + * Finds the first node of the specified type in the chain of children. + * + * @param parent The parent + * @param type The type to find. + * + * @return The first node of the specified type, or null if not found. + */ + public static AST findTypeInChildren(AST parent, int type) { + AST n = parent.getFirstChild(); + while ( n != null && n.getType() != type ) { + n = n.getNextSibling(); + } + return n; + } + + /** + * Returns the last direct child of 'n'. + * + * @param n The parent + * + * @return The last direct child of 'n'. + */ + public static AST getLastChild(AST n) { + return getLastSibling( n.getFirstChild() ); + } + + /** + * Returns the last sibling of 'a'. + * + * @param a The sibling. + * + * @return The last sibling of 'a'. + */ + private static AST getLastSibling(AST a) { + AST last = null; + while ( a != null ) { + last = a; + a = a.getNextSibling(); + } + return last; + } + + /** + * Returns the 'list' representation with some brackets around it for debugging. + * + * @param n The tree. + * + * @return The list representation of the tree. + */ + public static String getDebugString(AST n) { + StringBuffer buf = new StringBuffer(); + buf.append( "[ " ); + buf.append( ( n == null ) ? "{null}" : n.toStringTree() ); + buf.append( " ]" ); + return buf.toString(); + } + + /** + * Find the previous sibling in the parent for the given child. + * + * @param parent the parent node + * @param child the child to find the previous sibling of + * + * @return the previous sibling of the child + */ + public static AST findPreviousSibling(AST parent, AST child) { + AST prev = null; + AST n = parent.getFirstChild(); + while ( n != null ) { + if ( n == child ) { + return prev; + } + prev = n; + n = n.getNextSibling(); + } + throw new IllegalArgumentException( "Child not found in parent!" ); + } + /** * Makes the child node a sibling of the parent, reconnecting all siblings. * * @param parent the parent - * @param child the child + * @param child the child */ public static void makeSiblingOfParent(AST parent, AST child) { AST prev = findPreviousSibling( parent, child ); @@ -295,7 +302,7 @@ public final class ASTUtil { * Inserts the child as the first child of the parent, all other children are shifted over to the 'right'. * * @param parent the parent - * @param child the new first child + * @param child the new first child */ public static void insertChild(AST parent, AST child) { if ( parent.getFirstChild() == null ) { @@ -308,6 +315,13 @@ public final class ASTUtil { } } + private static ASTArray createAstArray(ASTFactory factory, int size, int parentType, String parentText, AST child1) { + ASTArray array = new ASTArray( size ); + array.add( factory.create( parentType, parentText ) ); + array.add( child1 ); + return array; + } + /** * Filters nodes out of a tree. */ @@ -316,6 +330,7 @@ public final class ASTUtil { * Returns true if the node should be filtered out. * * @param n The node. + * * @return true if the node should be filtered out, false to keep the node. */ boolean exclude(AST n); @@ -332,17 +347,7 @@ public final class ASTUtil { public abstract boolean include(AST node); } - private static ASTArray createAstArray(ASTFactory factory, int size, int parentType, String parentText, AST child1) { - ASTArray array = new ASTArray( size ); - array.add( factory.create( parentType, parentText ) ); - array.add( child1 ); - return array; - } - public static List collectChildren(AST root, FilterPredicate predicate) { -// List children = new ArrayList(); -// collectChildren( children, root, predicate ); -// return children; return new CollectingNodeVisitor( predicate ).collect( root ); } @@ -371,13 +376,89 @@ public final class ASTUtil { } } - private static void collectChildren(List children, AST root, FilterPredicate predicate) { - for ( AST n = root.getFirstChild(); n != null; n = n.getNextSibling() ) { - if ( predicate == null || !predicate.exclude( n ) ) { - children.add( n ); + /** + * Method to generate a map of token type names, keyed by their token type values. + * + * @param tokenTypeInterface The *TokenTypes interface (or implementor of said interface). + * @return The generated map. + */ + public static Map generateTokenNameCache(Class tokenTypeInterface) { + final Field[] fields = tokenTypeInterface.getFields(); + Map cache = new HashMap( (int)( fields.length * .75 ) + 1 ); + for ( int i = 0; i < fields.length; i++ ) { + final Field field = fields[i]; + if ( Modifier.isStatic( field.getModifiers() ) ) { + try { + cache.put( field.get( null ), field.getName() ); + } + catch ( Throwable ignore ) { + } } - collectChildren( children, n, predicate ); } + return cache; } + /** + * Get the name of a constant defined on the given class which has the given value. + *

+ * Note, if multiple constants have this value, the first will be returned which is known to be different + * on different JVM implementations. + * + * @param owner The class which defines the constant + * @param value The value of the constant. + * + * @return The token type name, *or* the integer value if the name could not be found. + * + * @deprecated Use #getTokenTypeName instead + */ + public static String getConstantName(Class owner, int value) { + return getTokenTypeName( owner, value ); + } + + /** + * Intended to retrieve the name of an AST token type based on the token type interface. However, this + * method can be used to look up the name of any constant defined on a class/interface based on the constant value. + * Note that if multiple constants have this value, the first will be returned which is known to be different + * on different JVM implementations. + * + * @param tokenTypeInterface The *TokenTypes interface (or one of its implementors). + * @param tokenType The token type value. + * + * @return The corresponding name. + */ + public static String getTokenTypeName(Class tokenTypeInterface, int tokenType) { + String tokenTypeName = Integer.toString( tokenType ); + if ( tokenTypeInterface != null ) { + Field[] fields = tokenTypeInterface.getFields(); + for ( int i = 0; i < fields.length; i++ ) { + final Integer fieldValue = extractIntegerValue( fields[i] ); + if ( fieldValue != null && fieldValue.intValue() == tokenType ) { + tokenTypeName = fields[i].getName(); + break; + } + } + } + return tokenTypeName; + } + + private static Integer extractIntegerValue(Field field) { + Integer rtn = null; + try { + Object value = field.get( null ); + if ( value instanceof Integer ) { + rtn = ( Integer ) value; + } + else if ( value instanceof Short ) { + rtn = new Integer( ( ( Short ) value ).intValue() ); + } + else if ( value instanceof Long ) { + if ( ( ( Long ) value ).longValue() <= Integer.MAX_VALUE ) { + rtn = new Integer( ( ( Long ) value ).intValue() ); + } + } + } + catch ( IllegalAccessException ignore ) { + } + return rtn; + } } diff --git a/core/src/main/java/org/hibernate/loader/hql/QueryLoader.java b/core/src/main/java/org/hibernate/loader/hql/QueryLoader.java index 5ed86db81b..3e27c0d2ea 100644 --- a/core/src/main/java/org/hibernate/loader/hql/QueryLoader.java +++ b/core/src/main/java/org/hibernate/loader/hql/QueryLoader.java @@ -47,6 +47,7 @@ import org.hibernate.hql.ast.QueryTranslatorImpl; import org.hibernate.hql.ast.tree.FromElement; import org.hibernate.hql.ast.tree.SelectClause; import org.hibernate.hql.ast.tree.QueryNode; +import org.hibernate.hql.ast.tree.AggregatedSelectExpression; import org.hibernate.impl.IteratorImpl; import org.hibernate.loader.BasicLoader; import org.hibernate.param.ParameterSpecification; @@ -96,7 +97,7 @@ public class QueryLoader extends BasicLoader { private int selectLength; - private ResultTransformer selectNewTransformer; + private ResultTransformer implicitResultTransformer; private String[] queryReturnAliases; private LockMode[] defaultLockModes; @@ -128,10 +129,10 @@ public class QueryLoader extends BasicLoader { //sqlResultTypes = selectClause.getSqlResultTypes(); queryReturnTypes = selectClause.getQueryReturnTypes(); - selectNewTransformer = HolderInstantiator.createSelectNewTransformer( - selectClause.getConstructor(), - selectClause.isMap(), - selectClause.isList()); + AggregatedSelectExpression aggregatedSelectExpression = selectClause.getAggregatedSelectExpression(); + implicitResultTransformer = aggregatedSelectExpression == null + ? null + : aggregatedSelectExpression.getResultTransformer(); queryReturnAliases = selectClause.getQueryReturnAliases(); List collectionFromElements = selectClause.getCollectionFromElements(); @@ -341,7 +342,7 @@ public class QueryLoader extends BasicLoader { } private boolean hasSelectNew() { - return selectNewTransformer!=null; + return implicitResultTransformer != null; } protected Object getResultColumnOrRow(Object[] row, ResultTransformer transformer, ResultSet rs, SessionImplementor session) @@ -374,7 +375,7 @@ public class QueryLoader extends BasicLoader { protected List getResultList(List results, ResultTransformer resultTransformer) throws QueryException { // meant to handle dynamic instantiation queries... - HolderInstantiator holderInstantiator = HolderInstantiator.getHolderInstantiator(selectNewTransformer, resultTransformer, queryReturnAliases); + HolderInstantiator holderInstantiator = buildHolderInstantiator( resultTransformer ); if ( holderInstantiator.isRequired() ) { for ( int i = 0; i < results.size(); i++ ) { Object[] row = ( Object[] ) results.get( i ); @@ -382,16 +383,25 @@ public class QueryLoader extends BasicLoader { results.set( i, result ); } - if(!hasSelectNew() && resultTransformer!=null) { + if ( !hasSelectNew() && resultTransformer != null ) { return resultTransformer.transformList(results); - } else { + } + else { return results; } - } else { + } + else { return results; } } + private HolderInstantiator buildHolderInstantiator(ResultTransformer queryLocalResultTransformer) { + return HolderInstantiator.getHolderInstantiator( + implicitResultTransformer, + queryLocalResultTransformer, + queryReturnAliases + ); + } // --- Query translator methods --- public List list( @@ -418,10 +428,8 @@ public class QueryLoader extends BasicLoader { } try { - final PreparedStatement st = prepareQueryStatement( queryParameters, false, session ); - - if(queryParameters.isCallable()) { + if ( queryParameters.isCallable() ) { throw new QueryException("iterate() not supported for callable statements"); } final ResultSet rs = getResultSet(st, queryParameters.hasAutoDiscoverScalarTypes(), false, queryParameters.getRowSelection(), session); @@ -431,8 +439,8 @@ public class QueryLoader extends BasicLoader { session, queryReturnTypes, queryTranslator.getColumnNames(), - HolderInstantiator.getHolderInstantiator(selectNewTransformer, queryParameters.getResultTransformer(), queryReturnAliases) - ); + buildHolderInstantiator( queryParameters.getResultTransformer() ) + ); if ( stats ) { session.getFactory().getStatisticsImplementor().queryExecuted( @@ -461,7 +469,12 @@ public class QueryLoader extends BasicLoader { final QueryParameters queryParameters, final SessionImplementor session) throws HibernateException { checkQuery( queryParameters ); - return scroll( queryParameters, queryReturnTypes, HolderInstantiator.getHolderInstantiator(selectNewTransformer, queryParameters.getResultTransformer(), queryReturnAliases), session ); + return scroll( + queryParameters, + queryReturnTypes, + buildHolderInstantiator( queryParameters.getResultTransformer() ), + session + ); } // -- Implementation private methods -- diff --git a/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index b80c30a17a..69b8e5ed4e 100644 --- a/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -99,6 +99,7 @@ import org.hibernate.sql.SelectFragment; import org.hibernate.sql.SimpleSelect; import org.hibernate.sql.Template; import org.hibernate.sql.Update; +import org.hibernate.sql.AliasGenerator; import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.tuple.Tuplizer; @@ -982,8 +983,14 @@ public abstract class AbstractEntityPersister } - public String propertySelectFragment(String name, String suffix, boolean allProperties) { + public String propertySelectFragment(String tableAlias, String suffix, boolean allProperties) { + return propertySelectFragmentFragment( tableAlias, suffix, allProperties ).toFragmentString(); + } + public SelectFragment propertySelectFragmentFragment( + String tableAlias, + String suffix, + boolean allProperties) { SelectFragment select = new SelectFragment() .setSuffix( suffix ) .setUsedAliases( getIdentifierAliases() ); @@ -996,7 +1003,7 @@ public abstract class AbstractEntityPersister !isSubclassTableSequentialSelect( columnTableNumbers[i] ) && subclassColumnSelectableClosure[i]; if ( selectable ) { - String subalias = generateTableAlias( name, columnTableNumbers[i] ); + String subalias = generateTableAlias( tableAlias, columnTableNumbers[i] ); select.addColumn( subalias, columns[i], columnAliases[i] ); } } @@ -1008,20 +1015,20 @@ public abstract class AbstractEntityPersister boolean selectable = ( allProperties || !subclassFormulaLazyClosure[i] ) && !isSubclassTableSequentialSelect( formulaTableNumbers[i] ); if ( selectable ) { - String subalias = generateTableAlias( name, formulaTableNumbers[i] ); + String subalias = generateTableAlias( tableAlias, formulaTableNumbers[i] ); select.addFormula( subalias, formulaTemplates[i], formulaAliases[i] ); } } if ( entityMetamodel.hasSubclasses() ) { - addDiscriminatorToSelect( select, name, suffix ); + addDiscriminatorToSelect( select, tableAlias, suffix ); } if ( hasRowId() ) { - select.addColumn( name, rowIdName, ROWID_ALIAS ); + select.addColumn( tableAlias, rowIdName, ROWID_ALIAS ); } - return select.toFragmentString(); + return select; } public Object[] getDatabaseSnapshot(Serializable id, SessionImplementor session) diff --git a/core/src/main/java/org/hibernate/persister/entity/Queryable.java b/core/src/main/java/org/hibernate/persister/entity/Queryable.java index ddf9498a51..ba6ec2ceb1 100644 --- a/core/src/main/java/org/hibernate/persister/entity/Queryable.java +++ b/core/src/main/java/org/hibernate/persister/entity/Queryable.java @@ -24,6 +24,8 @@ */ package org.hibernate.persister.entity; +import org.hibernate.sql.SelectFragment; + /** * Extends the generic EntityPersister contract to add * operations required by the Hibernate Query Language @@ -60,6 +62,7 @@ public interface Queryable extends Loadable, PropertyMapping, Joinable { */ public String propertySelectFragment(String alias, String suffix, boolean allProperties); + public SelectFragment propertySelectFragmentFragment(String alias, String suffix, boolean allProperties); /** * Get the names of columns used to persist the identifier */ diff --git a/core/src/main/java/org/hibernate/sql/AliasGenerator.java b/core/src/main/java/org/hibernate/sql/AliasGenerator.java new file mode 100644 index 0000000000..b3e5830dba --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/AliasGenerator.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC. + * + * 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.sql; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public interface AliasGenerator { + public String generateAlias(String sqlExpression); +} diff --git a/core/src/main/java/org/hibernate/sql/SelectExpression.java b/core/src/main/java/org/hibernate/sql/SelectExpression.java new file mode 100644 index 0000000000..a511ff9d4d --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/SelectExpression.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC. + * + * 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.sql; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public interface SelectExpression { + public String getExpression(); + public String getAlias(); +} diff --git a/core/src/main/java/org/hibernate/sql/SelectFragment.java b/core/src/main/java/org/hibernate/sql/SelectFragment.java index ceeff052f7..e0b67b4b60 100644 --- a/core/src/main/java/org/hibernate/sql/SelectFragment.java +++ b/core/src/main/java/org/hibernate/sql/SelectFragment.java @@ -46,7 +46,15 @@ public class SelectFragment { private String[] usedAliases; public SelectFragment() {} - + + public List getColumns() { + return columns; + } + + public String getExtraSelectList() { + return extraSelectList; + } + public SelectFragment setUsedAliases(String[] aliases) { usedAliases = aliases; return this; diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java index 7db8b527f4..f2775f01e1 100644 --- a/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java @@ -32,6 +32,7 @@ import antlr.collections.AST; import org.hibernate.sql.Template; import org.hibernate.dialect.function.SQLFunction; +import org.hibernate.util.StringHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,6 +53,28 @@ public class OrderByFragmentParser extends GeneratedOrderByFragmentParser { this.context = context; } + + // handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private int traceDepth = 0; + + + public void traceIn(String ruleName) { + if ( inputState.guessing > 0 ) { + return; + } + String prefix = StringHelper.repeat( '-', (traceDepth++ * 2) ) + "-> "; + log.trace( prefix + ruleName ); + } + + public void traceOut(String ruleName) { + if ( inputState.guessing > 0 ) { + return; + } + String prefix = "<-" + StringHelper.repeat( '-', (--traceDepth * 2) ) + " "; + log.trace( prefix + ruleName ); + } + /** * {@inheritDoc} */ diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java index 741ef77055..163ec4b1df 100644 --- a/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java @@ -26,13 +26,50 @@ package org.hibernate.sql.ordering.antlr; import antlr.collections.AST; +import org.hibernate.util.StringHelper; +import org.hibernate.hql.ast.util.ASTPrinter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * TODO : javadoc * * @author Steve Ebersole */ public class OrderByFragmentRenderer extends GeneratedOrderByFragmentRenderer { + private static final Logger log = LoggerFactory.getLogger( OrderByFragmentRenderer.class ); + private static final ASTPrinter printer = new ASTPrinter( GeneratedOrderByFragmentRendererTokenTypes.class ); + protected void out(AST ast) { out( ( ( Node ) ast ).getRenderableText() ); } + + + // handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private int traceDepth = 0; + + public void traceIn(String ruleName, AST tree) { + if ( inputState.guessing > 0 ) { + return; + } + String prefix = StringHelper.repeat( '-', (traceDepth++ * 2) ) + "-> "; + String traceText = ruleName + " (" + buildTraceNodeName(tree) + ")"; + log.trace( prefix + traceText ); + } + + private String buildTraceNodeName(AST tree) { + return tree == null + ? "???" + : tree.getText() + " [" + printer.getTokenTypeName( tree.getType() ) + "]"; + } + + public void traceOut(String ruleName, AST tree) { + if ( inputState.guessing > 0 ) { + return; + } + String prefix = "<-" + StringHelper.repeat( '-', (--traceDepth * 2) ) + " "; + log.trace( prefix + ruleName ); + } } diff --git a/core/src/main/java/org/hibernate/util/ArrayHelper.java b/core/src/main/java/org/hibernate/util/ArrayHelper.java index 4a7c14a525..48a527bfa3 100644 --- a/core/src/main/java/org/hibernate/util/ArrayHelper.java +++ b/core/src/main/java/org/hibernate/util/ArrayHelper.java @@ -84,7 +84,7 @@ public final class ArrayHelper { } public static String[] toStringArray(Collection coll) { - return (String[]) coll.toArray(EMPTY_STRING_ARRAY); + return (String[]) coll.toArray( new String[coll.size()] ); } public static String[][] to2DStringArray(Collection coll) { @@ -96,7 +96,7 @@ public final class ArrayHelper { } public static Type[] toTypeArray(Collection coll) { - return (Type[]) coll.toArray(EMPTY_TYPE_ARRAY); + return (Type[]) coll.toArray( new Type[coll.size()] ); } public static int[] toIntArray(Collection coll) { @@ -136,17 +136,13 @@ public final class ArrayHelper { public static String[] slice(String[] strings, int begin, int length) { String[] result = new String[length]; - for ( int i=0; i