[hibernate-commits] Hibernate SVN: r19452 - in core/branches/Branch_3_5: core/src/main/java/org/hibernate/dialect and 5 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Mon May 10 15:40:53 EDT 2010


Author: steve.ebersole at jboss.com
Date: 2010-05-10 15:40:52 -0400 (Mon, 10 May 2010)
New Revision: 19452

Added:
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/AvgFunction.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/AvgWithArgumentCastFunction.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/TemplateRenderer.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/FunctionNode.java
Modified:
   core/branches/Branch_3_5/core/src/main/antlr/hql-sql.g
   core/branches/Branch_3_5/core/src/main/antlr/sql-gen.g
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/DB2Dialect.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/Dialect.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/H2Dialect.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/HSQLDialect.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/SQLFunction.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/SQLFunctionTemplate.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/SqlGenerator.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/AggregateNode.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/MethodNode.java
   core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/util/SessionFactoryHelper.java
   core/branches/Branch_3_5/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java
Log:
HHH-5173 - hql - average returns double but looses the decimal part


Modified: core/branches/Branch_3_5/core/src/main/antlr/hql-sql.g
===================================================================
--- core/branches/Branch_3_5/core/src/main/antlr/hql-sql.g	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/antlr/hql-sql.g	2010-05-10 19:40:52 UTC (rev 19452)
@@ -197,6 +197,8 @@
 
 	protected void processFunction(AST functionCall,boolean inSelect) throws SemanticException { }
 
+	protected void processAggregation(AST node, boolean inSelect) throws SemanticException { }
+
 	protected void processConstructor(AST constructor) throws SemanticException { }
 
 	protected AST generateNamedParameter(AST delimiterNode, AST nameNode) throws SemanticException {

Modified: core/branches/Branch_3_5/core/src/main/antlr/sql-gen.g
===================================================================
--- core/branches/Branch_3_5/core/src/main/antlr/sql-gen.g	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/antlr/sql-gen.g	2010-05-10 19:40:52 UTC (rev 19452)
@@ -406,10 +406,13 @@
 	;
 
 aggregate
-	: #(a:AGGREGATE { out(a); out("("); }  expr { out(")"); } )
-	;
+    : #(
+        a:AGGREGATE { beginFunctionTemplate( a, a ); }
+     	expr
+        { endFunctionTemplate( a ); }
+    )
+    ;
 
-
 methodCall
 	: #(m:METHOD_CALL i:METHOD_NAME { beginFunctionTemplate(m,i); }
 	 ( #(EXPR_LIST (arguments)? ) )?

Modified: core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/DB2Dialect.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/DB2Dialect.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/DB2Dialect.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -31,6 +31,7 @@
 
 import org.hibernate.Hibernate;
 import org.hibernate.cfg.Environment;
+import org.hibernate.dialect.function.AvgWithArgumentCastFunction;
 import org.hibernate.dialect.function.NoArgSQLFunction;
 import org.hibernate.dialect.function.SQLFunctionTemplate;
 import org.hibernate.dialect.function.StandardSQLFunction;
@@ -64,6 +65,8 @@
 		registerColumnType( Types.LONGVARCHAR, "long varchar" );
 		registerColumnType( Types.LONGVARBINARY, "long varchar for bit data" );
 
+		registerFunction( "avg", new AvgWithArgumentCastFunction( "double" ) );
+
 		registerFunction("abs", new StandardSQLFunction("abs") );
 		registerFunction("absval", new StandardSQLFunction("absval") );
 		registerFunction("sign", new StandardSQLFunction("sign", Hibernate.INTEGER) );

Modified: core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/Dialect.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/Dialect.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/Dialect.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -44,6 +44,7 @@
 import org.hibernate.QueryException;
 import org.hibernate.LockOptions;
 import org.hibernate.cfg.Environment;
+import org.hibernate.dialect.function.AvgFunction;
 import org.hibernate.dialect.function.CastFunction;
 import org.hibernate.dialect.function.SQLFunction;
 import org.hibernate.dialect.function.SQLFunctionTemplate;
@@ -128,24 +129,7 @@
 				}
 		);
 
