From 1e45c666e6a217af46b976c67c7b22d0c8e764e9 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 3 Feb 2009 00:47:23 +0000 Subject: [PATCH] HHH-3701 : trim function emulation for sybase git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@15860 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- .../AbstractAnsiTrimEmulationFunction.java | 227 ++++++++++++ .../function/AnsiTrimEmulationFunction.java | 322 +++++++++++------- .../AnsiTrimEmulationFunctionTest.java | 169 +++++++++ 3 files changed, 600 insertions(+), 118 deletions(-) create mode 100644 core/src/main/java/org/hibernate/dialect/function/AbstractAnsiTrimEmulationFunction.java create mode 100644 testsuite/src/test/java/org/hibernate/test/dialect/function/AnsiTrimEmulationFunctionTest.java diff --git a/core/src/main/java/org/hibernate/dialect/function/AbstractAnsiTrimEmulationFunction.java b/core/src/main/java/org/hibernate/dialect/function/AbstractAnsiTrimEmulationFunction.java new file mode 100644 index 0000000000..278fd9fe01 --- /dev/null +++ b/core/src/main/java/org/hibernate/dialect/function/AbstractAnsiTrimEmulationFunction.java @@ -0,0 +1,227 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.dialect.function; + +import org.hibernate.Hibernate; +import org.hibernate.QueryException; +import org.hibernate.engine.Mapping; +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.type.Type; + +import java.util.List; +import java.util.ArrayList; + +/** + * A {@link org.hibernate.dialect.function.SQLFunction} providing support for implementing TRIM functionality + * (as defined by both the ANSI SQL and JPA specs) in cases where the dialect may not support the full trim + * function itself. + *

+ * Follows the template pattern in order to implement + * the {@link #render} method. + * + * @author Steve Ebersole + */ +public abstract class AbstractAnsiTrimEmulationFunction implements SQLFunction { + /** + * {@inheritDoc} + */ + public final Type getReturnType(Type columnType, Mapping mapping) throws QueryException { + return Hibernate.STRING; + } + + /** + * {@inheritDoc} + */ + public final boolean hasArguments() { + return true; + } + + /** + * {@inheritDoc} + */ + public final boolean hasParenthesesIfNoArguments() { + return false; + } + + /** + * {@inheritDoc} + */ + public final String render(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: + // + // ::= + // TRIM + // + // ::= + // [ [ ] [ ] FROM ] + // + // ::= + // LEADING + // | TRAILING + // | BOTH + // + // If is omitted, BOTH is assumed. + // If is omitted, space is assumed + if ( args.size() == 1 ) { + // we have the form: trim(trimSource) + // so we trim leading and trailing spaces + return resolveBothSpaceTrimFunction().render( 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!!!! + } + else { + // otherwise, a trim-specification and/or a trim-character + // have been specified; we need to decide which options + // are present and "do the right thing" + boolean leading = true; // should leading trim-characters be trimmed? + boolean trailing = true; // should trailing trim-characters be trimmed? + String trimCharacter; // the trim-character (what is to be trimmed off?) + String trimSource; // the trim-source (from where should it be trimmed?) + + // potentialTrimCharacterArgIndex = 1 assumes that a + // trim-specification has been specified. we handle the + // exception to that explicitly + int potentialTrimCharacterArgIndex = 1; + String firstArg = ( String ) args.get( 0 ); + if ( "leading".equalsIgnoreCase( firstArg ) ) { + trailing = false; + } + else if ( "trailing".equalsIgnoreCase( firstArg ) ) { + leading = false; + } + else if ( "both".equalsIgnoreCase( firstArg ) ) { + } + else { + potentialTrimCharacterArgIndex = 0; + } + + String potentialTrimCharacter = ( String ) args.get( potentialTrimCharacterArgIndex ); + if ( "from".equalsIgnoreCase( potentialTrimCharacter ) ) { + trimCharacter = "' '"; + trimSource = ( String ) args.get( potentialTrimCharacterArgIndex + 1 ); + } + else if ( potentialTrimCharacterArgIndex + 1 >= args.size() ) { + trimCharacter = "' '"; + trimSource = potentialTrimCharacter; + } + else { + trimCharacter = potentialTrimCharacter; + if ( "from".equalsIgnoreCase( ( String ) args.get( potentialTrimCharacterArgIndex + 1 ) ) ) { + trimSource = ( String ) args.get( potentialTrimCharacterArgIndex + 2 ); + } + else { + trimSource = ( String ) args.get( potentialTrimCharacterArgIndex + 1 ); + } + } + + List argsToUse = new ArrayList(); + argsToUse.add( trimSource ); + argsToUse.add( trimCharacter ); + + if ( trimCharacter.equals( "' '" ) ) { + if ( leading && trailing ) { + return resolveBothSpaceTrimFunction().render( argsToUse, factory ); + } + else if ( leading ) { + return resolveLeadingSpaceTrimFunction().render( argsToUse, factory ); + } + else { + return resolveTrailingSpaceTrimFunction().render( argsToUse, factory ); + } + } + else { + if ( leading && trailing ) { + return resolveBothTrimFunction().render( argsToUse, factory ); + } + else if ( leading ) { + return resolveLeadingTrimFunction().render( argsToUse, factory ); + } + else { + return resolveTrailingTrimFunction().render( argsToUse, factory ); + } + } + } + } + + /** + * Resolve the function definition which should be used to trim both leading and trailing spaces. + *

+ * In this form, the imput arguments is missing the FROM keyword. + * + * @return The sql function + */ + protected abstract SQLFunction resolveBothSpaceTrimFunction(); + + /** + * Resolve the function definition which should be used to trim both leading and trailing spaces. + *

+ * The same as {#link resolveBothSpaceTrimFunction} except that here theFROM is included and + * will need to be accounted for during {@link SQLFunction#render} processing. + * + * @return The sql function + */ + protected abstract SQLFunction resolveBothSpaceTrimFromFunction(); + + /** + * Resolve the function definition which should be used to trim leading spaces. + * + * @return The sql function + */ + protected abstract SQLFunction resolveLeadingSpaceTrimFunction(); + + /** + * Resolve the function definition which should be used to trim trailing spaces. + * + * @return The sql function + */ + protected abstract SQLFunction resolveTrailingSpaceTrimFunction(); + + /** + * Resolve the function definition which should be used to trim the specified character from both the + * beginning (leading) and end (trailing) of the trim source. + * + * @return The sql function + */ + protected abstract SQLFunction resolveBothTrimFunction(); + + /** + * Resolve the function definition which should be used to trim the specified character from the + * beginning (leading) of the trim source. + * + * @return The sql function + */ + protected abstract SQLFunction resolveLeadingTrimFunction(); + + /** + * Resolve the function definition which should be used to trim the specified character from the + * end (trailing) of the trim source. + * + * @return The sql function + */ + protected abstract SQLFunction resolveTrailingTrimFunction(); +} diff --git a/core/src/main/java/org/hibernate/dialect/function/AnsiTrimEmulationFunction.java b/core/src/main/java/org/hibernate/dialect/function/AnsiTrimEmulationFunction.java index cd730fcd71..6a63db4e11 100644 --- a/core/src/main/java/org/hibernate/dialect/function/AnsiTrimEmulationFunction.java +++ b/core/src/main/java/org/hibernate/dialect/function/AnsiTrimEmulationFunction.java @@ -25,13 +25,6 @@ package org.hibernate.dialect.function; import org.hibernate.Hibernate; -import org.hibernate.QueryException; -import org.hibernate.engine.Mapping; -import org.hibernate.engine.SessionFactoryImplementor; -import org.hibernate.type.Type; - -import java.util.List; -import java.util.ArrayList; /** * A {@link SQLFunction} implementation that emulates the ANSI SQL trim function @@ -42,129 +35,222 @@ import java.util.ArrayList; * * @author Steve Ebersole */ -public class AnsiTrimEmulationFunction implements SQLFunction { +public class AnsiTrimEmulationFunction extends AbstractAnsiTrimEmulationFunction { + public static final String LTRIM = "ltrim"; + public static final String RTRIM = "rtrim"; + public static final String REPLACE = "replace"; + public static final String SPACE_PLACEHOLDER = "${space}$"; - private static final SQLFunction LEADING_SPACE_TRIM = new SQLFunctionTemplate( Hibernate.STRING, "ltrim( ?1 )"); - private static final SQLFunction TRAILING_SPACE_TRIM = new SQLFunctionTemplate( Hibernate.STRING, "rtrim( ?1 )"); - private static final SQLFunction BOTH_SPACE_TRIM = new SQLFunctionTemplate( Hibernate.STRING, "ltrim( rtrim( ?1 ) )"); - private static final SQLFunction BOTH_SPACE_TRIM_FROM = new SQLFunctionTemplate( Hibernate.STRING, "ltrim( rtrim( ?2 ) )"); + public static final String LEADING_SPACE_TRIM_TEMPLATE = LTRIM + "(?1)"; + public static final String TRAILING_SPACE_TRIM_TEMPLATE = RTRIM + "(?1)"; + public static final String BOTH_SPACE_TRIM_TEMPLATE = LTRIM + "(" + RTRIM + "(?1))"; + public static final String BOTH_SPACE_TRIM_FROM_TEMPLATE = LTRIM + "(" + RTRIM + "(?2))"; //skip the FROM keyword in params - private static final SQLFunction LEADING_TRIM = new SQLFunctionTemplate( Hibernate.STRING, "replace( replace( rtrim( replace( replace( ?1, ' ', '${space}$' ), ?2, ' ' ) ), ' ', ?2 ), '${space}$', ' ' )" ); - private static final SQLFunction TRAILING_TRIM = new SQLFunctionTemplate( Hibernate.STRING, "replace( replace( ltrim( replace( replace( ?1, ' ', '${space}$' ), ?2, ' ' ) ), ' ', ?2 ), '${space}$', ' ' )" ); - private static final SQLFunction BOTH_TRIM = new SQLFunctionTemplate( Hibernate.STRING, "replace( replace( ltrim( rtrim( replace( replace( ?1, ' ', '${space}$' ), ?2, ' ' ) ) ), ' ', ?2 ), '${space}$', ' ' )" ); + /** + * A template for the series of calls required to trim non-space chars from the beginning of text. + *

+ * NOTE : essentially we: + *

  • replace all space chars with the text '${space}$'
  • + *
  • replace all the actual replacement chars with space chars
  • + *
  • perform left-trimming (that removes any of the space chars we just added which occur at the beginning of the text)
  • + *
  • replace all space chars with the replacement char
  • + *
  • replace all the '${space}$' text with space chars
  • + * + */ + public static final String LEADING_TRIM_TEMPLATE = + REPLACE + "(" + + REPLACE + "(" + + LTRIM + "(" + + REPLACE + "(" + + REPLACE + "(" + + "?1," + + "' '," + + "'" + SPACE_PLACEHOLDER + "'" + + ")," + + "?2," + + "' '" + + ")" + + ")," + + "' '," + + "?2" + + ")," + + "'" + SPACE_PLACEHOLDER + "'," + + "' '" + + ")"; - public Type getReturnType(Type columnType, Mapping mapping) throws QueryException { - return Hibernate.STRING; + /** + * A template for the series of calls required to trim non-space chars from the end of text. + *

    + * NOTE: essentially the same series of calls as outlined in {@link #LEADING_TRIM_TEMPLATE} except that here, + * instead of left-trimming the added spaces, we right-trim them to remove them from the end of the text. + */ + public static final String TRAILING_TRIM_TEMPLATE = + REPLACE + "(" + + REPLACE + "(" + + RTRIM + "(" + + REPLACE + "(" + + REPLACE + "(" + + "?1," + + "' '," + + "'" + SPACE_PLACEHOLDER + "'" + + ")," + + "?2," + + "' '" + + ")" + + ")," + + "' '," + + "?2" + + ")," + + "'" + SPACE_PLACEHOLDER + "'," + + "' '" + + ")"; + + /** + * A template for the series of calls required to trim non-space chars from both the beginning and the end of text. + *

    + * NOTE: again, we have a series of calls that is essentially the same as outlined in {@link #LEADING_TRIM_TEMPLATE} + * except that here we perform both left (leading) and right (trailing) trimming. + */ + public static final String BOTH_TRIM_TEMPLATE = + REPLACE + "(" + + REPLACE + "(" + + LTRIM + "(" + + RTRIM + "(" + + REPLACE + "(" + + REPLACE + "(" + + "?1," + + "' '," + + "'" + SPACE_PLACEHOLDER + "'" + + ")," + + "?2," + + "' '" + + ")" + + ")" + + ")," + + "' '," + + "?2" + + ")," + + "'" + SPACE_PLACEHOLDER + "'," + + "' '" + + ")"; + + private final SQLFunction leadingSpaceTrim; + private final SQLFunction trailingSpaceTrim; + private final SQLFunction bothSpaceTrim; + private final SQLFunction bothSpaceTrimFrom; + + private final SQLFunction leadingTrim; + private final SQLFunction trailingTrim; + private final SQLFunction bothTrim; + + /** + * Constructs a new AnsiTrimEmulationFunction using {@link #LTRIM}, {@link #RTRIM}, and {@link #REPLACE} + * respectively. + * + * @see #AnsiTrimEmulationFunction(String,String,String) + */ + public AnsiTrimEmulationFunction() { + this( LTRIM, RTRIM, REPLACE ); } - public boolean hasArguments() { - return true; + /** + * Constructs a trim() emulation function definition using the specified function calls. + * + * @param ltrimFunctionName The left trim function to use. + * @param rtrimFunctionName The right trim function to use. + * @param replaceFunctionName The replace function to use. + */ + public AnsiTrimEmulationFunction(String ltrimFunctionName, String rtrimFunctionName, String replaceFunctionName) { + leadingSpaceTrim = new SQLFunctionTemplate( + Hibernate.STRING, + LEADING_SPACE_TRIM_TEMPLATE.replaceAll( LTRIM, ltrimFunctionName ) + ); + + trailingSpaceTrim = new SQLFunctionTemplate( + Hibernate.STRING, + TRAILING_SPACE_TRIM_TEMPLATE.replaceAll( RTRIM, rtrimFunctionName ) + ); + + bothSpaceTrim = new SQLFunctionTemplate( + Hibernate.STRING, + BOTH_SPACE_TRIM_TEMPLATE.replaceAll( LTRIM, ltrimFunctionName ) + .replaceAll( RTRIM, rtrimFunctionName ) + ); + + bothSpaceTrimFrom = new SQLFunctionTemplate( + Hibernate.STRING, + BOTH_SPACE_TRIM_FROM_TEMPLATE.replaceAll( LTRIM, ltrimFunctionName ) + .replaceAll( RTRIM, rtrimFunctionName ) + ); + + leadingTrim = new SQLFunctionTemplate( + Hibernate.STRING, + LEADING_TRIM_TEMPLATE.replaceAll( LTRIM, ltrimFunctionName ) + .replaceAll( RTRIM, rtrimFunctionName ) + .replaceAll( REPLACE,replaceFunctionName ) + ); + + trailingTrim = new SQLFunctionTemplate( + Hibernate.STRING, + TRAILING_TRIM_TEMPLATE.replaceAll( LTRIM, ltrimFunctionName ) + .replaceAll( RTRIM, rtrimFunctionName ) + .replaceAll( REPLACE,replaceFunctionName ) + ); + + bothTrim = new SQLFunctionTemplate( + Hibernate.STRING, + BOTH_TRIM_TEMPLATE.replaceAll( LTRIM, ltrimFunctionName ) + .replaceAll( RTRIM, rtrimFunctionName ) + .replaceAll( REPLACE,replaceFunctionName ) + ); } - public boolean hasParenthesesIfNoArguments() { - return false; + /** + * {@inheritDoc} + */ + protected SQLFunction resolveBothSpaceTrimFunction() { + return bothSpaceTrim; } - public String render(List args, SessionFactoryImplementor factory) throws QueryException { - // according to both the ANSI-SQL and EJB3 specs, trim can either take - // exactly one parameter or a variable number of parameters between 1 and 4. - // from the SQL spec: - // - // ::= - // TRIM - // - // ::= - // [ [ ] [ ] FROM ] - // - // ::= - // LEADING - // | TRAILING - // | BOTH - // - // If only is omitted, BOTH is assumed; - // if is omitted, space is assumed - if ( args.size() == 1 ) { - // we have the form: trim(trimSource) - // so we trim leading and trailing spaces - return BOTH_SPACE_TRIM.render( args, factory ); - } - else if ( "from".equalsIgnoreCase( ( String ) args.get( 0 ) ) ) { - // we have the form: trim(from trimSource). - // This is functionally equivalent to trim(trimSource) - return BOTH_SPACE_TRIM_FROM.render( args, factory ); - } - else { - // otherwise, a trim-specification and/or a trim-character - // have been specified; we need to decide which options - // are present and "do the right thing" - boolean leading = true; // should leading trim-characters be trimmed? - boolean trailing = true; // should trailing trim-characters be trimmed? - String trimCharacter = null; // the trim-character - String trimSource = null; // the trim-source + /** + * {@inheritDoc} + */ + protected SQLFunction resolveBothSpaceTrimFromFunction() { + return bothSpaceTrimFrom; + } - // potentialTrimCharacterArgIndex = 1 assumes that a - // trim-specification has been specified. we handle the - // exception to that explicitly - int potentialTrimCharacterArgIndex = 1; - String firstArg = ( String ) args.get( 0 ); - if ( "leading".equalsIgnoreCase( firstArg ) ) { - trailing = false; - } - else if ( "trailing".equalsIgnoreCase( firstArg ) ) { - leading = false; - } - else if ( "both".equalsIgnoreCase( firstArg ) ) { - } - else { - potentialTrimCharacterArgIndex = 0; - } + /** + * {@inheritDoc} + */ + protected SQLFunction resolveLeadingSpaceTrimFunction() { + return leadingSpaceTrim; + } - String potentialTrimCharacter = ( String ) args.get( potentialTrimCharacterArgIndex ); - if ( "from".equalsIgnoreCase( potentialTrimCharacter ) ) { - trimCharacter = "' '"; - trimSource = ( String ) args.get( potentialTrimCharacterArgIndex + 1 ); - } - else if ( potentialTrimCharacterArgIndex + 1 >= args.size() ) { - trimCharacter = "' '"; - trimSource = potentialTrimCharacter; - } - else { - trimCharacter = potentialTrimCharacter; - if ( "from".equalsIgnoreCase( ( String ) args.get( potentialTrimCharacterArgIndex + 1 ) ) ) { - trimSource = ( String ) args.get( potentialTrimCharacterArgIndex + 2 ); - } - else { - trimSource = ( String ) args.get( potentialTrimCharacterArgIndex + 1 ); - } - } + /** + * {@inheritDoc} + */ + protected SQLFunction resolveTrailingSpaceTrimFunction() { + return trailingSpaceTrim; + } - List argsToUse = null; - argsToUse = new ArrayList(); - argsToUse.add( trimSource ); - argsToUse.add( trimCharacter ); + /** + * {@inheritDoc} + */ + protected SQLFunction resolveBothTrimFunction() { + return bothTrim; + } - if ( trimCharacter.equals( "' '" ) ) { - if ( leading && trailing ) { - return BOTH_SPACE_TRIM.render( argsToUse, factory ); - } - else if ( leading ) { - return LEADING_SPACE_TRIM.render( argsToUse, factory ); - } - else { - return TRAILING_SPACE_TRIM.render( argsToUse, factory ); - } - } - else { - if ( leading && trailing ) { - return BOTH_TRIM.render( argsToUse, factory ); - } - else if ( leading ) { - return LEADING_TRIM.render( argsToUse, factory ); - } - else { - return TRAILING_TRIM.render( argsToUse, factory ); - } - } - } + /** + * {@inheritDoc} + */ + protected SQLFunction resolveLeadingTrimFunction() { + return leadingTrim; + } + + /** + * {@inheritDoc} + */ + protected SQLFunction resolveTrailingTrimFunction() { + return trailingTrim; } } 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 new file mode 100644 index 0000000000..f4cc4c4d9f --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/dialect/function/AnsiTrimEmulationFunctionTest.java @@ -0,0 +1,169 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.dialect.function; + +import java.util.List; +import java.util.ArrayList; + +import junit.framework.TestCase; + +import org.hibernate.dialect.function.AnsiTrimEmulationFunction; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public class AnsiTrimEmulationFunctionTest extends TestCase { + private static final String trimSource = "a.column"; + + public void testBasicSqlServerProcessing() { + AnsiTrimEmulationFunction function = new AnsiTrimEmulationFunction(); + + performBasicSpaceTrimmingTests( function ); + + final String expectedTrimPrep = "replace(replace(a.column,' ','${space}$'),'-',' ')"; + final String expectedPostTrimPrefix = "replace(replace("; + final String expectedPostTrimSuffix = ",' ','-'),'${space}$',' ')"; + + // -> trim(LEADING '-' FROM a.column) + String rendered = function.render( 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 ); + expected = expectedPostTrimPrefix + "rtrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; + assertEquals( expected, rendered ); + + // -> trim(BOTH '-' FROM a.column) + rendered = function.render( 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 ); + expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix; + assertEquals( expected, rendered ); + } + + public void testBasicSybaseProcessing() { + AnsiTrimEmulationFunction function = new AnsiTrimEmulationFunction( + AnsiTrimEmulationFunction.LTRIM, + AnsiTrimEmulationFunction.RTRIM, + "str_replace" + ); + + performBasicSpaceTrimmingTests( function ); + + final String expectedTrimPrep = "str_replace(str_replace(a.column,' ','${space}$'),'-',' ')"; + final String expectedPostTrimPrefix = "str_replace(str_replace("; + final String expectedPostTrimSuffix = ",' ','-'),'${space}$',' ')"; + + // -> trim(LEADING '-' FROM a.column) + String rendered = function.render( 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 ); + expected = expectedPostTrimPrefix + "rtrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; + assertEquals( expected, rendered ); + + // -> trim(BOTH '-' FROM a.column) + rendered = function.render( 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 ); + expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix; + assertEquals( expected, rendered ); + } + + private void performBasicSpaceTrimmingTests(AnsiTrimEmulationFunction function) { + // -> trim(a.column) + String rendered = function.render( argList( trimSource ), null ); + assertEquals( "ltrim(rtrim(a.column))", rendered ); + + // -> trim(FROM a.column) + rendered = function.render( argList( "FROM", trimSource ), null ); + assertEquals( "ltrim(rtrim(a.column))", rendered ); + + // -> trim(BOTH FROM a.column) + rendered = function.render( argList( "BOTH", "FROM", trimSource ), null ); + assertEquals( "ltrim(rtrim(a.column))", rendered ); + + // -> trim(BOTH ' ' FROM a.column) + rendered = function.render( argList( "BOTH", "' '", "FROM", trimSource ), null ); + assertEquals( "ltrim(rtrim(a.column))", rendered ); + + // -> trim(LEADING FROM a.column) + rendered = function.render( argList( "LEADING", "FROM", trimSource ), null ); + assertEquals( "ltrim(a.column)", rendered ); + + // -> trim(LEADING ' ' FROM a.column) + rendered = function.render( argList( "LEADING", "' '", "FROM", trimSource ), null ); + assertEquals( "ltrim(a.column)", rendered ); + + // -> trim(TRAILING FROM a.column) + rendered = function.render( argList( "TRAILING", "FROM", trimSource ), null ); + assertEquals( "rtrim(a.column)", rendered ); + + // -> trim(TRAILING ' ' FROM a.column) + rendered = function.render( 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; + } + +}