diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/AltibaseDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/AltibaseDialect.java index 62fda8d2b1..ecc99eaa99 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/AltibaseDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/AltibaseDialect.java @@ -138,23 +138,23 @@ public class AltibaseDialect extends Dialect { } @Override - public String trimPattern(TrimSpec specification, char character) { + public String trimPattern(TrimSpec specification, boolean isWhitespace) { switch ( specification ) { case BOTH: - return character == ' ' + return isWhitespace ? "trim(?1)" - : "trim(?1, '" + character + "')"; + : "trim(?1,?2)"; case LEADING: - return character == ' ' + return isWhitespace ? "ltrim(?1)" - : "ltrim(?1,'" + character + "')"; + : "ltrim(?1,?2)"; case TRAILING: - return character == ' ' + return isWhitespace ? "rtrim(?1)" - : "rtrim(?1,'" + character + "')"; + : "rtrim(?1,?2)"; } - return super.trimPattern( specification, character ); + return super.trimPattern( specification, isWhitespace ); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java index 746a565f32..03b25ea348 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java @@ -34,6 +34,7 @@ import org.hibernate.dialect.function.CountFunction; import org.hibernate.dialect.function.DB2FormatEmulation; import org.hibernate.dialect.function.DB2PositionFunction; import org.hibernate.dialect.function.DB2SubstringFunction; +import org.hibernate.dialect.function.TrimFunction; import org.hibernate.dialect.identity.DB2IdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.DB2LimitHandler; @@ -403,6 +404,13 @@ public class DB2LegacyDialect extends Dialect { .setArgumentListSignature("(STRING string, STRING pattern)") .register(); + //trim() requires trim characters to be constant literals + functionContributions.getFunctionRegistry().register( "trim", new TrimFunction( + this, + functionContributions.getTypeConfiguration(), + SqlAstNodeRenderingMode.INLINE_PARAMETERS + ) ); + functionFactory.windowFunctions(); if ( getDB2Version().isSameOrAfter( 9, 5 ) ) { functionFactory.listagg( null ); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java index 3a59956a70..d3ddb745a3 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java @@ -16,6 +16,7 @@ import org.hibernate.StaleObjectStateException; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.dialect.*; import org.hibernate.dialect.function.CommonFunctionFactory; +import org.hibernate.dialect.function.TrimFunction; import org.hibernate.dialect.identity.HSQLIdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.lock.LockingStrategy; @@ -268,6 +269,13 @@ public class HSQLLegacyDialect extends Dialect { functionFactory.arrayTrim_trim_array(); functionFactory.arrayFill_hsql(); functionFactory.arrayToString_hsql(); + + //trim() requires parameters to be cast when used as trim character + functionContributions.getFunctionRegistry().register( "trim", new TrimFunction( + this, + functionContributions.getTypeConfiguration(), + SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER + ) ); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MaxDBDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MaxDBDialect.java index fe2ee0fa27..e527bd5baa 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MaxDBDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MaxDBDialect.java @@ -203,8 +203,8 @@ public class MaxDBDialect extends Dialect { } @Override - public String trimPattern(TrimSpec specification, char character) { - return AbstractTransactSQLDialect.replaceLtrimRtrim( specification, character); + public String trimPattern(TrimSpec specification, boolean isWhitespace) { + return AbstractTransactSQLDialect.replaceLtrimRtrim( specification, isWhitespace ); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/RDMSOS2200Dialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/RDMSOS2200Dialect.java index 3bdea363ea..b4b111aa52 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/RDMSOS2200Dialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/RDMSOS2200Dialect.java @@ -431,7 +431,7 @@ public class RDMSOS2200Dialect extends Dialect { } @Override - public String trimPattern(TrimSpec specification, char character) { - return AbstractTransactSQLDialect.replaceLtrimRtrim( specification, character); + public String trimPattern(TrimSpec specification, boolean isWhitespace) { + return AbstractTransactSQLDialect.replaceLtrimRtrim( specification, isWhitespace ); } } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java index 53b67b94a3..0600065ad2 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java @@ -390,25 +390,25 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect { } @Override - public String trimPattern(TrimSpec specification, char character) { + public String trimPattern(TrimSpec specification, boolean isWhitespace) { if ( getVersion().isSameOrAfter( 16 ) ) { switch ( specification ) { case BOTH: - return character == ' ' + return isWhitespace ? "trim(?1)" - : "trim('" + character + "' from ?1)"; + : "trim(?2 from ?1)"; case LEADING: - return character == ' ' + return isWhitespace ? "ltrim(?1)" - : "ltrim(?1,'" + character + "')"; + : "ltrim(?1,?2)"; case TRAILING: - return character == ' ' + return isWhitespace ? "rtrim(?1)" - : "rtrim(?1,'" + character + "')"; + : "rtrim(?1,?2)"; } throw new UnsupportedOperationException( "Unsupported specification: " + specification ); } - return super.trimPattern( specification, character ); + return super.trimPattern( specification, isWhitespace ); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLiteDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLiteDialect.java index 0f66ac3356..65725af641 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLiteDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLiteDialect.java @@ -353,20 +353,20 @@ public class SQLiteDialect extends Dialect { } @Override - public String trimPattern(TrimSpec specification, char character) { + public String trimPattern(TrimSpec specification, boolean isWhitespace) { switch ( specification ) { case BOTH: - return character == ' ' + return isWhitespace ? "trim(?1)" - : "trim(?1,'" + character + "')"; + : "trim(?1,?2)"; case LEADING: - return character == ' ' + return isWhitespace ? "ltrim(?1)" - : "ltrim(?1,'" + character + "')"; + : "ltrim(?1,?2)"; case TRAILING: - return character == ' ' + return isWhitespace ? "rtrim(?1)" - : "rtrim(?1,'" + character + "')"; + : "rtrim(?1,?2)"; } throw new UnsupportedOperationException( "Unsupported specification: " + specification ); } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java index 5e143c7540..b5fade8185 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java @@ -425,12 +425,6 @@ public class SybaseLegacyDialect extends AbstractTransactSQLDialect { return "datediff(?1,?2,?3)"; } - @Override - public String trimPattern(TrimSpec specification, char character) { - return super.trimPattern(specification, character) - .replace("replace", "str_replace"); - } - @Override public void appendDatetimeFormat(SqlAppender appender, String format) { throw new UnsupportedOperationException( "format() function not supported on Sybase"); diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index cb69e6bc15..1d5b5904fa 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -1369,6 +1369,7 @@ trimSpecification trimCharacter : STRING_LITERAL + | parameter ; /** diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java index ba2a49f1a6..2b6e5252b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java @@ -166,28 +166,32 @@ public abstract class AbstractTransactSQLDialect extends Dialect { } @Override - public String trimPattern(TrimSpec specification, char character) { - return replaceLtrimRtrim(specification, character); + public String trimPattern(TrimSpec specification, boolean isWhitespace) { + return replaceLtrimRtrim( specification, isWhitespace ); } + /** + * @deprecated Use {@link #replaceLtrimRtrim(TrimSpec, boolean)} instead. + */ + @Deprecated( forRemoval = true ) public static String replaceLtrimRtrim(TrimSpec specification, char character) { - boolean blank = character == ' '; + return replaceLtrimRtrim( specification, character == ' ' ); + } + + public static String replaceLtrimRtrim(TrimSpec specification, boolean isWhitespace) { switch ( specification ) { case LEADING: - return blank + return isWhitespace ? "ltrim(?1)" - : "replace(replace(ltrim(replace(replace(?1,' ','#%#%'),'@',' ')),' ','@'),'#%#%',' ')" - .replace('@', character); + : "substring(?1,patindex('%[^'+?2+']%',?1),len(?1)-patindex('%[^'+?2+']%',?1)+1)"; case TRAILING: - return blank + return isWhitespace ? "rtrim(?1)" - : "replace(replace(rtrim(replace(replace(?1,' ','#%#%'),'@',' ')),' ','@'),'#%#%',' ')" - .replace('@', character); + : "substring(?1,1,len(?1)-patindex('%[^'+?2+']%',reverse(?1))+1)"; default: - return blank + return isWhitespace ? "ltrim(rtrim(?1))" - : "replace(replace(ltrim(rtrim(replace(replace(?1,' ','#%#%'),'@',' '))),' ','@'),'#%#%',' ')" - .replace('@', character); + : "substring(?1,patindex('%[^'+?2+']%',?1),len(?1)-patindex('%[^'+?2+']%',?1)-patindex('%[^'+?2+']%',reverse(?1))+2)"; } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index 7b8e8105e5..f4503d2c8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -28,6 +28,7 @@ import org.hibernate.dialect.function.CountFunction; import org.hibernate.dialect.function.DB2FormatEmulation; import org.hibernate.dialect.function.DB2PositionFunction; import org.hibernate.dialect.function.DB2SubstringFunction; +import org.hibernate.dialect.function.TrimFunction; import org.hibernate.dialect.identity.DB2IdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.DB2LimitHandler; @@ -391,6 +392,13 @@ public class DB2Dialect extends Dialect { .setArgumentListSignature("(STRING string, STRING pattern)") .register(); + //trim() requires trim characters to be constant literals + functionContributions.getFunctionRegistry().register( "trim", new TrimFunction( + this, + functionContributions.getTypeConfiguration(), + SqlAstNodeRenderingMode.INLINE_PARAMETERS + ) ); + functionFactory.windowFunctions(); functionFactory.listagg( null ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index d937500b65..41606b5db3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -1484,11 +1484,29 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun * * @param specification {@code leading} or {@code trailing} * @param character the character to trim + * + * @deprecated Use {@link #trimPattern(TrimSpec, boolean)} instead. */ + @Deprecated( forRemoval = true ) public String trimPattern(TrimSpec specification, char character) { - return character == ' ' - ? "trim(" + specification + " from ?1)" - : "trim(" + specification + " '" + character + "' from ?1)"; + return trimPattern( specification, character == ' ' ); + } + + /** + * Obtain a pattern for the SQL equivalent to a + * {@code trim()} function call. The resulting + * pattern must contain a ?1 placeholder for the + * argument of type {@link String} and a ?2 placeholder + * for the trim character if {@code isWhitespace} + * was false. + * + * @param specification {@linkplain TrimSpec#LEADING leading}, {@linkplain TrimSpec#TRAILING trailing} + * or {@linkplain TrimSpec#BOTH both} + * @param isWhitespace {@code true} if the trim character is a whitespace and can be omitted, + * {@code false} if it must be explicit and a ?2 placeholder should be included in the pattern + */ + public String trimPattern(TrimSpec specification, boolean isWhitespace) { + return "trim(" + specification + ( isWhitespace ? "" : " ?2" ) + " from ?1)"; } /** diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DialectDelegateWrapper.java b/hibernate-core/src/main/java/org/hibernate/dialect/DialectDelegateWrapper.java index 64befb71da..a6370d76aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DialectDelegateWrapper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DialectDelegateWrapper.java @@ -290,6 +290,11 @@ public class DialectDelegateWrapper extends Dialect { return wrapped.trimPattern( specification, character ); } + @Override + public String trimPattern(TrimSpec specification, boolean isWhitespace) { + return wrapped.trimPattern( specification, isWhitespace ); + } + @Override public boolean supportsFractionalTimestampArithmetic() { return wrapped.supportsFractionalTimestampArithmetic(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java index 2357b9f9ad..f14dfac708 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -12,6 +12,7 @@ import java.sql.Types; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.dialect.function.CommonFunctionFactory; +import org.hibernate.dialect.function.TrimFunction; import org.hibernate.dialect.identity.HSQLIdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; @@ -208,6 +209,13 @@ public class HSQLDialect extends Dialect { functionFactory.arrayTrim_trim_array(); functionFactory.arrayFill_hsql(); functionFactory.arrayToString_hsql(); + + //trim() requires parameters to be cast when used as trim character + functionContributions.getFunctionRegistry().register( "trim", new TrimFunction( + this, + functionContributions.getTypeConfiguration(), + SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER + ) ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index f4a37338d8..beec70ab5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -399,25 +399,25 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { } @Override - public String trimPattern(TrimSpec specification, char character) { + public String trimPattern(TrimSpec specification, boolean isWhitespace) { if ( getVersion().isSameOrAfter( 16 ) ) { switch ( specification ) { case BOTH: - return character == ' ' + return isWhitespace ? "trim(?1)" - : "trim('" + character + "' from ?1)"; + : "trim(?2 from ?1)"; case LEADING: - return character == ' ' + return isWhitespace ? "ltrim(?1)" - : "ltrim(?1,'" + character + "')"; + : "ltrim(?1,?2)"; case TRAILING: - return character == ' ' + return isWhitespace ? "rtrim(?1)" - : "rtrim(?1,'" + character + "')"; + : "rtrim(?1,?2)"; } throw new UnsupportedOperationException( "Unsupported specification: " + specification ); } - return super.trimPattern( specification, character ); + return super.trimPattern( specification, isWhitespace ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java index a30ddc165f..0cba4a8f84 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -447,12 +447,6 @@ public class SybaseDialect extends AbstractTransactSQLDialect { return "datediff(?1,?2,?3)"; } - @Override - public String trimPattern(TrimSpec specification, char character) { - return super.trimPattern(specification, character) - .replace("replace", "str_replace"); - } - @Override public void appendDatetimeFormat(SqlAppender appender, String format) { throw new UnsupportedOperationException( "format() function not supported on Sybase"); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/TrimFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/TrimFunction.java index fa44b7ba69..1f39f70b72 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/TrimFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/TrimFunction.java @@ -6,44 +6,56 @@ */ package org.hibernate.dialect.function; +import java.util.Collections; +import java.util.List; + import org.hibernate.dialect.Dialect; import org.hibernate.query.ReturnableType; import org.hibernate.query.sqm.TrimSpec; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; +import org.hibernate.query.sqm.produce.function.FunctionArgumentException; import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; import org.hibernate.query.sqm.produce.function.internal.PatternRenderer; +import org.hibernate.query.sqm.sql.internal.SqmParameterInterpretation; +import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Literal; import org.hibernate.sql.ast.tree.expression.TrimSpecification; +import org.hibernate.type.SqlTypes; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.spi.TypeConfiguration; -import java.util.Collections; -import java.util.List; - -import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TRIM_SPEC; import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING; +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TRIM_SPEC; /** * ANSI SQL-standard {@code trim()} function, which has a funny syntax * involving a {@link TrimSpec}, and portability is achieved using - * {@link Dialect#trimPattern(TrimSpec, char)}. + * {@link Dialect#trimPattern(TrimSpec, boolean)}. *

* For example, {@code trim(leading ' ' from text)}. * * @author Gavin King */ public class TrimFunction extends AbstractSqmSelfRenderingFunctionDescriptor { - private final Dialect dialect; + private SqlAstNodeRenderingMode argumentRenderingMode; public TrimFunction(Dialect dialect, TypeConfiguration typeConfiguration) { + this( dialect, typeConfiguration, SqlAstNodeRenderingMode.DEFAULT ); + } + + public TrimFunction( + Dialect dialect, + TypeConfiguration typeConfiguration, + SqlAstNodeRenderingMode argumentRenderingMode) { super( "trim", new ArgumentTypesValidator( @@ -56,6 +68,7 @@ public class TrimFunction extends AbstractSqmSelfRenderingFunctionDescriptor { StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, TRIM_SPEC, STRING, STRING ) ); this.dialect = dialect; + this.argumentRenderingMode = argumentRenderingMode; } @Override @@ -65,12 +78,36 @@ public class TrimFunction extends AbstractSqmSelfRenderingFunctionDescriptor { ReturnableType returnType, SqlAstTranslator walker) { final TrimSpec specification = ( (TrimSpecification) sqlAstArguments.get( 0 ) ).getSpecification(); - final Object trimCharacter = ( (Literal) sqlAstArguments.get( 1 ) ).getLiteralValue(); + final SqlAstNode trimCharacter = sqlAstArguments.get( 1 ); + final boolean isWhitespace = isWhitespace( trimCharacter ); final Expression sourceExpr = (Expression) sqlAstArguments.get( 2 ); - String trim = dialect.trimPattern( specification, (char) trimCharacter ); + final String trim = dialect.trimPattern( specification, isWhitespace ); - new PatternRenderer( trim ).render( sqlAppender, Collections.singletonList( sourceExpr ), walker ); + final List args = isWhitespace ? + Collections.singletonList( sourceExpr ) : + List.of( sourceExpr, trimCharacter ); + new PatternRenderer( trim, argumentRenderingMode ).render( sqlAppender, args, walker ); + } + + private static boolean isWhitespace(SqlAstNode trimCharacter) { + if ( trimCharacter instanceof Literal ) { + final char literalValue = (char) ( (Literal) trimCharacter ).getLiteralValue(); + return literalValue == ' '; + } + else { + assert trimCharacter instanceof SqmParameterInterpretation; + final JdbcType jdbcType = ( (SqmParameterInterpretation) trimCharacter ).getExpressionType() + .getSingleJdbcMapping() + .getJdbcType(); + if ( jdbcType.getJdbcTypeCode() != SqlTypes.CHAR ) { + throw new FunctionArgumentException( String.format( + "Expected parameter used as trim character to be Character typed, instead was [%s]", + jdbcType.getFriendlyName() + ) ); + } + return false; + } } // @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index ef6fd0d549..f7a34a7e9f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -4782,7 +4782,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem public SqmExpression visitTrimFunction(HqlParser.TrimFunctionContext ctx) { final SqmExpression source = (SqmExpression) ctx.expression().accept( this ); final SqmTrimSpecification trimSpec = visitTrimSpecification( ctx.trimSpecification() );; - final SqmLiteral trimChar = visitTrimCharacter( ctx.trimCharacter() ); + final SqmExpression trimChar = visitTrimCharacter( ctx.trimCharacter() ); return getFunctionDescriptor("trim").generateSqmExpression( asList( @@ -4816,15 +4816,25 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem } @Override - public SqmLiteral visitTrimCharacter(HqlParser.TrimCharacterContext ctx) { - final String trimCharText = ctx != null - ? unquoteStringLiteral( ctx.getText() ) - : " "; // JPA says space is the default - - if ( trimCharText.length() != 1 ) { - throw new SemanticException( "Trim character for trim() function must be single character, found '" + trimCharText + "'" ); + public SqmExpression visitTrimCharacter(HqlParser.TrimCharacterContext ctx) { + final String trimCharText; + if ( ctx == null ) { + // JPA says space is the default + trimCharText = " "; + } + else { + final ParseTree child = ctx.getChild( 0 ); + if ( child instanceof HqlParser.ParameterContext ) { + //noinspection unchecked + return (SqmExpression) child.accept( this ); + } + else { + trimCharText = unquoteStringLiteral( ctx.getText() ); + if ( trimCharText.length() != 1 ) { + throw new SemanticException( "Trim character for trim() function must be single character, found '" + trimCharText + "'" ); + } + } } - return new SqmLiteral<>( trimCharText.charAt( 0 ), resolveExpressibleTypeBasic( Character.class ), diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/function/AnsiTrimEmulationFunctionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/function/AnsiTrimEmulationFunctionTest.java index eec8198aec..bb895b1de9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/function/AnsiTrimEmulationFunctionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/function/AnsiTrimEmulationFunctionTest.java @@ -32,6 +32,7 @@ import org.hibernate.type.descriptor.jdbc.CharJdbcType; import org.hibernate.type.internal.BasicTypeImpl; import org.hibernate.type.spi.TypeConfiguration; +import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.ServiceRegistryScope; import org.junit.jupiter.api.Test; @@ -41,65 +42,62 @@ import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertEquals; /** - * TODO : javadoc + * Tests correct rendering of trim function emulation for {@link org.hibernate.dialect.AbstractTransactSQLDialect} dialects. * * @author Christian Beikov */ @ServiceRegistry public class AnsiTrimEmulationFunctionTest { private static final String trimSource = "a.column"; + private static final String LEADING = "substring(?1,patindex('%[^'+?2+']%',?1),len(?1)-patindex('%[^'+?2+']%',?1)+1)"; + private static final String TRAILING = "substring(?1,1,len(?1)-patindex('%[^'+?2+']%',reverse(?1))+1)"; + private static final String BOTH = "substring(?1,patindex('%[^'+?2+']%',?1),len(?1)-patindex('%[^'+?2+']%',?1)-patindex('%[^'+?2+']%',reverse(?1))+2)"; @Test + @RequiresDialect( SQLServerDialect.class ) public void testBasicSqlServerProcessing(ServiceRegistryScope scope) { Dialect dialect = new SQLServerDialect(); TrimFunction function = new TrimFunction( dialect, new TypeConfiguration() ); performBasicSpaceTrimmingTests( dialect, scope.getRegistry(), function ); - final String expectedTrimPrep = "replace(replace(a.column,' ','#%#%'),'-',' ')"; - final String expectedPostTrimPrefix = "replace(replace("; - final String expectedPostTrimSuffix = ",' ','-'),'#%#%',' ')"; - // -> trim(LEADING '-' FROM a.column) String rendered = render( dialect, scope.getRegistry(), function, TrimSpec.LEADING, '-', trimSource ); - String expected = expectedPostTrimPrefix + "ltrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; + String expected = LEADING.replace( "?1", trimSource ).replace( "?2", "'-'" ); assertEquals( expected, rendered ); // -> trim(TRAILING '-' FROM a.column) rendered = render( dialect, scope.getRegistry(), function, TrimSpec.TRAILING, '-', trimSource ); - expected = expectedPostTrimPrefix + "rtrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; + expected = TRAILING.replace( "?1", trimSource ).replace( "?2", "'-'" ); assertEquals( expected, rendered ); // -> trim(BOTH '-' FROM a.column) rendered = render( dialect, scope.getRegistry(), function, TrimSpec.BOTH, '-', trimSource ); - expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix; + expected = BOTH.replace( "?1", trimSource ).replace( "?2", "'-'" ); assertEquals( expected, rendered ); } @Test + @RequiresDialect( SybaseDialect.class ) public void testBasicSybaseProcessing(ServiceRegistryScope scope) { Dialect dialect = new SybaseDialect(); TrimFunction function = new TrimFunction( dialect, new TypeConfiguration() ); performBasicSpaceTrimmingTests( dialect, scope.getRegistry(), function ); - final String expectedTrimPrep = "str_replace(str_replace(a.column,' ','#%#%'),'-',' ')"; - final String expectedPostTrimPrefix = "str_replace(str_replace("; - final String expectedPostTrimSuffix = ",' ','-'),'#%#%',' ')"; - // -> trim(LEADING '-' FROM a.column) String rendered = render( dialect, scope.getRegistry(), function, TrimSpec.LEADING, '-', trimSource ); - String expected = expectedPostTrimPrefix + "ltrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; + String expected = LEADING.replace( "?1", trimSource ).replace( "?2", "'-'" ); assertEquals( expected, rendered ); // -> trim(TRAILING '-' FROM a.column) rendered = render( dialect, scope.getRegistry(), function, TrimSpec.TRAILING, '-', trimSource ); - expected = expectedPostTrimPrefix + "rtrim(" + expectedTrimPrep + ")" + expectedPostTrimSuffix; + expected = TRAILING.replace( "?1", trimSource ).replace( "?2", "'-'" ); assertEquals( expected, rendered ); // -> trim(BOTH '-' FROM a.column) rendered = render( dialect, scope.getRegistry(), function, TrimSpec.BOTH, '-', trimSource ); - expected = expectedPostTrimPrefix + "ltrim(rtrim(" + expectedTrimPrep + "))" + expectedPostTrimSuffix; + expected = BOTH.replace( "?1", trimSource ).replace( "?2", "'-'" ); assertEquals( expected, rendered ); }