-		STANDARD_AGGREGATE_FUNCTIONS.put(
-				"avg",
-				new StandardSQLFunction("avg") {
-					public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
-						int[] sqlTypes;
-						try {
-							sqlTypes = columnType.sqlTypes( mapping );
-						}
-						catch ( MappingException me ) {
-							throw new QueryException( me );
-						}
-						if ( sqlTypes.length != 1 ) {
-							throw new QueryException( "multi-column type in avg()" );
-						}
-						return Hibernate.DOUBLE;
-					}
-				}
-		);
+		STANDARD_AGGREGATE_FUNCTIONS.put( "avg", new AvgFunction() );
 
 		STANDARD_AGGREGATE_FUNCTIONS.put( "max", new StandardSQLFunction("max") );
 		STANDARD_AGGREGATE_FUNCTIONS.put( "min", new StandardSQLFunction("min") );

Modified: core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/H2Dialect.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/H2Dialect.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/H2Dialect.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -28,6 +28,7 @@
 
 import org.hibernate.Hibernate;
 import org.hibernate.cfg.Environment;
+import org.hibernate.dialect.function.AvgWithArgumentCastFunction;
 import org.hibernate.dialect.function.NoArgSQLFunction;
 import org.hibernate.dialect.function.StandardSQLFunction;
 import org.hibernate.dialect.function.VarArgsSQLFunction;
@@ -84,6 +85,9 @@
 		registerColumnType( Types.BLOB, "blob" );
 		registerColumnType( Types.CLOB, "clob" );
 
+		// Aggregations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+		registerFunction( "avg", new AvgWithArgumentCastFunction( "double" ) );
+
 		// select topic, syntax from information_schema.help
 		// where section like 'Function%' order by section, topic
 		//

Modified: core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/HSQLDialect.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/HSQLDialect.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/HSQLDialect.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -32,6 +32,7 @@
 import org.hibernate.LockMode;
 import org.hibernate.StaleObjectStateException;
 import org.hibernate.JDBCException;
+import org.hibernate.dialect.function.AvgWithArgumentCastFunction;
 import org.hibernate.engine.SessionImplementor;
 import org.hibernate.persister.entity.Lockable;
 import org.hibernate.cfg.Environment;
@@ -83,6 +84,8 @@
 		registerColumnType( Types.LONGVARBINARY, "longvarbinary" );
 		registerColumnType( Types.LONGVARCHAR, "longvarchar" );
 
+		registerFunction( "avg", new AvgWithArgumentCastFunction( "double" ) );
+
 		registerFunction( "ascii", new StandardSQLFunction( "ascii", Hibernate.INTEGER ) );
 		registerFunction( "char", new StandardSQLFunction( "char", Hibernate.CHARACTER ) );
 		registerFunction( "lower", new StandardSQLFunction( "lower" ) );

Added: core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/AvgFunction.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/AvgFunction.java	                        (rev 0)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/AvgFunction.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -0,0 +1,70 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, 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.dialect.function;
+
+import java.util.List;
+
+import org.hibernate.Hibernate;
+import org.hibernate.MappingException;
+import org.hibernate.QueryException;
+import org.hibernate.engine.Mapping;
+import org.hibernate.engine.SessionFactoryImplementor;
+import org.hibernate.type.Type;
+
+/**
+ * The basic JPA spec compliant definition po<tt>AVG</tt> aggregation function.
+ *
+ * @author Steve Ebersole
+ */
+public class AvgFunction implements SQLFunction {
+	public final Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
+		int[] sqlTypes;
+		try {
+			sqlTypes = columnType.sqlTypes( mapping );
+		}
+		catch ( MappingException me ) {
+			throw new QueryException( me );
+		}
+		if ( sqlTypes.length != 1 ) {
+			throw new QueryException( "multiple-column type in avg()" );
+		}
+		return Hibernate.DOUBLE;
+	}
+
+	public final boolean hasArguments() {
+		return true;
+	}
+
+	public final boolean hasParenthesesIfNoArguments() {
+		return true;
+	}
+
+	public String render(List args, SessionFactoryImplementor factory) throws QueryException {
+		return "avg(" + args.get( 0 ) + ")";
+	}
+
+	public final String toString() {
+		return "avg";
+	}
+}

