From 217898d8aa8455c99e6b0146279afe9b8e80d9e5 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Mon, 2 Aug 2010 18:51:45 +0000 Subject: [PATCH] HHH-5212 - Alter SQLFunction contract to be more flexible git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@20097 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- .../criterion/AggregateProjection.java | 4 +- .../criterion/RowCountProjection.java | 7 +- .../java/org/hibernate/dialect/Dialect.java | 124 ++--------- .../AbstractAnsiTrimEmulationFunction.java | 32 +-- .../dialect/function/AnsiTrimFunction.java | 2 +- .../dialect/function/AvgFunction.java | 71 ------ .../function/AvgWithArgumentCastFunction.java | 25 +-- .../dialect/function/CastFunction.java | 17 +- .../dialect/function/CharIndexFunction.java | 11 +- .../dialect/function/ConvertFunction.java | 15 +- .../dialect/function/DerbyConcatFunction.java | 22 +- .../dialect/function/NoArgSQLFunction.java | 20 +- .../dialect/function/NvlFunction.java | 21 +- .../function/PositionSubstringFunction.java | 16 +- .../dialect/function/SQLFunction.java | 31 +-- .../dialect/function/SQLFunctionTemplate.java | 5 +- .../StandardAnsiSqlAggregationFunctions.java | 205 ++++++++++++++++++ .../function/StandardJDBCEscapeFunction.java | 4 +- .../dialect/function/StandardSQLFunction.java | 40 ++-- .../function/TrimFunctionTemplate.java | 10 +- .../dialect/function/VarArgsSQLFunction.java | 41 ++-- .../org/hibernate/hql/ast/SqlGenerator.java | 5 +- .../hibernate/hql/ast/tree/AggregateNode.java | 15 ++ .../hibernate/hql/ast/tree/FunctionNode.java | 2 + .../org/hibernate/hql/ast/tree/IdentNode.java | 5 +- .../hibernate/hql/ast/tree/MethodNode.java | 14 ++ .../hql/ast/util/SessionFactoryHelper.java | 1 - .../hibernate/hql/classic/SelectParser.java | 9 +- .../ordering/antlr/OrderByFragmentParser.java | 2 +- .../test/component/basic/ComponentTest.java | 2 +- .../CompositeElementTest.java | 3 +- .../AnsiTrimEmulationFunctionTest.java | 68 ++---- .../java/org/hibernate/test/hql/HQLTest.java | 2 +- 33 files changed, 443 insertions(+), 408 deletions(-) delete mode 100644 core/src/main/java/org/hibernate/dialect/function/AvgFunction.java create mode 100644 core/src/main/java/org/hibernate/dialect/function/StandardAnsiSqlAggregationFunctions.java diff --git a/core/src/main/java/org/hibernate/criterion/AggregateProjection.java b/core/src/main/java/org/hibernate/criterion/AggregateProjection.java index 0729ddb1b6..60a0f88879 100644 --- a/core/src/main/java/org/hibernate/criterion/AggregateProjection.java +++ b/core/src/main/java/org/hibernate/criterion/AggregateProjection.java @@ -72,9 +72,9 @@ public class AggregateProjection extends SimpleProjection { /** * {@inheritDoc} */ - public String toSqlString(Criteria criteria, int loc, CriteriaQuery criteriaQuery) - throws HibernateException { + public String toSqlString(Criteria criteria, int loc, CriteriaQuery criteriaQuery) throws HibernateException { final String functionFragment = getFunction( criteriaQuery ).render( + criteriaQuery.getType( criteria, getPropertyName() ), buildFunctionParameterList( criteria, criteriaQuery ), criteriaQuery.getFactory() ); diff --git a/core/src/main/java/org/hibernate/criterion/RowCountProjection.java b/core/src/main/java/org/hibernate/criterion/RowCountProjection.java index bb677346c4..7d98c99010 100755 --- a/core/src/main/java/org/hibernate/criterion/RowCountProjection.java +++ b/core/src/main/java/org/hibernate/criterion/RowCountProjection.java @@ -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.criterion; @@ -50,7 +49,7 @@ public class RowCountProjection extends SimpleProjection { } public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery) throws HibernateException { - return getFunction( criteriaQuery ).render( ARGS, criteriaQuery.getFactory() ) + return getFunction( criteriaQuery ).render( null, ARGS, criteriaQuery.getFactory() ) + " as y" + position + '_'; } diff --git a/core/src/main/java/org/hibernate/dialect/Dialect.java b/core/src/main/java/org/hibernate/dialect/Dialect.java index 35a4da698a..8e8949b796 100644 --- a/core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/core/src/main/java/org/hibernate/dialect/Dialect.java @@ -32,26 +32,28 @@ import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; -import java.util.List; -import java.util.Iterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.LockMode; -import org.hibernate.MappingException; -import org.hibernate.QueryException; import org.hibernate.LockOptions; +import org.hibernate.MappingException; 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; +import org.hibernate.dialect.function.StandardAnsiSqlAggregationFunctions; import org.hibernate.dialect.function.StandardSQLFunction; -import org.hibernate.dialect.lock.*; -import org.hibernate.engine.Mapping; -import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.dialect.lock.LockingStrategy; +import org.hibernate.dialect.lock.OptimisticForceIncrementLockingStrategy; +import org.hibernate.dialect.lock.OptimisticLockingStrategy; +import org.hibernate.dialect.lock.PessimisticForceIncrementLockingStrategy; +import org.hibernate.dialect.lock.PessimisticReadSelectLockingStrategy; +import org.hibernate.dialect.lock.PessimisticWriteSelectLockingStrategy; +import org.hibernate.dialect.lock.SelectLockingStrategy; import org.hibernate.exception.SQLExceptionConverter; import org.hibernate.exception.SQLStateConverter; import org.hibernate.exception.ViolatedConstraintNameExtracter; @@ -63,9 +65,8 @@ import org.hibernate.persister.entity.Lockable; import org.hibernate.sql.ANSICaseFragment; import org.hibernate.sql.ANSIJoinFragment; import org.hibernate.sql.CaseFragment; -import org.hibernate.sql.JoinFragment; import org.hibernate.sql.ForUpdateFragment; -import org.hibernate.type.Type; +import org.hibernate.sql.JoinFragment; import org.hibernate.util.ReflectHelper; import org.hibernate.util.StringHelper; @@ -93,110 +94,11 @@ public abstract class Dialect { public static final String QUOTE = "`\"["; public static final String CLOSED_QUOTE = "`\"]"; - - // build the map of standard ANSI SQL aggregation functions ~~~~~~~~~~~~~~~ - - private static final Map STANDARD_AGGREGATE_FUNCTIONS = new HashMap(); - - static { - STANDARD_AGGREGATE_FUNCTIONS.put( - "count", - new StandardSQLFunction("count") { - public Type getReturnType(Type columnType, Mapping mapping) { - return Hibernate.LONG; - } - public String render(List args, SessionFactoryImplementor factory) { - if ( args.size() > 1 ) { - if ( "distinct".equalsIgnoreCase( args.get( 0 ).toString() ) ) { - return renderCountDistinct( args ); - } - } - return super.render( args, factory ); - } - private String renderCountDistinct(List args) { - StringBuffer buffer = new StringBuffer(); - buffer.append( "count(distinct " ); - String sep = ""; - Iterator itr = args.iterator(); - itr.next(); // intentionally skip first - while ( itr.hasNext() ) { - buffer.append( sep ) - .append( itr.next() ); - sep = ", "; - } - return buffer.append( ")" ).toString(); - } - } - ); - - STANDARD_AGGREGATE_FUNCTIONS.put( "avg", new AvgFunction() ); - - STANDARD_AGGREGATE_FUNCTIONS.put( "max", new StandardSQLFunction("max") ); - STANDARD_AGGREGATE_FUNCTIONS.put( "min", new StandardSQLFunction("min") ); - - STANDARD_AGGREGATE_FUNCTIONS.put( - "sum", - new StandardSQLFunction("sum") { - public Type getReturnType(Type columnType, Mapping mapping) { - //pre H3.2 behavior: super.getReturnType(ct, m); - 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 sum()" ); - } - int sqlType = sqlTypes[0]; - - // First allow the actual type to control the return value; the underlying sqltype could - // actually be different - if ( columnType == Hibernate.BIG_INTEGER ) { - return Hibernate.BIG_INTEGER; - } - else if ( columnType == Hibernate.BIG_DECIMAL ) { - return Hibernate.BIG_DECIMAL; - } - else if ( columnType == Hibernate.LONG - || columnType == Hibernate.SHORT - || columnType == Hibernate.INTEGER ) { - return Hibernate.LONG; - } - else if ( columnType == Hibernate.FLOAT || columnType == Hibernate.DOUBLE) { - return Hibernate.DOUBLE; - } - - // finally use the sqltype if == on Hibernate types did not find a match. - if ( sqlType == Types.NUMERIC ) { - return columnType; //because numeric can be anything - } - else if ( sqlType == Types.FLOAT - || sqlType == Types.DOUBLE - || sqlType == Types.DECIMAL - || sqlType == Types.REAL) { - return Hibernate.DOUBLE; - } - else if ( sqlType == Types.BIGINT - || sqlType == Types.INTEGER - || sqlType == Types.SMALLINT - || sqlType == Types.TINYINT ) { - return Hibernate.LONG; - } - else { - return columnType; - } - } - } - ); - } - private final TypeNames typeNames = new TypeNames(); private final TypeNames hibernateTypeNames = new TypeNames(); private final Properties properties = new Properties(); - private final Map sqlFunctions = new HashMap(); + private final Map sqlFunctions = new HashMap(); private final Set sqlKeywords = new HashSet(); @@ -204,7 +106,7 @@ public abstract class Dialect { protected Dialect() { log.info( "Using dialect: " + this ); - sqlFunctions.putAll( STANDARD_AGGREGATE_FUNCTIONS ); + StandardAnsiSqlAggregationFunctions.primeFunctionMap( sqlFunctions ); // standard sql92 functions (can be overridden by subclasses) registerFunction( "substring", new SQLFunctionTemplate( Hibernate.STRING, "substring(?1, ?2, ?3)" ) ); diff --git a/core/src/main/java/org/hibernate/dialect/function/AbstractAnsiTrimEmulationFunction.java b/core/src/main/java/org/hibernate/dialect/function/AbstractAnsiTrimEmulationFunction.java index 278fd9fe01..a655eca46c 100644 --- a/core/src/main/java/org/hibernate/dialect/function/AbstractAnsiTrimEmulationFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/AbstractAnsiTrimEmulationFunction.java @@ -43,13 +43,6 @@ import java.util.ArrayList; * @author Steve Ebersole */ public abstract class AbstractAnsiTrimEmulationFunction implements SQLFunction { - /** - * {@inheritDoc} - */ - public final Type getReturnType(Type columnType, Mapping mapping) throws QueryException { - return Hibernate.STRING; - } - /** * {@inheritDoc} */ @@ -64,10 +57,17 @@ public abstract class AbstractAnsiTrimEmulationFunction implements SQLFunction { return false; } + /** + * {@inheritDoc} + */ + public final Type getReturnType(Type argumentType, Mapping mapping) throws QueryException { + return Hibernate.STRING; + } + /** * {@inheritDoc} */ - public final String render(List args, SessionFactoryImplementor factory) throws QueryException { + public final String render(Type argumentType, List args, SessionFactoryImplementor factory) throws QueryException { // According to both the ANSI-SQL and JPA specs, trim takes a variable number of parameters between 1 and 4. // at least one paramer (trimSource) is required. From the SQL spec: // @@ -87,12 +87,12 @@ public abstract class AbstractAnsiTrimEmulationFunction implements SQLFunction { if ( args.size() == 1 ) { // we have the form: trim(trimSource) // so we trim leading and trailing spaces - return resolveBothSpaceTrimFunction().render( args, factory ); // EARLY EXIT!!!! + return resolveBothSpaceTrimFunction().render( argumentType, args, factory ); // EARLY EXIT!!!! } else if ( "from".equalsIgnoreCase( ( String ) args.get( 0 ) ) ) { // we have the form: trim(from trimSource). // This is functionally equivalent to trim(trimSource) - return resolveBothSpaceTrimFromFunction().render( args, factory ); // EARLY EXIT!!!! + return resolveBothSpaceTrimFromFunction().render( argumentType, args, factory ); // EARLY EXIT!!!! } else { // otherwise, a trim-specification and/or a trim-character @@ -145,24 +145,24 @@ public abstract class AbstractAnsiTrimEmulationFunction implements SQLFunction { if ( trimCharacter.equals( "' '" ) ) { if ( leading && trailing ) { - return resolveBothSpaceTrimFunction().render( argsToUse, factory ); + return resolveBothSpaceTrimFunction().render( argumentType, argsToUse, factory ); } else if ( leading ) { - return resolveLeadingSpaceTrimFunction().render( argsToUse, factory ); + return resolveLeadingSpaceTrimFunction().render( argumentType, argsToUse, factory ); } else { - return resolveTrailingSpaceTrimFunction().render( argsToUse, factory ); + return resolveTrailingSpaceTrimFunction().render( argumentType, argsToUse, factory ); } } else { if ( leading && trailing ) { - return resolveBothTrimFunction().render( argsToUse, factory ); + return resolveBothTrimFunction().render( argumentType, argsToUse, factory ); } else if ( leading ) { - return resolveLeadingTrimFunction().render( argsToUse, factory ); + return resolveLeadingTrimFunction().render( argumentType, argsToUse, factory ); } else { - return resolveTrailingTrimFunction().render( argsToUse, factory ); + return resolveTrailingTrimFunction().render( argumentType, argsToUse, factory ); } } } diff --git a/core/src/main/java/org/hibernate/dialect/function/AnsiTrimFunction.java b/core/src/main/java/org/hibernate/dialect/function/AnsiTrimFunction.java index f9a27e778a..c75c143082 100644 --- a/core/src/main/java/org/hibernate/dialect/function/AnsiTrimFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/AnsiTrimFunction.java @@ -26,7 +26,7 @@ package org.hibernate.dialect.function; import org.hibernate.engine.SessionFactoryImplementor; /** - * Defines support for rendering according to ANSI SQL TRIM function specification. + * Defines support for rendering according to ANSI SQL TRIM function specification. * * @author Steve Ebersole */ diff --git a/core/src/main/java/org/hibernate/dialect/function/AvgFunction.java b/core/src/main/java/org/hibernate/dialect/function/AvgFunction.java deleted file mode 100644 index f50f69abb9..0000000000 --- a/core/src/main/java/org/hibernate/dialect/function/AvgFunction.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.MappingException; -import org.hibernate.QueryException; -import org.hibernate.engine.Mapping; -import org.hibernate.engine.SessionFactoryImplementor; -import org.hibernate.type.DoubleType; -import org.hibernate.type.Type; - -/** - * The basic JPA spec compliant definition poAVG 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 DoubleType.INSTANCE; - } - - 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 ) + ")"; - } - - @Override - public final String toString() { - return "avg"; - } -} diff --git a/core/src/main/java/org/hibernate/dialect/function/AvgWithArgumentCastFunction.java b/core/src/main/java/org/hibernate/dialect/function/AvgWithArgumentCastFunction.java index 9b6ee8bfa2..f75311e9b8 100644 --- a/core/src/main/java/org/hibernate/dialect/function/AvgWithArgumentCastFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/AvgWithArgumentCastFunction.java @@ -23,32 +23,29 @@ */ package org.hibernate.dialect.function; -import java.util.List; - -import org.hibernate.QueryException; -import org.hibernate.engine.SessionFactoryImplementor; +import java.sql.Types; /** * Some databases strictly return the type of the of the aggregation value for AVG 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. - *

- * 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 class AvgWithArgumentCastFunction extends StandardAnsiSqlAggregationFunctions.AvgFunction { + private final String castType; public AvgWithArgumentCastFunction(String castType) { - renderer = new TemplateRenderer( "avg(cast(?1 as " + castType + "))" ); + this.castType = castType; } @Override - public String render(List args, SessionFactoryImplementor factory) throws QueryException { - return renderer.render( args, factory ); + protected String renderArgument(String argument, int firstArgumentJdbcType) { + if ( firstArgumentJdbcType == Types.DOUBLE || firstArgumentJdbcType == Types.FLOAT ) { + return argument; + } + else { + return "cast(" + argument + " as " + castType + ")"; + } } } diff --git a/core/src/main/java/org/hibernate/dialect/function/CastFunction.java b/core/src/main/java/org/hibernate/dialect/function/CastFunction.java index 60edd4e373..ec6fdd38f3 100755 --- a/core/src/main/java/org/hibernate/dialect/function/CastFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/CastFunction.java @@ -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; @@ -30,7 +29,6 @@ import org.hibernate.QueryException; import org.hibernate.engine.Mapping; import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.type.Type; -import org.hibernate.type.TypeFactory; /** * ANSI-SQL style cast(foo as type) where the type is @@ -38,11 +36,6 @@ import org.hibernate.type.TypeFactory; * @author Gavin King */ public class CastFunction implements SQLFunction { - - public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { - return columnType; //note there is a wierd implementation in the client side - } - public boolean hasArguments() { return true; } @@ -51,7 +44,11 @@ public class CastFunction implements SQLFunction { return true; } - public String render(List args, SessionFactoryImplementor factory) throws QueryException { + public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { + return columnType; // this is really just a guess, unless the caller properly identifies the 'type' argument here + } + + public String render(Type columnType, List args, SessionFactoryImplementor factory) throws QueryException { if ( args.size()!=2 ) { throw new QueryException("cast() requires two arguments"); } diff --git a/core/src/main/java/org/hibernate/dialect/function/CharIndexFunction.java b/core/src/main/java/org/hibernate/dialect/function/CharIndexFunction.java index 7f88459a6b..643e7a8170 100755 --- a/core/src/main/java/org/hibernate/dialect/function/CharIndexFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/CharIndexFunction.java @@ -37,11 +37,6 @@ import org.hibernate.type.Type; * @author Nathan Moon */ public class CharIndexFunction implements SQLFunction { - - public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { - return Hibernate.INTEGER; - } - public boolean hasArguments() { return true; } @@ -50,7 +45,11 @@ public class CharIndexFunction implements SQLFunction { return true; } - public String render(List args, SessionFactoryImplementor factory) throws QueryException { + public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { + return Hibernate.INTEGER; + } + + public String render(Type columnType, List args, SessionFactoryImplementor factory) throws QueryException { boolean threeArgs = args.size() > 2; Object pattern = args.get(0); Object string = args.get(1); diff --git a/core/src/main/java/org/hibernate/dialect/function/ConvertFunction.java b/core/src/main/java/org/hibernate/dialect/function/ConvertFunction.java index 5ee5c48c03..66b717c44e 100644 --- a/core/src/main/java/org/hibernate/dialect/function/ConvertFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/ConvertFunction.java @@ -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; @@ -39,10 +38,6 @@ import org.hibernate.type.Type; */ public class ConvertFunction implements SQLFunction { - public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { - return Hibernate.STRING; - } - public boolean hasArguments() { return true; } @@ -51,7 +46,11 @@ public class ConvertFunction implements SQLFunction { return true; } - public String render(List args, SessionFactoryImplementor factory) throws QueryException { + public Type getReturnType(Type firstArgumentType, Mapping mapping) throws QueryException { + return Hibernate.STRING; + } + + public String render(Type firstArgumentType, List args, SessionFactoryImplementor factory) throws QueryException { if ( args.size() != 2 && args.size() != 3 ) { throw new QueryException( "convert() requires two or three arguments" ); } diff --git a/core/src/main/java/org/hibernate/dialect/function/DerbyConcatFunction.java b/core/src/main/java/org/hibernate/dialect/function/DerbyConcatFunction.java index 8264f3d7e4..0e25d5dccd 100644 --- a/core/src/main/java/org/hibernate/dialect/function/DerbyConcatFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/DerbyConcatFunction.java @@ -48,15 +48,6 @@ import org.hibernate.type.Type; * @author Steve Ebersole */ public class DerbyConcatFunction implements SQLFunction { - /** - * {@inheritDoc} - *

- * Here we always return {@link Hibernate#STRING}. - */ - public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { - return Hibernate.STRING; - } - /** * {@inheritDoc} *

@@ -75,6 +66,15 @@ public class DerbyConcatFunction implements SQLFunction { return true; } + /** + * {@inheritDoc} + *

+ * Here we always return {@link Hibernate#STRING}. + */ + public Type getReturnType(Type argumentType, Mapping mapping) throws QueryException { + return Hibernate.STRING; + } + /** * {@inheritDoc} *

@@ -82,10 +82,10 @@ public class DerbyConcatFunction implements SQLFunction { * this method. The logic here says that if not all the incoming args are dynamic parameters * (i.e. ?) then we simply use the Derby concat operator (||) on the unchanged * arg elements. However, if all the args are dynamic parameters, then we need to wrap the individual - * arg elements in cast function calls, use the concantenation operator on the cast + * arg elements in cast function calls, use the concatenation operator on the cast * returns, and then wrap that whole thing in a call to the Derby varchar function. */ - public String render(List args, SessionFactoryImplementor factory) throws QueryException { + public String render(Type argumentType, List args, SessionFactoryImplementor factory) throws QueryException { boolean areAllArgsParams = true; Iterator itr = args.iterator(); while ( itr.hasNext() ) { diff --git a/core/src/main/java/org/hibernate/dialect/function/NoArgSQLFunction.java b/core/src/main/java/org/hibernate/dialect/function/NoArgSQLFunction.java index 0c21941591..a6f325841d 100644 --- a/core/src/main/java/org/hibernate/dialect/function/NoArgSQLFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/NoArgSQLFunction.java @@ -51,19 +51,19 @@ public class NoArgSQLFunction implements SQLFunction { this.name = name; } - public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { + public boolean hasArguments() { + return false; + } + + public boolean hasParenthesesIfNoArguments() { + return hasParenthesesIfNoArguments; + } + + public Type getReturnType(Type argumentType, Mapping mapping) throws QueryException { return returnType; } - public boolean hasArguments() { - return false; - } - - public boolean hasParenthesesIfNoArguments() { - return hasParenthesesIfNoArguments; - } - - public String render(List args, SessionFactoryImplementor factory) throws QueryException { + public String render(Type argumentType, List args, SessionFactoryImplementor factory) throws QueryException { if ( args.size()>0 ) { throw new QueryException("function takes no arguments: " + name); } diff --git a/core/src/main/java/org/hibernate/dialect/function/NvlFunction.java b/core/src/main/java/org/hibernate/dialect/function/NvlFunction.java index ac3826b144..a540170d26 100755 --- a/core/src/main/java/org/hibernate/dialect/function/NvlFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/NvlFunction.java @@ -32,16 +32,11 @@ import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.type.Type; /** - * Emulation of coalesce() on Oracle, using multiple - * nvl() calls + * Emulation of coalesce() on Oracle, using multiple nvl() calls + * * @author Gavin King */ public class NvlFunction implements SQLFunction { - - public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { - return columnType; - } - public boolean hasArguments() { return true; } @@ -50,14 +45,20 @@ public class NvlFunction implements SQLFunction { return true; } - public String render(List args, SessionFactoryImplementor factory) throws QueryException { + public Type getReturnType(Type argumentType, Mapping mapping) throws QueryException { + return argumentType; + } + + public String render(Type argumentType, List args, SessionFactoryImplementor factory) throws QueryException { int lastIndex = args.size()-1; Object last = args.remove(lastIndex); - if ( lastIndex==0 ) return last.toString(); + if ( lastIndex==0 ) { + return last.toString(); + } Object secondLast = args.get(lastIndex-1); String nvl = "nvl(" + secondLast + ", " + last + ")"; args.set(lastIndex-1, nvl); - return render(args, factory); + return render( argumentType, args, factory ); } diff --git a/core/src/main/java/org/hibernate/dialect/function/PositionSubstringFunction.java b/core/src/main/java/org/hibernate/dialect/function/PositionSubstringFunction.java index 38c8356340..dece628c19 100755 --- a/core/src/main/java/org/hibernate/dialect/function/PositionSubstringFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/PositionSubstringFunction.java @@ -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; @@ -37,11 +36,6 @@ import org.hibernate.type.Type; * @author Gavin King */ public class PositionSubstringFunction implements SQLFunction { - - public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { - return Hibernate.INTEGER; - } - public boolean hasArguments() { return true; } @@ -50,7 +44,11 @@ public class PositionSubstringFunction implements SQLFunction { return true; } - public String render(List args, SessionFactoryImplementor factory) throws QueryException { + public Type getReturnType(Type firstArgumentType, Mapping mapping) throws QueryException { + return Hibernate.INTEGER; + } + + public String render(Type firstArgumentType, List args, SessionFactoryImplementor factory) throws QueryException { boolean threeArgs = args.size() > 2; Object pattern = args.get(0); Object string = args.get(1); diff --git a/core/src/main/java/org/hibernate/dialect/function/SQLFunction.java b/core/src/main/java/org/hibernate/dialect/function/SQLFunction.java index 9a570b8391..366b5e4242 100644 --- a/core/src/main/java/org/hibernate/dialect/function/SQLFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/SQLFunction.java @@ -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; @@ -40,6 +39,7 @@ import org.hibernate.type.Type; * provide details required for processing of the function. * * @author David Channon + * @author Steve Ebersole */ public interface SQLFunction { /** @@ -50,40 +50,41 @@ public interface SQLFunction { public boolean hasArguments(); /** - * If there are no arguments, are parens required? + * If there are no arguments, are parentheses required? * * @return True if a no-arg call of this function requires parentheses. */ 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. + * 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. + *

+ * Note, the 'firstArgumentType' parameter should match the one passed into {@link #render} * - * @param columnType the type of the first argument + * @param firstArgumentType 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; + public Type getReturnType(Type firstArgumentType, Mapping mapping) throws QueryException; + /** * Render the function call as SQL fragment. + *

+ * Note, the 'firstArgumentType' parameter should match the one passed into {@link #getReturnType} * - * @param args The function arguments + * @param firstArgumentType The type of the first argument + * @param arguments 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; + public String render(Type firstArgumentType, List arguments, SessionFactoryImplementor factory) throws QueryException; } diff --git a/core/src/main/java/org/hibernate/dialect/function/SQLFunctionTemplate.java b/core/src/main/java/org/hibernate/dialect/function/SQLFunctionTemplate.java index 82b43c5cb5..765c563e1b 100755 --- a/core/src/main/java/org/hibernate/dialect/function/SQLFunctionTemplate.java +++ b/core/src/main/java/org/hibernate/dialect/function/SQLFunctionTemplate.java @@ -39,7 +39,6 @@ import java.util.List; * parameters with '?' followed by parameter's index (first index is 1). * * @author Alexey Loubyansky - * @version $Revision: 6608 $ */ public class SQLFunctionTemplate implements SQLFunction { private final Type type; @@ -59,14 +58,14 @@ public class SQLFunctionTemplate implements SQLFunction { /** * {@inheritDoc} */ - public String render(List args, SessionFactoryImplementor factory) { + public String render(Type argumentType, List args, SessionFactoryImplementor factory) { return renderer.render( args, factory ); } /** * {@inheritDoc} */ - public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { + public Type getReturnType(Type argumentType, Mapping mapping) throws QueryException { return type; } diff --git a/core/src/main/java/org/hibernate/dialect/function/StandardAnsiSqlAggregationFunctions.java b/core/src/main/java/org/hibernate/dialect/function/StandardAnsiSqlAggregationFunctions.java new file mode 100644 index 0000000000..5e7a668f1c --- /dev/null +++ b/core/src/main/java/org/hibernate/dialect/function/StandardAnsiSqlAggregationFunctions.java @@ -0,0 +1,205 @@ +/* + * 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.sql.Types; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +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; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public class StandardAnsiSqlAggregationFunctions { + /** + * Definition of a standard ANSI SQL compliant COUNT function + */ + public static class CountFunction extends StandardSQLFunction { + public static final CountFunction INSTANCE = new CountFunction(); + + public CountFunction() { + super( "count", Hibernate.LONG ); + } + + @Override + public String render(Type firstArgumentType, List arguments, SessionFactoryImplementor factory) { + if ( arguments.size() > 1 ) { + if ( "distinct".equalsIgnoreCase( arguments.get( 0 ).toString() ) ) { + return renderCountDistinct( arguments ); + } + } + return super.render( firstArgumentType, arguments, factory ); + } + + private String renderCountDistinct(List arguments) { + StringBuffer buffer = new StringBuffer(); + buffer.append( "count(distinct " ); + String sep = ""; + Iterator itr = arguments.iterator(); + itr.next(); // intentionally skip first + while ( itr.hasNext() ) { + buffer.append( sep ) + .append( itr.next() ); + sep = ", "; + } + return buffer.append( ")" ).toString(); + } + } + + + /** + * Definition of a standard ANSI SQL compliant AVG function + */ + public static class AvgFunction extends StandardSQLFunction { + public static final AvgFunction INSTANCE = new AvgFunction(); + + public AvgFunction() { + super( "avg", Hibernate.DOUBLE ); + } + + @Override + public String render(Type firstArgumentType, List arguments, SessionFactoryImplementor factory) throws QueryException { + int jdbcTypeCode = determineJdbcTypeCode( firstArgumentType, factory ); + return render( jdbcTypeCode, arguments.get(0).toString(), factory ); + } + + protected final int determineJdbcTypeCode(Type firstArgumentType, SessionFactoryImplementor factory) throws QueryException { + try { + final int[] jdbcTypeCodes = firstArgumentType.sqlTypes( factory ); + if ( jdbcTypeCodes.length != 1 ) { + throw new QueryException( "multiple-column type in avg()" ); + } + return jdbcTypeCodes[0]; + } + catch ( MappingException me ) { + throw new QueryException( me ); + } + } + + protected String render(int firstArgumentJdbcType, String argument, SessionFactoryImplementor factory) { + return "avg(" + renderArgument( argument, firstArgumentJdbcType ) + ")"; + } + + protected String renderArgument(String argument, int firstArgumentJdbcType) { + return argument; + } + } + + + public static class MaxFunction extends StandardSQLFunction { + public static final MaxFunction INSTANCE = new MaxFunction(); + + public MaxFunction() { + super( "max" ); + } + } + + public static class MinFunction extends StandardSQLFunction { + public static final MinFunction INSTANCE = new MinFunction(); + + public MinFunction() { + super( "min" ); + } + } + + + public static class SumFunction extends StandardSQLFunction { + public static final SumFunction INSTANCE = new SumFunction(); + + public SumFunction() { + super( "sum" ); + } + + protected final int determineJdbcTypeCode(Type type, Mapping mapping) throws QueryException { + try { + final int[] jdbcTypeCodes = type.sqlTypes( mapping ); + if ( jdbcTypeCodes.length != 1 ) { + throw new QueryException( "multiple-column type in sum()" ); + } + return jdbcTypeCodes[0]; + } + catch ( MappingException me ) { + throw new QueryException( me ); + } + } + + public Type getReturnType(Type firstArgumentType, Mapping mapping) { + final int jdbcType = determineJdbcTypeCode( firstArgumentType, mapping ); + + // First allow the actual type to control the return value; the underlying sqltype could + // actually be different + if ( firstArgumentType == Hibernate.BIG_INTEGER ) { + return Hibernate.BIG_INTEGER; + } + else if ( firstArgumentType == Hibernate.BIG_DECIMAL ) { + return Hibernate.BIG_DECIMAL; + } + else if ( firstArgumentType == Hibernate.LONG + || firstArgumentType == Hibernate.SHORT + || firstArgumentType == Hibernate.INTEGER ) { + return Hibernate.LONG; + } + else if ( firstArgumentType == Hibernate.FLOAT || firstArgumentType == Hibernate.DOUBLE) { + return Hibernate.DOUBLE; + } + + // finally use the jdbcType if == on Hibernate types did not find a match. + // + // IMPL NOTE : we do not match on Types.NUMERIC because it could be either, so we fall-through to the + // first argument type + if ( jdbcType == Types.FLOAT + || jdbcType == Types.DOUBLE + || jdbcType == Types.DECIMAL + || jdbcType == Types.REAL) { + return Hibernate.DOUBLE; + } + else if ( jdbcType == Types.BIGINT + || jdbcType == Types.INTEGER + || jdbcType == Types.SMALLINT + || jdbcType == Types.TINYINT ) { + return Hibernate.LONG; + } + + // as a last resort, return the type of the first argument + return firstArgumentType; + } + } + + public static void primeFunctionMap(Map functionMap) { + functionMap.put( AvgFunction.INSTANCE.getName(), AvgFunction.INSTANCE ); + functionMap.put( CountFunction.INSTANCE.getName(), CountFunction.INSTANCE ); + functionMap.put( MaxFunction.INSTANCE.getName(), MaxFunction.INSTANCE ); + functionMap.put( MinFunction.INSTANCE.getName(), MinFunction.INSTANCE ); + functionMap.put( SumFunction.INSTANCE.getName(), SumFunction.INSTANCE ); + } +} diff --git a/core/src/main/java/org/hibernate/dialect/function/StandardJDBCEscapeFunction.java b/core/src/main/java/org/hibernate/dialect/function/StandardJDBCEscapeFunction.java index 6cc4123383..c5a7da2eb4 100644 --- a/core/src/main/java/org/hibernate/dialect/function/StandardJDBCEscapeFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/StandardJDBCEscapeFunction.java @@ -45,8 +45,8 @@ public class StandardJDBCEscapeFunction extends StandardSQLFunction { super( name, typeValue ); } - public String render(List args, SessionFactoryImplementor factory) { - return "{fn " + super.render( args, factory ) + "}"; + public String render(Type argumentType, List args, SessionFactoryImplementor factory) { + return "{fn " + super.render( argumentType, args, factory ) + "}"; } public String toString() { diff --git a/core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java b/core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java index 1716cdbc4f..9f03d62f8b 100644 --- a/core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java @@ -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; @@ -40,7 +39,7 @@ import org.hibernate.type.Type; */ public class StandardSQLFunction implements SQLFunction { private final String name; - private final Type type; + private final Type registeredType; /** * Construct a standard SQL function definition with a variable return type; @@ -60,11 +59,11 @@ public class StandardSQLFunction implements SQLFunction { * Construct a standard SQL function definition with a static return type. * * @param name The name of the function. - * @param type The static return type. + * @param registeredType The static return type. */ - public StandardSQLFunction(String name, Type type) { + public StandardSQLFunction(String name, Type registeredType) { this.name = name; - this.type = type; + this.registeredType = registeredType; } /** @@ -83,16 +82,7 @@ public class StandardSQLFunction implements SQLFunction { * not static. */ public Type getType() { - return type; - } - - /** - * {@inheritDoc} - */ - public Type getReturnType(Type columnType, Mapping mapping) { - // return the concrete type, or the underlying type if a concrete type - // was not specified - return type == null ? columnType : type; + return registeredType; } /** @@ -112,12 +102,19 @@ public class StandardSQLFunction implements SQLFunction { /** * {@inheritDoc} */ - public String render(List args, SessionFactoryImplementor factory) { + public Type getReturnType(Type firstArgumentType, Mapping mapping) { + return registeredType == null ? firstArgumentType : registeredType; + } + + /** + * {@inheritDoc} + */ + public String render(Type firstArgumentType, List arguments, SessionFactoryImplementor sessionFactory) { StringBuffer buf = new StringBuffer(); buf.append( name ).append( '(' ); - for ( int i = 0; i < args.size(); i++ ) { - buf.append( args.get( i ) ); - if ( i < args.size() - 1 ) { + for ( int i = 0; i < arguments.size(); i++ ) { + buf.append( arguments.get( i ) ); + if ( i < arguments.size() - 1 ) { buf.append( ", " ); } } @@ -127,4 +124,5 @@ public class StandardSQLFunction implements SQLFunction { public String toString() { return name; } + } diff --git a/core/src/main/java/org/hibernate/dialect/function/TrimFunctionTemplate.java b/core/src/main/java/org/hibernate/dialect/function/TrimFunctionTemplate.java index 952cbcf58d..c474a0bc33 100644 --- a/core/src/main/java/org/hibernate/dialect/function/TrimFunctionTemplate.java +++ b/core/src/main/java/org/hibernate/dialect/function/TrimFunctionTemplate.java @@ -37,10 +37,6 @@ import org.hibernate.type.Type; * @author Steve Ebersole */ public abstract class TrimFunctionTemplate implements SQLFunction { - public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { - return Hibernate.STRING; - } - public boolean hasArguments() { return true; } @@ -49,7 +45,11 @@ public abstract class TrimFunctionTemplate implements SQLFunction { return false; } - public String render(List args, SessionFactoryImplementor factory) throws QueryException { + public Type getReturnType(Type firstArgument, Mapping mapping) throws QueryException { + return Hibernate.STRING; + } + + public String render(Type firstArgument, List args, SessionFactoryImplementor factory) throws QueryException { final Options options = new Options(); final String trimSource; diff --git a/core/src/main/java/org/hibernate/dialect/function/VarArgsSQLFunction.java b/core/src/main/java/org/hibernate/dialect/function/VarArgsSQLFunction.java index 56bd282f24..1ebbf84b8d 100755 --- a/core/src/main/java/org/hibernate/dialect/function/VarArgsSQLFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/VarArgsSQLFunction.java @@ -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; @@ -32,8 +31,7 @@ import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.type.Type; /** - * Support for slightly more general templating than {@link StandardSQLFunction}, - * with an unlimited number of arguments. + * Support for slightly more general templating than {@link StandardSQLFunction}, with an unlimited number of arguments. * * @author Gavin King */ @@ -41,20 +39,20 @@ public class VarArgsSQLFunction implements SQLFunction { private final String begin; private final String sep; private final String end; - private final Type type; + private final Type registeredType; /** * Constructs a VarArgsSQLFunction instance with a 'static' return type. An example of a 'static' * return type would be something like an UPPER function which is always returning * a SQL VARCHAR and thus a string type. * - * @param type The return type. + * @param registeredType The return type. * @param begin The beginning of the function templating. * @param sep The separator for each individual function argument. * @param end The end of the function templating. */ - public VarArgsSQLFunction(Type type, String begin, String sep, String end) { - this.type = type; + public VarArgsSQLFunction(Type registeredType, String begin, String sep, String end) { + this.registeredType = registeredType; this.begin = begin; this.sep = sep; this.end = end; @@ -70,19 +68,12 @@ public class VarArgsSQLFunction implements SQLFunction { * @param sep The separator for each individual function argument. * @param end The end of the function templating. * - * @see #getReturnType Specifically, the 'columnType' argument is the 'dynamic' type. + * @see #getReturnType Specifically, the 'firstArgumentType' argument is the 'dynamic' type. */ public VarArgsSQLFunction(String begin, String sep, String end) { this( null, begin, sep, end ); } - /** - * {@inheritDoc} - */ - public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { - return type == null ? columnType : type; - } - /** * {@inheritDoc} *

@@ -104,11 +95,15 @@ public class VarArgsSQLFunction implements SQLFunction { /** * {@inheritDoc} */ - public String render(List args, SessionFactoryImplementor factory) throws QueryException { + public Type getReturnType(Type firstArgumentType, Mapping mapping) throws QueryException { + return registeredType == null ? firstArgumentType : registeredType; + } + + public String render(Type firstArgumentType, List arguments, SessionFactoryImplementor factory) { StringBuffer buf = new StringBuffer().append( begin ); - for ( int i = 0; i < args.size(); i++ ) { - buf.append( transformArgument( ( String ) args.get( i ) ) ); - if ( i < args.size() - 1 ) { + for ( int i = 0; i < arguments.size(); i++ ) { + buf.append( transformArgument( ( String ) arguments.get( i ) ) ); + if ( i < arguments.size() - 1 ) { buf.append( sep ); } } @@ -116,8 +111,8 @@ public class VarArgsSQLFunction implements SQLFunction { } /** - * Called from {@link #render} to allow applying a change or transformation to each individual - * argument. + * Called from {@link #render} to allow applying a change or transformation + * to each individual argument. * * @param argument The argument being processed. * @return The transformed argument; may be the same, though should never be null. 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 99cbbb6b84..27872ff3e1 100644 --- a/core/src/main/java/org/hibernate/hql/ast/SqlGenerator.java +++ b/core/src/main/java/org/hibernate/hql/ast/SqlGenerator.java @@ -33,6 +33,8 @@ import antlr.RecognitionException; import antlr.collections.AST; import org.hibernate.QueryException; import org.hibernate.hql.ast.tree.FunctionNode; +import org.hibernate.hql.ast.tree.SqlNode; +import org.hibernate.type.Type; import org.hibernate.util.StringHelper; import org.hibernate.param.ParameterSpecification; import org.hibernate.dialect.function.SQLFunction; @@ -202,10 +204,11 @@ public class SqlGenerator extends SqlGeneratorBase implements ErrorReporter { super.endFunctionTemplate( node ); } else { + final Type functionType = functionNode.getFirstArgumentType(); // this function has a registered SQLFunction -> redirect output and catch the arguments FunctionArguments functionArguments = ( FunctionArguments ) writer; writer = outputStack.removeFirst(); - out( sqlFunction.render( functionArguments.getArgs(), sessionFactory ) ); + out( sqlFunction.render( functionType, functionArguments.getArgs(), sessionFactory ) ); } } diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/AggregateNode.java b/core/src/main/java/org/hibernate/hql/ast/tree/AggregateNode.java index 70577f11b6..40cd73af5d 100644 --- a/core/src/main/java/org/hibernate/hql/ast/tree/AggregateNode.java +++ b/core/src/main/java/org/hibernate/hql/ast/tree/AggregateNode.java @@ -29,6 +29,7 @@ import org.hibernate.hql.ast.util.ColumnHelper; import org.hibernate.type.Type; import antlr.SemanticException; +import antlr.collections.AST; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,6 +63,20 @@ public class AggregateNode extends AbstractSelectExpression implements SelectExp return sqlFunction; } + public Type getFirstArgumentType() { + AST argument = getFirstChild(); + while ( argument != null ) { + if ( argument instanceof SqlNode ) { + final Type type = ( (SqlNode) argument ).getDataType(); + if ( type != null ) { + return type; + } + argument = argument.getNextSibling(); + } + } + return null; + } + public Type getDataType() { // Get the function return value type, based on the type of the first argument. return getSessionFactoryHelper().findFunctionReturnType( getText(), resolveFunction(), getFirstChild() ); diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/FunctionNode.java b/core/src/main/java/org/hibernate/hql/ast/tree/FunctionNode.java index 1f856a83df..931641b665 100644 --- a/core/src/main/java/org/hibernate/hql/ast/tree/FunctionNode.java +++ b/core/src/main/java/org/hibernate/hql/ast/tree/FunctionNode.java @@ -24,6 +24,7 @@ package org.hibernate.hql.ast.tree; import org.hibernate.dialect.function.SQLFunction; +import org.hibernate.type.Type; /** * Identifies a node which models a SQL function. @@ -32,4 +33,5 @@ import org.hibernate.dialect.function.SQLFunction; */ public interface FunctionNode { public SQLFunction getSQLFunction(); + public Type getFirstArgumentType(); } diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/IdentNode.java b/core/src/main/java/org/hibernate/hql/ast/tree/IdentNode.java index 30a8cc9cd0..3da42e6d99 100644 --- a/core/src/main/java/org/hibernate/hql/ast/tree/IdentNode.java +++ b/core/src/main/java/org/hibernate/hql/ast/tree/IdentNode.java @@ -291,7 +291,10 @@ public class IdentNode extends FromReferenceNode implements SelectExpression { return fe.getDataType(); } SQLFunction sf = getWalker().getSessionFactoryHelper().findSQLFunction( getText() ); - return sf == null ? null : sf.getReturnType( null, null ); + if ( sf != null ) { + return sf.getReturnType( null, getWalker().getSessionFactoryHelper().getFactory() ); + } + return null; } public void setScalarColumnText(int i) throws SemanticException { diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/MethodNode.java b/core/src/main/java/org/hibernate/hql/ast/tree/MethodNode.java index c6abeb3e35..9d857a6e7b 100644 --- a/core/src/main/java/org/hibernate/hql/ast/tree/MethodNode.java +++ b/core/src/main/java/org/hibernate/hql/ast/tree/MethodNode.java @@ -96,6 +96,20 @@ public class MethodNode extends AbstractSelectExpression implements SelectExpres return function; } + public Type getFirstArgumentType() { + AST argument = getFirstChild(); + while ( argument != null ) { + if ( argument instanceof SqlNode ) { + final Type type = ( (SqlNode) argument ).getDataType(); + if ( type != null ) { + return type; + } + argument = argument.getNextSibling(); + } + } + return null; + } + private void dialectFunction(AST exprList) { function = getSessionFactoryHelper().findSQLFunction( methodName ); if ( function != null ) { diff --git a/core/src/main/java/org/hibernate/hql/ast/util/SessionFactoryHelper.java b/core/src/main/java/org/hibernate/hql/ast/util/SessionFactoryHelper.java index 0455e1eb82..f31c5abe8e 100644 --- a/core/src/main/java/org/hibernate/hql/ast/util/SessionFactoryHelper.java +++ b/core/src/main/java/org/hibernate/hql/ast/util/SessionFactoryHelper.java @@ -46,7 +46,6 @@ import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; -import org.hibernate.type.TypeFactory; import antlr.SemanticException; import antlr.collections.AST; diff --git a/core/src/main/java/org/hibernate/hql/classic/SelectParser.java b/core/src/main/java/org/hibernate/hql/classic/SelectParser.java index c2a661e57e..84f6b678c8 100644 --- a/core/src/main/java/org/hibernate/hql/classic/SelectParser.java +++ b/core/src/main/java/org/hibernate/hql/classic/SelectParser.java @@ -142,9 +142,14 @@ public class SelectParser implements Parser { } } else if ( COUNT_MODIFIERS.contains( lctoken ) ) { - if ( !ready || !aggregate ) throw new QueryException( token + " only allowed inside aggregate function in SELECT" ); + if ( !ready || !aggregate ) { + throw new QueryException( token + " only allowed inside aggregate function in SELECT" ); + } q.appendScalarSelectToken( token ); - if ( "*".equals( token ) ) q.addSelectScalar( getFunction( "count", q ).getReturnType( Hibernate.LONG, q.getFactory() ) ); //special case + if ( "*".equals( token ) ) { + // special case + q.addSelectScalar( getFunction( "count", q ).getReturnType( Hibernate.LONG, q.getFactory() ) ); + } } else if ( getFunction( lctoken, q ) != null && token.equals( q.unalias( token ) ) ) { // the name of an SQL function 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 f2775f01e1..53683ebfc3 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 @@ -137,7 +137,7 @@ public class OrderByFragmentParser extends GeneratedOrderByFragmentParser { expressions.add( child.getText() ); child = child.getNextSibling(); } - final String text = function.render( expressions, context.getSessionFactory() ); + final String text = function.render( null, expressions, context.getSessionFactory() ); return getASTFactory().create( OrderByTemplateTokenTypes.IDENT, text ); } } diff --git a/testsuite/src/test/java/org/hibernate/test/component/basic/ComponentTest.java b/testsuite/src/test/java/org/hibernate/test/component/basic/ComponentTest.java index 4cbfb06d81..dd8639431a 100755 --- a/testsuite/src/test/java/org/hibernate/test/component/basic/ComponentTest.java +++ b/testsuite/src/test/java/org/hibernate/test/component/basic/ComponentTest.java @@ -66,7 +66,7 @@ public class ComponentTest extends FunctionalTestCase { else { List args = new ArrayList(); args.add( "dob" ); - f.setFormula( yearFunction.render( args, null ) ); + f.setFormula( yearFunction.render( Hibernate.INTEGER, args, null ) ); } } diff --git a/testsuite/src/test/java/org/hibernate/test/compositeelement/CompositeElementTest.java b/testsuite/src/test/java/org/hibernate/test/compositeelement/CompositeElementTest.java index 1f8bc1f72f..5907c806e4 100755 --- a/testsuite/src/test/java/org/hibernate/test/compositeelement/CompositeElementTest.java +++ b/testsuite/src/test/java/org/hibernate/test/compositeelement/CompositeElementTest.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import junit.framework.Test; +import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.cfg.Mappings; @@ -40,7 +41,7 @@ public class CompositeElementTest extends FunctionalTestCase { if ( lengthFunction != null ) { ArrayList args = new ArrayList(); args.add( "bio" ); - f.setFormula( lengthFunction.render( args, null ) ); + f.setFormula( lengthFunction.render( Hibernate.INTEGER, args, null ) ); } } diff --git a/testsuite/src/test/java/org/hibernate/test/dialect/function/AnsiTrimEmulationFunctionTest.java b/testsuite/src/test/java/org/hibernate/test/dialect/function/AnsiTrimEmulationFunctionTest.java index f4cc4c4d9f..6f64a3c7fb 100644 --- a/testsuite/src/test/java/org/hibernate/test/dialect/function/AnsiTrimEmulationFunctionTest.java +++ b/testsuite/src/test/java/org/hibernate/test/dialect/function/AnsiTrimEmulationFunctionTest.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2009, 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 @@ -23,8 +23,8 @@ */ package org.hibernate.test.dialect.function; +import java.util.Arrays; import java.util.List; -import java.util.ArrayList; import junit.framework.TestCase; @@ -48,22 +48,22 @@ public class AnsiTrimEmulationFunctionTest extends TestCase { final String expectedPostTrimSuffix = ",' ','-'),'${space}$',' ')"; // -> trim(LEADING '-' FROM a.column) - String rendered = function.render( argList( "LEADING", "'-'", "FROM", trimSource ), null ); + String rendered = function.render( null, argList( "LEADING", "'-'", "FROM", trimSource ), null ); String expected = expectedPostTrimPrefix + "ltrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; assertEquals( expected, rendered ); // -> trim(TRAILING '-' FROM a.column) - rendered = function.render( argList( "TRAILING", "'-'", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "TRAILING", "'-'", "FROM", trimSource ), null ); expected = expectedPostTrimPrefix + "rtrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; assertEquals( expected, rendered ); // -> trim(BOTH '-' FROM a.column) - rendered = function.render( argList( "BOTH", "'-'", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "BOTH", "'-'", "FROM", trimSource ), null ); expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix; assertEquals( expected, rendered ); // -> trim('-' FROM a.column) - rendered = function.render( argList( "'-'", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "'-'", "FROM", trimSource ), null ); expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix; assertEquals( expected, rendered ); } @@ -82,88 +82,62 @@ public class AnsiTrimEmulationFunctionTest extends TestCase { final String expectedPostTrimSuffix = ",' ','-'),'${space}$',' ')"; // -> trim(LEADING '-' FROM a.column) - String rendered = function.render( argList( "LEADING", "'-'", "FROM", trimSource ), null ); + String rendered = function.render( null, argList( "LEADING", "'-'", "FROM", trimSource ), null ); String expected = expectedPostTrimPrefix + "ltrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; assertEquals( expected, rendered ); // -> trim(TRAILING '-' FROM a.column) - rendered = function.render( argList( "TRAILING", "'-'", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "TRAILING", "'-'", "FROM", trimSource ), null ); expected = expectedPostTrimPrefix + "rtrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; assertEquals( expected, rendered ); // -> trim(BOTH '-' FROM a.column) - rendered = function.render( argList( "BOTH", "'-'", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "BOTH", "'-'", "FROM", trimSource ), null ); expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix; assertEquals( expected, rendered ); // -> trim('-' FROM a.column) - rendered = function.render( argList( "'-'", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "'-'", "FROM", trimSource ), null ); expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix; assertEquals( expected, rendered ); } private void performBasicSpaceTrimmingTests(AnsiTrimEmulationFunction function) { // -> trim(a.column) - String rendered = function.render( argList( trimSource ), null ); + String rendered = function.render( null, argList( trimSource ), null ); assertEquals( "ltrim(rtrim(a.column))", rendered ); // -> trim(FROM a.column) - rendered = function.render( argList( "FROM", trimSource ), null ); + rendered = function.render( null, argList( "FROM", trimSource ), null ); assertEquals( "ltrim(rtrim(a.column))", rendered ); // -> trim(BOTH FROM a.column) - rendered = function.render( argList( "BOTH", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "BOTH", "FROM", trimSource ), null ); assertEquals( "ltrim(rtrim(a.column))", rendered ); // -> trim(BOTH ' ' FROM a.column) - rendered = function.render( argList( "BOTH", "' '", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "BOTH", "' '", "FROM", trimSource ), null ); assertEquals( "ltrim(rtrim(a.column))", rendered ); // -> trim(LEADING FROM a.column) - rendered = function.render( argList( "LEADING", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "LEADING", "FROM", trimSource ), null ); assertEquals( "ltrim(a.column)", rendered ); // -> trim(LEADING ' ' FROM a.column) - rendered = function.render( argList( "LEADING", "' '", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "LEADING", "' '", "FROM", trimSource ), null ); assertEquals( "ltrim(a.column)", rendered ); // -> trim(TRAILING FROM a.column) - rendered = function.render( argList( "TRAILING", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "TRAILING", "FROM", trimSource ), null ); assertEquals( "rtrim(a.column)", rendered ); // -> trim(TRAILING ' ' FROM a.column) - rendered = function.render( argList( "TRAILING", "' '", "FROM", trimSource ), null ); + rendered = function.render( null, argList( "TRAILING", "' '", "FROM", trimSource ), null ); assertEquals( "rtrim(a.column)", rendered ); } - private List argList(String arg) { - ArrayList args = new ArrayList(); - args.add( arg ); - return args; - } - - private List argList(String arg1, String arg2) { - ArrayList args = new ArrayList(); - args.add( arg1 ); - args.add( arg2 ); - return args; - } - - private List argList(String arg1, String arg2, String arg3) { - ArrayList args = new ArrayList(); - args.add( arg1 ); - args.add( arg2 ); - args.add( arg3 ); - return args; - } - - private List argList(String arg1, String arg2, String arg3, String arg4) { - ArrayList args = new ArrayList(); - args.add( arg1 ); - args.add( arg2 ); - args.add( arg3 ); - args.add( arg4 ); - return args; + private List argList(String... args) { + return Arrays.asList( args ); } } diff --git a/testsuite/src/test/java/org/hibernate/test/hql/HQLTest.java b/testsuite/src/test/java/org/hibernate/test/hql/HQLTest.java index 8e712b0fdd..aee2c3a138 100644 --- a/testsuite/src/test/java/org/hibernate/test/hql/HQLTest.java +++ b/testsuite/src/test/java/org/hibernate/test/hql/HQLTest.java @@ -682,7 +682,7 @@ public class HQLTest extends QueryTranslatorTestCase { assertTranslation( "from Animal an where an.bodyWeight > abs(3*5)" ); SQLFunction concat = getSessionFactoryImplementor().getSqlFunctionRegistry().findSQLFunction( "concat"); List list = new ArrayList(); list.add("'fat'"); list.add("'skinny'"); - assertTranslation( "from Animal an where an.description = " + concat.render(list, getSessionFactoryImplementor()) ); + assertTranslation( "from Animal an where an.description = " + concat.render(Hibernate.STRING, list, getSessionFactoryImplementor()) ); } public void testNotOrWhereClause() {