diff --git a/Jenkinsfile b/Jenkinsfile index baf70ec0ad..5f9b377cb1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -242,6 +242,10 @@ void runBuildOnNode(String label, Closure body) { timeout( [time: 90, unit: 'MINUTES'], body ) } finally { + // If this is a PR, we clean the workspace at the end + if ( env.CHANGE_BRANCH != null ) { + cleanWs() + } pruneDockerContainers() } } 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 5523bd5086..663b23ba2c 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 @@ -525,26 +525,6 @@ searchedCaseWhen : WHEN predicate THEN expression ; -greatestFunction - : GREATEST LEFT_PAREN expression (COMMA expression)+ RIGHT_PAREN - ; - -leastFunction - : LEAST LEFT_PAREN expression (COMMA expression)+ RIGHT_PAREN - ; - -coalesceFunction - : COALESCE LEFT_PAREN expression (COMMA expression)+ RIGHT_PAREN - ; - -ifnullFunction - : IFNULL LEFT_PAREN expression COMMA expression RIGHT_PAREN - ; - -nullifFunction - : NULLIF LEFT_PAREN expression COMMA expression RIGHT_PAREN - ; - literal : STRING_LITERAL | INTEGER_LITERAL @@ -652,7 +632,7 @@ function | jpaCollectionFunction | hqlCollectionFunction | jpaNonStandardFunction - | nonStandardFunction + | genericFunction ; jpaNonStandardFunction @@ -663,16 +643,16 @@ jpaNonStandardFunctionName : STRING_LITERAL ; -nonStandardFunction - : nonStandardFunctionName LEFT_PAREN nonStandardFunctionArguments? RIGHT_PAREN +genericFunction + : genericFunctionName LEFT_PAREN (nonStandardFunctionArguments | ASTERISK)? RIGHT_PAREN filterClause? ; -nonStandardFunctionName +genericFunctionName : dotIdentifierSequence ; nonStandardFunctionArguments - : (datetimeField COMMA)? expression (COMMA expression)* + : (DISTINCT | datetimeField COMMA)? expression (COMMA expression)* ; jpaCollectionFunction @@ -688,35 +668,10 @@ hqlCollectionFunction ; aggregateFunction - : avgFunction - | sumFunction - | minFunction - | maxFunction - | countFunction - | everyFunction + : everyFunction | anyFunction ; -avgFunction - : AVG LEFT_PAREN DISTINCT? expression RIGHT_PAREN filterClause? - ; - -sumFunction - : SUM LEFT_PAREN DISTINCT? expression RIGHT_PAREN filterClause? - ; - -minFunction - : MIN LEFT_PAREN DISTINCT? expression RIGHT_PAREN filterClause? - ; - -maxFunction - : MAX LEFT_PAREN DISTINCT? expression RIGHT_PAREN filterClause? - ; - -countFunction - : COUNT LEFT_PAREN DISTINCT? (expression | ASTERISK) RIGHT_PAREN filterClause? - ; - everyFunction : (EVERY|ALL) LEFT_PAREN predicate RIGHT_PAREN filterClause? | (EVERY|ALL) LEFT_PAREN subQuery RIGHT_PAREN @@ -736,38 +691,12 @@ filterClause standardFunction : castFunction | extractFunction - | coalesceFunction - | nullifFunction - | ifnullFunction | formatFunction - | concatFunction | substringFunction - | leftFunction - | rightFunction | overlayFunction - | replaceFunction | trimFunction | padFunction - | upperFunction - | lowerFunction - | locateFunction | positionFunction - | lengthFunction - | absFunction - | signFunction - | sqrtFunction - | lnFunction - | expFunction - | modFunction - | powerFunction - | ceilingFunction - | floorFunction - | roundFunction - | trigFunction - | atan2Function - | strFunction - | greatestFunction - | leastFunction | currentDateFunction | currentTimeFunction | currentTimestampFunction @@ -797,17 +726,6 @@ castTargetType : (i=identifier { $fullTargetName = _localctx.i.getText(); }) (DOT c=identifier { $fullTargetName += ("." + _localctx.c.getText() ); })* ; -concatFunction - : CONCAT LEFT_PAREN expression (COMMA expression)+ RIGHT_PAREN - ; - -leftFunction - : LEFT LEFT_PAREN expression COMMA expression RIGHT_PAREN - ; -rightFunction - : RIGHT LEFT_PAREN expression COMMA expression RIGHT_PAREN - ; - substringFunction : SUBSTRING LEFT_PAREN expression COMMA substringFunctionStartArgument (COMMA substringFunctionLengthArgument)? RIGHT_PAREN | SUBSTRING LEFT_PAREN expression FROM substringFunctionStartArgument (FOR substringFunctionLengthArgument)? RIGHT_PAREN @@ -852,30 +770,6 @@ padLength : expression ; -upperFunction - : UPPER LEFT_PAREN expression RIGHT_PAREN - ; - -lowerFunction - : LOWER LEFT_PAREN expression RIGHT_PAREN - ; - -locateFunction - : LOCATE LEFT_PAREN locateFunctionPatternArgument COMMA locateFunctionStringArgument (COMMA locateFunctionStartArgument)? RIGHT_PAREN - ; - -locateFunctionPatternArgument - : expression - ; - -locateFunctionStringArgument - : expression - ; - -locateFunctionStartArgument - : expression - ; - overlayFunction : OVERLAY LEFT_PAREN overlayFunctionStringArgument PLACING overlayFunctionReplacementArgument FROM overlayFunctionStartArgument (FOR overlayFunctionLengthArgument)? RIGHT_PAREN ; @@ -896,108 +790,6 @@ overlayFunctionLengthArgument : expression ; -replaceFunction - : REPLACE LEFT_PAREN replaceFunctionStringArgument COMMA replaceFunctionPatternArgument COMMA replaceFunctionReplacementArgument RIGHT_PAREN - ; - -replaceFunctionStringArgument - : expression - ; - -replaceFunctionPatternArgument - : expression - ; - -replaceFunctionReplacementArgument - : expression - ; - -lengthFunction - : LENGTH LEFT_PAREN expression RIGHT_PAREN - ; - -absFunction - : ABS LEFT_PAREN expression RIGHT_PAREN - ; - -signFunction - : SIGN LEFT_PAREN expression RIGHT_PAREN - ; - -sqrtFunction - : SQRT LEFT_PAREN expression RIGHT_PAREN - ; - -lnFunction - : LN LEFT_PAREN expression RIGHT_PAREN - ; - -expFunction - : EXP LEFT_PAREN expression RIGHT_PAREN - ; - -powerFunction - : POWER LEFT_PAREN powerBaseArgument COMMA powerPowerArgument RIGHT_PAREN - ; - -powerBaseArgument - : expression - ; - -powerPowerArgument - : expression - ; - -modFunction - : MOD LEFT_PAREN modDividendArgument COMMA modDivisorArgument RIGHT_PAREN - ; - -modDividendArgument - : expression - ; - -modDivisorArgument - : expression - ; - -ceilingFunction - : CEILING LEFT_PAREN expression RIGHT_PAREN - ; - -floorFunction - : FLOOR LEFT_PAREN expression RIGHT_PAREN - ; - -roundFunction - : ROUND LEFT_PAREN expression COMMA roundFunctionPrecision RIGHT_PAREN - ; - -roundFunctionPrecision - : expression - ; - -trigFunction - : trigFunctionName LEFT_PAREN expression RIGHT_PAREN - ; - -trigFunctionName - : COS - | SIN - | TAN - | ACOS - | ASIN - | ATAN - //ATAN2 is different! - ; - -atan2Function - : ATAN2 LEFT_PAREN expression COMMA expression RIGHT_PAREN - ; - -strFunction - : STR LEFT_PAREN expression RIGHT_PAREN - ; - currentDateFunction : CURRENT_DATE (LEFT_PAREN RIGHT_PAREN)? | CURRENT DATE @@ -1125,16 +917,19 @@ rollup identifier : IDENTIFIER | (ABS + | ACOS | ALL | AND | ANY | AS | ASC + | ASIN + | ATAN | ATAN2 | AVG - | BY | BETWEEN | BOTH + | BY | CASE | CAST | CEILING @@ -1142,15 +937,16 @@ identifier | COALESCE | COLLATE | CONCAT + | COS | COUNT | CROSS | CURRENT_DATE | CURRENT_INSTANT | CURRENT_TIME | CURRENT_TIMESTAMP - | DAY | DATE | DAY + | DAY | DELETE | DESC | DISTINCT @@ -1159,17 +955,17 @@ identifier | EMPTY | END | ENTRY - | EVERY | ESCAPE + | EVERY | EXISTS | EXP | EXTRACT | FETCH | FILTER | FLOOR - | FROM | FOR | FORMAT + | FROM | FULL | FUNCTION | GREATEST @@ -1202,13 +998,13 @@ identifier | MAXELEMENT | MAXINDEX | MEMBER + | MEMBER | MICROSECOND | MILLISECOND | MIN | MINELEMENT | MININDEX | MINUTE - | MEMBER | MOD | MONTH | NANOSECOND @@ -1228,18 +1024,20 @@ identifier | QUARTER | REPLACE | RIGHT - | ROUND | RIGHT + | ROUND | SECOND | SELECT | SET | SIGN + | SIN | SIZE | SOME | SQRT | STR | SUBSTRING | SUM + | TAN | THEN | TIME | TIMESTAMP @@ -1257,8 +1055,7 @@ identifier | WEEK | WHERE | WITH - | YEAR - | trigFunctionName) { + | YEAR) { logUseOfReservedWordAsIdentifier( getCurrentToken() ); } ; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java index 86cc3ad227..153d015e10 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java @@ -64,6 +64,8 @@ import java.sql.Types; import javax.persistence.TemporalType; +import static org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers.useArgType; + /** * Hibernate Dialect for Apache Derby / Cloudscape 10 * @@ -203,8 +205,8 @@ public class DerbyDialect extends Dialect { CommonFunctionFactory.power_expLn( queryEngine ); queryEngine.getSqmFunctionRegistry().patternDescriptorBuilder( "round", "floor(?1*1e?2+0.5)/1e?2") + .setReturnTypeResolver( useArgType(1) ) .setExactArgumentCount( 2 ) - .setInvariantType( StandardBasicTypes.DOUBLE ) .register(); //no way I can see to pad with anything other than spaces diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CaseWhenEveryAnyEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CaseWhenEveryAnyEmulation.java index 26011a5693..078d9506a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CaseWhenEveryAnyEmulation.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CaseWhenEveryAnyEmulation.java @@ -9,6 +9,7 @@ package org.hibernate.dialect.function; import java.util.List; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.function.FunctionKind; import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; import org.hibernate.sql.ast.SqlAstTranslator; @@ -27,7 +28,7 @@ public class CaseWhenEveryAnyEmulation extends AbstractSqmSelfRenderingFunctionD public CaseWhenEveryAnyEmulation(boolean every) { super( every ? "every" : "any", - true, + FunctionKind.AGGREGATE, StandardArgumentsValidators.exactly( 1 ), StandardFunctionReturnTypeResolvers.invariant( StandardBasicTypes.BOOLEAN ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CastStrEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CastStrEmulation.java index 214b03a78a..fd7bd41138 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CastStrEmulation.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CastStrEmulation.java @@ -47,7 +47,7 @@ public class CastStrEmulation asList( argument, new SqmCastTarget<>( - impliedResultType, + StandardBasicTypes.STRING, argument.nodeBuilder() ) ), diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java index efd88094ad..4c78b28843 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java @@ -16,6 +16,9 @@ import java.util.function.Supplier; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; +import org.hibernate.metamodel.mapping.BasicValuedMapping; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; @@ -1612,6 +1615,7 @@ public class CommonFunctionFactory { queryEngine.getSqmFunctionRegistry().namedDescriptorBuilder("power") .setExactArgumentCount(2) + .setReturnTypeResolver( new PowerReturnTypeResolver() ) .register(); } @@ -1625,7 +1629,7 @@ public class CommonFunctionFactory { public static void power_expLn(QueryEngine queryEngine) { queryEngine.getSqmFunctionRegistry().patternDescriptorBuilder( "power", "exp(ln(?1)*?2)") .setExactArgumentCount( 2 ) - .setInvariantType( StandardBasicTypes.DOUBLE ) + .setReturnTypeResolver( new PowerReturnTypeResolver() ) .register(); } @@ -1863,4 +1867,45 @@ public class CommonFunctionFactory { .register(); } + private static class PowerReturnTypeResolver implements FunctionReturnTypeResolver { + + @Override + public AllowableFunctionReturnType resolveFunctionReturnType( + AllowableFunctionReturnType impliedType, + List> arguments, + TypeConfiguration typeConfiguration) { + final JdbcMapping baseType = StandardFunctionReturnTypeResolvers + .extractArgumentJdbcMapping( typeConfiguration, arguments, 1 ); + final JdbcMapping powerType = StandardFunctionReturnTypeResolvers + .extractArgumentJdbcMapping( typeConfiguration, arguments, 2 ); + + if ( baseType.getJdbcTypeDescriptor().isDecimal() ) { + return (AllowableFunctionReturnType) arguments.get( 0 ).getNodeType(); + } + else if ( powerType.getJdbcTypeDescriptor().isDecimal() ) { + return (AllowableFunctionReturnType) arguments.get( 1 ).getNodeType(); + } + return typeConfiguration.getBasicTypeForJavaType( Double.class ); + } + + @Override + public BasicValuedMapping resolveFunctionReturnType( + Supplier impliedTypeAccess, List arguments) { + final BasicValuedMapping baseMapping = StandardFunctionReturnTypeResolvers.extractArgumentValuedMapping( + arguments, + 1 + ); + final BasicValuedMapping powerMapping = StandardFunctionReturnTypeResolvers.extractArgumentValuedMapping( + arguments, + 2 + ); + if ( baseMapping.getJdbcMapping().getJdbcTypeDescriptor().isDecimal() ) { + return baseMapping; + } + else if ( powerMapping.getJdbcMapping().getJdbcTypeDescriptor().isDecimal() ) { + return powerMapping; + } + return StandardBasicTypes.DOUBLE; + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/EveryAnyEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/EveryAnyEmulation.java index f6593df297..6cb8f927e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/EveryAnyEmulation.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/EveryAnyEmulation.java @@ -11,6 +11,7 @@ import java.util.List; import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.function.FunctionKind; import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; import org.hibernate.query.sqm.produce.function.ArgumentsValidator; import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; @@ -34,7 +35,7 @@ public class EveryAnyEmulation extends AbstractSqmSelfRenderingFunctionDescripto public EveryAnyEmulation(boolean every) { super( every ? "every" : "any", - true, + FunctionKind.AGGREGATE, StandardArgumentsValidators.exactly( 1 ), StandardFunctionReturnTypeResolvers.invariant( StandardBasicTypes.BOOLEAN ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/InsertSubstringOverlayEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/InsertSubstringOverlayEmulation.java index 2fa2d524c9..b5d66c6081 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/InsertSubstringOverlayEmulation.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/InsertSubstringOverlayEmulation.java @@ -55,7 +55,8 @@ public class InsertSubstringOverlayEmulation AllowableFunctionReturnType impliedResultType, QueryEngine queryEngine, TypeConfiguration typeConfiguration) { - BasicType intType = typeConfiguration.getBasicTypeForJavaType(Integer.class); + final BasicType intType = typeConfiguration.getBasicTypeForJavaType( Integer.class ); + final BasicType stringType = typeConfiguration.getBasicTypeForJavaType( String.class ); SqmTypedNode string = arguments.get(0); SqmTypedNode replacement = arguments.get(1); @@ -93,7 +94,6 @@ public class InsertSubstringOverlayEmulation intType, queryEngine.getCriteriaBuilder() ); - SqmExpressable stringType = (SqmExpressable) impliedResultType; SqmTypedNode restString = substring.generateSqmExpression( asList( string, startPlusLength ), impliedResultType, @@ -101,7 +101,7 @@ public class InsertSubstringOverlayEmulation typeConfiguration ); if ( strictSubstring ) { - restString = (SqmTypedNode) new SqmCaseSearched<>( stringType, start.nodeBuilder() ) + restString = new SqmCaseSearched<>( stringType, start.nodeBuilder() ) .when( new SqmComparisonPredicate( startPlusLength, @@ -114,8 +114,8 @@ public class InsertSubstringOverlayEmulation ), string.nodeBuilder() ), - (Expression) new SqmLiteral<>( "", stringType, string.nodeBuilder() ) - ).otherwise( (Expression) restString ); + new SqmLiteral<>( "", stringType, string.nodeBuilder() ) + ).otherwise( (Expression) restString ); } return concat.generateSqmExpression( asList( diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLServerEveryAnyEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLServerEveryAnyEmulation.java index ead8e9e701..80668a60ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLServerEveryAnyEmulation.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLServerEveryAnyEmulation.java @@ -9,6 +9,7 @@ package org.hibernate.dialect.function; import java.util.List; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.function.FunctionKind; import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; import org.hibernate.sql.ast.SqlAstTranslator; @@ -27,7 +28,7 @@ public class SQLServerEveryAnyEmulation extends AbstractSqmSelfRenderingFunction public SQLServerEveryAnyEmulation(boolean every) { super( every ? "every" : "any", - true, + FunctionKind.AGGREGATE, StandardArgumentsValidators.exactly( 1 ), StandardFunctionReturnTypeResolvers.invariant( StandardBasicTypes.BOOLEAN ) ); 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 70cc016983..72bb8b7f01 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 @@ -26,9 +26,11 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.GregorianCalendar; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; import org.hibernate.NotYetImplementedFor6Exception; @@ -76,6 +78,7 @@ import org.hibernate.query.sqm.SqmQuerySource; import org.hibernate.query.sqm.SqmTreeCreationLogger; import org.hibernate.query.sqm.StrictJpaComplianceViolation; import org.hibernate.query.sqm.UnknownEntityException; +import org.hibernate.query.sqm.function.FunctionKind; import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor; import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.internal.ParameterCollector; @@ -177,6 +180,7 @@ import org.hibernate.query.sqm.tree.select.SqmSelection; import org.hibernate.query.sqm.tree.select.SqmSortSpecification; import org.hibernate.query.sqm.tree.select.SqmSubQuery; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -220,6 +224,35 @@ import static org.hibernate.type.spi.TypeConfiguration.isJdbcTemporalType; public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCreationState { private static final Logger log = Logger.getLogger( SemanticQueryBuilder.class ); + private static final Set JPA_STANDARD_FUNCTIONS; + + static { + final Set jpaStandardFunctions = new HashSet<>(); + // Extracted from the BNF in JPA spec 4.14. + jpaStandardFunctions.add( "avg" ); + jpaStandardFunctions.add( "max" ); + jpaStandardFunctions.add( "min" ); + jpaStandardFunctions.add( "sum" ); + jpaStandardFunctions.add( "count" ); + jpaStandardFunctions.add( "length" ); + jpaStandardFunctions.add( "locate" ); + jpaStandardFunctions.add( "abs" ); + jpaStandardFunctions.add( "sqrt" ); + jpaStandardFunctions.add( "mod" ); + jpaStandardFunctions.add( "size" ); + jpaStandardFunctions.add( "index" ); + jpaStandardFunctions.add( "current_date" ); + jpaStandardFunctions.add( "current_time" ); + jpaStandardFunctions.add( "current_timestamp" ); + jpaStandardFunctions.add( "concat" ); + jpaStandardFunctions.add( "substring" ); + jpaStandardFunctions.add( "trim" ); + jpaStandardFunctions.add( "lower" ); + jpaStandardFunctions.add( "upper" ); + jpaStandardFunctions.add( "coalesce" ); + jpaStandardFunctions.add( "nullif" ); + JPA_STANDARD_FUNCTIONS = jpaStandardFunctions; + } /** * Main entry point into analysis of HQL/JPQL parse tree - producing a semantic model of the @@ -2192,7 +2225,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem (SqmExpression) ctx.getChild( 0 ).accept( this ), (SqmExpression) ctx.getChild( 2 ).accept( this ) ), - resolveExpressableTypeBasic( String.class ), + null, creationContext.getQueryEngine(), creationContext.getJpaMetamodel().getTypeConfiguration() ); @@ -2264,7 +2297,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem if ( operator == BinaryArithmeticOperator.MODULO ) { return getFunctionDescriptor("mod").generateSqmExpression( asList( left, right ), - (AllowableFunctionReturnType) left.getNodeType(), + null, creationContext.getQueryEngine(), creationContext.getJpaMetamodel().getTypeConfiguration() ); @@ -2505,68 +2538,6 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); } - @Override - public Object visitLeastFunction(HqlParser.LeastFunctionContext ctx) { - final List> arguments = visitExpressions( ctx ); - return getFunctionDescriptor("least") - .generateSqmExpression( - arguments, - (AllowableFunctionReturnType) arguments.get( 0 ).getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public Object visitGreatestFunction(HqlParser.GreatestFunctionContext ctx) { - final List> arguments = visitExpressions( ctx ); - return getFunctionDescriptor("greatest") - .generateSqmExpression( - arguments, - (AllowableFunctionReturnType) arguments.get( 0 ).getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitCoalesceFunction(HqlParser.CoalesceFunctionContext ctx) { - final List> arguments = visitExpressions( ctx ); - return getFunctionDescriptor("coalesce") - .generateSqmExpression( - arguments, - (AllowableFunctionReturnType) arguments.get( 0 ).getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitNullifFunction(HqlParser.NullifFunctionContext ctx) { - final SqmExpression arg1 = (SqmExpression) ctx.getChild( 2 ).accept( this ); - final SqmExpression arg2 = (SqmExpression) ctx.getChild( 4 ).accept( this ); - - return getFunctionDescriptor("nullif").generateSqmExpression( - asList( arg1, arg2 ), - (AllowableFunctionReturnType) arg1.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitIfnullFunction(HqlParser.IfnullFunctionContext ctx) { - final SqmExpression arg1 = (SqmExpression) ctx.getChild( 2 ).accept( this ); - final SqmExpression arg2 = (SqmExpression) ctx.getChild( 4 ).accept( this ); - - return getFunctionDescriptor("ifnull").generateSqmExpression( - asList( arg1, arg2 ), - (AllowableFunctionReturnType) arg1.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - @Override public SqmExpression visitLiteralExpression(HqlParser.LiteralExpressionContext ctx) { return (SqmExpression) ctx.getChild( 0 ).accept( this ); @@ -3129,39 +3100,66 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem } @Override - public SqmExpression visitNonStandardFunction(HqlParser.NonStandardFunctionContext ctx) { - if ( creationOptions.useStrictJpaCompliance() ) { + public Object visitGenericFunction(HqlParser.GenericFunctionContext ctx) { + final String originalFunctionName = ctx.getChild( 0 ).getText(); + final String functionName = originalFunctionName.toLowerCase(); + if ( creationOptions.useStrictJpaCompliance() && !JPA_STANDARD_FUNCTIONS.contains( functionName ) ) { throw new StrictJpaComplianceViolation( "Encountered non-compliant non-standard function call [" + - ctx.nonStandardFunctionName() + "], but strict JPA " + + originalFunctionName + "], but strict JPA " + "compliance was requested; use JPA's FUNCTION(functionName[,...]) " + "syntax name instead", StrictJpaComplianceViolation.Type.FUNCTION_CALL ); } - final String functionName = ctx.getChild( 0 ).getText().toLowerCase(); - //noinspection unchecked - final List> functionArguments = ctx.getChildCount() == 3 - ? emptyList() - : (List>) ctx.getChild( 2 ).accept( this ); + final ParseTree argumentChild = ctx.getChild( 2 ); + final List> functionArguments; + if ( argumentChild instanceof HqlParser.NonStandardFunctionArgumentsContext ) { + functionArguments = (List>) argumentChild.accept( this ); + } + else if ( "*".equals( argumentChild.getText() ) ) { + functionArguments = Collections.singletonList( new SqmStar( getCreationContext().getNodeBuilder() ) ); + } + else { + functionArguments = emptyList(); + } + final SqmPredicate filterExpression = getFilterExpression( ctx ); SqmFunctionDescriptor functionTemplate = getFunctionDescriptor( functionName ); if ( functionTemplate == null ) { functionTemplate = new NamedSqmFunctionDescriptor( functionName, true, null, - StandardFunctionReturnTypeResolvers.invariant( StandardBasicTypes.OBJECT_TYPE ) + StandardFunctionReturnTypeResolvers.invariant( StandardBasicTypes.OBJECT_TYPE ), + functionName, + filterExpression != null ? FunctionKind.AGGREGATE : FunctionKind.NORMAL, + null, + SqlAstNodeRenderingMode.DEFAULT ); } - return functionTemplate.generateSqmExpression( - functionArguments, - null, - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); + if ( functionTemplate.getFunctionKind() == FunctionKind.AGGREGATE ) { + return functionTemplate.generateAggregateSqmExpression( + functionArguments, + filterExpression, + null, + creationContext.getQueryEngine(), + creationContext.getJpaMetamodel().getTypeConfiguration() + ); + } + else { + if ( filterExpression != null ) { + throw new ParsingException( "Illegal use of a FILTER clause for non-aggregate function: " + originalFunctionName ); + } + return functionTemplate.generateSqmExpression( + functionArguments, + null, + creationContext.getQueryEngine(), + creationContext.getJpaMetamodel().getTypeConfiguration() + ); + } } @Override @@ -3173,11 +3171,16 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final List> arguments = new ArrayList<>( estimateArgumentCount ); int i = 0; + boolean distinct = false; final ParseTree firstChild = ctx.getChild( 0 ); if ( firstChild instanceof HqlParser.DatetimeFieldContext ) { arguments.add( toDurationUnit( (SqmExtractUnit) firstChild.accept( this ) ) ); i += 2; } + else if ( firstChild instanceof TerminalNode ) { + distinct = true; + i++; + } for ( ; i < size; i += 2 ) { // we handle the final argument differently... @@ -3189,6 +3192,22 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem } } + if ( distinct ) { + final NodeBuilder nodeBuilder = getCreationContext().getNodeBuilder(); + if ( arguments.size() == 1 ) { + arguments.set( 0, new SqmDistinct<>( (SqmExpression) arguments.get( 0 ), nodeBuilder ) ); + } + else { + final List> newArguments = new ArrayList<>( 1 ); + //noinspection unchecked + newArguments.add( + new SqmDistinct<>( + new SqmTuple<>( (List>) (List) arguments, nodeBuilder ), nodeBuilder + ) + ); + return newArguments; + } + } return arguments; } @@ -3204,161 +3223,12 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem } } - @Override - public SqmExpression visitCeilingFunction(HqlParser.CeilingFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.getChild( 2 ).accept( this ); - - return getFunctionDescriptor("ceiling").generateSqmExpression( - arg, - (AllowableFunctionReturnType) arg.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitFloorFunction(HqlParser.FloorFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.getChild( 2 ).accept( this ); - - return getFunctionDescriptor("floor").generateSqmExpression( - arg, - (AllowableFunctionReturnType) arg.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - private SqmFunctionDescriptor getFunctionDescriptor(String name) { return creationContext.getQueryEngine().getSqmFunctionRegistry().findFunctionDescriptor(name); } @Override - public SqmExpression visitAbsFunction(HqlParser.AbsFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.getChild( 2 ).accept( this ); - - return getFunctionDescriptor("abs").generateSqmExpression( - arg, - (AllowableFunctionReturnType) arg.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitSignFunction(HqlParser.SignFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.getChild( 2 ).accept( this ); - - return getFunctionDescriptor("sign").generateSqmExpression( - arg, - resolveExpressableTypeBasic( Integer.class ), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitModFunction(HqlParser.ModFunctionContext ctx) { - final SqmExpression dividend = (SqmExpression) ctx.getChild( 2 ).accept( this ); - final SqmExpression divisor = (SqmExpression) ctx.getChild( 4 ).accept( this ); - - return getFunctionDescriptor("mod").generateSqmExpression( - asList( dividend, divisor ), - (AllowableFunctionReturnType) dividend.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitPowerFunction(HqlParser.PowerFunctionContext ctx) { - final SqmExpression base = (SqmExpression) ctx.getChild( 2 ).accept( this ); - final SqmExpression power = (SqmExpression) ctx.getChild( 4 ).accept( this ); - - return getFunctionDescriptor("power").generateSqmExpression( - asList( base, power ), - (AllowableFunctionReturnType) base.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitTrigFunction(HqlParser.TrigFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.getChild( 2 ).accept( this ); - - return getFunctionDescriptor( ctx.getChild( 0 ).getText() ).generateSqmExpression( - arg, - resolveExpressableTypeBasic( Double.class ), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitSqrtFunction(HqlParser.SqrtFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.getChild( 2 ).accept( this ); - - return getFunctionDescriptor("sqrt").generateSqmExpression( - arg, - null, - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitRoundFunction(HqlParser.RoundFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.getChild( 2 ).accept( this ); - final SqmExpression precision = (SqmExpression) ctx.getChild( 4 ).accept( this ); - - return getFunctionDescriptor("round").generateSqmExpression( - asList(arg, precision), - (AllowableFunctionReturnType) arg.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitAtan2Function(HqlParser.Atan2FunctionContext ctx) { - final SqmExpression sin = (SqmExpression) ctx.getChild( 2 ).accept( this ); - final SqmExpression cos = (SqmExpression) ctx.getChild( 4 ).accept( this ); - - return getFunctionDescriptor("atan2").generateSqmExpression( - asList(sin, cos), - (AllowableFunctionReturnType) sin.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitLnFunction(HqlParser.LnFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.getChild( 2 ).accept( this ); - - return getFunctionDescriptor("ln").generateSqmExpression( - arg, - (AllowableFunctionReturnType) arg.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - - } - - @Override - public SqmExpression visitExpFunction(HqlParser.ExpFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.getChild( 2 ).accept( this ); - - return getFunctionDescriptor("exp").generateSqmExpression( - arg, - (AllowableFunctionReturnType) arg.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public Object visitDatetimeField(HqlParser.DatetimeFieldContext ctx) { + public SqmExtractUnit visitDatetimeField(HqlParser.DatetimeFieldContext ctx) { final NodeBuilder nodeBuilder = creationContext.getNodeBuilder(); switch ( ( (TerminalNode) ctx.getChild( 0 ) ).getSymbol().getType() ) { case HqlParser.DAY: @@ -3416,7 +3286,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem nodeBuilder ); } - throw new ParsingException("Unsupported datetime field [" + ctx.getText() + "]"); + throw new ParsingException( "Unsupported datetime field [" + ctx.getText() + "]" ); } @Override @@ -3551,7 +3421,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return getFunctionDescriptor("format").generateSqmExpression( asList( expressionToCast, format ), - resolveExpressableTypeBasic( String.class ), + null, creationContext.getQueryEngine(), creationContext.getJpaMetamodel().getTypeConfiguration() ); @@ -3599,60 +3469,6 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); } - @Override - public SqmExpression visitUpperFunction(HqlParser.UpperFunctionContext ctx) { - final SqmExpression expression = (SqmExpression) ctx.getChild( 2 ).accept( this ); - return getFunctionDescriptor("upper").generateSqmExpression( - expression, - (AllowableFunctionReturnType) expression.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitLowerFunction(HqlParser.LowerFunctionContext ctx) { - // todo (6.0) : why pass both the expression and its expression-type? - // can't we just pass the expression? - final SqmExpression expression = (SqmExpression) ctx.getChild( 2 ).accept( this ); - return getFunctionDescriptor("lower").generateSqmExpression( - expression, - (AllowableFunctionReturnType) expression.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitConcatFunction(HqlParser.ConcatFunctionContext ctx) { - final int size = ctx.getChildCount(); - // Shift 1 bit instead of division by 2 - final int estimateArgumentCount = (size >> 1) - 1; - final List> arguments = new ArrayList<>( estimateArgumentCount ); - for ( int i = 2; i < size; i += 2 ) { - arguments.add( (SqmTypedNode) ctx.getChild( i ).accept( this ) ); - } - - return getFunctionDescriptor("concat").generateSqmExpression( - arguments, - resolveExpressableTypeBasic( String.class ), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public Object visitLengthFunction(HqlParser.LengthFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.getChild( 2 ).accept( this ); - - return getFunctionDescriptor("length").generateSqmExpression( - arg, - resolveExpressableTypeBasic( Integer.class ), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - @Override public Object visitPositionFunction(HqlParser.PositionFunctionContext ctx) { final SqmExpression pattern = (SqmExpression) ctx.getChild( 2 ).accept( this ); @@ -3660,29 +3476,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return getFunctionDescriptor("position").generateSqmExpression( asList( pattern, string ), - resolveExpressableTypeBasic( Integer.class ), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public Object visitLocateFunction(HqlParser.LocateFunctionContext ctx) { - final SqmExpression pattern = (SqmExpression) ctx.getChild( 2 ).accept( this ); - final SqmExpression string = (SqmExpression) ctx.getChild( 4 ).accept( this ); - final SqmExpression start; - if ( ctx.getChildCount() == 8 ) { - start = (SqmExpression) ctx.getChild( 6 ).accept( this ); - } - else { - start = null; - } - - return getFunctionDescriptor("locate").generateSqmExpression( - start == null - ? asList( pattern, string ) - : asList( pattern, string, start ), - resolveExpressableTypeBasic( Integer.class ), + null, creationContext.getQueryEngine(), creationContext.getJpaMetamodel().getTypeConfiguration() ); @@ -3705,76 +3499,6 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem length == null ? asList( string, replacement, start ) : asList( string, replacement, start, length ), - resolveExpressableTypeBasic( String.class ), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public Object visitReplaceFunction(HqlParser.ReplaceFunctionContext ctx) { - final SqmExpression string = (SqmExpression) ctx.getChild( 2 ).accept( this ); - final SqmExpression pattern = (SqmExpression) ctx.getChild( 4 ).accept( this ); - final SqmExpression replacement = (SqmExpression) ctx.getChild( 6 ).accept( this ); - - return getFunctionDescriptor("replace").generateSqmExpression( - asList( string, pattern, replacement ), - resolveExpressableTypeBasic( String.class ), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public Object visitStrFunction(HqlParser.StrFunctionContext ctx) { - final SqmExpression arg = (SqmExpression) ctx.getChild( 2 ).accept( this ); - return getFunctionDescriptor("str").generateSqmExpression( - singletonList( arg ), - resolveExpressableTypeBasic( String.class ), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitMaxFunction(HqlParser.MaxFunctionContext ctx) { - final SqmPredicate filterExpression = getFilterExpression( ctx ); - final int expressionIndex = ctx.getChildCount() - ( filterExpression == null ? 2 : 3 ); - final SqmExpression arg = (SqmExpression) ctx.getChild( expressionIndex ).accept( this ); - //ignore DISTINCT - return getFunctionDescriptor("max").generateAggregateSqmExpression( - singletonList( arg ), - filterExpression, - (AllowableFunctionReturnType) arg.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitMinFunction(HqlParser.MinFunctionContext ctx) { - final SqmPredicate filterExpression = getFilterExpression( ctx ); - final int expressionIndex = ctx.getChildCount() - ( filterExpression == null ? 2 : 3 ); - final SqmExpression arg = (SqmExpression) ctx.getChild( expressionIndex ).accept( this ); - //ignore DISTINCT - return getFunctionDescriptor("min").generateAggregateSqmExpression( - singletonList( arg ), - filterExpression, - (AllowableFunctionReturnType) arg.getNodeType(), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitSumFunction(HqlParser.SumFunctionContext ctx) { - final SqmPredicate filterExpression = getFilterExpression( ctx ); - final int expressionIndex = ctx.getChildCount() - ( filterExpression == null ? 2 : 3 ); - final SqmExpression arg = (SqmExpression) ctx.getChild( expressionIndex ).accept( this ); - - return getFunctionDescriptor("sum").generateAggregateSqmExpression( - singletonList( applyDistinct( arg, ctx ) ), - filterExpression, null, creationContext.getQueryEngine(), creationContext.getJpaMetamodel().getTypeConfiguration() @@ -3905,51 +3629,6 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return (SqmSubQuery) subQuery; } - @Override - public SqmExpression visitAvgFunction(HqlParser.AvgFunctionContext ctx) { - final SqmPredicate filterExpression = getFilterExpression( ctx ); - final int expressionIndex = ctx.getChildCount() - ( filterExpression == null ? 2 : 3 ); - final SqmExpression arg = (SqmExpression) ctx.getChild( expressionIndex ).accept( this ); - - return getFunctionDescriptor("avg").generateAggregateSqmExpression( - singletonList( applyDistinct( arg, ctx ) ), - filterExpression, - resolveExpressableTypeBasic( Double.class ), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitCountFunction(HqlParser.CountFunctionContext ctx) { - final SqmPredicate filterExpression = getFilterExpression( ctx ); - final int expressionIndex = ctx.getChildCount() - ( filterExpression == null ? 2 : 3 ); - final ParseTree argumentChild = ctx.getChild( expressionIndex ); - final SqmExpression arg; - if ( argumentChild instanceof TerminalNode ) { - arg = new SqmStar( getCreationContext().getNodeBuilder() ); - } - else { - arg = (SqmExpression) argumentChild.accept( this ); - } - - return getFunctionDescriptor("count").generateAggregateSqmExpression( - singletonList( applyDistinct( arg, ctx ) ), - filterExpression, - resolveExpressableTypeBasic( Long.class ), - creationContext.getQueryEngine(), - creationContext.getJpaMetamodel().getTypeConfiguration() - ); - } - - private SqmTypedNode applyDistinct(SqmExpression expression, ParseTree functionCtx) { - final ParseTree node = functionCtx.getChild( 2 ); - if ( node instanceof TerminalNode && ( (TerminalNode) node ).getSymbol().getType() == HqlParser.DISTINCT ) { - return new SqmDistinct<>( expression, getCreationContext().getNodeBuilder() ); - } - return expression; - } - private SqmPredicate getFilterExpression(ParseTree functionCtx) { final ParseTree lastChild = functionCtx.getChild( functionCtx.getChildCount() - 1 ); if ( lastChild instanceof HqlParser.FilterClauseContext ) { @@ -3990,38 +3669,12 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return getFunctionDescriptor("substring").generateSqmExpression( length == null ? asList( source, start ) : asList( source, start, length ), - resolveExpressableTypeBasic( String.class ), + null, creationContext.getQueryEngine(), creationContext.getJpaMetamodel().getTypeConfiguration() ); } - @Override - public SqmExpression visitLeftFunction(HqlParser.LeftFunctionContext ctx) { - final SqmExpression source = (SqmExpression) ctx.getChild( 2 ).accept( this ); - final SqmExpression length = (SqmExpression) ctx.getChild( 4 ).accept( this ); - - return getFunctionDescriptor("left").generateSqmExpression( - asList( source, length ), - resolveExpressableTypeBasic( String.class ), - creationContext.getQueryEngine(), - creationContext.getNodeBuilder().getTypeConfiguration() - ); - } - - @Override - public SqmExpression visitRightFunction(HqlParser.RightFunctionContext ctx) { - final SqmExpression source = (SqmExpression) ctx.getChild( 2 ).accept( this ); - final SqmExpression length = (SqmExpression) ctx.getChild( 4 ).accept( this ); - - return getFunctionDescriptor("right").generateSqmExpression( - asList( source, length ), - resolveExpressableTypeBasic( String.class ), - creationContext.getQueryEngine(), - creationContext.getNodeBuilder().getTypeConfiguration() - ); - } - @Override public SqmExpression visitPadFunction(HqlParser.PadFunctionContext ctx) { final SqmExpression source = (SqmExpression) ctx.getChild( 2 ).accept( this ); @@ -4038,7 +3691,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem padChar != null ? asList( source, length, padSpec, padChar ) : asList( source, length, padSpec ), - resolveExpressableTypeBasic( String.class ), + null, creationContext.getQueryEngine(), creationContext.getJpaMetamodel().getTypeConfiguration() ); @@ -4100,7 +3753,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem trimChar, source ), - resolveExpressableTypeBasic( String.class ), + null, creationContext.getQueryEngine(), creationContext.getJpaMetamodel().getTypeConfiguration() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/AbstractSqmSelfRenderingFunctionDescriptor.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/AbstractSqmSelfRenderingFunctionDescriptor.java index 708592207e..10d6cf35b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/AbstractSqmSelfRenderingFunctionDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/AbstractSqmSelfRenderingFunctionDescriptor.java @@ -26,16 +26,28 @@ import java.util.List; public abstract class AbstractSqmSelfRenderingFunctionDescriptor extends AbstractSqmFunctionDescriptor implements FunctionRenderingSupport { - private final boolean isAggregate; + private final FunctionKind functionKind; - public AbstractSqmSelfRenderingFunctionDescriptor(String name, ArgumentsValidator argumentsValidator, FunctionReturnTypeResolver returnTypeResolver) { + public AbstractSqmSelfRenderingFunctionDescriptor( + String name, + ArgumentsValidator argumentsValidator, + FunctionReturnTypeResolver returnTypeResolver) { super( name, argumentsValidator, returnTypeResolver ); - this.isAggregate = false; + this.functionKind = FunctionKind.NORMAL; } - public AbstractSqmSelfRenderingFunctionDescriptor(String name, boolean isAggregate, ArgumentsValidator argumentsValidator, FunctionReturnTypeResolver returnTypeResolver) { + public AbstractSqmSelfRenderingFunctionDescriptor( + String name, + FunctionKind functionKind, + ArgumentsValidator argumentsValidator, + FunctionReturnTypeResolver returnTypeResolver) { super( name, argumentsValidator, returnTypeResolver ); - this.isAggregate = isAggregate; + this.functionKind = functionKind; + } + + @Override + public FunctionKind getFunctionKind() { + return functionKind; } @Override @@ -44,7 +56,7 @@ public abstract class AbstractSqmSelfRenderingFunctionDescriptor AllowableFunctionReturnType impliedResultType, QueryEngine queryEngine, TypeConfiguration typeConfiguration) { - if ( isAggregate ) { + if ( functionKind == FunctionKind.AGGREGATE ) { return generateAggregateSqmExpression( arguments, null, impliedResultType, queryEngine, typeConfiguration ); } return new SelfRenderingSqmFunction<>( @@ -65,7 +77,7 @@ public abstract class AbstractSqmSelfRenderingFunctionDescriptor AllowableFunctionReturnType impliedResultType, QueryEngine queryEngine, TypeConfiguration typeConfiguration) { - if ( !isAggregate ) { + if ( functionKind != FunctionKind.AGGREGATE ) { throw new UnsupportedOperationException( "The function " + getName() + " is not an aggregate function!" ); } return new SelfRenderingSqmAggregateFunction<>( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/FunctionKind.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/FunctionKind.java new file mode 100644 index 0000000000..0ed02609a9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/FunctionKind.java @@ -0,0 +1,17 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.function; + +/** + * The kind of a function e.g. normal, aggregate etc. + * + * @author Christian Beikov + */ +public enum FunctionKind { + NORMAL, + AGGREGATE; +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/NamedSqmFunctionDescriptor.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/NamedSqmFunctionDescriptor.java index c5e3a1852c..b1763ab162 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/NamedSqmFunctionDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/NamedSqmFunctionDescriptor.java @@ -46,7 +46,7 @@ public class NamedSqmFunctionDescriptor argumentsValidator, returnTypeResolver, functionName, - false, + FunctionKind.NORMAL, null, SqlAstNodeRenderingMode.DEFAULT ); @@ -58,10 +58,10 @@ public class NamedSqmFunctionDescriptor ArgumentsValidator argumentsValidator, FunctionReturnTypeResolver returnTypeResolver, String name, - boolean isAggregate, + FunctionKind functionKind, String argumentListSignature, SqlAstNodeRenderingMode argumentRenderingMode) { - super( name, isAggregate, argumentsValidator, returnTypeResolver ); + super( name, functionKind, argumentsValidator, returnTypeResolver ); this.functionName = functionName; this.useParenthesesWhenNoArgs = useParenthesesWhenNoArgs; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/PatternBasedSqmFunctionDescriptor.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/PatternBasedSqmFunctionDescriptor.java index 161144b3c5..c59a0e1129 100755 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/PatternBasedSqmFunctionDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/PatternBasedSqmFunctionDescriptor.java @@ -48,11 +48,11 @@ public class PatternBasedSqmFunctionDescriptor ArgumentsValidator argumentsValidator, FunctionReturnTypeResolver returnTypeResolver, String name, - boolean isAggregate, + FunctionKind functionKind, String argumentListSignature) { super( name, - isAggregate, + functionKind, argumentsValidator != null ? argumentsValidator // If no validator is given, it's still better to diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionDescriptor.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionDescriptor.java index fde6fef05a..ea433fef38 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionDescriptor.java @@ -118,4 +118,8 @@ public interface SqmFunctionDescriptor { default String getSignature(String name) { return name; } + + default FunctionKind getFunctionKind() { + return FunctionKind.NORMAL; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java index 192f979b57..5d899d3782 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java @@ -112,7 +112,7 @@ public class SqmFunctionRegistry { * @return The builder */ public PatternFunctionDescriptorBuilder patternDescriptorBuilder(String registrationKey, String pattern) { - return new PatternFunctionDescriptorBuilder( this, registrationKey, false, pattern ); + return new PatternFunctionDescriptorBuilder( this, registrationKey, FunctionKind.NORMAL, pattern ); } /** @@ -124,7 +124,7 @@ public class SqmFunctionRegistry { * @return The builder */ public PatternFunctionDescriptorBuilder patternAggregateDescriptorBuilder(String registrationKey, String pattern) { - return new PatternFunctionDescriptorBuilder( this, registrationKey, true, pattern ); + return new PatternFunctionDescriptorBuilder( this, registrationKey, FunctionKind.AGGREGATE, pattern ); } /** @@ -184,7 +184,7 @@ public class SqmFunctionRegistry { * @return The builder */ public NamedFunctionDescriptorBuilder namedDescriptorBuilder(String registrationKey, String name) { - return new NamedFunctionDescriptorBuilder( this, registrationKey, false, name ); + return new NamedFunctionDescriptorBuilder( this, registrationKey, FunctionKind.NORMAL, name ); } /** @@ -196,7 +196,7 @@ public class SqmFunctionRegistry { * @return The builder */ public NamedFunctionDescriptorBuilder namedAggregateDescriptorBuilder(String registrationKey, String name) { - return new NamedFunctionDescriptorBuilder( this, registrationKey, true, name ); + return new NamedFunctionDescriptorBuilder( this, registrationKey, FunctionKind.AGGREGATE, name ); } public NamedFunctionDescriptorBuilder noArgsBuilder(String name) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index 2817f7a41f..212a5b4111 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -246,13 +246,12 @@ public class QuerySqmImpl final SqmQuerySpec sqmQuerySpec = (SqmQuerySpec) queryPart; final List> sqmSelections = sqmQuerySpec.getSelectClause().getSelections(); - // make sure there is at least one root - final List> sqmRoots = sqmQuerySpec.getFromClause().getRoots(); - if ( sqmRoots == null || sqmRoots.isEmpty() ) { - throw new IllegalArgumentException( "Criteria did not define any query roots" ); - } - if ( sqmSelections == null || sqmSelections.isEmpty() ) { + // make sure there is at least one root + final List> sqmRoots = sqmQuerySpec.getFromClause().getRoots(); + if ( sqmRoots == null || sqmRoots.isEmpty() ) { + throw new IllegalArgumentException( "Criteria did not define any query roots" ); + } // if there is a single root, use that as the selection if ( sqmRoots.size() == 1 ) { final SqmRoot sqmRoot = sqmRoots.get( 0 ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/NamedFunctionDescriptorBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/NamedFunctionDescriptorBuilder.java index c683265c06..46d2656de8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/NamedFunctionDescriptorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/NamedFunctionDescriptorBuilder.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.produce.function; +import org.hibernate.query.sqm.function.FunctionKind; import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor; import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.function.SqmFunctionRegistry; @@ -19,7 +20,7 @@ public class NamedFunctionDescriptorBuilder { private final SqmFunctionRegistry registry; private final String registrationKey; - private final boolean isAggregate; + private final FunctionKind functionKind; private final String functionName; @@ -33,11 +34,11 @@ public class NamedFunctionDescriptorBuilder { public NamedFunctionDescriptorBuilder( SqmFunctionRegistry registry, String registrationKey, - boolean isAggregate, + FunctionKind functionKind, String functionName) { this.registry = registry; this.registrationKey = registrationKey; - this.isAggregate = isAggregate; + this.functionKind = functionKind; this.functionName = functionName; } @@ -94,7 +95,7 @@ public class NamedFunctionDescriptorBuilder { argumentsValidator, returnTypeResolver, registrationKey, - isAggregate, + functionKind, argumentListSignature, argumentRenderingMode ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/PatternFunctionDescriptorBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/PatternFunctionDescriptorBuilder.java index 4b4884b3dc..dc0ae220e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/PatternFunctionDescriptorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/PatternFunctionDescriptorBuilder.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.produce.function; +import org.hibernate.query.sqm.function.FunctionKind; import org.hibernate.query.sqm.function.PatternBasedSqmFunctionDescriptor; import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.function.SqmFunctionRegistry; @@ -19,7 +20,7 @@ import org.hibernate.type.BasicType; public class PatternFunctionDescriptorBuilder { private final SqmFunctionRegistry registry; private final String registrationKey; - private final boolean isAggregate; + private final FunctionKind functionKind; private final String pattern; private String argumentListSignature; @@ -27,10 +28,14 @@ public class PatternFunctionDescriptorBuilder { private FunctionReturnTypeResolver returnTypeResolver; private SqlAstNodeRenderingMode argumentRenderingMode = SqlAstNodeRenderingMode.DEFAULT; - public PatternFunctionDescriptorBuilder(SqmFunctionRegistry registry, String registrationKey, boolean isAggregate, String pattern) { + public PatternFunctionDescriptorBuilder( + SqmFunctionRegistry registry, + String registrationKey, + FunctionKind functionKind, + String pattern) { this.registry = registry; this.registrationKey = registrationKey; - this.isAggregate = isAggregate; + this.functionKind = functionKind; this.pattern = pattern; } @@ -73,7 +78,7 @@ public class PatternFunctionDescriptorBuilder { argumentsValidator, returnTypeResolver, registrationKey, - isAggregate, + functionKind, argumentListSignature ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/StandardFunctionReturnTypeResolvers.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/StandardFunctionReturnTypeResolvers.java index 6ec9eb2b6b..a6e7300608 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/StandardFunctionReturnTypeResolvers.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/StandardFunctionReturnTypeResolvers.java @@ -13,6 +13,7 @@ import java.util.function.Supplier; import org.hibernate.QueryException; import org.hibernate.metamodel.mapping.BasicValuedMapping; +import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; import org.hibernate.query.sqm.SqmExpressable; @@ -220,6 +221,35 @@ public class StandardFunctionReturnTypeResolvers { return (AllowableFunctionReturnType) specifiedArgType; } + public static JdbcMapping extractArgumentJdbcMapping( + TypeConfiguration typeConfiguration, + List> arguments, + int position) { + final SqmTypedNode specifiedArgument = arguments.get( position - 1 ); + final SqmExpressable specifiedArgType = specifiedArgument.getNodeType(); + if ( specifiedArgType instanceof BasicType ) { + return ( (BasicType) specifiedArgType ).getJdbcMapping(); + } + else { + final BasicType basicType = typeConfiguration.getBasicTypeForJavaType( + specifiedArgType.getExpressableJavaTypeDescriptor().getJavaTypeClass() + ); + if ( basicType == null ) { + throw new QueryException( + String.format( + Locale.ROOT, + "Function argument [%s] of type [%s] at specified position [%d] in call arguments was not typed as basic type", + specifiedArgument, + specifiedArgType, + position + ) + ); + } + + return basicType.getJdbcMapping(); + } + } + public static BasicValuedMapping extractArgumentValuedMapping(List arguments, int position) { final SqlAstNode specifiedArgument = arguments.get( position-1 ); final JdbcMappingContainer specifiedArgType = specifiedArgument instanceof Expression diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java index e5ce86ea19..aab9fcf8d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCaseSearched.java @@ -50,14 +50,16 @@ public class SqmCaseSearched return otherwise; } - public void when(SqmPredicate predicate, SqmExpression result) { + public SqmCaseSearched when(SqmPredicate predicate, SqmExpression result) { whenFragments.add( new WhenFragment<>( predicate, result ) ); applyInferableResultType( result.getNodeType() ); + return this; } - public void otherwise(SqmExpression otherwiseExpression) { + public SqmCaseSearched otherwise(SqmExpression otherwiseExpression) { this.otherwise = otherwiseExpression; applyInferableResultType( otherwiseExpression.getNodeType() ); + return this; } private void applyInferableResultType(SqmExpressable type) { @@ -144,20 +146,20 @@ public class SqmCaseSearched } @Override - public JpaSearchedCase when(Expression condition, Expression result) { + public SqmCaseSearched when(Expression condition, Expression result) { //noinspection unchecked when( nodeBuilder().wrap( condition ), (SqmExpression) result ); return this; } @Override - public JpaExpression otherwise(R result) { + public SqmExpression otherwise(R result) { otherwise( nodeBuilder().value( result ) ); return this; } @Override - public JpaExpression otherwise(Expression result) { + public SqmExpression otherwise(Expression result) { //noinspection unchecked otherwise( (SqmExpression) result ); return this; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcTypeDescriptor.java index 422af9cfdb..f3b9460bda 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcTypeDescriptor.java @@ -111,6 +111,25 @@ public interface JdbcTypeDescriptor extends Serializable { return false; } + default boolean isFloat() { + switch ( getJdbcTypeCode() ) { + case Types.FLOAT: + case Types.REAL: + case Types.DOUBLE: + return true; + } + return false; + } + + default boolean isDecimal() { + switch ( getJdbcTypeCode() ) { + case Types.DECIMAL: + case Types.NUMERIC: + return true; + } + return false; + } + default boolean isNumber() { switch ( getJdbcTypeCode() ) { case Types.BIT: diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java index 4b4f366915..a2aed3ef72 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java @@ -27,6 +27,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import org.hamcrest.Matchers; + import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; @@ -42,6 +44,8 @@ import static org.hamcrest.MatcherAssert.assertThat; @SessionFactory public class FunctionTests { + public static final double ERROR = 0.00001d; + @BeforeAll public void prepareData(SessionFactoryScope scope) { scope.inTransaction( @@ -163,7 +167,13 @@ public class FunctionTests { .list(); assertThat( session.createQuery("select abs(-2)").getSingleResult(), is(2) ); assertThat( session.createQuery("select sign(-2)").getSingleResult(), is(-1) ); - assertThat( session.createQuery("select power(3.0,2.0)").getSingleResult(), is(9.0f) ); + assertThat( + session.createQuery("select power(3.0,2.0)", Double.class).getSingleResult(), + // The LN/EXP emulation can cause some precision loss + // i.e. on Derby the 16th decimal for LN(3.0) is off by 1 when compared to e.g. PostgreSQL + // Fetching the result as float would "hide" the error as that would do some rounding + Matchers.closeTo( 9.0d, ERROR ) + ); assertThat( session.createQuery("select round(32.12345,2)").getSingleResult(), is(32.12f) ); assertThat( session.createQuery("select mod(3,2)").getSingleResult(), is(1) ); assertThat( session.createQuery("select 3%2").getSingleResult(), is(1) );