Added: core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/AvgWithArgumentCastFunction.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/AvgWithArgumentCastFunction.java	                        (rev 0)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/AvgWithArgumentCastFunction.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -0,0 +1,53 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, 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.dialect.function;
+
+import java.util.List;
+
+import org.hibernate.QueryException;
+import org.hibernate.engine.SessionFactoryImplementor;
+
+/**
+ * Some databases strictly return the type of the of the aggregation value for <tt>AVG</tt> which is
+ * problematic in the case of averaging integers because the decimals will be dropped.  The usual workaround
+ * is to cast the integer argument as some form of double/decimal.
+ * <p/>
+ * A downside to this approach is that we always wrap the avg() argument in a cast even though we may not need or want
+ * to.  A more full-featured solution would be defining {@link SQLFunction} such that we render based on the first
+ * argument; essentially have {@link SQLFunction} describe the basic metadata about the function and merge the
+ * {@link SQLFunction#getReturnType} and {@link SQLFunction#render} methods into a
+ *
+ * @author Steve Ebersole
+ */
+public class AvgWithArgumentCastFunction extends AvgFunction {
+	private final TemplateRenderer renderer;
+
+	public AvgWithArgumentCastFunction(String castType) {
+		renderer = new TemplateRenderer( "avg(cast(?1 as " + castType + "))" );
+	}
+
+	public String render(List args, SessionFactoryImplementor factory) throws QueryException {
+		return renderer.render( args, factory );
+	}
+}

Modified: core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/SQLFunction.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/SQLFunction.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/SQLFunction.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -43,18 +43,6 @@
  */
 public interface SQLFunction {
 	/**
-	 * The return type of the function.  May be either a concrete type which
-	 * is preset, or variable depending upon the type of the first function
-	 * argument.
-	 *
-	 * @param columnType the type of the first argument
-	 * @param mapping The mapping source.
-	 * @return The type to be expected as a return.
-	 * @throws org.hibernate.QueryException Indicates an issue resolving the return type.
-	 */
-	public Type getReturnType(Type columnType, Mapping mapping) throws QueryException;
-
-	/**
 	 * Does this function have any arguments?
 	 *
 	 * @return True if the function expects to have parameters; false otherwise.
@@ -69,13 +57,33 @@
 	public boolean hasParenthesesIfNoArguments();
 
 	/**
+	 * The return type of the function.  May be either a concrete type which
+	 * is preset, or variable depending upon the type of the first function
+	 * argument.
+	 *
+	 * @param columnType the type of the first argument
+	 * @param mapping The mapping source.
+	 *
+	 * @return The type to be expected as a return.
+	 *
+	 * @throws org.hibernate.QueryException Indicates an issue resolving the return type.
+	 *
+	 * @deprecated See http://opensource.atlassian.com/projects/hibernate/browse/HHH-5212
+	 */
+	public Type getReturnType(Type columnType, Mapping mapping) throws QueryException;
+
+	/**
 	 * Render the function call as SQL fragment.
 	 *
 	 * @param args The function arguments
 	 * @param factory The SessionFactory
+	 *
 	 * @return The rendered function call
+	 *
 	 * @throws org.hibernate.QueryException Indicates a problem rendering the
 	 * function call.
+	 *
+	 * @deprecated See http://opensource.atlassian.com/projects/hibernate/browse/HHH-5212
 	 */
 	public String render(List args, SessionFactoryImplementor factory) throws QueryException;
 }

Modified: core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/SQLFunctionTemplate.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/SQLFunctionTemplate.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/SQLFunctionTemplate.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -1,10 +1,10 @@
 /*
  * Hibernate, Relational Persistence for Idiomatic Java
  *
- * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * Copyright (c) 2010, 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 Middleware LLC.
+ * 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
@@ -20,7 +20,6 @@
  * Free Software Foundation, Inc.
  * 51 Franklin Street, Fifth Floor
  * Boston, MA  02110-1301  USA
- *
  */
 package org.hibernate.dialect.function;
 
