Cleanup the grammar and generify same-structured function rules

This commit is contained in:
Christian Beikov 2021-08-05 11:33:43 +02:00
parent 07ec4f208d
commit be485796b1
23 changed files with 324 additions and 721 deletions

4
Jenkinsfile vendored
View File

@ -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()
}
}

View File

@ -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() );
}
;

View File

@ -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

View File

@ -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 )
);

View File

@ -47,7 +47,7 @@ public class CastStrEmulation
asList(
argument,
new SqmCastTarget<>(
impliedResultType,
StandardBasicTypes.STRING,
argument.nodeBuilder()
)
),

View File

@ -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<? extends SqmTypedNode<?>> 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<BasicValuedMapping> impliedTypeAccess, List<? extends SqlAstNode> 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;
}
}
}

View File

@ -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 )
);

View File

@ -55,7 +55,8 @@ public class InsertSubstringOverlayEmulation
AllowableFunctionReturnType<T> impliedResultType,
QueryEngine queryEngine,
TypeConfiguration typeConfiguration) {
BasicType<Integer> intType = typeConfiguration.getBasicTypeForJavaType(Integer.class);
final BasicType<Integer> intType = typeConfiguration.getBasicTypeForJavaType( Integer.class );
final BasicType<String> 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<Object> stringType = (SqmExpressable<Object>) 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<? extends String>) restString );
}
return concat.generateSqmExpression(
asList(

View File

@ -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 )
);

View File

@ -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<R> extends HqlParserBaseVisitor<Object> implements SqmCreationState {
private static final Logger log = Logger.getLogger( SemanticQueryBuilder.class );
private static final Set<String> JPA_STANDARD_FUNCTIONS;
static {
final Set<String> 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<R> extends HqlParserBaseVisitor<Object> 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<R> extends HqlParserBaseVisitor<Object> 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<R> extends HqlParserBaseVisitor<Object> implem
);
}
@Override
public Object visitLeastFunction(HqlParser.LeastFunctionContext ctx) {
final List<SqmExpression<?>> 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<SqmExpression<?>> 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<SqmExpression<?>> 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<R> extends HqlParserBaseVisitor<Object> 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<SqmTypedNode<?>> functionArguments = ctx.getChildCount() == 3
? emptyList()
: (List<SqmTypedNode<?>>) ctx.getChild( 2 ).accept( this );
final ParseTree argumentChild = ctx.getChild( 2 );
final List<SqmTypedNode<?>> functionArguments;
if ( argumentChild instanceof HqlParser.NonStandardFunctionArgumentsContext ) {
functionArguments = (List<SqmTypedNode<?>>) 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<R> extends HqlParserBaseVisitor<Object> implem
final List<SqmTypedNode<?>> 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<R> extends HqlParserBaseVisitor<Object> 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<SqmTypedNode<?>> newArguments = new ArrayList<>( 1 );
//noinspection unchecked
newArguments.add(
new SqmDistinct<>(
new SqmTuple<>( (List<SqmExpression<?>>) (List<?>) arguments, nodeBuilder ), nodeBuilder
)
);
return newArguments;
}
}
return arguments;
}
@ -3204,161 +3223,12 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> 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<R> extends HqlParserBaseVisitor<Object> 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<R> extends HqlParserBaseVisitor<Object> implem
return getFunctionDescriptor("format").generateSqmExpression(
asList( expressionToCast, format ),
resolveExpressableTypeBasic( String.class ),
null,
creationContext.getQueryEngine(),
creationContext.getJpaMetamodel().getTypeConfiguration()
);
@ -3599,60 +3469,6 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> 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<SqmTypedNode<?>> 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<R> extends HqlParserBaseVisitor<Object> 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<R> extends HqlParserBaseVisitor<Object> 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<R> extends HqlParserBaseVisitor<Object> implem
return (SqmSubQuery<X>) 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<R> extends HqlParserBaseVisitor<Object> 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<R> extends HqlParserBaseVisitor<Object> 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<R> extends HqlParserBaseVisitor<Object> implem
trimChar,
source
),
resolveExpressableTypeBasic( String.class ),
null,
creationContext.getQueryEngine(),
creationContext.getJpaMetamodel().getTypeConfiguration()
);

View File

@ -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<T> 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<T> 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<>(

View File

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

View File

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

View File

@ -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

View File

@ -118,4 +118,8 @@ public interface SqmFunctionDescriptor {
default String getSignature(String name) {
return name;
}
default FunctionKind getFunctionKind() {
return FunctionKind.NORMAL;
}
}

View File

@ -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) {

View File

@ -246,13 +246,12 @@ public class QuerySqmImpl<R>
final SqmQuerySpec<R> sqmQuerySpec = (SqmQuerySpec<R>) queryPart;
final List<SqmSelection<?>> sqmSelections = sqmQuerySpec.getSelectClause().getSelections();
// make sure there is at least one root
final List<SqmRoot<?>> 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<SqmRoot<?>> 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 );

View File

@ -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
);

View File

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

View File

@ -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<? extends SqmTypedNode<?>> 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<? extends SqlAstNode> arguments, int position) {
final SqlAstNode specifiedArgument = arguments.get( position-1 );
final JdbcMappingContainer specifiedArgType = specifiedArgument instanceof Expression

View File

@ -50,14 +50,16 @@ public class SqmCaseSearched<R>
return otherwise;
}
public void when(SqmPredicate predicate, SqmExpression<R> result) {
public SqmCaseSearched<R> when(SqmPredicate predicate, SqmExpression<R> result) {
whenFragments.add( new WhenFragment<>( predicate, result ) );
applyInferableResultType( result.getNodeType() );
return this;
}
public void otherwise(SqmExpression<R> otherwiseExpression) {
public SqmCaseSearched<R> otherwise(SqmExpression<R> otherwiseExpression) {
this.otherwise = otherwiseExpression;
applyInferableResultType( otherwiseExpression.getNodeType() );
return this;
}
private void applyInferableResultType(SqmExpressable<?> type) {
@ -144,20 +146,20 @@ public class SqmCaseSearched<R>
}
@Override
public JpaSearchedCase<R> when(Expression<Boolean> condition, Expression<? extends R> result) {
public SqmCaseSearched<R> when(Expression<Boolean> condition, Expression<? extends R> result) {
//noinspection unchecked
when( nodeBuilder().wrap( condition ), (SqmExpression) result );
return this;
}
@Override
public JpaExpression<R> otherwise(R result) {
public SqmExpression<R> otherwise(R result) {
otherwise( nodeBuilder().value( result ) );
return this;
}
@Override
public JpaExpression<R> otherwise(Expression<? extends R> result) {
public SqmExpression<R> otherwise(Expression<? extends R> result) {
//noinspection unchecked
otherwise( (SqmExpression) result );
return this;

View File

@ -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:

View File

@ -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) );