From c44e5c395337408ad2a93f415a5e328a6b6702da Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 26 Nov 2024 16:49:10 +0100 Subject: [PATCH] Add support for missing clauses and functions. Original Pull Request: #3691 --- .../data/jpa/repository/query/Hql.g4 | 602 +++++-- .../repository/query/HqlQueryRenderer.java | 1449 +++++++++++++---- .../query/HqlQueryRendererTests.java | 9 +- .../query/HqlSpecificationTests.java | 255 ++- 4 files changed, 1910 insertions(+), 405 deletions(-) diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 index 42c159876..728d3fe7b 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 @@ -188,10 +188,6 @@ instantiation : NEW instantiationTarget '(' instantiationArguments ')' ; -alias - : AS? identifier // spec says IDENTIFIER but clearly does NOT mean a reserved word - ; - groupedItem : identifier | INTEGER_LITERAL @@ -354,6 +350,17 @@ dateTimeLiteral | INSTANT ; +/** + * A field that may be extracted from a date, time, or datetime + */ +extractField + : datetimeField + | dayField + | weekField + | timeZoneField + | dateOrTimeField + ; + // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-duration-literals datetimeField : YEAR @@ -368,6 +375,27 @@ datetimeField | EPOCH ; +dayField + : DAY OF MONTH + | DAY OF WEEK + | DAY OF YEAR + ; + +weekField + : WEEK OF MONTH + | WEEK OF YEAR + ; + +timeZoneField + : OFFSET (HOUR | MINUTE)? + | TIMEZONE_HOUR | TIMEZONE_MINUTE + ; + +dateOrTimeField + : DATE + | TIME + ; + // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-binary-literals binaryLiteral : BINARY_LITERAL @@ -416,11 +444,6 @@ primaryExpression // TBD // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-path-expressions -identificationVariable - : identifier - | simplePath - ; - path : treatedPath pathContinutation? | generalPathFragment @@ -467,112 +490,498 @@ caseWhenPredicateClause ; // Functions -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-exp-functions +/** + * A function invocation that may occur in an arbitrary expression + */ function - : functionName '(' (functionArguments | ASTERISK)? ')' pathContinutation? filterClause? withinGroup? overClause? # GenericFunction - | functionName '(' subquery ')' # FunctionWithSubquery - | castFunction # CastFunctionInvocation - | extractFunction # ExtractFunctionInvocation - | trimFunction # TrimFunctionInvocation - | everyFunction # EveryFunctionInvocation - | anyFunction # AnyFunctionInvocation - | treatedPath # TreatedPathInvocation + : standardFunction # StandardFunctionInvocation + | aggregateFunction # AggregateFunctionInvocation + | collectionSizeFunction # CollectionSizeFunctionInvocation + | collectionAggregateFunction # CollectionAggregateFunctionInvocation + | collectionFunctionMisuse # CollectionFunctionMisuseInvocation + | jpaNonstandardFunction # JpaNonstandardFunctionInvocation + | columnFunction # ColumnFunctionInvocation + | genericFunction # GenericFunctionInvocation ; -functionArguments - : DISTINCT? expressionOrPredicate (',' expressionOrPredicate)* +/** + * Any function with an irregular syntax for the argument list + * + * These are all inspired by the syntax of ANSI SQL + */ +standardFunction + : castFunction + | treatedPath + | extractFunction + | truncFunction + | formatFunction + | collateFunction + | substringFunction + | overlayFunction + | trimFunction + | padFunction + | positionFunction + | currentDateFunction + | currentTimeFunction + | currentTimestampFunction + | instantFunction + | localDateFunction + | localTimeFunction + | localDateTimeFunction + | offsetDateTimeFunction + | cube + | rollup ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-filter -filterClause - : FILTER '(' whereClause ')' - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-orderedset -withinGroup - : WITHIN GROUP '(' orderByClause ')' - ; - -overClause - : OVER '(' partitionClause? orderByClause? frameClause? ')' - ; - -partitionClause - : PARTITION BY expression (',' expression)* - ; - -frameClause - : (RANGE|ROWS|GROUPS) frameStart frameExclusion? - | (RANGE|ROWS|GROUPS) BETWEEN frameStart AND frameEnd frameExclusion? - ; - -frameStart - : UNBOUNDED PRECEDING # UnboundedPrecedingFrameStart - | expression PRECEDING # ExpressionPrecedingFrameStart - | CURRENT ROW # CurrentRowFrameStart - | expression FOLLOWING # ExpressionFollowingFrameStart - ; - -frameExclusion - : EXCLUDE CURRENT ROW # CurrentRowFrameExclusion - | EXCLUDE GROUP # GroupFrameExclusion - | EXCLUDE TIES # TiesFrameExclusion - | EXCLUDE NO OTHERS # NoOthersFrameExclusion - ; - -frameEnd - : expression PRECEDING # ExpressionPrecedingFrameEnd - | CURRENT ROW # CurrentRowFrameEnd - | expression FOLLOWING # ExpressionFollowingFrameEnd - | UNBOUNDED FOLLOWING # UnboundedFollowingFrameEnd - ; - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-functions +/** + * The 'cast()' function for typecasting + */ castFunction : CAST '(' expression AS castTarget ')' ; +/** + * The target type for a typecast: a typename, together with length or precision/scale + */ castTarget : castTargetType ('(' INTEGER_LITERAL (',' INTEGER_LITERAL)? ')')? ; +/** + * The name of the target type in a typecast + * + * Like the 'entityName' rule, we have a specialized dotIdentifierSequence rule + */ castTargetType returns [String fullTargetName] : (i=identifier { $fullTargetName = _localctx.i.getText(); }) ('.' c=identifier { $fullTargetName += ("." + _localctx.c.getText() ); })* ; -extractFunction - : EXTRACT '(' expression FROM expression ')' - | dateTimeFunction '(' expression ')' +/** + * The two formats for the 'substring() function: one defined by JPQL, the other by ANSI SQL + */ +substringFunction + : SUBSTRING '(' expression ',' substringFunctionStartArgument (',' substringFunctionLengthArgument)? ')' + | SUBSTRING '(' expression FROM substringFunctionStartArgument (FOR substringFunctionLengthArgument)? ')' ; +substringFunctionStartArgument + : expression + ; + +substringFunctionLengthArgument + : expression + ; + +/** + * The ANSI SQL-style 'trim()' function + */ trimFunction - : TRIM '(' (LEADING | TRAILING | BOTH)? stringLiteral? FROM? expression ')' + : TRIM '(' trimSpecification? trimCharacter? FROM? expression ')' ; -dateTimeFunction - : d=(YEAR - | MONTH - | DAY - | WEEK - | QUARTER - | HOUR - | MINUTE - | SECOND - | NANOSECOND - | EPOCH) +trimSpecification + : LEADING + | TRAILING + | BOTH ; +trimCharacter + : stringLiteral + | parameter + ; + +/** + * A 'pad()' function inspired by 'trim()' + */ +padFunction + : PAD '(' expression WITH padLength padSpecification padCharacter? ')' + ; + +padSpecification + : LEADING + | TRAILING + ; + +padCharacter + : stringLiteral + ; + +padLength + : expression + ; + +/** + * The ANSI SQL-style 'position()' function + */ +positionFunction + : POSITION '(' positionFunctionPatternArgument IN positionFunctionStringArgument ')' + ; + +positionFunctionPatternArgument + : expression + ; + +positionFunctionStringArgument + : expression + ; + +/** + * The ANSI SQL-style 'overlay()' function + */ +overlayFunction + : OVERLAY '(' overlayFunctionStringArgument PLACING overlayFunctionReplacementArgument FROM overlayFunctionStartArgument (FOR overlayFunctionLengthArgument)? ')' + ; + +overlayFunctionStringArgument + : expression + ; + +overlayFunctionReplacementArgument + : expression + ; + +overlayFunctionStartArgument + : expression + ; + +overlayFunctionLengthArgument + : expression + ; + +/** + * The deprecated current_date function required by JPQL + */ +currentDateFunction + : CURRENT_DATE ('(' ')')? + | CURRENT DATE + ; + +/** + * The deprecated current_time function required by JPQL + */ +currentTimeFunction + : CURRENT_TIME ('(' ')')? + | CURRENT TIME + ; + +/** + * The deprecated current_timestamp function required by JPQL + */ +currentTimestampFunction + : CURRENT_TIMESTAMP ('(' ')')? + | CURRENT TIMESTAMP + ; + +/** + * The instant function, and deprecated current_instant function + */ +instantFunction + : CURRENT_INSTANT ('(' ')')? //deprecated legacy syntax + | INSTANT + ; + +/** + * The 'local datetime' function (or literal if you prefer) + */ +localDateTimeFunction + : LOCAL_DATETIME ('(' ')')? + | LOCAL DATETIME + ; + +/** + * The 'offset datetime' function (or literal if you prefer) + */ +offsetDateTimeFunction + : OFFSET_DATETIME ('(' ')')? + | OFFSET DATETIME + ; + +/** + * The 'local date' function (or literal if you prefer) + */ +localDateFunction + : LOCAL_DATE ('(' ')')? + | LOCAL DATE + ; + +/** + * The 'local time' function (or literal if you prefer) + */ +localTimeFunction + : LOCAL_TIME ('(' ')')? + | LOCAL TIME + ; + +/** + * The 'format()' function for formatting dates and times according to a pattern + */ +formatFunction + : FORMAT '(' expression AS format ')' + ; + +/** + * The name of a database-defined collation + * + * Certain databases allow a period in a collation name + */ +collation + : simplePath + ; + +/** + * The special 'collate()' functions + */ +collateFunction + : COLLATE '(' expression AS collation ')' + ; + +/** + * The 'cube()' function specific to the 'group by' clause + */ +cube + : CUBE '(' expressionOrPredicate (',' expressionOrPredicate)* ')' + ; + +/** + * The 'rollup()' function specific to the 'group by' clause + */ +rollup + : ROLLUP '(' expressionOrPredicate (',' expressionOrPredicate)* ')' + ; + +/** + * A format pattern, with a syntax inspired by by java.time.format.DateTimeFormatter + * + * see 'Dialect.appendDatetimeFormat()' + */ +format + : stringLiteral + ; + +/** + * The 'extract()' function for extracting fields of dates, times, and datetimes + */ +extractFunction + : EXTRACT '(' extractField FROM expression ')' + | datetimeField '(' expression ')' + ; + +/** + * The 'trunc()' function for truncating both numeric and datetime values + */ +truncFunction + : (TRUNC | TRUNCATE) '(' expression (',' (datetimeField | expression))? ')' + ; + +/** + * A syntax for calling user-defined or native database functions, required by JPQL + */ +jpaNonstandardFunction + : FUNCTION '(' jpaNonstandardFunctionName (AS castTarget)? (',' genericFunctionArguments)? ')' + ; + +/** + * The name of a user-defined or native database function, given as a quoted string + */ +jpaNonstandardFunctionName + : stringLiteral + | identifier + ; + +columnFunction + : COLUMN '(' path '.' jpaNonstandardFunctionName (AS castTarget)? ')' + ; + +/** + * Any function invocation that follows the regular syntax + * + * The function name, followed by a parenthesized list of ','-separated expressions + */ +genericFunction + : genericFunctionName '(' (genericFunctionArguments | ASTERISK)? ')' pathContinutation? + nthSideClause? nullsClause? withinGroupClause? filterClause? overClause? + ; + +/** + * The name of a generic function, which may contain periods and quoted identifiers + * + * Names of generic functions are resolved against the SqmFunctionRegistry + */ +genericFunctionName + : simplePath + ; + +/** + * The arguments of a generic function + */ +genericFunctionArguments + : (DISTINCT | datetimeField ',')? expressionOrPredicate (',' expressionOrPredicate)* + ; + +/** + * The special 'size()' function defined by JPQL + */ +collectionSizeFunction + : SIZE '(' path ')' + ; + +/** + * Special rule for 'max(elements())`, 'avg(keys())', 'sum(indices())`, etc., as defined by HQL + * Also the deprecated 'maxindex()', 'maxelement()', 'minindex()', 'minelement()' functions from old HQL + */ +collectionAggregateFunction + : (MAX|MIN|SUM|AVG) '(' elementsValuesQuantifier '(' path ')' ')' # ElementAggregateFunction + | (MAX|MIN|SUM|AVG) '(' indicesKeysQuantifier '(' path ')' ')' # IndexAggregateFunction + | (MAXELEMENT|MINELEMENT) '(' path ')' # ElementAggregateFunction + | (MAXINDEX|MININDEX) '(' path ')' # IndexAggregateFunction + ; + +/** + * To accommodate the misuse of elements() and indices() in the select clause + * + * (At some stage in the history of HQL, someone mixed them up with value() and index(), + * and so we have tests that insist they're interchangeable. Ugh.) + */ +collectionFunctionMisuse + : elementsValuesQuantifier '(' path ')' + | indicesKeysQuantifier '(' path ')' + ; + +/** + * The special 'every()', 'all()', 'any()' and 'some()' functions defined by HQL + * + * May be applied to a subquery or collection reference, or may occur as an aggregate function in the 'select' clause + */ +aggregateFunction + : everyFunction + | anyFunction + | listaggFunction + ; + +/** + * The functions 'every()' and 'all()' are synonyms + */ everyFunction - : every=(EVERY | ALL) '(' predicate ')' - | every=(EVERY | ALL) '(' subquery ')' - | every=(EVERY | ALL) (ELEMENTS | INDICES) '(' simplePath ')' + : everyAllQuantifier '(' predicate ')' filterClause? overClause? + | everyAllQuantifier '(' subquery ')' + | everyAllQuantifier collectionQuantifier '(' simplePath ')' ; +/** + * The functions 'any()' and 'some()' are synonyms + */ anyFunction - : any=(ANY | SOME) '(' predicate ')' - | any=(ANY | SOME) '(' subquery ')' - | any=(ANY | SOME) (ELEMENTS | INDICES) '(' simplePath ')' + : anySomeQuantifier '(' predicate ')' filterClause? overClause? + | anySomeQuantifier '(' subquery ')' + | anySomeQuantifier collectionQuantifier '(' simplePath ')' + ; + +everyAllQuantifier + : EVERY + | ALL + ; + +anySomeQuantifier + : ANY + | SOME + ; + +/** + * The 'listagg()' ordered set-aggregate function + */ +listaggFunction + : LISTAGG '(' DISTINCT? expressionOrPredicate ',' expressionOrPredicate onOverflowClause? ')' + withinGroupClause? filterClause? overClause? + ; + +/** + * A 'on overflow' clause: what to do when the text data type used for 'listagg' overflows + */ +onOverflowClause + : ON OVERFLOW (ERROR | TRUNCATE expression? (WITH|WITHOUT) COUNT) + ; + +/** + * A 'within group' clause: defines the order in which the ordered set-aggregate function should work + */ +withinGroupClause + : WITHIN GROUP '(' orderByClause ')' + ; + +/** + * A 'filter' clause: a restriction applied to an aggregate function + */ +filterClause + : FILTER '(' whereClause ')' + ; + +/** + * A `nulls` clause: what should a value access window function do when encountering a `null` + */ +nullsClause + : RESPECT NULLS + | IGNORE NULLS + ; + +/** + * A `nulls` clause: what should a value access window function do when encountering a `null` + */ +nthSideClause + : FROM FIRST + | FROM LAST + ; + +/** + * A 'over' clause: the specification of a window within which the function should act + */ +overClause + : OVER '(' partitionClause? orderByClause? frameClause? ')' + ; + +/** + * A 'partition' clause: the specification the group within which a function should act in a window + */ +partitionClause + : PARTITION BY expression (',' expression)* + ; + +/** + * A 'frame' clause: the specification the content of the window + */ +frameClause + : (RANGE|ROWS|GROUPS) frameStart frameExclusion? + | (RANGE|ROWS|GROUPS) BETWEEN frameStart AND frameEnd frameExclusion? + ; + +/** + * The start of the window content + */ +frameStart + : CURRENT ROW + | UNBOUNDED PRECEDING + | expression PRECEDING + | expression FOLLOWING + ; + +/** + * The end of the window content + */ +frameEnd + : CURRENT ROW + | UNBOUNDED FOLLOWING + | expression PRECEDING + | expression FOLLOWING + ; + +/** + * A 'exclusion' clause: the specification what to exclude from the window content + */ +frameExclusion + : EXCLUDE CURRENT ROW + | EXCLUDE GROUP + | EXCLUDE TIES + | EXCLUDE NO OTHERS ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-treat-type @@ -608,6 +1017,21 @@ expressionOrPredicate | predicate ; +collectionQuantifier + : elementsValuesQuantifier + | indicesKeysQuantifier + ; + +elementsValuesQuantifier + : ELEMENTS + | VALUES + ; + +indicesKeysQuantifier + : INDICES + | KEYS + ; + // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-relational-comparisons // NOTE: The TIP shows that "!=" is also supported. Hibernate's source code shows that "^=" is another NOT_EQUALS option as well. relationalExpression @@ -691,10 +1115,6 @@ identifier : reservedWord ; -character - : CHARACTER - ; - functionName : reservedWord ('.' reservedWord)* ; @@ -998,6 +1418,7 @@ INTO : I N T O; IS : I S; JOIN : J O I N; KEY : K E Y; +KEYS : K E Y S; LAST : L A S T; LATERAL : L A T E R A L; LEADING : L E A D I N G; @@ -1114,4 +1535,3 @@ BINARY_LITERAL : [xX] '\'' HEX_DIGIT+ '\'' ; IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ; - diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java index 127068f4a..2ef49b95f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java @@ -662,20 +662,6 @@ class HqlQueryRenderer extends HqlBaseVisitor { return builder; } - @Override - public QueryTokenStream visitAlias(HqlParser.AliasContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - if (ctx.AS() != null) { - builder.append(QueryTokens.expression(ctx.AS())); - } - - builder.append(visit(ctx.identifier())); - - return builder; - } - @Override public QueryTokenStream visitGroupedItem(HqlParser.GroupedItemContext ctx) { @@ -1146,30 +1132,105 @@ class HqlQueryRenderer extends HqlBaseVisitor { public QueryTokenStream visitDatetimeField(HqlParser.DatetimeFieldContext ctx) { if (ctx.YEAR() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.YEAR())); + return QueryRendererBuilder.from(QueryTokens.token(ctx.YEAR())); } else if (ctx.MONTH() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.MONTH())); + return QueryRendererBuilder.from(QueryTokens.token(ctx.MONTH())); } else if (ctx.DAY() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.DAY())); + return QueryRendererBuilder.from(QueryTokens.token(ctx.DAY())); } else if (ctx.WEEK() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.WEEK())); + return QueryRendererBuilder.from(QueryTokens.token(ctx.WEEK())); } else if (ctx.QUARTER() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.QUARTER())); + return QueryRendererBuilder.from(QueryTokens.token(ctx.QUARTER())); } else if (ctx.HOUR() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.HOUR())); + return QueryRendererBuilder.from(QueryTokens.token(ctx.HOUR())); } else if (ctx.MINUTE() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.MINUTE())); + return QueryRendererBuilder.from(QueryTokens.token(ctx.MINUTE())); } else if (ctx.SECOND() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.SECOND())); + return QueryRendererBuilder.from(QueryTokens.token(ctx.SECOND())); } else if (ctx.NANOSECOND() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.NANOSECOND())); + return QueryRendererBuilder.from(QueryTokens.token(ctx.NANOSECOND())); } else if (ctx.EPOCH() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.EPOCH())); + return QueryRendererBuilder.from(QueryTokens.token(ctx.EPOCH())); } else { return QueryTokenStream.empty(); } } + @Override + public QueryTokenStream visitDayField(HqlParser.DayFieldContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.DAY())); + builder.append(QueryTokens.expression(ctx.OF())); + + if (ctx.MONTH() != null) { + builder.append(QueryTokens.expression(ctx.MONTH())); + } + + if (ctx.WEEK() != null) { + builder.append(QueryTokens.expression(ctx.WEEK())); + } + + if (ctx.YEAR() != null) { + builder.append(QueryTokens.expression(ctx.YEAR())); + } + + return builder; + } + + @Override + public QueryTokenStream visitWeekField(HqlParser.WeekFieldContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.WEEK())); + builder.append(QueryTokens.expression(ctx.OF())); + + if (ctx.MONTH() != null) { + builder.append(QueryTokens.expression(ctx.MONTH())); + } + + if (ctx.YEAR() != null) { + builder.append(QueryTokens.expression(ctx.YEAR())); + } + + return builder; + } + + @Override + public QueryTokenStream visitTimeZoneField(HqlParser.TimeZoneFieldContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.OFFSET() != null) { + builder.append(QueryTokens.expression(ctx.OFFSET())); + + if (ctx.HOUR() != null) { + builder.append(QueryTokens.expression(ctx.HOUR())); + } + + if (ctx.MINUTE() != null) { + builder.append(QueryTokens.expression(ctx.MINUTE())); + } + } + + if (ctx.TIMEZONE_HOUR() != null) { + builder.append(QueryTokens.expression(ctx.TIMEZONE_HOUR())); + } + + if (ctx.TIMEZONE_HOUR() != null) { + builder.append(QueryTokens.expression(ctx.TIMEZONE_MINUTE())); + } + + return builder; + } + + @Override + public QueryTokenStream visitDateOrTimeField(HqlParser.DateOrTimeFieldContext ctx) { + return QueryRendererBuilder.from(QueryTokens.expression(ctx.DATE() != null ? ctx.DATE() : ctx.TIME())); + } + @Override public QueryTokenStream visitBinaryLiteral(HqlParser.BinaryLiteralContext ctx) { @@ -1332,7 +1393,7 @@ class HqlQueryRenderer extends HqlBaseVisitor { QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(visit(ctx.expression())); - builder.append(visit(ctx.datetimeField())); + builder.appendExpression(visit(ctx.datetimeField())); return builder; } @@ -1344,7 +1405,7 @@ class HqlQueryRenderer extends HqlBaseVisitor { builder.append(visit(ctx.expression())); builder.append(QueryTokens.expression(ctx.BY())); - builder.append(visit(ctx.datetimeField())); + builder.appendExpression(visit(ctx.datetimeField())); return builder; } @@ -1370,20 +1431,946 @@ class HqlQueryRenderer extends HqlBaseVisitor { } @Override - public QueryTokenStream visitGeneralPathExpression(HqlParser.GeneralPathExpressionContext ctx) { - return visit(ctx.generalPathFragment()); + public QueryTokenStream visitStandardFunctionInvocation(HqlParser.StandardFunctionInvocationContext ctx) { + return visit(ctx.standardFunction()); } @Override - public QueryTokenStream visitIdentificationVariable(HqlParser.IdentificationVariableContext ctx) { + public QueryTokenStream visitAggregateFunctionInvocation(HqlParser.AggregateFunctionInvocationContext ctx) { + return visit(ctx.aggregateFunction()); + } + + @Override + public QueryTokenStream visitCollectionSizeFunctionInvocation(HqlParser.CollectionSizeFunctionInvocationContext ctx) { + return visit(ctx.collectionSizeFunction()); + } + + @Override + public QueryTokenStream visitCollectionAggregateFunctionInvocation( + HqlParser.CollectionAggregateFunctionInvocationContext ctx) { + return visit(ctx.collectionAggregateFunction()); + } + + @Override + public QueryTokenStream visitCollectionFunctionMisuseInvocation( + HqlParser.CollectionFunctionMisuseInvocationContext ctx) { + return visit(ctx.collectionFunctionMisuse()); + } + + @Override + public QueryTokenStream visitJpaNonstandardFunctionInvocation(HqlParser.JpaNonstandardFunctionInvocationContext ctx) { + return visit(ctx.jpaNonstandardFunction()); + } + + @Override + public QueryTokenStream visitColumnFunctionInvocation(HqlParser.ColumnFunctionInvocationContext ctx) { + return visit(ctx.columnFunction()); + } + + @Override + public QueryTokenStream visitGenericFunctionInvocation(HqlParser.GenericFunctionInvocationContext ctx) { + return visit(ctx.genericFunction()); + } + + @Override + public QueryTokenStream visitStandardFunction(HqlParser.StandardFunctionContext ctx) { + + if (ctx.castFunction() != null) { + return visit(ctx.castFunction()); + } + + if (ctx.treatedPath() != null) { + return visit(ctx.treatedPath()); + } + + if (ctx.extractFunction() != null) { + return visit(ctx.extractFunction()); + } + + if (ctx.truncFunction() != null) { + return visit(ctx.truncFunction()); + } + + if (ctx.formatFunction() != null) { + return visit(ctx.formatFunction()); + } + + if (ctx.collateFunction() != null) { + return visit(ctx.collateFunction()); + } + + if (ctx.substringFunction() != null) { + return visit(ctx.substringFunction()); + } + + if (ctx.overlayFunction() != null) { + return visit(ctx.overlayFunction()); + } + + if (ctx.trimFunction() != null) { + return visit(ctx.trimFunction()); + } + + if (ctx.padFunction() != null) { + return visit(ctx.padFunction()); + } + + if (ctx.positionFunction() != null) { + return visit(ctx.positionFunction()); + } + + if (ctx.currentDateFunction() != null) { + return visit(ctx.currentDateFunction()); + } + + if (ctx.currentTimeFunction() != null) { + return visit(ctx.currentTimeFunction()); + } + + if (ctx.currentTimestampFunction() != null) { + return visit(ctx.currentTimestampFunction()); + } + + if (ctx.instantFunction() != null) { + return visit(ctx.instantFunction()); + } + + if (ctx.localDateFunction() != null) { + return visit(ctx.localDateFunction()); + } + + if (ctx.localTimeFunction() != null) { + return visit(ctx.localTimeFunction()); + } + + if (ctx.localDateTimeFunction() != null) { + return visit(ctx.localDateTimeFunction()); + } + + if (ctx.offsetDateTimeFunction() != null) { + return visit(ctx.offsetDateTimeFunction()); + } + + if (ctx.cube() != null) { + return visit(ctx.cube()); + } + + if (ctx.rollup() != null) { + return visit(ctx.rollup()); + } + + return QueryTokenStream.empty(); + } + + @Override + public QueryTokenStream visitSubstringFunction(HqlParser.SubstringFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.SUBSTRING())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.expression())); + + if (ctx.FROM() == null) { + builder.append(TOKEN_COMMA); + } else { + builder.append(QueryTokens.expression(ctx.FROM())); + } + + builder.append(visit(ctx.substringFunctionStartArgument())); + + if (ctx.substringFunctionLengthArgument() != null) { + if (ctx.FOR() == null) { + builder.append(TOKEN_COMMA); + } else { + builder.append(QueryTokens.expression(ctx.FOR())); + } + + builder.append(visit(ctx.substringFunctionLengthArgument())); + } + + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitSubstringFunctionStartArgument(HqlParser.SubstringFunctionStartArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitSubstringFunctionLengthArgument(HqlParser.SubstringFunctionLengthArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitPadFunction(HqlParser.PadFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.PAD())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.WITH())); + builder.appendExpression(visit(ctx.padLength())); + + if (ctx.padCharacter() != null) { + builder.appendExpression(visit(ctx.padSpecification())); + builder.appendInline(visit(ctx.padCharacter())); + } else { + builder.append(visit(ctx.padSpecification())); + } + + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitPadSpecification(HqlParser.PadSpecificationContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.LEADING() != null ? ctx.LEADING() : ctx.TRAILING())); + } + + @Override + public QueryTokenStream visitPadCharacter(HqlParser.PadCharacterContext ctx) { + return visit(ctx.stringLiteral()); + } + + @Override + public QueryTokenStream visitPadLength(HqlParser.PadLengthContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitPositionFunction(HqlParser.PositionFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.POSITION())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.positionFunctionPatternArgument())); + builder.append(QueryTokens.expression(ctx.IN())); + builder.appendInline(visit(ctx.positionFunctionStringArgument())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitPositionFunctionPatternArgument(HqlParser.PositionFunctionPatternArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitPositionFunctionStringArgument(HqlParser.PositionFunctionStringArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitOverlayFunction(HqlParser.OverlayFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.OVERLAY())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.overlayFunctionStringArgument())); + builder.append(QueryTokens.expression(ctx.PLACING())); + builder.append(visit(ctx.overlayFunctionReplacementArgument())); + builder.append(QueryTokens.expression(ctx.FROM())); + builder.append(visit(ctx.overlayFunctionStartArgument())); + + if (ctx.overlayFunctionLengthArgument() != null) { + builder.append(QueryTokens.expression(ctx.FOR())); + builder.append(visit(ctx.overlayFunctionLengthArgument())); + } + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitOverlayFunctionStringArgument(HqlParser.OverlayFunctionStringArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitOverlayFunctionReplacementArgument( + HqlParser.OverlayFunctionReplacementArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitOverlayFunctionStartArgument(HqlParser.OverlayFunctionStartArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitOverlayFunctionLengthArgument(HqlParser.OverlayFunctionLengthArgumentContext ctx) { + return visit(ctx.expression()); + } + + @Override + public QueryTokenStream visitCurrentDateFunction(HqlParser.CurrentDateFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT_DATE() != null) { + builder.append(QueryTokens.token(ctx.CURRENT_DATE())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.DATE())); + } + + return builder; + } + + @Override + public QueryTokenStream visitCurrentTimeFunction(HqlParser.CurrentTimeFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT_TIME() != null) { + builder.append(QueryTokens.token(ctx.CURRENT_TIME())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.TIME())); + } + + return builder; + } + + @Override + public QueryTokenStream visitCurrentTimestampFunction(HqlParser.CurrentTimestampFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT_TIMESTAMP() != null) { + builder.append(QueryTokens.token(ctx.CURRENT_TIMESTAMP())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.TIMESTAMP())); + } + + return builder; + } + + @Override + public QueryTokenStream visitInstantFunction(HqlParser.InstantFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT_INSTANT() != null) { + builder.append(QueryTokens.token(ctx.CURRENT_INSTANT())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.INSTANT())); + } + + return builder; + } + + @Override + public QueryTokenStream visitLocalDateTimeFunction(HqlParser.LocalDateTimeFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.LOCAL_DATETIME() != null) { + builder.append(QueryTokens.token(ctx.LOCAL_DATETIME())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.LOCAL())); + builder.append(QueryTokens.expression(ctx.DATETIME())); + } + + return builder; + } + + @Override + public QueryTokenStream visitOffsetDateTimeFunction(HqlParser.OffsetDateTimeFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.OFFSET_DATETIME() != null) { + builder.append(QueryTokens.token(ctx.OFFSET_DATETIME())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.OFFSET())); + builder.append(QueryTokens.expression(ctx.DATETIME())); + } + + return builder; + } + + @Override + public QueryTokenStream visitLocalDateFunction(HqlParser.LocalDateFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.LOCAL_DATE() != null) { + builder.append(QueryTokens.token(ctx.LOCAL_DATE())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.LOCAL())); + builder.append(QueryTokens.expression(ctx.DATE())); + } + + return builder; + } + + @Override + public QueryTokenStream visitLocalTimeFunction(HqlParser.LocalTimeFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.LOCAL_TIME() != null) { + builder.append(QueryTokens.token(ctx.LOCAL_TIME())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } else { + builder.append(QueryTokens.expression(ctx.LOCAL())); + builder.append(QueryTokens.expression(ctx.TIME())); + } + + return builder; + } + + @Override + public QueryTokenStream visitFormatFunction(HqlParser.FormatFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.FORMAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.AS())); + builder.appendInline(visit(ctx.format())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitCollation(HqlParser.CollationContext ctx) { + return visit(ctx.simplePath()); + } + + @Override + public QueryTokenStream visitCollateFunction(HqlParser.CollateFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.COLLATE())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.AS())); + builder.appendInline(visit(ctx.collation())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitCube(HqlParser.CubeContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.CUBE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryTokenStream.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitRollup(HqlParser.RollupContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.ROLLUP())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryTokenStream.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitFormat(HqlParser.FormatContext ctx) { + return visit(ctx.stringLiteral()); + } + + @Override + public QueryTokenStream visitTruncFunction(HqlParser.TruncFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.TRUNC() != null) { + builder.append(QueryTokens.token(ctx.TRUNC())); + } else { + builder.append(QueryTokens.token(ctx.TRUNCATE())); + } + + builder.append(TOKEN_OPEN_PAREN); + + if (ctx.datetimeField() != null) { + builder.append(visit(ctx.expression(0))); + builder.append(TOKEN_COMMA); + builder.append(visit(ctx.datetimeField())); + } else { + builder.append(QueryTokenStream.concat(ctx.expression(), this::visit, TOKEN_COMMA)); + } + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitJpaNonstandardFunction(HqlParser.JpaNonstandardFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.FUNCTION())); + builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); + nested.appendInline(visit(ctx.jpaNonstandardFunctionName())); + + if (ctx.castTarget() != null) { + nested.append(QueryTokens.expression(ctx.AS())); + nested.append(visit(ctx.castTarget())); + } + + if (ctx.genericFunctionArguments() != null) { + nested.append(TOKEN_COMMA); + nested.appendInline(visit(ctx.genericFunctionArguments())); + } + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitJpaNonstandardFunctionName(HqlParser.JpaNonstandardFunctionNameContext ctx) { if (ctx.identifier() != null) { return visit(ctx.identifier()); - } else if (ctx.simplePath() != null) { - return visit(ctx.simplePath()); - } else { - return QueryTokenStream.empty(); } + + return visit(ctx.stringLiteral()); + } + + @Override + public QueryTokenStream visitColumnFunction(HqlParser.ColumnFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.COLUMN())); + builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); + nested.appendInline(visit(ctx.path())); + nested.append(TOKEN_DOT); + nested.appendExpression(visit(ctx.jpaNonstandardFunctionName())); + + if (ctx.castTarget() != null) { + nested.append(QueryTokens.expression(ctx.AS())); + nested.appendExpression(visit(ctx.jpaNonstandardFunctionName())); + } + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitGenericFunctionName(HqlParser.GenericFunctionNameContext ctx) { + return visit(ctx.simplePath()); + } + + @Override + public QueryTokenStream visitGenericFunctionArguments(HqlParser.GenericFunctionArgumentsContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.DISTINCT() != null) { + builder.append(QueryTokens.expression(ctx.DISTINCT())); + } + + if (ctx.datetimeField() != null) { + builder.append(visit(ctx.datetimeField())); + builder.append(TOKEN_COMMA); + } + + builder.append(QueryTokenStream.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); + + return builder; + } + + @Override + public QueryTokenStream visitCollectionSizeFunction(HqlParser.CollectionSizeFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.SIZE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitElementAggregateFunction(HqlParser.ElementAggregateFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.MAXELEMENT() != null || ctx.MINELEMENT() != null) { + builder.append(QueryTokens.token(ctx.MAXELEMENT() != null ? ctx.MAXELEMENT() : ctx.MINELEMENT())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + } else { + + if (ctx.MAX() != null) { + builder.append(QueryTokens.token(ctx.MAX())); + } + if (ctx.MIN() != null) { + builder.append(QueryTokens.token(ctx.MIN())); + } + if (ctx.SUM() != null) { + builder.append(QueryTokens.token(ctx.SUM())); + } + if (ctx.AVG() != null) { + builder.append(QueryTokens.token(ctx.AVG())); + } + + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.elementsValuesQuantifier())); + builder.append(TOKEN_OPEN_PAREN); + + if (ctx.path() != null) { + builder.append(visit(ctx.path())); + } + + builder.append(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } + + return builder; + } + + @Override + public QueryTokenStream visitIndexAggregateFunction(HqlParser.IndexAggregateFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.MAXINDEX() != null || ctx.MININDEX() != null) { + builder.append(QueryTokens.token(ctx.MAXINDEX() != null ? ctx.MAXINDEX() : ctx.MININDEX())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + } else { + + if (ctx.MAX() != null) { + builder.append(QueryTokens.token(ctx.MAX())); + } + if (ctx.MIN() != null) { + builder.append(QueryTokens.token(ctx.MIN())); + } + if (ctx.SUM() != null) { + builder.append(QueryTokens.token(ctx.SUM())); + } + if (ctx.AVG() != null) { + builder.append(QueryTokens.token(ctx.AVG())); + } + + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.indicesKeysQuantifier())); + builder.append(TOKEN_OPEN_PAREN); + + if (ctx.path() != null) { + builder.append(visit(ctx.path())); + } + + builder.append(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); + } + + return builder; + } + + @Override + public QueryTokenStream visitCollectionFunctionMisuse(HqlParser.CollectionFunctionMisuseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append( + visit(ctx.elementsValuesQuantifier() != null ? ctx.elementsValuesQuantifier() : ctx.indicesKeysQuantifier())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitAggregateFunction(HqlParser.AggregateFunctionContext ctx) { + + if (ctx.everyFunction() != null) { + return visit(ctx.everyFunction()); + } + + if (ctx.anyFunction() != null) { + return visit(ctx.anyFunction()); + } + + return visit(ctx.listaggFunction()); + } + + @Override + public QueryTokenStream visitEveryAllQuantifier(HqlParser.EveryAllQuantifierContext ctx) { + + if (ctx.EVERY() != null) { + return QueryRenderer.from(QueryTokens.token(ctx.EVERY())); + } + + return QueryRenderer.from(QueryTokens.token(ctx.ALL())); + } + + @Override + public QueryTokenStream visitAnySomeQuantifier(HqlParser.AnySomeQuantifierContext ctx) { + + if (ctx.ANY() != null) { + return QueryRenderer.from(QueryTokens.token(ctx.ANY())); + } + + return QueryRenderer.from(QueryTokens.token(ctx.SOME())); + } + + @Override + public QueryTokenStream visitListaggFunction(HqlParser.ListaggFunctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.LISTAGG())); + builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); + + if (ctx.DISTINCT() != null) { + builder.append(QueryTokens.expression(ctx.DISTINCT())); + } + + builder.appendInline(visit(ctx.expressionOrPredicate(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.expressionOrPredicate(1))); + + if (ctx.onOverflowClause() != null) { + builder.appendExpression(visit(ctx.onOverflowClause())); + } + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); + + if (ctx.withinGroupClause() != null) { + builder.appendExpression(visit(ctx.withinGroupClause())); + } + + if (ctx.filterClause() != null) { + builder.appendExpression(visit(ctx.filterClause())); + } + + if (ctx.overClause() != null) { + builder.appendExpression(visit(ctx.overClause())); + } + + return builder; + } + + @Override + public QueryTokenStream visitOnOverflowClause(HqlParser.OnOverflowClauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.ON())); + builder.append(QueryTokens.expression(ctx.OVERFLOW())); + + if (ctx.ERROR() != null) { + builder.append(QueryTokens.expression(ctx.ERROR())); + } else { + + builder.append(QueryTokens.expression(ctx.TRUNCATE())); + + if (ctx.expression() != null) { + builder.appendExpression(visit(ctx.expression())); + } + + if (ctx.WITH() != null) { + builder.append(QueryTokens.expression(ctx.WITH())); + } + + if (ctx.WITHOUT() != null) { + builder.append(QueryTokens.expression(ctx.WITHOUT())); + } + + if (ctx.COUNT() != null) { + builder.append(QueryTokens.expression(ctx.COUNT())); + } + } + + return builder; + } + + @Override + public QueryTokenStream visitWithinGroupClause(HqlParser.WithinGroupClauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.WITHIN())); + builder.append(QueryTokens.expression(ctx.GROUP())); + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.orderByClause())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitNullsClause(HqlParser.NullsClauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.IGNORE() != null) { + builder.append(QueryTokens.expression(ctx.IGNORE())); + } else { + builder.append(QueryTokens.expression(ctx.RESPECT())); + } + + builder.append(QueryTokens.expression(ctx.NULLS())); + + return builder; + } + + @Override + public QueryTokenStream visitNthSideClause(HqlParser.NthSideClauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.FROM())); + + if (ctx.FIRST() != null) { + builder.append(QueryTokens.expression(ctx.FIRST())); + } else { + builder.append(QueryTokens.expression(ctx.LAST())); + } + + return builder; + } + + @Override + public QueryTokenStream visitFrameStart(HqlParser.FrameStartContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT() != null) { + + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.ROW())); + } else if (ctx.UNBOUNDED() != null) { + builder.append(QueryTokens.expression(ctx.UNBOUNDED())); + builder.append(QueryTokens.expression(ctx.PRECEDING())); + } else { + + builder.appendExpression(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.PRECEDING() != null ? ctx.PRECEDING() : ctx.FOLLOWING())); + } + + return builder; + + } + + @Override + public QueryTokenStream visitFrameEnd(HqlParser.FrameEndContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.CURRENT() != null) { + + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.ROW())); + } else if (ctx.UNBOUNDED() != null) { + builder.append(QueryTokens.expression(ctx.UNBOUNDED())); + builder.append(QueryTokens.expression(ctx.FOLLOWING())); + } else { + + builder.appendExpression(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.PRECEDING() != null ? ctx.PRECEDING() : ctx.FOLLOWING())); + } + + return builder; + } + + @Override + public QueryTokenStream visitFrameExclusion(HqlParser.FrameExclusionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.EXCLUDE())); + + if (ctx.CURRENT() != null) { + builder.append(QueryTokens.expression(ctx.CURRENT())); + builder.append(QueryTokens.expression(ctx.ROW())); + } else if (ctx.GROUP() != null) { + builder.append(QueryTokens.expression(ctx.GROUP())); + } else if (ctx.TIES() != null) { + builder.append(QueryTokens.expression(ctx.TIES())); + } else { + builder.append(QueryTokens.expression(ctx.NO())); + builder.append(QueryTokens.expression(ctx.OTHERS())); + } + + return builder; + } + + @Override + public QueryTokenStream visitCollectionQuantifier(HqlParser.CollectionQuantifierContext ctx) { + + if (ctx.elementsValuesQuantifier() != null) { + return visit(ctx.elementsValuesQuantifier()); + } + + return visit(ctx.indicesKeysQuantifier()); + } + + @Override + public QueryTokenStream visitElementsValuesQuantifier(HqlParser.ElementsValuesQuantifierContext ctx) { + return QueryRenderer.from(QueryTokens.token(ctx.ELEMENTS() != null ? ctx.ELEMENTS() : ctx.VALUES())); + } + + @Override + public QueryTokenStream visitIndicesKeysQuantifier(HqlParser.IndicesKeysQuantifierContext ctx) { + return QueryRenderer.from(QueryTokens.token(ctx.INDICES() != null ? ctx.INDICES() : ctx.KEYS())); + } + + @Override + public QueryTokenStream visitGeneralPathExpression(HqlParser.GeneralPathExpressionContext ctx) { + return visit(ctx.generalPathFragment()); } @Override @@ -1550,31 +2537,38 @@ class HqlQueryRenderer extends HqlBaseVisitor { QueryRendererBuilder builder = QueryRenderer.builder(); QueryRendererBuilder nested = QueryRenderer.builder(); - nested.append(visit(ctx.functionName())); + nested.append(visit(ctx.genericFunctionName())); nested.append(TOKEN_OPEN_PAREN); - if (ctx.functionArguments() != null) { - nested.appendInline(visit(ctx.functionArguments())); + if (ctx.genericFunctionArguments() != null) { + nested.appendInline(visit(ctx.genericFunctionArguments())); } else if (ctx.ASTERISK() != null) { nested.append(QueryTokens.token(ctx.ASTERISK())); } nested.append(TOKEN_CLOSE_PAREN); - builder.append(nested); if (ctx.pathContinutation() != null) { - builder.appendInline(visit(ctx.pathContinutation())); + builder.append(visit(ctx.pathContinutation())); + } + + if (ctx.nthSideClause() != null) { + builder.appendExpression(visit(ctx.nthSideClause())); + } + + if (ctx.nullsClause() != null) { + builder.appendExpression(visit(ctx.nullsClause())); + } + + if (ctx.withinGroupClause() != null) { + builder.appendExpression(visit(ctx.withinGroupClause())); } if (ctx.filterClause() != null) { builder.appendExpression(visit(ctx.filterClause())); } - if (ctx.withinGroup() != null) { - builder.appendExpression(visit(ctx.withinGroup())); - } - if (ctx.overClause() != null) { builder.appendExpression(visit(ctx.overClause())); } @@ -1582,63 +2576,6 @@ class HqlQueryRenderer extends HqlBaseVisitor { return builder; } - @Override - public QueryTokenStream visitFunctionWithSubquery(HqlParser.FunctionWithSubqueryContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.functionName())); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.subquery())); - builder.append(TOKEN_CLOSE_PAREN); - - return builder; - } - - @Override - public QueryTokenStream visitCastFunctionInvocation(HqlParser.CastFunctionInvocationContext ctx) { - return visit(ctx.castFunction()); - } - - @Override - public QueryTokenStream visitExtractFunctionInvocation(HqlParser.ExtractFunctionInvocationContext ctx) { - return visit(ctx.extractFunction()); - } - - @Override - public QueryTokenStream visitTrimFunctionInvocation(HqlParser.TrimFunctionInvocationContext ctx) { - return visit(ctx.trimFunction()); - } - - @Override - public QueryTokenStream visitEveryFunctionInvocation(HqlParser.EveryFunctionInvocationContext ctx) { - return visit(ctx.everyFunction()); - } - - @Override - public QueryTokenStream visitAnyFunctionInvocation(HqlParser.AnyFunctionInvocationContext ctx) { - return visit(ctx.anyFunction()); - } - - @Override - public QueryTokenStream visitTreatedPathInvocation(HqlParser.TreatedPathInvocationContext ctx) { - return visit(ctx.treatedPath()); - } - - @Override - public QueryTokenStream visitFunctionArguments(HqlParser.FunctionArgumentsContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - if (ctx.DISTINCT() != null) { - builder.append(QueryTokens.expression(ctx.DISTINCT())); - } - - builder.append(QueryTokenStream.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); - - return builder; - } - @Override public QueryTokenStream visitFilterClause(HqlParser.FilterClauseContext ctx) { @@ -1652,20 +2589,6 @@ class HqlQueryRenderer extends HqlBaseVisitor { return builder; } - @Override - public QueryTokenStream visitWithinGroup(HqlParser.WithinGroupContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.WITHIN())); - builder.append(QueryTokens.expression(ctx.GROUP())); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.orderByClause())); - builder.append(TOKEN_CLOSE_PAREN); - - return builder; - } - @Override public QueryTokenStream visitOverClause(HqlParser.OverClauseContext ctx) { @@ -1742,140 +2665,6 @@ class HqlQueryRenderer extends HqlBaseVisitor { return builder; } - @Override - public QueryTokenStream visitUnboundedPrecedingFrameStart(HqlParser.UnboundedPrecedingFrameStartContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.UNBOUNDED())); - builder.append(QueryTokens.expression(ctx.PRECEDING())); - - return builder; - } - - @Override - public QueryTokenStream visitExpressionPrecedingFrameStart(HqlParser.ExpressionPrecedingFrameStartContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.expression())); - builder.append(QueryTokens.expression(ctx.PRECEDING())); - - return builder; - } - - @Override - public QueryTokenStream visitCurrentRowFrameStart(HqlParser.CurrentRowFrameStartContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.CURRENT())); - builder.append(QueryTokens.expression(ctx.ROW())); - - return builder; - } - - @Override - public QueryTokenStream visitExpressionFollowingFrameStart(HqlParser.ExpressionFollowingFrameStartContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.expression())); - builder.append(QueryTokens.expression(ctx.FOLLOWING())); - - return builder; - } - - @Override - public QueryTokenStream visitCurrentRowFrameExclusion(HqlParser.CurrentRowFrameExclusionContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.EXCLUDE())); - builder.append(QueryTokens.expression(ctx.CURRENT())); - builder.append(QueryTokens.expression(ctx.ROW())); - - return builder; - } - - @Override - public QueryTokenStream visitGroupFrameExclusion(HqlParser.GroupFrameExclusionContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.EXCLUDE())); - builder.append(QueryTokens.expression(ctx.GROUP())); - - return builder; - } - - @Override - public QueryTokenStream visitTiesFrameExclusion(HqlParser.TiesFrameExclusionContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.EXCLUDE())); - builder.append(QueryTokens.expression(ctx.TIES())); - - return builder; - } - - @Override - public QueryTokenStream visitNoOthersFrameExclusion(HqlParser.NoOthersFrameExclusionContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.EXCLUDE())); - builder.append(QueryTokens.expression(ctx.NO())); - builder.append(QueryTokens.expression(ctx.OTHERS())); - - return builder; - } - - @Override - public QueryTokenStream visitExpressionPrecedingFrameEnd(HqlParser.ExpressionPrecedingFrameEndContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.expression())); - builder.append(QueryTokens.expression(ctx.PRECEDING())); - - return builder; - } - - @Override - public QueryTokenStream visitCurrentRowFrameEnd(HqlParser.CurrentRowFrameEndContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.CURRENT())); - builder.append(QueryTokens.expression(ctx.ROW())); - - return builder; - } - - @Override - public QueryTokenStream visitExpressionFollowingFrameEnd(HqlParser.ExpressionFollowingFrameEndContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.appendExpression(visit(ctx.expression())); - builder.append(QueryTokens.expression(ctx.FOLLOWING())); - - return builder; - } - - @Override - public QueryTokenStream visitUnboundedFollowingFrameEnd(HqlParser.UnboundedFollowingFrameEndContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - builder.append(QueryTokens.expression(ctx.UNBOUNDED())); - builder.append(QueryTokens.expression(ctx.FOLLOWING())); - - return builder; - } - @Override public QueryTokenStream visitCastFunction(HqlParser.CastFunctionContext ctx) { @@ -1941,23 +2730,49 @@ class HqlQueryRenderer extends HqlBaseVisitor { QueryRendererBuilder nested = QueryRenderer.builder(); - nested.appendExpression(visit(ctx.expression(0))); + nested.appendExpression(visit(ctx.extractField())); nested.append(QueryTokens.expression(ctx.FROM())); - nested.append(visit(ctx.expression(1))); + nested.append(visit(ctx.expression())); builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); - } else if (ctx.dateTimeFunction() != null) { + } else if (ctx.datetimeField() != null) { - builder.append(visit(ctx.dateTimeFunction())); + builder.append(visit(ctx.datetimeField())); builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.expression(0))); + builder.appendInline(visit(ctx.expression())); builder.append(TOKEN_CLOSE_PAREN); } return builder; } + @Override + public QueryTokenStream visitExtractField(HqlParser.ExtractFieldContext ctx) { + + if (ctx.datetimeField() != null) { + return visit(ctx.datetimeField()); + } + + if (ctx.dayField() != null) { + return visit(ctx.dayField()); + } + + if (ctx.weekField() != null) { + return visit(ctx.weekField()); + } + + if (ctx.timeZoneField() != null) { + return visit(ctx.timeZoneField()); + } + + if (ctx.dateOrTimeField() != null) { + return visit(ctx.dateOrTimeField()); + } + + return QueryRenderer.builder(); + } + @Override public QueryTokenStream visitTrimFunction(HqlParser.TrimFunctionContext ctx) { @@ -1966,31 +2781,51 @@ class HqlQueryRenderer extends HqlBaseVisitor { builder.append(QueryTokens.token(ctx.TRIM())); builder.append(TOKEN_OPEN_PAREN); - if (ctx.LEADING() != null) { - builder.append(QueryTokens.expression(ctx.LEADING())); - } else if (ctx.TRAILING() != null) { - builder.append(QueryTokens.expression(ctx.TRAILING())); - } else if (ctx.BOTH() != null) { - builder.append(QueryTokens.expression(ctx.BOTH())); + if (ctx.trimSpecification() != null) { + builder.appendExpression(visit(ctx.trimSpecification())); } - if (ctx.stringLiteral() != null) { - builder.append(visit(ctx.stringLiteral())); + if (ctx.trimCharacter() != null) { + builder.appendExpression(visit(ctx.trimCharacter())); } if (ctx.FROM() != null) { builder.append(QueryTokens.expression(ctx.FROM())); } - builder.appendInline(visit(ctx.expression())); + if (ctx.expression() != null) { + builder.append(visit(ctx.expression())); + } + builder.append(TOKEN_CLOSE_PAREN); return builder; } @Override - public QueryTokenStream visitDateTimeFunction(HqlParser.DateTimeFunctionContext ctx) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.d)); + public QueryTokenStream visitTrimSpecification(HqlParser.TrimSpecificationContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.BOTH() != null) { + builder.append(QueryTokens.expression(ctx.BOTH())); + } else if (ctx.LEADING() != null) { + builder.append(QueryTokens.expression(ctx.LEADING())); + } else if (ctx.TRAILING() != null) { + builder.append(QueryTokens.expression(ctx.TRAILING())); + } + + return builder.build(); + } + + @Override + public QueryTokenStream visitTrimCharacter(HqlParser.TrimCharacterContext ctx) { + + if (ctx.stringLiteral() != null) { + return visit(ctx.stringLiteral()); + } + + return visit(ctx.parameter()); } @Override @@ -1998,25 +2833,32 @@ class HqlQueryRenderer extends HqlBaseVisitor { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.every)); - - if (ctx.ELEMENTS() != null) { - builder.append(QueryTokens.expression(ctx.ELEMENTS())); - } else if (ctx.INDICES() != null) { - builder.append(QueryTokens.expression(ctx.INDICES())); - } - - builder.append(TOKEN_OPEN_PAREN); + builder.appendExpression(visit(ctx.everyAllQuantifier())); if (ctx.predicate() != null) { - builder.append(visit(ctx.predicate())); - } else if (ctx.subquery() != null) { - builder.append(visit(ctx.subquery())); - } else if (ctx.simplePath() != null) { - builder.append(visit(ctx.simplePath())); - } + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.predicate())); + builder.append(TOKEN_CLOSE_PAREN); - builder.append(TOKEN_CLOSE_PAREN); + if (ctx.filterClause() != null) { + builder.appendExpression(visit(ctx.filterClause())); + } + + if (ctx.overClause() != null) { + builder.appendExpression(visit(ctx.overClause())); + } + } else if (ctx.subquery() != null) { + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); + } else { + + builder.appendExpression(visit(ctx.collectionQuantifier())); + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.simplePath())); + builder.append(TOKEN_CLOSE_PAREN); + } return builder; } @@ -2026,25 +2868,32 @@ class HqlQueryRenderer extends HqlBaseVisitor { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.any)); - - if (ctx.ELEMENTS() != null) { - builder.append(QueryTokens.expression(ctx.ELEMENTS())); - } else if (ctx.INDICES() != null) { - builder.append(QueryTokens.expression(ctx.INDICES())); - } - - builder.append(TOKEN_OPEN_PAREN); + builder.appendExpression(visit(ctx.anySomeQuantifier())); if (ctx.predicate() != null) { - builder.append(visit(ctx.predicate())); - } else if (ctx.subquery() != null) { - builder.append(visit(ctx.subquery())); - } else if (ctx.simplePath() != null) { - builder.append(visit(ctx.simplePath())); - } + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.predicate())); + builder.append(TOKEN_CLOSE_PAREN); - builder.append(TOKEN_CLOSE_PAREN); + if (ctx.filterClause() != null) { + builder.appendExpression(visit(ctx.filterClause())); + } + + if (ctx.overClause() != null) { + builder.appendExpression(visit(ctx.overClause())); + } + } else if (ctx.subquery() != null) { + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); + } else { + + builder.appendExpression(visit(ctx.collectionQuantifier())); + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.simplePath())); + builder.append(TOKEN_CLOSE_PAREN); + } return builder; } @@ -2529,16 +3378,6 @@ class HqlQueryRenderer extends HqlBaseVisitor { } } - @Override - public QueryTokenStream visitCharacter(HqlParser.CharacterContext ctx) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.CHARACTER())); - } - - @Override - public QueryTokenStream visitFunctionName(HqlParser.FunctionNameContext ctx) { - return QueryTokenStream.concat(ctx.reservedWord(), this::visit, TOKEN_DOT); - } - @Override public QueryTokenStream visitReservedWord(HqlParser.ReservedWordContext ctx) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java index 8a23e279c..4c25ec66f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java @@ -37,6 +37,7 @@ import org.junit.jupiter.params.provider.ValueSource; * * @author Greg Turnquist * @author Christoph Strobl + * @author Mark Paluch * @since 3.1 */ class HqlQueryRendererTests { @@ -1551,9 +1552,11 @@ class HqlQueryRendererTests { assertQuery("SELECT o FROM Order o WHERE CAST(:userId AS java.util.UUID) IS NULL OR o.user.id = :userId"); } - @Test // GH-3025 - void durationLiteralsShouldWork() { - assertQuery("SELECT ce.id FROM CalendarEvent ce WHERE (ce.endDate - ce.startDate) > 5 MINUTE"); + @ParameterizedTest // GH-3025 + @ValueSource(strings = { "YEAR", "MONTH", "DAY", "WEEK", "QUARTER", "HOUR", "MINUTE", "SECOND", "NANOSECOND", + "NANOSECOND", "EPOCH" }) + void durationLiteralsShouldWork(String dtField) { + assertQuery("SELECT ce.id FROM CalendarEvent ce WHERE (ce.endDate - ce.startDate) > 5 %s".formatted(dtField)); } @Test // GH-3025 diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java index 3e459fa26..0dff2c328 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java @@ -21,6 +21,9 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + import org.springframework.data.jpa.repository.query.QueryRenderer.TokenRenderer; /** @@ -31,6 +34,7 @@ import org.springframework.data.jpa.repository.query.QueryRenderer.TokenRenderer * IMPORTANT: Purely verifies the parser without any transformations. * * @author Greg Turnquist + * @author Mark Paluch * @since 3.1 */ class HqlSpecificationTests { @@ -331,6 +335,177 @@ class HqlSpecificationTests { """); } + @Test // GH-3689 + void generic() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE FOO(x).bar RESPECT NULLS + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE FOO(x).bar IGNORE NULLS + """); + } + + @Test // GH-3689 + void size() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE SIZE(x) > 1 + """); + } + + @Test // GH-3689 + void collectionAggregate() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE MAXELEMENT(foo) > MINELEMENT(bar) + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE MININDEX(foo) > MAXINDEX(bar) + """); + } + + @Test // GH-3689 + void trunc() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE TRUNC(x) = TRUNCATE(y) + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE TRUNC(e, 'foo') = TRUNCATE(e, 'bar') + """); + } + + @ParameterizedTest // GH-3689 + @ValueSource(strings = { "YYYY", "MONTH", "DAY", "WEEK", "QUARTER", "HOUR", "MINUTE", "SECOND", "NANOSECOND", + "NANOSECOND", "EPOCH" }) + void trunc(String truncation) { + + assertQuery(""" + SELECT e FROM Employee e + WHERE TRUNC(e, %1$s) = TRUNCATE(e, %1$s) + """.formatted(truncation)); + } + + @Test // GH-3689 + void format() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE FORMAT(x AS 'foo') = FORMAT(x AS 'bar') + """); + } + + @Test // GH-3689 + void collate() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE COLLATE(x AS foo) = COLLATE(x AS foo.bar) + """); + } + + @Test // GH-3689 + void substring() { + + assertQuery("select substring(c.number, 1, 2) " + // + "from Call c"); + + assertQuery("select substring(c.number, 1) " + // + "from Call c"); + + assertQuery("select substring(c.number FROM 1 FOR 2) " + // + "from Call c"); + + assertQuery("select substring(c.number FROM 1) " + // + "from Call c"); + } + + @Test // GH-3689 + void overlay() { + + assertQuery("select OVERLAY(c.number PLACING 1 FROM 2) " + // + "from Call c "); + + assertQuery("select OVERLAY(p.number PLACING 1 FROM 2 FOR 3) " + // + "from Call c "); + } + + @Test // GH-3689 + void pad() { + + assertQuery("select PAD(c.number WITH 1 LEADING) " + // + "from Call c "); + + assertQuery("select PAD(c.number WITH 1 TRAILING) " + // + "from Call c "); + + assertQuery("select PAD(c.number WITH 1 LEADING '0') " + // + "from Call c "); + + assertQuery("select PAD(c.number WITH 1 TRAILING '0') " + // + "from Call c "); + } + + @Test // GH-3689 + void position() { + + assertQuery("select POSITION(c.number IN 'foo') " + // + "from Call c "); + } + + @Test // GH-3689 + void currentDateFunctions() { + + assertQuery("select CURRENT DATE, CURRENT_DATE() " + // + "from Call c "); + + assertQuery("select CURRENT TIME, CURRENT_TIME() " + // + "from Call c "); + + assertQuery("select CURRENT TIMESTAMP, CURRENT_TIMESTAMP() " + // + "from Call c "); + + assertQuery("select INSTANT, CURRENT_INSTANT() " + // + "from Call c "); + + assertQuery("select LOCAL DATE, LOCAL_DATE() " + // + "from Call c "); + + assertQuery("select LOCAL TIME, LOCAL_TIME() " + // + "from Call c "); + + assertQuery("select LOCAL DATETIME, LOCAL_DATETIME() " + // + "from Call c "); + + assertQuery("select OFFSET DATETIME, OFFSET_DATETIME() " + // + "from Call c "); + } + + @Test // GH-3689 + void cube() { + + assertQuery("select CUBE(foo), CUBE(foo, bar) " + // + "from Call c "); + } + + @Test // GH-3689 + void rollup() { + + assertQuery("select ROLLUP(foo), ROLLUP(foo, bar) " + // + "from Call c "); + } + @Test void pathExpressionsNamedParametersExample() { @@ -383,6 +558,80 @@ class HqlSpecificationTests { """); } + @Test // GH-3689 + void everyAll() { + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE EVERY (SELECT spouseEmp + FROM Employee spouseEmp) > 1 + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ALL (SELECT spouseEmp + FROM Employee spouseEmp) > 1 + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ALL (foo > 1) OVER (PARTITION BY bar) + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ALL VALUES (foo) > 1 + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ALL ELEMENTS (foo) > 1 + """); + } + + @Test // GH-3689 + void anySome() { + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ANY (SELECT spouseEmp + FROM Employee spouseEmp) > 1 + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE SOME (SELECT spouseEmp + FROM Employee spouseEmp) > 1 + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ANY (foo > 1) OVER (PARTITION BY bar) + """); + + assertQuery(""" + SELECT DISTINCT emp + FROM Employee emp + WHERE ANY VALUES (foo) > 1 + """); + } + + @Test // GH-3689 + void listAgg() { + + assertQuery("select listagg(p.number, ', ') within group (order by p.type, p.number) " + // + "from Phone p " + // + "group by p.person"); + } + @Test void allExample() { @@ -1119,9 +1368,6 @@ class HqlSpecificationTests { assertQuery("select concat(p.number, ' : ', cast(c.duration as string)) " + // "from Call c " + // "join c.phone p"); - assertQuery("select substring(p.number, 1, 2) " + // - "from Call c " + // - "join c.phone p"); assertQuery("select upper(p.name) " + // "from Person p "); assertQuery("select lower(p.name) " + // @@ -1450,9 +1696,6 @@ class HqlSpecificationTests { "from Call c " + // "join c.phone p " + // "group by p.number"); - assertQuery("select listagg(p.number, ', ') within group (order by p.type, p.number) " + // - "from Phone p " + // - "group by p.person"); assertQuery("select sum(c.duration) " + // "from Call c "); assertQuery("select p.name, sum(c.duration) " + //