@@ -45,102 +44,51 @@
  */
 public class SQLFunctionTemplate implements SQLFunction {
 	private final Type type;
-	private final boolean hasArguments;
+	private final TemplateRenderer renderer;
 	private final boolean hasParenthesesIfNoArgs;
 
-	private final String template;
-	private final String[] chunks;
-	private final int[] paramIndexes;
-
 	public SQLFunctionTemplate(Type type, String template) {
 		this( type, template, true );
 	}
 
 	public SQLFunctionTemplate(Type type, String template, boolean hasParenthesesIfNoArgs) {
 		this.type = type;
-		this.template = template;
-
-		List chunkList = new ArrayList();
-		List paramList = new ArrayList();
-		StringBuffer chunk = new StringBuffer( 10 );
-		StringBuffer index = new StringBuffer( 2 );
-
-		for ( int i = 0; i < template.length(); ++i ) {
-			char c = template.charAt( i );
-			if ( c == '?' ) {
-				chunkList.add( chunk.toString() );
-				chunk.delete( 0, chunk.length() );
-
-				while ( ++i < template.length() ) {
-					c = template.charAt( i );
-					if ( Character.isDigit( c ) ) {
-						index.append( c );
-					}
-					else {
-						chunk.append( c );
-						break;
-					}
-				}
-
-				paramList.add( new Integer( Integer.parseInt( index.toString() ) - 1 ) );
-				index.delete( 0, index.length() );
-			}
-			else {
-				chunk.append( c );
-			}
-		}
-
-		if ( chunk.length() > 0 ) {
-			chunkList.add( chunk.toString() );
-		}
-
-		chunks = ( String[] ) chunkList.toArray( new String[chunkList.size()] );
-		paramIndexes = new int[paramList.size()];
-		for ( int i = 0; i < paramIndexes.length; ++i ) {
-			paramIndexes[i] = ( ( Integer ) paramList.get( i ) ).intValue();
-		}
-
-		hasArguments = paramIndexes.length > 0;
+		this.renderer = new TemplateRenderer( template );
 		this.hasParenthesesIfNoArgs = hasParenthesesIfNoArgs;
 	}
 
 	/**
-	 * Applies the template to passed in arguments.
-	 * @param args function arguments
-	 *
-	 * @return generated SQL function call
+	 * {@inheritDoc}
 	 */
 	public String render(List args, SessionFactoryImplementor factory) {
-		StringBuffer buf = new StringBuffer();
-		for ( int i = 0; i < chunks.length; ++i ) {
-			if ( i < paramIndexes.length ) {
-				Object arg = paramIndexes[i] < args.size() ? args.get( paramIndexes[i] ) : null;
-				if ( arg != null ) {
-					buf.append( chunks[i] ).append( arg );
-				}
-			}
-			else {
-				buf.append( chunks[i] );
-			}
-		}
-		return buf.toString();
+		return renderer.render( args, factory );
 	}
 
-	// SQLFunction implementation
-
+	/**
+	 * {@inheritDoc}
+	 */
 	public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
 		return type;
 	}
 
+	/**
+	 * {@inheritDoc}
+	 */
 	public boolean hasArguments() {
-		return hasArguments;
+		return renderer.getAnticipatedNumberOfArguments() > 0;
 	}
 
+	/**
+	 * {@inheritDoc}
+	 */
 	public boolean hasParenthesesIfNoArguments() {
 		return hasParenthesesIfNoArgs;
 	}
-	
+
+	/**
+	 * {@inheritDoc}
+	 */
 	public String toString() {
-		return template;
+		return renderer.getTemplate();
 	}
 }

