HHH-3424 : concat function on Derby

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@15157 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2008-09-08 19:43:09 +00:00
parent 343e727e4b
commit c6c0a826a5
3 changed files with 247 additions and 20 deletions

View File

@ -30,9 +30,9 @@ import org.hibernate.HibernateException;
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.dialect.function.VarArgsSQLFunction;
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.DerbyConcatFunction;
import org.hibernate.id.TableHiLoGenerator; import org.hibernate.id.TableHiLoGenerator;
import org.hibernate.sql.CaseFragment; import org.hibernate.sql.CaseFragment;
import org.hibernate.sql.DerbyCaseFragment; import org.hibernate.sql.DerbyCaseFragment;
@ -41,18 +41,18 @@ import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* @author Simon Johnston
*
* Hibernate Dialect for Cloudscape 10 - aka Derby. This implements both an * Hibernate Dialect for Cloudscape 10 - aka Derby. This implements both an
* override for the identity column generator as well as for the case statement * override for the identity column generator as well as for the case statement
* issue documented at: * issue documented at:
* http://www.jroller.com/comments/kenlars99/Weblog/cloudscape_soon_to_be_derby * http://www.jroller.com/comments/kenlars99/Weblog/cloudscape_soon_to_be_derby
*
* @author Simon Johnston
*/ */
public class DerbyDialect extends DB2Dialect { public class DerbyDialect extends DB2Dialect {
public DerbyDialect() { public DerbyDialect() {
super(); super();
registerFunction( "concat", new VarArgsSQLFunction( Hibernate.STRING, "(","||",")" ) ); registerFunction( "concat", new DerbyConcatFunction() );
registerFunction( "trim", new DerbyTrimFunctionEmulation() ); registerFunction( "trim", new DerbyTrimFunctionEmulation() );
} }

View File

@ -0,0 +1,180 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.dialect.function;
import java.util.List;
import java.util.Iterator;
import org.hibernate.Hibernate;
import org.hibernate.QueryException;
import org.hibernate.engine.Mapping;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.type.Type;
/**
* A specialized concat() function definition in which:<ol>
* <li>we translate to use the concat operator ('||')</li>
* <li>wrap dynamic parameters in CASTs to VARCHAR</li>
* </ol>
* <p/>
* This last spec is to deal with a limitation on DB2 and variants (e.g. Derby)
* where dynamic parameters cannot be used in concatenation unless they are being
* concatenated with at least one non-dynamic operand. And even then, the rules
* are so convoluted as to what is allowed and when the CAST is needed and when
* it is not that we just go ahead and do the CASTing.
*
* @author Steve Ebersole
*/
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}
* <p/>
* Here we always return <tt>true</tt>
*/
public boolean hasArguments() {
return true;
}
/**
* {@inheritDoc}
* <p/>
* Here we always return <tt>true</tt>
*/
public boolean hasParenthesesIfNoArguments() {
return true;
}
/**
* {@inheritDoc}
* <p/>
* Here's the meat.. The whole reason we have a separate impl for this for Derby is to re-define
* 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
* 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>
* 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 {
boolean areAllArgsParams = true;
Iterator itr = args.iterator();
while ( itr.hasNext() ) {
final String arg = ( String ) itr.next();
if ( ! "?".equals( arg ) ) {
areAllArgsParams = false;
break;
}
}
if ( areAllArgsParams ) {
return join(
args.iterator(),
new StringTransformer() {
public String transform(String string) {
return "cast( ? as varchar(32672) )";
}
},
new StringJoinTemplate() {
public String getBeginning() {
return "varchar( ";
}
public String getSeparator() {
return " || ";
}
public String getEnding() {
return " )";
}
}
);
}
else {
return join(
args.iterator(),
new StringTransformer() {
public String transform(String string) {
return string;
}
},
new StringJoinTemplate() {
public String getBeginning() {
return "(";
}
public String getSeparator() {
return "||";
}
public String getEnding() {
return ")";
}
}
);
}
}
private static interface StringTransformer {
public String transform(String string);
}
private static interface StringJoinTemplate {
/**
* Getter for property 'beginning'.
*
* @return Value for property 'beginning'.
*/
public String getBeginning();
/**
* Getter for property 'separator'.
*
* @return Value for property 'separator'.
*/
public String getSeparator();
/**
* Getter for property 'ending'.
*
* @return Value for property 'ending'.
*/
public String getEnding();
}
private static String join(Iterator/*<String>*/ elements, StringTransformer elementTransformer, StringJoinTemplate template) {
// todo : make this available via StringHelper?
StringBuffer buffer = new StringBuffer( template.getBeginning() );
while ( elements.hasNext() ) {
final String element = ( String ) elements.next();
buffer.append( elementTransformer.transform( element ) );
if ( elements.hasNext() ) {
buffer.append( template.getSeparator() );
}
}
return buffer.append( template.getEnding() ).toString();
}
}

View File

@ -32,50 +32,97 @@ import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.type.Type; import org.hibernate.type.Type;
/** /**
* Support for slightly more general templating than <tt>StandardSQLFunction</tt>, * 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
*/ */
public class VarArgsSQLFunction implements SQLFunction { 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 type;
/**
* 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
* a SQL VARCHAR and thus a string type.
*
* @param type 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) { public VarArgsSQLFunction(Type type, String begin, String sep, String end) {
this.begin = begin;
this.sep = sep;
this.end = end;
this.type = type; this.type = type;
}
public VarArgsSQLFunction(String begin, String sep, String end) {
this.begin = begin; this.begin = begin;
this.sep = sep; this.sep = sep;
this.end = end; this.end = end;
this.type = null;
} }
/**
* Constructs a VarArgsSQLFunction instance with a 'dynamic' return type. For a dynamic return type,
* the type of the arguments are used to resolve the type. An example of a function with a
* 'dynamic' return would be <tt>MAX</tt> or <tt>MIN</tt> which return a double or an integer etc
* based on the types of the arguments.
*
* @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.
*
* @see #getReturnType Specifically, the 'columnType' 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 { public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
return type == null ? columnType : type; return type == null ? columnType : type;
} }
/**
* {@inheritDoc}
* <p/>
* Always returns true here.
*/
public boolean hasArguments() { public boolean hasArguments() {
return true; return true;
} }
/**
* {@inheritDoc}
* <p/>
* Always returns true here.
*/
public boolean hasParenthesesIfNoArguments() { public boolean hasParenthesesIfNoArguments() {
return true; return true;
} }
/**
* {@inheritDoc}
*/
public String render(List args, SessionFactoryImplementor factory) throws QueryException { public String render(List args, SessionFactoryImplementor factory) throws QueryException {
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 < args.size(); i++ ) {
buf.append( args.get(i) ); buf.append( transformArgument( ( String ) args.get( i ) ) );
if (i<args.size()-1) buf.append(sep); if ( i < args.size() - 1 ) {
buf.append( sep );
}
} }
return buf.append( end ).toString(); return buf.append( end ).toString();
} }
/**
* 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.
*/
protected String transformArgument(String argument) {
return argument;
}
} }