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
This commit is contained in:
Steve Ebersole 2010-08-02 18:51:45 +00:00
parent fbe7d325e2
commit 217898d8aa
33 changed files with 443 additions and 408 deletions

View File

@ -72,9 +72,9 @@ public class AggregateProjection extends SimpleProjection {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public String toSqlString(Criteria criteria, int loc, CriteriaQuery criteriaQuery) public String toSqlString(Criteria criteria, int loc, CriteriaQuery criteriaQuery) throws HibernateException {
throws HibernateException {
final String functionFragment = getFunction( criteriaQuery ).render( final String functionFragment = getFunction( criteriaQuery ).render(
criteriaQuery.getType( criteria, getPropertyName() ),
buildFunctionParameterList( criteria, criteriaQuery ), buildFunctionParameterList( criteria, criteriaQuery ),
criteriaQuery.getFactory() criteriaQuery.getFactory()
); );

View File

@ -1,10 +1,10 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * 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 * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * 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, * 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 * copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc. * Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*
*/ */
package org.hibernate.criterion; package org.hibernate.criterion;
@ -50,7 +49,7 @@ public class RowCountProjection extends SimpleProjection {
} }
public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery) throws HibernateException { 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 + '_'; + " as y" + position + '_';
} }

View File

@ -32,26 +32,28 @@ import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.List;
import java.util.Iterator;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.MappingException;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
import org.hibernate.dialect.function.AvgFunction;
import org.hibernate.dialect.function.CastFunction; import org.hibernate.dialect.function.CastFunction;
import org.hibernate.dialect.function.SQLFunction; import org.hibernate.dialect.function.SQLFunction;
import org.hibernate.dialect.function.SQLFunctionTemplate; import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.dialect.function.StandardAnsiSqlAggregationFunctions;
import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.lock.*; import org.hibernate.dialect.lock.LockingStrategy;
import org.hibernate.engine.Mapping; import org.hibernate.dialect.lock.OptimisticForceIncrementLockingStrategy;
import org.hibernate.engine.SessionFactoryImplementor; 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.SQLExceptionConverter;
import org.hibernate.exception.SQLStateConverter; import org.hibernate.exception.SQLStateConverter;
import org.hibernate.exception.ViolatedConstraintNameExtracter; import org.hibernate.exception.ViolatedConstraintNameExtracter;
@ -63,9 +65,8 @@ import org.hibernate.persister.entity.Lockable;
import org.hibernate.sql.ANSICaseFragment; import org.hibernate.sql.ANSICaseFragment;
import org.hibernate.sql.ANSIJoinFragment; import org.hibernate.sql.ANSIJoinFragment;
import org.hibernate.sql.CaseFragment; import org.hibernate.sql.CaseFragment;
import org.hibernate.sql.JoinFragment;
import org.hibernate.sql.ForUpdateFragment; import org.hibernate.sql.ForUpdateFragment;
import org.hibernate.type.Type; import org.hibernate.sql.JoinFragment;
import org.hibernate.util.ReflectHelper; import org.hibernate.util.ReflectHelper;
import org.hibernate.util.StringHelper; import org.hibernate.util.StringHelper;
@ -93,110 +94,11 @@ public abstract class Dialect {
public static final String QUOTE = "`\"["; public static final String QUOTE = "`\"[";
public static final String CLOSED_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 typeNames = new TypeNames();
private final TypeNames hibernateTypeNames = new TypeNames(); private final TypeNames hibernateTypeNames = new TypeNames();
private final Properties properties = new Properties(); private final Properties properties = new Properties();
private final Map sqlFunctions = new HashMap(); private final Map<String, SQLFunction> sqlFunctions = new HashMap<String, SQLFunction>();
private final Set sqlKeywords = new HashSet(); private final Set sqlKeywords = new HashSet();
@ -204,7 +106,7 @@ public abstract class Dialect {
protected Dialect() { protected Dialect() {
log.info( "Using dialect: " + this ); log.info( "Using dialect: " + this );
sqlFunctions.putAll( STANDARD_AGGREGATE_FUNCTIONS ); StandardAnsiSqlAggregationFunctions.primeFunctionMap( sqlFunctions );
// standard sql92 functions (can be overridden by subclasses) // standard sql92 functions (can be overridden by subclasses)
registerFunction( "substring", new SQLFunctionTemplate( Hibernate.STRING, "substring(?1, ?2, ?3)" ) ); registerFunction( "substring", new SQLFunctionTemplate( Hibernate.STRING, "substring(?1, ?2, ?3)" ) );

View File

@ -43,13 +43,6 @@ import java.util.ArrayList;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public abstract class AbstractAnsiTrimEmulationFunction implements SQLFunction { public abstract class AbstractAnsiTrimEmulationFunction implements SQLFunction {
/**
* {@inheritDoc}
*/
public final Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
return Hibernate.STRING;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -67,7 +60,14 @@ public abstract class AbstractAnsiTrimEmulationFunction implements SQLFunction {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public final String render(List args, SessionFactoryImplementor factory) throws QueryException { public final Type getReturnType(Type argumentType, Mapping mapping) throws QueryException {
return Hibernate.STRING;
}
/**
* {@inheritDoc}
*/
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. // 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: // 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 ) { if ( args.size() == 1 ) {
// we have the form: trim(trimSource) // we have the form: trim(trimSource)
// so we trim leading and trailing spaces // 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 ) ) ) { else if ( "from".equalsIgnoreCase( ( String ) args.get( 0 ) ) ) {
// we have the form: trim(from trimSource). // we have the form: trim(from trimSource).
// This is functionally equivalent to trim(trimSource) // This is functionally equivalent to trim(trimSource)
return resolveBothSpaceTrimFromFunction().render( args, factory ); // EARLY EXIT!!!! return resolveBothSpaceTrimFromFunction().render( argumentType, args, factory ); // EARLY EXIT!!!!
} }
else { else {
// otherwise, a trim-specification and/or a trim-character // otherwise, a trim-specification and/or a trim-character
@ -145,24 +145,24 @@ public abstract class AbstractAnsiTrimEmulationFunction implements SQLFunction {
if ( trimCharacter.equals( "' '" ) ) { if ( trimCharacter.equals( "' '" ) ) {
if ( leading && trailing ) { if ( leading && trailing ) {
return resolveBothSpaceTrimFunction().render( argsToUse, factory ); return resolveBothSpaceTrimFunction().render( argumentType, argsToUse, factory );
} }
else if ( leading ) { else if ( leading ) {
return resolveLeadingSpaceTrimFunction().render( argsToUse, factory ); return resolveLeadingSpaceTrimFunction().render( argumentType, argsToUse, factory );
} }
else { else {
return resolveTrailingSpaceTrimFunction().render( argsToUse, factory ); return resolveTrailingSpaceTrimFunction().render( argumentType, argsToUse, factory );
} }
} }
else { else {
if ( leading && trailing ) { if ( leading && trailing ) {
return resolveBothTrimFunction().render( argsToUse, factory ); return resolveBothTrimFunction().render( argumentType, argsToUse, factory );
} }
else if ( leading ) { else if ( leading ) {
return resolveLeadingTrimFunction().render( argsToUse, factory ); return resolveLeadingTrimFunction().render( argumentType, argsToUse, factory );
} }
else { else {
return resolveTrailingTrimFunction().render( argsToUse, factory ); return resolveTrailingTrimFunction().render( argumentType, argsToUse, factory );
} }
} }
} }

View File

@ -26,7 +26,7 @@ package org.hibernate.dialect.function;
import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.SessionFactoryImplementor;
/** /**
* Defines support for rendering according to ANSI SQL <tt>TRIM<//tt> function specification. * Defines support for rendering according to ANSI SQL <tt>TRIM</tt> function specification.
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */

View File

@ -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 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 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";
}
}

View File

@ -23,32 +23,29 @@
*/ */
package org.hibernate.dialect.function; package org.hibernate.dialect.function;
import java.util.List; import java.sql.Types;
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 * 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 * 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. * 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 * @author Steve Ebersole
*/ */
public class AvgWithArgumentCastFunction extends AvgFunction { public class AvgWithArgumentCastFunction extends StandardAnsiSqlAggregationFunctions.AvgFunction {
private final TemplateRenderer renderer; private final String castType;
public AvgWithArgumentCastFunction(String castType) { public AvgWithArgumentCastFunction(String castType) {
renderer = new TemplateRenderer( "avg(cast(?1 as " + castType + "))" ); this.castType = castType;
} }
@Override @Override
public String render(List args, SessionFactoryImplementor factory) throws QueryException { protected String renderArgument(String argument, int firstArgumentJdbcType) {
return renderer.render( args, factory ); if ( firstArgumentJdbcType == Types.DOUBLE || firstArgumentJdbcType == Types.FLOAT ) {
return argument;
}
else {
return "cast(" + argument + " as " + castType + ")";
}
} }
} }

View File

@ -1,10 +1,10 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * 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 * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * 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, * 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 * copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc. * Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*
*/ */
package org.hibernate.dialect.function; package org.hibernate.dialect.function;
@ -30,7 +29,6 @@ import org.hibernate.QueryException;
import org.hibernate.engine.Mapping; import org.hibernate.engine.Mapping;
import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.hibernate.type.TypeFactory;
/** /**
* ANSI-SQL style <tt>cast(foo as type)</tt> where the type is * ANSI-SQL style <tt>cast(foo as type)</tt> where the type is
@ -38,11 +36,6 @@ import org.hibernate.type.TypeFactory;
* @author Gavin King * @author Gavin King
*/ */
public class CastFunction implements SQLFunction { 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() { public boolean hasArguments() {
return true; return true;
} }
@ -51,7 +44,11 @@ public class CastFunction implements SQLFunction {
return true; 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 ) { if ( args.size()!=2 ) {
throw new QueryException("cast() requires two arguments"); throw new QueryException("cast() requires two arguments");
} }

View File

@ -37,11 +37,6 @@ import org.hibernate.type.Type;
* @author Nathan Moon * @author Nathan Moon
*/ */
public class CharIndexFunction implements SQLFunction { public class CharIndexFunction implements SQLFunction {
public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
return Hibernate.INTEGER;
}
public boolean hasArguments() { public boolean hasArguments() {
return true; return true;
} }
@ -50,7 +45,11 @@ public class CharIndexFunction implements SQLFunction {
return true; 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; boolean threeArgs = args.size() > 2;
Object pattern = args.get(0); Object pattern = args.get(0);
Object string = args.get(1); Object string = args.get(1);

View File

@ -1,10 +1,10 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * 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 * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * 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, * 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 * copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc. * Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*
*/ */
package org.hibernate.dialect.function; package org.hibernate.dialect.function;
@ -39,10 +38,6 @@ import org.hibernate.type.Type;
*/ */
public class ConvertFunction implements SQLFunction { public class ConvertFunction implements SQLFunction {
public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
return Hibernate.STRING;
}
public boolean hasArguments() { public boolean hasArguments() {
return true; return true;
} }
@ -51,7 +46,11 @@ public class ConvertFunction implements SQLFunction {
return true; 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 ) { if ( args.size() != 2 && args.size() != 3 ) {
throw new QueryException( "convert() requires two or three arguments" ); throw new QueryException( "convert() requires two or three arguments" );
} }

View File

@ -48,15 +48,6 @@ import org.hibernate.type.Type;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class DerbyConcatFunction implements SQLFunction { public class DerbyConcatFunction implements SQLFunction {
/**
* {@inheritDoc}
* <p/>
* Here we always return {@link Hibernate#STRING}.
*/
public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
return Hibernate.STRING;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* <p/> * <p/>
@ -75,6 +66,15 @@ public class DerbyConcatFunction implements SQLFunction {
return true; return true;
} }
/**
* {@inheritDoc}
* <p/>
* Here we always return {@link Hibernate#STRING}.
*/
public Type getReturnType(Type argumentType, Mapping mapping) throws QueryException {
return Hibernate.STRING;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* <p/> * <p/>
@ -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 * this method. The logic here says that if not all the incoming args are dynamic parameters
* (i.e. <tt>?</tt>) then we simply use the Derby concat operator (<tt>||</tt>) on the unchanged * (i.e. <tt>?</tt>) then we simply use the Derby concat operator (<tt>||</tt>) on the unchanged
* arg elements. However, if all the args are dynamic parameters, then we need to wrap the individual * arg elements. However, if all the args are dynamic parameters, then we need to wrap the individual
* arg elements in <tt>cast</tt> function calls, use the concantenation operator on the <tt>cast</tt> * arg elements in <tt>cast</tt> function calls, use the concatenation operator on the <tt>cast</tt>
* returns, and then wrap that whole thing in a call to the Derby <tt>varchar</tt> function. * returns, and then wrap that whole thing in a call to the Derby <tt>varchar</tt> function.
*/ */
public String render(List args, SessionFactoryImplementor factory) throws QueryException { public String render(Type argumentType, List args, SessionFactoryImplementor factory) throws QueryException {
boolean areAllArgsParams = true; boolean areAllArgsParams = true;
Iterator itr = args.iterator(); Iterator itr = args.iterator();
while ( itr.hasNext() ) { while ( itr.hasNext() ) {

View File

@ -51,10 +51,6 @@ public class NoArgSQLFunction implements SQLFunction {
this.name = name; this.name = name;
} }
public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
return returnType;
}
public boolean hasArguments() { public boolean hasArguments() {
return false; return false;
} }
@ -63,7 +59,11 @@ public class NoArgSQLFunction implements SQLFunction {
return hasParenthesesIfNoArguments; return hasParenthesesIfNoArguments;
} }
public String render(List args, SessionFactoryImplementor factory) throws QueryException { public Type getReturnType(Type argumentType, Mapping mapping) throws QueryException {
return returnType;
}
public String render(Type argumentType, List args, SessionFactoryImplementor factory) throws QueryException {
if ( args.size()>0 ) { if ( args.size()>0 ) {
throw new QueryException("function takes no arguments: " + name); throw new QueryException("function takes no arguments: " + name);
} }

View File

@ -32,16 +32,11 @@ import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.type.Type; import org.hibernate.type.Type;
/** /**
* Emulation of <tt>coalesce()</tt> on Oracle, using multiple * Emulation of <tt>coalesce()</tt> on Oracle, using multiple <tt>nvl()</tt> calls
* <tt>nvl()</tt> calls *
* @author Gavin King * @author Gavin King
*/ */
public class NvlFunction implements SQLFunction { public class NvlFunction implements SQLFunction {
public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
return columnType;
}
public boolean hasArguments() { public boolean hasArguments() {
return true; return true;
} }
@ -50,14 +45,20 @@ public class NvlFunction implements SQLFunction {
return true; 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; int lastIndex = args.size()-1;
Object last = args.remove(lastIndex); Object last = args.remove(lastIndex);
if ( lastIndex==0 ) return last.toString(); if ( lastIndex==0 ) {
return last.toString();
}
Object secondLast = args.get(lastIndex-1); Object secondLast = args.get(lastIndex-1);
String nvl = "nvl(" + secondLast + ", " + last + ")"; String nvl = "nvl(" + secondLast + ", " + last + ")";
args.set(lastIndex-1, nvl); args.set(lastIndex-1, nvl);
return render(args, factory); return render( argumentType, args, factory );
} }

View File

@ -1,10 +1,10 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * 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 * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * 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, * 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 * copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc. * Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*
*/ */
package org.hibernate.dialect.function; package org.hibernate.dialect.function;
@ -37,11 +36,6 @@ import org.hibernate.type.Type;
* @author Gavin King * @author Gavin King
*/ */
public class PositionSubstringFunction implements SQLFunction { public class PositionSubstringFunction implements SQLFunction {
public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
return Hibernate.INTEGER;
}
public boolean hasArguments() { public boolean hasArguments() {
return true; return true;
} }
@ -50,7 +44,11 @@ public class PositionSubstringFunction implements SQLFunction {
return true; 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; boolean threeArgs = args.size() > 2;
Object pattern = args.get(0); Object pattern = args.get(0);
Object string = args.get(1); Object string = args.get(1);

View File

@ -1,10 +1,10 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * 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 * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * 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, * 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 * copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc. * Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*
*/ */
package org.hibernate.dialect.function; package org.hibernate.dialect.function;
@ -40,6 +39,7 @@ import org.hibernate.type.Type;
* provide details required for processing of the function. * provide details required for processing of the function.
* *
* @author David Channon * @author David Channon
* @author Steve Ebersole
*/ */
public interface SQLFunction { public interface SQLFunction {
/** /**
@ -50,40 +50,41 @@ public interface SQLFunction {
public boolean hasArguments(); 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. * @return True if a no-arg call of this function requires parentheses.
*/ */
public boolean hasParenthesesIfNoArguments(); public boolean hasParenthesesIfNoArguments();
/** /**
* The return type of the function. May be either a concrete type which * The return type of the function. May be either a concrete type which is preset, or variable depending upon
* is preset, or variable depending upon the type of the first function * the type of the first function argument.
* argument. * <p/>
* 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. * @param mapping The mapping source.
* *
* @return The type to be expected as a return. * @return The type to be expected as a return.
* *
* @throws org.hibernate.QueryException Indicates an issue resolving the return type. * @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. * Render the function call as SQL fragment.
* <p/>
* 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 * @param factory The SessionFactory
* *
* @return The rendered function call * @return The rendered function call
* *
* @throws org.hibernate.QueryException Indicates a problem rendering the * @throws org.hibernate.QueryException Indicates a problem rendering the
* function call. * 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;
} }

View File

@ -39,7 +39,6 @@ import java.util.List;
* parameters with '?' followed by parameter's index (first index is 1). * parameters with '?' followed by parameter's index (first index is 1).
* *
* @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a> * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
* @version <tt>$Revision: 6608 $</tt>
*/ */
public class SQLFunctionTemplate implements SQLFunction { public class SQLFunctionTemplate implements SQLFunction {
private final Type type; private final Type type;
@ -59,14 +58,14 @@ public class SQLFunctionTemplate implements SQLFunction {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public String render(List args, SessionFactoryImplementor factory) { public String render(Type argumentType, List args, SessionFactoryImplementor factory) {
return renderer.render( args, factory ); return renderer.render( args, factory );
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { public Type getReturnType(Type argumentType, Mapping mapping) throws QueryException {
return type; return type;
} }

View File

@ -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 <tt>COUNT</tt> 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 <tt>AVG</tt> 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<String, SQLFunction> 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 );
}
}

View File

@ -45,8 +45,8 @@ public class StandardJDBCEscapeFunction extends StandardSQLFunction {
super( name, typeValue ); super( name, typeValue );
} }
public String render(List args, SessionFactoryImplementor factory) { public String render(Type argumentType, List args, SessionFactoryImplementor factory) {
return "{fn " + super.render( args, factory ) + "}"; return "{fn " + super.render( argumentType, args, factory ) + "}";
} }
public String toString() { public String toString() {

View File

@ -1,10 +1,10 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * 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 * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * 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, * 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 * copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc. * Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*
*/ */
package org.hibernate.dialect.function; package org.hibernate.dialect.function;
@ -40,7 +39,7 @@ import org.hibernate.type.Type;
*/ */
public class StandardSQLFunction implements SQLFunction { public class StandardSQLFunction implements SQLFunction {
private final String name; private final String name;
private final Type type; private final Type registeredType;
/** /**
* Construct a standard SQL function definition with a variable return type; * 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. * Construct a standard SQL function definition with a static return type.
* *
* @param name The name of the function. * @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.name = name;
this.type = type; this.registeredType = registeredType;
} }
/** /**
@ -83,16 +82,7 @@ public class StandardSQLFunction implements SQLFunction {
* not static. * not static.
*/ */
public Type getType() { public Type getType() {
return type; return registeredType;
}
/**
* {@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;
} }
/** /**
@ -112,12 +102,19 @@ public class StandardSQLFunction implements SQLFunction {
/** /**
* {@inheritDoc} * {@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(); StringBuffer buf = new StringBuffer();
buf.append( name ).append( '(' ); buf.append( name ).append( '(' );
for ( int i = 0; i < args.size(); i++ ) { for ( int i = 0; i < arguments.size(); i++ ) {
buf.append( args.get( i ) ); buf.append( arguments.get( i ) );
if ( i < args.size() - 1 ) { if ( i < arguments.size() - 1 ) {
buf.append( ", " ); buf.append( ", " );
} }
} }
@ -127,4 +124,5 @@ public class StandardSQLFunction implements SQLFunction {
public String toString() { public String toString() {
return name; return name;
} }
} }

View File

@ -37,10 +37,6 @@ import org.hibernate.type.Type;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public abstract class TrimFunctionTemplate implements SQLFunction { public abstract class TrimFunctionTemplate implements SQLFunction {
public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
return Hibernate.STRING;
}
public boolean hasArguments() { public boolean hasArguments() {
return true; return true;
} }
@ -49,7 +45,11 @@ public abstract class TrimFunctionTemplate implements SQLFunction {
return false; 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 Options options = new Options();
final String trimSource; final String trimSource;

View File

@ -1,10 +1,10 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * 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 * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * 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, * 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 * copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc. * Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*
*/ */
package org.hibernate.dialect.function; package org.hibernate.dialect.function;
@ -32,8 +31,7 @@ import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.type.Type; import org.hibernate.type.Type;
/** /**
* Support for slightly more general templating than {@link StandardSQLFunction}, * Support for slightly more general templating than {@link StandardSQLFunction}, with an unlimited number of arguments.
* with an unlimited number of arguments.
* *
* @author Gavin King * @author Gavin King
*/ */
@ -41,20 +39,20 @@ public class VarArgsSQLFunction implements SQLFunction {
private final String begin; private final String begin;
private final String sep; private final String sep;
private final String end; 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' * Constructs a VarArgsSQLFunction instance with a 'static' return type. An example of a 'static'
* return type would be something like an <tt>UPPER</tt> function which is always returning * return type would be something like an <tt>UPPER</tt> function which is always returning
* a SQL VARCHAR and thus a string type. * 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 begin The beginning of the function templating.
* @param sep The separator for each individual function argument. * @param sep The separator for each individual function argument.
* @param end The end of the function templating. * @param end The end of the function templating.
*/ */
public VarArgsSQLFunction(Type type, String begin, String sep, String end) { public VarArgsSQLFunction(Type registeredType, String begin, String sep, String end) {
this.type = type; this.registeredType = registeredType;
this.begin = begin; this.begin = begin;
this.sep = sep; this.sep = sep;
this.end = end; this.end = end;
@ -70,19 +68,12 @@ public class VarArgsSQLFunction implements SQLFunction {
* @param sep The separator for each individual function argument. * @param sep The separator for each individual function argument.
* @param end The end of the function templating. * @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) { public VarArgsSQLFunction(String begin, String sep, String end) {
this( null, begin, sep, end ); this( null, begin, sep, end );
} }
/**
* {@inheritDoc}
*/
public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
return type == null ? columnType : type;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* <p/> * <p/>
@ -104,11 +95,15 @@ public class VarArgsSQLFunction implements SQLFunction {
/** /**
* {@inheritDoc} * {@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 ); StringBuffer buf = new StringBuffer().append( begin );
for ( int i = 0; i < args.size(); i++ ) { for ( int i = 0; i < arguments.size(); i++ ) {
buf.append( transformArgument( ( String ) args.get( i ) ) ); buf.append( transformArgument( ( String ) arguments.get( i ) ) );
if ( i < args.size() - 1 ) { if ( i < arguments.size() - 1 ) {
buf.append( sep ); 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 * Called from {@link #render} to allow applying a change or transformation
* argument. * to each individual argument.
* *
* @param argument The argument being processed. * @param argument The argument being processed.
* @return The transformed argument; may be the same, though should never be null. * @return The transformed argument; may be the same, though should never be null.

View File

@ -33,6 +33,8 @@ import antlr.RecognitionException;
import antlr.collections.AST; import antlr.collections.AST;
import org.hibernate.QueryException; import org.hibernate.QueryException;
import org.hibernate.hql.ast.tree.FunctionNode; 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.util.StringHelper;
import org.hibernate.param.ParameterSpecification; import org.hibernate.param.ParameterSpecification;
import org.hibernate.dialect.function.SQLFunction; import org.hibernate.dialect.function.SQLFunction;
@ -202,10 +204,11 @@ public class SqlGenerator extends SqlGeneratorBase implements ErrorReporter {
super.endFunctionTemplate( node ); super.endFunctionTemplate( node );
} }
else { else {
final Type functionType = functionNode.getFirstArgumentType();
// this function has a registered SQLFunction -> redirect output and catch the arguments // this function has a registered SQLFunction -> redirect output and catch the arguments
FunctionArguments functionArguments = ( FunctionArguments ) writer; FunctionArguments functionArguments = ( FunctionArguments ) writer;
writer = outputStack.removeFirst(); writer = outputStack.removeFirst();
out( sqlFunction.render( functionArguments.getArgs(), sessionFactory ) ); out( sqlFunction.render( functionType, functionArguments.getArgs(), sessionFactory ) );
} }
} }

View File

@ -29,6 +29,7 @@ import org.hibernate.hql.ast.util.ColumnHelper;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import antlr.SemanticException; import antlr.SemanticException;
import antlr.collections.AST;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -62,6 +63,20 @@ public class AggregateNode extends AbstractSelectExpression implements SelectExp
return sqlFunction; 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() { public Type getDataType() {
// Get the function return value type, based on the type of the first argument. // Get the function return value type, based on the type of the first argument.
return getSessionFactoryHelper().findFunctionReturnType( getText(), resolveFunction(), getFirstChild() ); return getSessionFactoryHelper().findFunctionReturnType( getText(), resolveFunction(), getFirstChild() );

View File

@ -24,6 +24,7 @@
package org.hibernate.hql.ast.tree; package org.hibernate.hql.ast.tree;
import org.hibernate.dialect.function.SQLFunction; import org.hibernate.dialect.function.SQLFunction;
import org.hibernate.type.Type;
/** /**
* Identifies a node which models a SQL function. * Identifies a node which models a SQL function.
@ -32,4 +33,5 @@ import org.hibernate.dialect.function.SQLFunction;
*/ */
public interface FunctionNode { public interface FunctionNode {
public SQLFunction getSQLFunction(); public SQLFunction getSQLFunction();
public Type getFirstArgumentType();
} }

View File

@ -291,7 +291,10 @@ public class IdentNode extends FromReferenceNode implements SelectExpression {
return fe.getDataType(); return fe.getDataType();
} }
SQLFunction sf = getWalker().getSessionFactoryHelper().findSQLFunction( getText() ); 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 { public void setScalarColumnText(int i) throws SemanticException {

View File

@ -96,6 +96,20 @@ public class MethodNode extends AbstractSelectExpression implements SelectExpres
return function; 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) { private void dialectFunction(AST exprList) {
function = getSessionFactoryHelper().findSQLFunction( methodName ); function = getSessionFactoryHelper().findSQLFunction( methodName );
if ( function != null ) { if ( function != null ) {

View File

@ -46,7 +46,6 @@ import org.hibernate.type.AssociationType;
import org.hibernate.type.CollectionType; import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType; import org.hibernate.type.EntityType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.hibernate.type.TypeFactory;
import antlr.SemanticException; import antlr.SemanticException;
import antlr.collections.AST; import antlr.collections.AST;

View File

@ -142,9 +142,14 @@ public class SelectParser implements Parser {
} }
} }
else if ( COUNT_MODIFIERS.contains( lctoken ) ) { 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 ); 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 ) ) ) { else if ( getFunction( lctoken, q ) != null && token.equals( q.unalias( token ) ) ) {
// the name of an SQL function // the name of an SQL function

View File

@ -137,7 +137,7 @@ public class OrderByFragmentParser extends GeneratedOrderByFragmentParser {
expressions.add( child.getText() ); expressions.add( child.getText() );
child = child.getNextSibling(); 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 ); return getASTFactory().create( OrderByTemplateTokenTypes.IDENT, text );
} }
} }

View File

@ -66,7 +66,7 @@ public class ComponentTest extends FunctionalTestCase {
else { else {
List args = new ArrayList(); List args = new ArrayList();
args.add( "dob" ); args.add( "dob" );
f.setFormula( yearFunction.render( args, null ) ); f.setFormula( yearFunction.render( Hibernate.INTEGER, args, null ) );
} }
} }

View File

@ -5,6 +5,7 @@ import java.util.ArrayList;
import junit.framework.Test; import junit.framework.Test;
import org.hibernate.Hibernate;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.Transaction; import org.hibernate.Transaction;
import org.hibernate.cfg.Mappings; import org.hibernate.cfg.Mappings;
@ -40,7 +41,7 @@ public class CompositeElementTest extends FunctionalTestCase {
if ( lengthFunction != null ) { if ( lengthFunction != null ) {
ArrayList args = new ArrayList(); ArrayList args = new ArrayList();
args.add( "bio" ); args.add( "bio" );
f.setFormula( lengthFunction.render( args, null ) ); f.setFormula( lengthFunction.render( Hibernate.INTEGER, args, null ) );
} }
} }

View File

@ -1,10 +1,10 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * 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 * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * 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, * 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 * copy, or redistribute it subject to the terms and conditions of the GNU
@ -23,8 +23,8 @@
*/ */
package org.hibernate.test.dialect.function; package org.hibernate.test.dialect.function;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.ArrayList;
import junit.framework.TestCase; import junit.framework.TestCase;
@ -48,22 +48,22 @@ public class AnsiTrimEmulationFunctionTest extends TestCase {
final String expectedPostTrimSuffix = ",' ','-'),'${space}$',' ')"; final String expectedPostTrimSuffix = ",' ','-'),'${space}$',' ')";
// -> trim(LEADING '-' FROM a.column) // -> 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; String expected = expectedPostTrimPrefix + "ltrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix;
assertEquals( expected, rendered ); assertEquals( expected, rendered );
// -> trim(TRAILING '-' FROM a.column) // -> 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; expected = expectedPostTrimPrefix + "rtrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix;
assertEquals( expected, rendered ); assertEquals( expected, rendered );
// -> trim(BOTH '-' FROM a.column) // -> 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; expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix;
assertEquals( expected, rendered ); assertEquals( expected, rendered );
// -> trim('-' FROM a.column) // -> 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; expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix;
assertEquals( expected, rendered ); assertEquals( expected, rendered );
} }
@ -82,88 +82,62 @@ public class AnsiTrimEmulationFunctionTest extends TestCase {
final String expectedPostTrimSuffix = ",' ','-'),'${space}$',' ')"; final String expectedPostTrimSuffix = ",' ','-'),'${space}$',' ')";
// -> trim(LEADING '-' FROM a.column) // -> 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; String expected = expectedPostTrimPrefix + "ltrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix;
assertEquals( expected, rendered ); assertEquals( expected, rendered );
// -> trim(TRAILING '-' FROM a.column) // -> 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; expected = expectedPostTrimPrefix + "rtrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix;
assertEquals( expected, rendered ); assertEquals( expected, rendered );
// -> trim(BOTH '-' FROM a.column) // -> 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; expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix;
assertEquals( expected, rendered ); assertEquals( expected, rendered );
// -> trim('-' FROM a.column) // -> 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; expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix;
assertEquals( expected, rendered ); assertEquals( expected, rendered );
} }
private void performBasicSpaceTrimmingTests(AnsiTrimEmulationFunction function) { private void performBasicSpaceTrimmingTests(AnsiTrimEmulationFunction function) {
// -> trim(a.column) // -> trim(a.column)
String rendered = function.render( argList( trimSource ), null ); String rendered = function.render( null, argList( trimSource ), null );
assertEquals( "ltrim(rtrim(a.column))", rendered ); assertEquals( "ltrim(rtrim(a.column))", rendered );
// -> trim(FROM a.column) // -> 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 ); assertEquals( "ltrim(rtrim(a.column))", rendered );
// -> trim(BOTH FROM a.column) // -> 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 ); assertEquals( "ltrim(rtrim(a.column))", rendered );
// -> trim(BOTH ' ' FROM a.column) // -> 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 ); assertEquals( "ltrim(rtrim(a.column))", rendered );
// -> trim(LEADING FROM a.column) // -> 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 ); assertEquals( "ltrim(a.column)", rendered );
// -> trim(LEADING ' ' FROM a.column) // -> 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 ); assertEquals( "ltrim(a.column)", rendered );
// -> trim(TRAILING FROM a.column) // -> 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 ); assertEquals( "rtrim(a.column)", rendered );
// -> trim(TRAILING ' ' FROM a.column) // -> 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 ); assertEquals( "rtrim(a.column)", rendered );
} }
private List argList(String arg) { private List argList(String... args) {
ArrayList args = new ArrayList(); return Arrays.asList( args );
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;
} }
} }

View File

@ -682,7 +682,7 @@ public class HQLTest extends QueryTranslatorTestCase {
assertTranslation( "from Animal an where an.bodyWeight > abs(3*5)" ); assertTranslation( "from Animal an where an.bodyWeight > abs(3*5)" );
SQLFunction concat = getSessionFactoryImplementor().getSqlFunctionRegistry().findSQLFunction( "concat"); SQLFunction concat = getSessionFactoryImplementor().getSqlFunctionRegistry().findSQLFunction( "concat");
List list = new ArrayList(); list.add("'fat'"); list.add("'skinny'"); 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() { public void testNotOrWhereClause() {