Added: core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/TemplateRenderer.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/TemplateRenderer.java	                        (rev 0)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/dialect/function/TemplateRenderer.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -0,0 +1,121 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, 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.dialect.function;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.hibernate.engine.SessionFactoryImplementor;
+
+/**
+ * Delegate for handling function "templates".
+ *
+ * @author Steve Ebersole
+ */
+public class TemplateRenderer {
+	private static final Logger log = LoggerFactory.getLogger( TemplateRenderer.class );
+
+	private final String template;
+	private final String[] chunks;
+	private final int[] paramIndexes;
+
+	public TemplateRenderer(String template) {
+		this.template = template;
+
+		List chunkList = new ArrayList();
+		List paramList = new ArrayList();
+		StringBuffer chunk = new StringBuffer( 10 );
+		StringBuffer index = new StringBuffer( 2 );
+
+		for ( int i = 0; i < template.length(); ++i ) {
+			char c = template.charAt( i );
+			if ( c == '?' ) {
+				chunkList.add( chunk.toString() );
+				chunk.delete( 0, chunk.length() );
+
+				while ( ++i < template.length() ) {
+					c = template.charAt( i );
+					if ( Character.isDigit( c ) ) {
+						index.append( c );
+					}
+					else {
+						chunk.append( c );
+						break;
+					}
+				}
+
+				paramList.add( Integer.valueOf( index.toString() ) );
+				index.delete( 0, index.length() );
+			}
+			else {
+				chunk.append( c );
+			}
+		}
+
+		if ( chunk.length() > 0 ) {
+			chunkList.add( chunk.toString() );
+		}
+
+		chunks = (String[]) chunkList.toArray( new String[chunkList.size()] );
+		paramIndexes = new int[paramList.size()];
+		for ( int i = 0; i < paramIndexes.length; ++i ) {
+			paramIndexes[i] = ( (Integer) paramList.get( i ) ).intValue();
+		}
+	}
+
+	public String getTemplate() {
+		return template;
+	}
+
+	public int getAnticipatedNumberOfArguments() {
+		return paramIndexes.length;
+	}
+
+	public String render(List args, SessionFactoryImplementor factory) {
+		int numberOfArguments = args.size();
+		if ( getAnticipatedNumberOfArguments() > 0 && numberOfArguments != getAnticipatedNumberOfArguments() ) {
+			log.warn(
+					"Function template anticipated " + getAnticipatedNumberOfArguments()
+							+ " arguments, but " + numberOfArguments + " arguments encountered"
+			);
+		}
+		StringBuffer buf = new StringBuffer();
+		for ( int i = 0; i < chunks.length; ++i ) {
+			if ( i < paramIndexes.length ) {
+				final int index = paramIndexes[i] - 1;
+				final Object arg =  index < numberOfArguments ? args.get( index ) : null;
+				if ( arg != null ) {
+					buf.append( chunks[i] ).append( arg );
+				}
+			}
+			else {
+				buf.append( chunks[i] );
+			}
+		}
+		return buf.toString();
+	}
+}

Modified: core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -47,6 +47,7 @@
 import org.hibernate.hql.antlr.HqlSqlTokenTypes;
 import org.hibernate.hql.antlr.HqlTokenTypes;
 import org.hibernate.hql.antlr.SqlTokenTypes;
+import org.hibernate.hql.ast.tree.AggregateNode;
 import org.hibernate.hql.ast.tree.AssignmentSpecification;
 import org.hibernate.hql.ast.tree.CollectionFunction;
 import org.hibernate.hql.ast.tree.ConstructorNode;
@@ -977,6 +978,11 @@
 		methodNode.resolve( inSelect );
 	}
 
+	protected void processAggregation(AST node, boolean inSelect) throws SemanticException {
+		AggregateNode aggregateNode = ( AggregateNode ) node;
+		aggregateNode.resolve();
+	}
+
 	protected void processConstructor(AST constructor) throws SemanticException {
 		ConstructorNode constructorNode = ( ConstructorNode ) constructor;
 		constructorNode.prepare();

Modified: core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/SqlGenerator.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/SqlGenerator.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/SqlGenerator.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -32,6 +32,7 @@
 import antlr.RecognitionException;
 import antlr.collections.AST;
 import org.hibernate.QueryException;
+import org.hibernate.hql.ast.tree.FunctionNode;
 import org.hibernate.util.StringHelper;
 import org.hibernate.param.ParameterSpecification;
 import org.hibernate.dialect.function.SQLFunction;
@@ -178,31 +179,33 @@
 		}
 	}
 
-	protected void beginFunctionTemplate(AST m, AST i) {
-		MethodNode methodNode = ( MethodNode ) m;
-		SQLFunction template = methodNode.getSQLFunction();
-		if ( template == null ) {
-			// if template is null we just write the function out as it appears in the hql statement
-			super.beginFunctionTemplate( m, i );
+	protected void beginFunctionTemplate(AST node, AST nameNode) {
+		// NOTE for AGGREGATE both nodes are the same; for METHOD the first is the METHOD, the second is the
+		// 		METHOD_NAME
+		FunctionNode functionNode = ( FunctionNode ) node;
+		SQLFunction sqlFunction = functionNode.getSQLFunction();
+		if ( sqlFunction == null ) {
+			// if SQLFunction is null we just write the function out as it appears in the hql statement
+			super.beginFunctionTemplate( node, nameNode );
 		}
 		else {
-			// this function has a template -> redirect output and catch the arguments
+			// this function has a registered SQLFunction -> redirect output and catch the arguments
 			outputStack.addFirst( writer );
 			writer = new FunctionArguments();
 		}
 	}
 
-	protected void endFunctionTemplate(AST m) {
-		MethodNode methodNode = ( MethodNode ) m;
-		SQLFunction template = methodNode.getSQLFunction();
-		if ( template == null ) {
-			super.endFunctionTemplate( m );
+	protected void endFunctionTemplate(AST node) {
+		FunctionNode functionNode = ( FunctionNode ) node;
+		SQLFunction sqlFunction = functionNode.getSQLFunction();
+		if ( sqlFunction == null ) {
+			super.endFunctionTemplate( node );
 		}
 		else {
-			// this function has a template -> restore output, apply the template and write the result out
-			FunctionArguments functionArguments = ( FunctionArguments ) writer;   // TODO: Downcast to avoid using an interface?  Yuck.
-			writer = ( SqlWriter ) outputStack.removeFirst();
-			out( template.render( functionArguments.getArgs(), sessionFactory ) );
+			// this function has a registered SQLFunction -> redirect output and catch the arguments
+			FunctionArguments functionArguments = ( FunctionArguments ) writer;
+			writer = (SqlWriter) outputStack.removeFirst();
+			out( sqlFunction.render( functionArguments.getArgs(), sessionFactory ) );
 		}
 	}
 

Modified: core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/AggregateNode.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/AggregateNode.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/AggregateNode.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -24,27 +24,56 @@
  */
 package org.hibernate.hql.ast.tree;
 
+import org.hibernate.dialect.function.SQLFunction;
+import org.hibernate.dialect.function.StandardSQLFunction;
 import org.hibernate.hql.ast.util.ColumnHelper;
 import org.hibernate.type.Type;
 
 import antlr.SemanticException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Represents an aggregate function i.e. min, max, sum, avg.
  *
  * @author Joshua Davis
  */
-public class AggregateNode extends AbstractSelectExpression implements SelectExpression {
+public class AggregateNode extends AbstractSelectExpression implements SelectExpression, FunctionNode {
+	private static final Logger log = LoggerFactory.getLogger( AggregateNode.class );
 
-	public AggregateNode() {
+	private SQLFunction sqlFunction;
+
+	public SQLFunction getSQLFunction() {
+		return sqlFunction;
 	}
 
+	public void resolve() {
+		resolveFunction();
+	}
+
+	private SQLFunction resolveFunction() {
+		if ( sqlFunction == null ) {
+			final String name = getText();
+			sqlFunction = getSessionFactoryHelper().findSQLFunction( getText() );
+			if ( sqlFunction == null ) {
+				log.info( "Could not resolve aggregate function {}; using standard definition", name );
+				sqlFunction = new StandardSQLFunction( name );
+			}
+		}
+		return sqlFunction;
+	}
+
 	public Type getDataType() {
 		// Get the function return value type, based on the type of the first argument.
-		return getSessionFactoryHelper().findFunctionReturnType( getText(), getFirstChild() );
+		return getSessionFactoryHelper().findFunctionReturnType( getText(), resolveFunction(), getFirstChild() );
 	}
 
 	public void setScalarColumnText(int i) throws SemanticException {
 		ColumnHelper.generateSingleScalarColumn( this, i );
 	}
+
+	public boolean isScalar() throws SemanticException {
+		// functions in a SELECT should always be considered scalar.
+		return true;
+	}
 }

Added: core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/FunctionNode.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/FunctionNode.java	                        (rev 0)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/FunctionNode.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -0,0 +1,35 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, 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.ast.tree;
+
+import org.hibernate.dialect.function.SQLFunction;
+
+/**
+ * Identifies a node which models a SQL function.
+ *
+ * @author Steve Ebersole
+ */
+public interface FunctionNode {
+	public SQLFunction getSQLFunction();
+}

Modified: core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/MethodNode.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/MethodNode.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/tree/MethodNode.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -47,7 +47,7 @@
  *
  * @author josh
  */
-public class MethodNode extends AbstractSelectExpression implements SelectExpression {
+public class MethodNode extends AbstractSelectExpression implements SelectExpression, FunctionNode {
 
 	private static final Logger log = LoggerFactory.getLogger( MethodNode.class );
 

Modified: core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/util/SessionFactoryHelper.java
===================================================================
--- core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/util/SessionFactoryHelper.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/core/src/main/java/org/hibernate/hql/ast/util/SessionFactoryHelper.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -388,17 +388,19 @@
 	 * @return the function return type given the function name and the first argument expression node.
 	 */
 	public Type findFunctionReturnType(String functionName, AST first) {
-		// locate the registered function by the given name
 		SQLFunction sqlFunction = requireSQLFunction( functionName );
+		return findFunctionReturnType( functionName, sqlFunction, first );
+	}
 
+	public Type findFunctionReturnType(String functionName, SQLFunction sqlFunction, AST firstArgument) {
 		// determine the type of the first argument...
 		Type argumentType = null;
-		if ( first != null ) {
+		if ( firstArgument != null ) {
 			if ( "cast".equals(functionName) ) {
-				argumentType = TypeFactory.heuristicType( first.getNextSibling().getText() );
+				argumentType = TypeFactory.heuristicType( firstArgument.getNextSibling().getText() );
 			}
-			else if ( first instanceof SqlNode ) {
-				argumentType = ( (SqlNode) first ).getDataType();
+			else if ( SqlNode.class.isInstance( firstArgument ) ) {
+				argumentType = ( (SqlNode) firstArgument ).getDataType();
 			}
 		}
 

Modified: core/branches/Branch_3_5/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java
===================================================================
--- core/branches/Branch_3_5/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java	2010-05-10 18:34:22 UTC (rev 19451)
+++ core/branches/Branch_3_5/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java	2010-05-10 19:40:52 UTC (rev 19452)
@@ -1567,7 +1567,7 @@
 
 	public void testAggregation() {
 		Session s = openSession();
-		Transaction t = s.beginTransaction();
+		s.beginTransaction();
 		Human h = new Human();
 		h.setBodyWeight( (float) 74.0 );
 		h.setHeightInches(120.5);
@@ -1580,8 +1580,38 @@
 		assertEquals(sum.floatValue(), 74.0, 0.01);
 		assertEquals(avg.doubleValue(), 120.5, 0.01);
 		Long id = (Long) s.createQuery("select max(a.id) from Animal a").uniqueResult();
+		assertNotNull( id );
+		s.delete( h );
+		s.getTransaction().commit();
+		s.close();
+
+		s = openSession();
+		s.beginTransaction();
+		h = new Human();
+		h.setFloatValue( 2.5F );
+		h.setIntValue( 1 );
+		s.persist( h );
+		Human h2 = new Human();
+		h2.setFloatValue( 2.5F );
+		h2.setIntValue( 2 );
+		s.persist( h2 );
+		Object[] results = (Object[]) s.createQuery( "select sum(h.floatValue), avg(h.floatValue), sum(h.intValue), avg(h.intValue) from Human h" )
+				.uniqueResult();
+		// spec says sum() on a float or double value should result in double
+		assertTrue( Double.class.isInstance( results[0] ) );
+		assertEquals( 5D, results[0] );
+		// avg() should return a double
+		assertTrue( Double.class.isInstance( results[1] ) );
+		assertEquals( 2.5D, results[1] );
+		// spec says sum() on short, int or long should result in long
+		assertTrue( Long.class.isInstance( results[2] ) );
+		assertEquals( 3L, results[2] );
+		// avg() should return a double
+		assertTrue( Double.class.isInstance( results[3] ) );
+		assertEquals( 1.5D, results[3] );
 		s.delete(h);
-		t.commit();
+		s.delete(h2);
+		s.getTransaction().commit();
 		s.close();
 	}
 



More information about the hibernate-commits mailing list