diff --git a/pom.xml b/pom.xml index 1847f4d2c4..c2cc0e49e3 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 3.5.0-SNAPSHOT + 3.5.0-GH-3689-SNAPSHOT pom Spring Data JPA Parent diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index 5dcbbb69fd..2d26e4ffe0 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 3.5.0-SNAPSHOT + 3.5.0-GH-3689-SNAPSHOT org.springframework.data spring-data-jpa-parent - 3.5.0-SNAPSHOT + 3.5.0-GH-3689-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index 38a234cb71..6273bff393 100644 --- a/spring-data-jpa-distribution/pom.xml +++ b/spring-data-jpa-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-jpa-parent - 3.5.0-SNAPSHOT + 3.5.0-GH-3689-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index c6d9301c02..825f122291 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jpa - 3.5.0-SNAPSHOT + 3.5.0-GH-3689-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-jpa-parent - 3.5.0-SNAPSHOT + 3.5.0-GH-3689-SNAPSHOT ../pom.xml 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 54a93e9ebf..728d3fe7b2 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 @@ -147,7 +147,7 @@ deleteStatement // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-insert insertStatement - : INSERT INTO? targetEntity targetFields (queryExpression | valuesList) + : INSERT INTO? targetEntity targetFields (queryExpression | valuesList) conflictClause? ; // Already defined underneath updateStatement @@ -167,12 +167,25 @@ values : '(' expression (',' expression)* ')' ; -instantiation - : NEW instantiationTarget '(' instantiationArguments ')' +/** + * a 'conflict' clause in an 'insert' statement + */ +conflictClause + : ON CONFLICT conflictTarget? DO conflictAction + ; + +conflictTarget + : ON CONSTRAINT identifier + | '(' simplePath (',' simplePath)* ')' + ; + +conflictAction + : NOTHING + | UPDATE setClause whereClause? ; -alias - : AS? identifier // spec says IDENTIFIER but clearly does NOT mean a reserved word +instantiation + : NEW instantiationTarget '(' instantiationArguments ')' ; groupedItem @@ -337,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 @@ -351,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 @@ -399,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 @@ -450,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 ')' +/** + * The 'cast()' function for typecasting + */ +castFunction + : CAST '(' expression AS castTarget ')' ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-orderedset -withinGroup - : WITHIN GROUP '(' orderByClause ')' +/** + * The target type for a typecast: a typename, together with length or precision/scale + */ +castTarget + : castTargetType ('(' INTEGER_LITERAL (',' INTEGER_LITERAL)? ')')? ; -overClause - : OVER '(' partitionClause? orderByClause? frameClause? ')' +/** + * 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() ); })* ; -partitionClause - : PARTITION BY expression (',' 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)? ')' ; -frameClause - : (RANGE|ROWS|GROUPS) frameStart frameExclusion? - | (RANGE|ROWS|GROUPS) BETWEEN frameStart AND frameEnd frameExclusion? +substringFunctionStartArgument + : expression ; -frameStart - : UNBOUNDED PRECEDING # UnboundedPrecedingFrameStart - | expression PRECEDING # ExpressionPrecedingFrameStart - | CURRENT ROW # CurrentRowFrameStart - | expression FOLLOWING # ExpressionFollowingFrameStart +substringFunctionLengthArgument + : expression ; -frameExclusion - : EXCLUDE CURRENT ROW # CurrentRowFrameExclusion - | EXCLUDE GROUP # GroupFrameExclusion - | EXCLUDE TIES # TiesFrameExclusion - | EXCLUDE NO OTHERS # NoOthersFrameExclusion +/** + * The ANSI SQL-style 'trim()' function + */ +trimFunction + : TRIM '(' trimSpecification? trimCharacter? FROM? expression ')' ; -frameEnd - : expression PRECEDING # ExpressionPrecedingFrameEnd - | CURRENT ROW # CurrentRowFrameEnd - | expression FOLLOWING # ExpressionFollowingFrameEnd - | UNBOUNDED FOLLOWING # UnboundedFollowingFrameEnd +trimSpecification + : LEADING + | TRAILING + | BOTH ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-functions -castFunction - : CAST '(' expression AS castTarget ')' +trimCharacter + : stringLiteral + | parameter ; -castTarget - : castTargetType ('(' INTEGER_LITERAL (',' INTEGER_LITERAL)? ')')? +/** + * A 'pad()' function inspired by 'trim()' + */ +padFunction + : PAD '(' expression WITH padLength padSpecification padCharacter? ')' ; -castTargetType - returns [String fullTargetName] - : (i=identifier { $fullTargetName = _localctx.i.getText(); }) ('.' c=identifier { $fullTargetName += ("." + _localctx.c.getText() ); })* +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 '(' expression FROM expression ')' - | dateTimeFunction '(' expression ')' + : EXTRACT '(' extractField FROM expression ')' + | datetimeField '(' expression ')' ; -trimFunction - : TRIM '(' (LEADING | TRAILING | BOTH)? stringLiteral? FROM? expression ')' +/** + * The 'trunc()' function for truncating both numeric and datetime values + */ +truncFunction + : (TRUNC | TRUNCATE) '(' expression (',' (datetimeField | expression))? ')' ; -dateTimeFunction - : d=(YEAR - | MONTH - | DAY - | WEEK - | QUARTER - | HOUR - | MINUTE - | SECOND - | NANOSECOND - | EPOCH) +/** + * 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 @@ -591,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 @@ -674,10 +1115,6 @@ identifier : reservedWord ; -character - : CHARACTER - ; - functionName : reservedWord ('.' reservedWord)* ; @@ -921,7 +1358,11 @@ CURRENT_DATE : C U R R E N T '_' D A T E; CURRENT_INSTANT : C U R R E N T '_' I N S T A N T; CURRENT_TIME : C U R R E N T '_' T I M E; CURRENT_TIMESTAMP : C U R R E N T '_' T I M E S T A M P; +CONFLICT : C O N F L I C T; +CONSTRAINT : C O N S T R A I N T; +COLUMN : C O L U M N; CYCLE : C Y C L E; +DO : D O; DATE : D A T E; DATETIME : D A T E T I M E ; DAY : D A Y; @@ -977,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; @@ -1009,6 +1451,7 @@ NEW : N E W; NEXT : N E X T; NO : N O; NOT : N O T; +NOTHING : N O T H I N G; NULL : N U L L; NULLS : N U L L S; OBJECT : O B J E C T; @@ -1092,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 9976347f1d..2ef49b95ff 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 @@ -24,6 +24,7 @@ import org.antlr.v4.runtime.tree.ParseTree; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.util.ObjectUtils; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders an HQL query without making any changes. @@ -545,6 +546,10 @@ public QueryTokenStream visitInsertStatement(HqlParser.InsertStatementContext ct builder.appendExpression(visit(ctx.valuesList())); } + if (ctx.conflictClause() != null) { + builder.appendExpression(visit(ctx.conflictClause())); + } + return builder; } @@ -584,29 +589,75 @@ public QueryTokenStream visitValues(HqlParser.ValuesContext ctx) { } @Override - public QueryTokenStream visitInstantiation(HqlParser.InstantiationContext ctx) { + public QueryTokenStream visitConflictClause(HqlParser.ConflictClauseContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.NEW())); - builder.append(visit(ctx.instantiationTarget())); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.instantiationArguments())); - builder.append(TOKEN_CLOSE_PAREN); + builder.append(QueryTokens.expression(ctx.ON())); + builder.append(QueryTokens.expression(ctx.CONFLICT())); + + if (ctx.conflictTarget() != null) { + builder.appendExpression(visit(ctx.conflictTarget())); + } + + builder.append(QueryTokens.expression(ctx.DO())); + builder.appendExpression(visit(ctx.conflictAction())); return builder; } @Override - public QueryTokenStream visitAlias(HqlParser.AliasContext ctx) { + public QueryTokenStream visitConflictTarget(HqlParser.ConflictTargetContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.AS() != null) { - builder.append(QueryTokens.expression(ctx.AS())); + if (ctx.identifier() != null) { + + builder.append(QueryTokens.expression(ctx.ON())); + builder.append(QueryTokens.expression(ctx.CONSTRAINT())); + builder.appendExpression(visit(ctx.identifier())); } - builder.append(visit(ctx.identifier())); + if (!ObjectUtils.isEmpty(ctx.simplePath())) { + + builder.append(TOKEN_OPEN_PAREN); + builder.append(QueryTokenStream.concat(ctx.simplePath(), this::visit, TOKEN_COMMA)); + + builder.append(TOKEN_CLOSE_PAREN); + } + + return builder; + } + + @Override + public QueryTokenStream visitConflictAction(HqlParser.ConflictActionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.NOTHING() != null) { + builder.append(QueryTokens.expression(ctx.NOTHING())); + } else { + builder.append(QueryTokens.expression(ctx.UPDATE())); + builder.appendExpression(visit(ctx.setClause())); + + if (ctx.whereClause() != null) { + builder.appendExpression(visit(ctx.whereClause())); + } + } + + return builder; + } + + @Override + public QueryTokenStream visitInstantiation(HqlParser.InstantiationContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.NEW())); + builder.append(visit(ctx.instantiationTarget())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.instantiationArguments())); + builder.append(TOKEN_CLOSE_PAREN); return builder; } @@ -1081,30 +1132,105 @@ public QueryTokenStream visitDateTimeLiteral(HqlParser.DateTimeLiteralContext ct 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) { @@ -1267,7 +1393,7 @@ public QueryTokenStream visitToDurationExpression(HqlParser.ToDurationExpression QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(visit(ctx.expression())); - builder.append(visit(ctx.datetimeField())); + builder.appendExpression(visit(ctx.datetimeField())); return builder; } @@ -1279,7 +1405,7 @@ public QueryTokenStream visitFromDurationExpression(HqlParser.FromDurationExpres builder.append(visit(ctx.expression())); builder.append(QueryTokens.expression(ctx.BY())); - builder.append(visit(ctx.datetimeField())); + builder.appendExpression(visit(ctx.datetimeField())); return builder; } @@ -1305,271 +1431,1147 @@ public QueryTokenStream visitFunctionExpression(HqlParser.FunctionExpressionCont } @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) { - - if (ctx.identifier() != null) { - return visit(ctx.identifier()); - } else if (ctx.simplePath() != null) { - return visit(ctx.simplePath()); - } else { - return QueryTokenStream.empty(); - } + public QueryTokenStream visitAggregateFunctionInvocation(HqlParser.AggregateFunctionInvocationContext ctx) { + return visit(ctx.aggregateFunction()); } @Override - public QueryTokenStream visitPath(HqlParser.PathContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); + public QueryTokenStream visitCollectionSizeFunctionInvocation(HqlParser.CollectionSizeFunctionInvocationContext ctx) { + return visit(ctx.collectionSizeFunction()); + } - if (ctx.treatedPath() != null) { + @Override + public QueryTokenStream visitCollectionAggregateFunctionInvocation( + HqlParser.CollectionAggregateFunctionInvocationContext ctx) { + return visit(ctx.collectionAggregateFunction()); + } - builder.append(visit(ctx.treatedPath())); + @Override + public QueryTokenStream visitCollectionFunctionMisuseInvocation( + HqlParser.CollectionFunctionMisuseInvocationContext ctx) { + return visit(ctx.collectionFunctionMisuse()); + } - if (ctx.pathContinutation() != null) { - builder.append(visit(ctx.pathContinutation())); - } - } else if (ctx.generalPathFragment() != null) { - builder.append(visit(ctx.generalPathFragment())); - } + @Override + public QueryTokenStream visitJpaNonstandardFunctionInvocation(HqlParser.JpaNonstandardFunctionInvocationContext ctx) { + return visit(ctx.jpaNonstandardFunction()); + } - return builder; + @Override + public QueryTokenStream visitColumnFunctionInvocation(HqlParser.ColumnFunctionInvocationContext ctx) { + return visit(ctx.columnFunction()); } @Override - public QueryTokenStream visitGeneralPathFragment(HqlParser.GeneralPathFragmentContext ctx) { + public QueryTokenStream visitGenericFunctionInvocation(HqlParser.GenericFunctionInvocationContext ctx) { + return visit(ctx.genericFunction()); + } - QueryRendererBuilder builder = QueryRenderer.builder(); + @Override + public QueryTokenStream visitStandardFunction(HqlParser.StandardFunctionContext ctx) { - builder.append(visit(ctx.simplePath())); + if (ctx.castFunction() != null) { + return visit(ctx.castFunction()); + } - if (ctx.indexedPathAccessFragment() != null) { - builder.append(visit(ctx.indexedPathAccessFragment())); + if (ctx.treatedPath() != null) { + return visit(ctx.treatedPath()); } - return builder; - } + if (ctx.extractFunction() != null) { + return visit(ctx.extractFunction()); + } - @Override - public QueryTokenStream visitIndexedPathAccessFragment(HqlParser.IndexedPathAccessFragmentContext ctx) { + if (ctx.truncFunction() != null) { + return visit(ctx.truncFunction()); + } - QueryRendererBuilder builder = QueryRenderer.builder(); + if (ctx.formatFunction() != null) { + return visit(ctx.formatFunction()); + } - builder.append(TOKEN_OPEN_SQUARE_BRACKET); - builder.appendInline(visit(ctx.expression())); - builder.append(TOKEN_CLOSE_SQUARE_BRACKET); + if (ctx.collateFunction() != null) { + return visit(ctx.collateFunction()); + } - if (ctx.generalPathFragment() != null) { + if (ctx.substringFunction() != null) { + return visit(ctx.substringFunction()); + } - builder.append(TOKEN_DOT); - builder.append(visit(ctx.generalPathFragment())); + if (ctx.overlayFunction() != null) { + return visit(ctx.overlayFunction()); } - return builder; - } + if (ctx.trimFunction() != null) { + return visit(ctx.trimFunction()); + } - @Override - public QueryTokenStream visitSimplePath(HqlParser.SimplePathContext ctx) { + if (ctx.padFunction() != null) { + return visit(ctx.padFunction()); + } - QueryRendererBuilder builder = QueryRenderer.builder(); + if (ctx.positionFunction() != null) { + return visit(ctx.positionFunction()); + } - builder.append(visit(ctx.identifier())); + if (ctx.currentDateFunction() != null) { + return visit(ctx.currentDateFunction()); + } - if (!ctx.simplePathElement().isEmpty()) { - builder.append(TOKEN_DOT); + if (ctx.currentTimeFunction() != null) { + return visit(ctx.currentTimeFunction()); } - builder.append(QueryTokenStream.concat(ctx.simplePathElement(), this::visit, TOKEN_DOT)); + if (ctx.currentTimestampFunction() != null) { + return visit(ctx.currentTimestampFunction()); + } - return builder; - } + if (ctx.instantFunction() != null) { + return visit(ctx.instantFunction()); + } - @Override - public QueryTokenStream visitSimplePathElement(HqlParser.SimplePathElementContext ctx) { + if (ctx.localDateFunction() != null) { + return visit(ctx.localDateFunction()); + } - QueryRendererBuilder builder = QueryRenderer.builder(); + if (ctx.localTimeFunction() != null) { + return visit(ctx.localTimeFunction()); + } - builder.append(visit(ctx.identifier())); + if (ctx.localDateTimeFunction() != null) { + return visit(ctx.localDateTimeFunction()); + } - return builder; - } + if (ctx.offsetDateTimeFunction() != null) { + return visit(ctx.offsetDateTimeFunction()); + } - @Override - public QueryTokenStream visitCaseList(HqlParser.CaseListContext ctx) { + if (ctx.cube() != null) { + return visit(ctx.cube()); + } - if (ctx.simpleCaseExpression() != null) { - return visit(ctx.simpleCaseExpression()); - } else if (ctx.searchedCaseExpression() != null) { - return visit(ctx.searchedCaseExpression()); - } else { - return QueryTokenStream.empty(); + if (ctx.rollup() != null) { + return visit(ctx.rollup()); } + + return QueryTokenStream.empty(); } @Override - public QueryTokenStream visitSimpleCaseExpression(HqlParser.SimpleCaseExpressionContext ctx) { + public QueryTokenStream visitSubstringFunction(HqlParser.SubstringFunctionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.CASE())); - builder.append(visit(ctx.expressionOrPredicate(0))); + builder.append(QueryTokens.token(ctx.SUBSTRING())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.expression())); - ctx.caseWhenExpressionClause().forEach(caseWhenExpressionClauseContext -> { - builder.append(visit(caseWhenExpressionClauseContext)); - }); + if (ctx.FROM() == null) { + builder.append(TOKEN_COMMA); + } else { + builder.append(QueryTokens.expression(ctx.FROM())); + } - if (ctx.ELSE() != null) { + builder.append(visit(ctx.substringFunctionStartArgument())); - builder.append(QueryTokens.expression(ctx.ELSE())); - builder.append(visit(ctx.expressionOrPredicate(1))); + 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(QueryTokens.expression(ctx.END())); + builder.append(TOKEN_CLOSE_PAREN); return builder; } @Override - public QueryTokenStream visitSearchedCaseExpression(HqlParser.SearchedCaseExpressionContext ctx) { + public QueryTokenStream visitSubstringFunctionStartArgument(HqlParser.SubstringFunctionStartArgumentContext ctx) { + return visit(ctx.expression()); + } - QueryRendererBuilder builder = QueryRenderer.builder(); + @Override + public QueryTokenStream visitSubstringFunctionLengthArgument(HqlParser.SubstringFunctionLengthArgumentContext ctx) { + return visit(ctx.expression()); + } - builder.append(QueryTokens.expression(ctx.CASE())); + @Override + public QueryTokenStream visitPadFunction(HqlParser.PadFunctionContext ctx) { - builder.append(QueryTokenStream.concat(ctx.caseWhenPredicateClause(), this::visit, TOKEN_SPACE)); + QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.ELSE() != null) { + 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())); - builder.append(QueryTokens.expression(ctx.ELSE())); - builder.appendExpression(visit(ctx.expressionOrPredicate())); + if (ctx.padCharacter() != null) { + builder.appendExpression(visit(ctx.padSpecification())); + builder.appendInline(visit(ctx.padCharacter())); + } else { + builder.append(visit(ctx.padSpecification())); } - builder.append(QueryTokens.expression(ctx.END())); + builder.append(TOKEN_CLOSE_PAREN); return builder; } @Override - public QueryTokenStream visitCaseWhenExpressionClause(HqlParser.CaseWhenExpressionClauseContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); + public QueryTokenStream visitPadSpecification(HqlParser.PadSpecificationContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.LEADING() != null ? ctx.LEADING() : ctx.TRAILING())); + } - builder.append(QueryTokens.expression(ctx.WHEN())); - builder.appendExpression(visit(ctx.expression())); - builder.append(QueryTokens.expression(ctx.THEN())); - builder.appendExpression(visit(ctx.expressionOrPredicate())); + @Override + public QueryTokenStream visitPadCharacter(HqlParser.PadCharacterContext ctx) { + return visit(ctx.stringLiteral()); + } - return builder; + @Override + public QueryTokenStream visitPadLength(HqlParser.PadLengthContext ctx) { + return visit(ctx.expression()); } @Override - public QueryTokenStream visitCaseWhenPredicateClause(HqlParser.CaseWhenPredicateClauseContext ctx) { + public QueryTokenStream visitPositionFunction(HqlParser.PositionFunctionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.WHEN())); - builder.appendExpression(visit(ctx.predicate())); - builder.append(QueryTokens.expression(ctx.THEN())); - builder.appendExpression(visit(ctx.expressionOrPredicate())); + 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 visitGenericFunction(HqlParser.GenericFunctionContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - QueryRendererBuilder nested = QueryRenderer.builder(); + public QueryTokenStream visitPositionFunctionPatternArgument(HqlParser.PositionFunctionPatternArgumentContext ctx) { + return visit(ctx.expression()); + } - nested.append(visit(ctx.functionName())); - nested.append(TOKEN_OPEN_PAREN); + @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()); + } + + 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; + } - if (ctx.functionArguments() != null) { - nested.appendInline(visit(ctx.functionArguments())); - } else if (ctx.ASTERISK() != null) { - nested.append(QueryTokens.token(ctx.ASTERISK())); + @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())); + } } - nested.append(TOKEN_CLOSE_PAREN); + return builder; + } - builder.append(nested); + @Override + public QueryTokenStream visitWithinGroupClause(HqlParser.WithinGroupClauseContext ctx) { - if (ctx.pathContinutation() != null) { - builder.appendInline(visit(ctx.pathContinutation())); + 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())); } - if (ctx.filterClause() != null) { - builder.appendExpression(visit(ctx.filterClause())); + 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())); } - if (ctx.withinGroup() != null) { - builder.appendExpression(visit(ctx.withinGroup())); + 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())); } - if (ctx.overClause() != null) { - builder.appendExpression(visit(ctx.overClause())); + 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 + public QueryTokenStream visitPath(HqlParser.PathContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.treatedPath() != null) { + + builder.append(visit(ctx.treatedPath())); + + if (ctx.pathContinutation() != null) { + builder.append(visit(ctx.pathContinutation())); + } + } else if (ctx.generalPathFragment() != null) { + builder.append(visit(ctx.generalPathFragment())); + } + + return builder; + } + + @Override + public QueryTokenStream visitGeneralPathFragment(HqlParser.GeneralPathFragmentContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.simplePath())); + + if (ctx.indexedPathAccessFragment() != null) { + builder.append(visit(ctx.indexedPathAccessFragment())); + } + + return builder; + } + + @Override + public QueryTokenStream visitIndexedPathAccessFragment(HqlParser.IndexedPathAccessFragmentContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(TOKEN_OPEN_SQUARE_BRACKET); + builder.appendInline(visit(ctx.expression())); + builder.append(TOKEN_CLOSE_SQUARE_BRACKET); + + if (ctx.generalPathFragment() != null) { + + builder.append(TOKEN_DOT); + builder.append(visit(ctx.generalPathFragment())); + } + + return builder; + } + + @Override + public QueryTokenStream visitSimplePath(HqlParser.SimplePathContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.identifier())); + + if (!ctx.simplePathElement().isEmpty()) { + builder.append(TOKEN_DOT); + } + + builder.append(QueryTokenStream.concat(ctx.simplePathElement(), this::visit, TOKEN_DOT)); + + return builder; + } + + @Override + public QueryTokenStream visitSimplePathElement(HqlParser.SimplePathElementContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.identifier())); + + return builder; + } + + @Override + public QueryTokenStream visitCaseList(HqlParser.CaseListContext ctx) { + + if (ctx.simpleCaseExpression() != null) { + return visit(ctx.simpleCaseExpression()); + } else if (ctx.searchedCaseExpression() != null) { + return visit(ctx.searchedCaseExpression()); + } else { + return QueryTokenStream.empty(); + } + } + + @Override + public QueryTokenStream visitSimpleCaseExpression(HqlParser.SimpleCaseExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.CASE())); + builder.append(visit(ctx.expressionOrPredicate(0))); + + ctx.caseWhenExpressionClause().forEach(caseWhenExpressionClauseContext -> { + builder.append(visit(caseWhenExpressionClauseContext)); + }); + + if (ctx.ELSE() != null) { + + builder.append(QueryTokens.expression(ctx.ELSE())); + builder.append(visit(ctx.expressionOrPredicate(1))); + } + + builder.append(QueryTokens.expression(ctx.END())); + + return builder; + } + + @Override + public QueryTokenStream visitSearchedCaseExpression(HqlParser.SearchedCaseExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.CASE())); + + builder.append(QueryTokenStream.concat(ctx.caseWhenPredicateClause(), this::visit, TOKEN_SPACE)); + + if (ctx.ELSE() != null) { + + builder.append(QueryTokens.expression(ctx.ELSE())); + builder.appendExpression(visit(ctx.expressionOrPredicate())); } + builder.append(QueryTokens.expression(ctx.END())); + + return builder; + } + + @Override + public QueryTokenStream visitCaseWhenExpressionClause(HqlParser.CaseWhenExpressionClauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.expression(ctx.WHEN())); + builder.appendExpression(visit(ctx.expression())); + builder.append(QueryTokens.expression(ctx.THEN())); + builder.appendExpression(visit(ctx.expressionOrPredicate())); + return builder; } @Override - public QueryTokenStream visitFunctionWithSubquery(HqlParser.FunctionWithSubqueryContext ctx) { + public QueryTokenStream visitCaseWhenPredicateClause(HqlParser.CaseWhenPredicateClauseContext 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); + builder.append(QueryTokens.expression(ctx.WHEN())); + builder.appendExpression(visit(ctx.predicate())); + builder.append(QueryTokens.expression(ctx.THEN())); + builder.appendExpression(visit(ctx.expressionOrPredicate())); return builder; } @Override - public QueryTokenStream visitCastFunctionInvocation(HqlParser.CastFunctionInvocationContext ctx) { - return visit(ctx.castFunction()); - } + public QueryTokenStream visitGenericFunction(HqlParser.GenericFunctionContext ctx) { - @Override - public QueryTokenStream visitExtractFunctionInvocation(HqlParser.ExtractFunctionInvocationContext ctx) { - return visit(ctx.extractFunction()); - } + QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); - @Override - public QueryTokenStream visitTrimFunctionInvocation(HqlParser.TrimFunctionInvocationContext ctx) { - return visit(ctx.trimFunction()); - } + nested.append(visit(ctx.genericFunctionName())); + nested.append(TOKEN_OPEN_PAREN); - @Override - public QueryTokenStream visitEveryFunctionInvocation(HqlParser.EveryFunctionInvocationContext ctx) { - return visit(ctx.everyFunction()); - } + if (ctx.genericFunctionArguments() != null) { + nested.appendInline(visit(ctx.genericFunctionArguments())); + } else if (ctx.ASTERISK() != null) { + nested.append(QueryTokens.token(ctx.ASTERISK())); + } - @Override - public QueryTokenStream visitAnyFunctionInvocation(HqlParser.AnyFunctionInvocationContext ctx) { - return visit(ctx.anyFunction()); - } + nested.append(TOKEN_CLOSE_PAREN); + builder.append(nested); - @Override - public QueryTokenStream visitTreatedPathInvocation(HqlParser.TreatedPathInvocationContext ctx) { - return visit(ctx.treatedPath()); - } + if (ctx.pathContinutation() != null) { + builder.append(visit(ctx.pathContinutation())); + } - @Override - public QueryTokenStream visitFunctionArguments(HqlParser.FunctionArgumentsContext ctx) { + if (ctx.nthSideClause() != null) { + builder.appendExpression(visit(ctx.nthSideClause())); + } - QueryRendererBuilder builder = QueryRenderer.builder(); + if (ctx.nullsClause() != null) { + builder.appendExpression(visit(ctx.nullsClause())); + } - if (ctx.DISTINCT() != null) { - builder.append(QueryTokens.expression(ctx.DISTINCT())); + if (ctx.withinGroupClause() != null) { + builder.appendExpression(visit(ctx.withinGroupClause())); } - builder.append(QueryTokenStream.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); + if (ctx.filterClause() != null) { + builder.appendExpression(visit(ctx.filterClause())); + } + + if (ctx.overClause() != null) { + builder.appendExpression(visit(ctx.overClause())); + } return builder; } @@ -1587,20 +2589,6 @@ public QueryTokenStream visitFilterClause(HqlParser.FilterClauseContext ctx) { 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) { @@ -1677,140 +2665,6 @@ public QueryTokenStream visitFrameClause(HqlParser.FrameClauseContext ctx) { 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) { @@ -1876,23 +2730,49 @@ public QueryTokenStream visitExtractFunction(HqlParser.ExtractFunctionContext ct 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) { @@ -1901,31 +2781,51 @@ public QueryTokenStream visitTrimFunction(HqlParser.TrimFunctionContext ctx) { 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 @@ -1933,25 +2833,32 @@ public QueryTokenStream visitEveryFunction(HqlParser.EveryFunctionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.every)); + builder.appendExpression(visit(ctx.everyAllQuantifier())); - if (ctx.ELEMENTS() != null) { - builder.append(QueryTokens.expression(ctx.ELEMENTS())); - } else if (ctx.INDICES() != null) { - builder.append(QueryTokens.expression(ctx.INDICES())); - } + if (ctx.predicate() != null) { + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.predicate())); + builder.append(TOKEN_CLOSE_PAREN); - builder.append(TOKEN_OPEN_PAREN); + if (ctx.filterClause() != null) { + builder.appendExpression(visit(ctx.filterClause())); + } - if (ctx.predicate() != null) { - builder.append(visit(ctx.predicate())); + if (ctx.overClause() != null) { + builder.appendExpression(visit(ctx.overClause())); + } } 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.subquery())); + builder.append(TOKEN_CLOSE_PAREN); + } else { - builder.append(TOKEN_CLOSE_PAREN); + builder.appendExpression(visit(ctx.collectionQuantifier())); + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.simplePath())); + builder.append(TOKEN_CLOSE_PAREN); + } return builder; } @@ -1961,25 +2868,32 @@ public QueryTokenStream visitAnyFunction(HqlParser.AnyFunctionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.any)); + builder.appendExpression(visit(ctx.anySomeQuantifier())); - if (ctx.ELEMENTS() != null) { - builder.append(QueryTokens.expression(ctx.ELEMENTS())); - } else if (ctx.INDICES() != null) { - builder.append(QueryTokens.expression(ctx.INDICES())); - } + if (ctx.predicate() != null) { + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.predicate())); + builder.append(TOKEN_CLOSE_PAREN); - builder.append(TOKEN_OPEN_PAREN); + if (ctx.filterClause() != null) { + builder.appendExpression(visit(ctx.filterClause())); + } - if (ctx.predicate() != null) { - builder.append(visit(ctx.predicate())); + if (ctx.overClause() != null) { + builder.appendExpression(visit(ctx.overClause())); + } } 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.subquery())); + builder.append(TOKEN_CLOSE_PAREN); + } else { - builder.append(TOKEN_CLOSE_PAREN); + builder.appendExpression(visit(ctx.collectionQuantifier())); + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.simplePath())); + builder.append(TOKEN_CLOSE_PAREN); + } return builder; } @@ -2464,16 +3378,6 @@ public QueryTokenStream visitIdentifier(HqlParser.IdentifierContext ctx) { } } - @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/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java index 1bdde97beb..87fe53e050 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java @@ -38,6 +38,7 @@ * * * @author Mark Paluch + * @author Christoph Strobl */ abstract class QueryRenderer implements QueryTokenStream { @@ -243,7 +244,7 @@ String render() { for (QueryRenderer queryRenderer : nested) { if (lastAppended != null && (lastExpression || queryRenderer.isExpression()) && !builder.isEmpty() - && !lastAppended.endsWith(" ")) { + && (!lastAppended.endsWith(" ") && !lastAppended.endsWith("("))) { builder.append(' '); } 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 8a23e279cd..99547994e1 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 @@ * * @author Greg Turnquist * @author Christoph Strobl + * @author Mark Paluch * @since 3.1 */ class HqlQueryRendererTests { @@ -1551,9 +1552,14 @@ void castFunctionWithFqdnShouldWork() { 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)); + assertQuery("SELECT ce.id FROM CalendarEvent ce WHERE ce.text LIKE :text GROUP BY year(cd.date) HAVING (ce.endDate - ce.startDate) > 5 %s".formatted(dtField)); + assertQuery("SELECT ce.id as id, cd.startDate + 5 %s AS summedDate FROM CalendarEvent ce".formatted(dtField)); } @Test // GH-3025 diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java index 482b02db47..40aa7d274b 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java @@ -1090,7 +1090,7 @@ void aliasesShouldNotOverlapWithSortProperties() { "SELECT t3 FROM Test3 t3 JOIN t3.test2 x WHERE x.id = :test2Id order by t3.testDuplicateColumnName desc"); } - @Test // GH-3269 + @Test // GH-3269, GH-3689 void createsCountQueryUsingAliasCorrectly() { assertCountQuery("select distinct 1 as x from Employee", "select count(distinct 1) from Employee AS __"); @@ -1102,6 +1102,7 @@ void createsCountQueryUsingAliasCorrectly() { "select count(distinct a, b, sum(amount), d) from Employee AS __ GROUP BY n"); assertCountQuery("select distinct a, count(b) as c from Employee GROUP BY n", "select count(distinct a, count(b)) from Employee AS __ GROUP BY n"); + assertCountQuery("select distinct substring(e.firstname, 1, position('a' in e.lastname)) as x from from Employee", "select count(distinct substring(e.firstname, 1, position('a' in e.lastname))) from from Employee"); } @Test // GH-3427 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 62efc2fdc2..80483a05b9 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,8 @@ 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 +33,8 @@ * IMPORTANT: Purely verifies the parser without any transformations. * * @author Greg Turnquist + * @author Mark Paluch + * @author Christoph Strobl * @since 3.1 */ class HqlSpecificationTests { @@ -331,6 +335,213 @@ OR TREAT(e AS Contractor).hours > 100 """); } + @ParameterizedTest // GH-3689 + @ValueSource(strings = { "RESPECT NULLS", "IGNORE NULLS" }) + void generic(String nullHandling) { + + // not in the official documentation but supported in the grammar. + assertQuery(""" + SELECT e FROM Employee e + WHERE FOO(x).bar %s + """.formatted(nullHandling)); + } + + @Test // GH-3689 + void size() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE SIZE(x) > 1 + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE SIZE(e.skills) > 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') + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE TRUNC(e, 'YEAR') = TRUNCATE(LOCAL DATETIME, 'YEAR') + """); + } + + @ParameterizedTest // GH-3689 + @ValueSource(strings = { "YEAR", "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 'yyyy') = FORMAT(e.hiringDate AS 'yyyy') + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE e.hiringDate = format(LOCAL DATETIME as 'yyyy-MM-dd') + """); + + assertQuery(""" + SELECT e FROM Employee e + WHERE e.hiringDate = format(LOCAL_DATE() as 'yyyy-MM-dd') + """); + } + + @Test // GH-3689 + void collate() { + + assertQuery(""" + SELECT e FROM Employee e + WHERE COLLATE(x AS ucs_basic) = COLLATE(e.name AS ucs_basic) + """); + } + + @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, 1, position('/0' in c.number)) " + // + "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"); + + assertQuery("select substring(c.number FROM 1 FOR position('/0' in c.number)) " + // + "from Call c"); + + assertQuery("select substring(c.number FROM 1) AS shortNumber " + // + "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 "); + + assertQuery("select POSITION(c.number IN 'foo') + 1 AS pos " + // + "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 "); + + assertQuery("select OFFSET DATETIME AS offsetDatetime, OFFSET_DATETIME() AS offset_datetime " + // + "from Call c "); + } + + @Test // GH-3689 + void cube() { + + assertQuery("select CUBE(foo), CUBE(foo, bar) " + // + "from Call c "); + + assertQuery("select c.callerId from Call c GROUP BY CUBE(state, province)"); + } + + @Test // GH-3689 + void rollup() { + + assertQuery("select ROLLUP(foo), ROLLUP(foo, bar) " + // + "from Call c "); + + assertQuery("select c.callerId from Call c GROUP BY ROLLUP(state, province)"); + } + @Test void pathExpressionsNamedParametersExample() { @@ -383,6 +594,80 @@ WHERE EXISTS (SELECT spouseEmp """); } + @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() { @@ -461,16 +746,13 @@ WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit) = TRUE """); } - @Test // GH-3628 - void functionInvocationWithIsBoolean() { - - assertQuery(""" - from RoleTmpl where find_in_set(:appId, appIds) is true - """); + @ParameterizedTest // GH-3628 + @ValueSource(strings = { "is true", "is not true", "is false", "is not false" }) + void functionInvocationWithIsBoolean(String booleanComparison) { assertQuery(""" - from RoleTmpl where find_in_set(:appId, appIds) is false - """); + from RoleTmpl where find_in_set(:appId, appIds) %s + """.formatted(booleanComparison)); } @Test @@ -845,20 +1127,36 @@ void booleanPredicate() { """); } - @Test // GH-3628 - void distinctFromPredicate() { + @ParameterizedTest // GH-3628 + @ValueSource(strings = { "IS DISTINCT FROM", "IS NOT DISTINCT FROM" }) + void distinctFromPredicate(String distinctFrom) { assertQuery(""" SELECT c FROM Customer c - WHERE c.orders IS DISTINCT FROM c.payments - """); + WHERE c.orders %s c.payments + """.formatted(distinctFrom)); assertQuery(""" SELECT c FROM Customer c - WHERE c.orders IS NOT DISTINCT FROM c.payments - """); + WHERE c.orders %s c.payments + """.formatted(distinctFrom)); + + assertQuery(""" + SELECT c + FROM Customer c + GROUP BY c.lastname + HAVING c.orders %s c.payments + """.formatted(distinctFrom)); + + assertQuery(""" + SELECT c + FROM Customer c + WHERE EXISTS (SELECT c2 + FROM Customer c2 + WHERE c2.orders %s c.orders) + """.formatted(distinctFrom)); } @Test @@ -983,6 +1281,29 @@ void theRest38() { """); } + @Test // GH-3689 + void insertQueries() { + + assertQuery("insert Person (id, name) values (100L, 'Jane Doe')"); + + assertQuery("insert Person (id, name) values " + // + "(101L, 'J A Doe III'), " + // + "(102L, 'J X Doe'), " + // + "(103L, 'John Doe, Jr')"); + + assertQuery("insert into Partner (id, name) " + // + "select p.id, p.name from Person p "); + + assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) " + + "ON CONFLICT (range) DO UPDATE SET price = :price, type = :priceType"); + + assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) " + + "ON CONFLICT ON CONSTRAINT foo DO UPDATE SET price = :price, type = :priceType"); + + assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) " + + "ON CONFLICT ON CONSTRAINT foo DO NOTHING"); + } + @Test void hqlQueries() { @@ -1000,15 +1321,7 @@ void hqlQueries() { assertQuery("update versioned Person " + // "set name = :newName " + // "where name = :oldName"); - assertQuery("insert Person (id, name) " + // - "values (100L, 'Jane Doe')"); - assertQuery("insert Person (id, name) " + // - "values (101L, 'J A Doe III'), " + // - "(102L, 'J X Doe'), " + // - "(103L, 'John Doe, Jr')"); - assertQuery("insert into Partner (id, name) " + // - "select p.id, p.name " + // - "from Person p "); + assertQuery("select p " + // "from Person p " + // "where p.name like 'Joe'"); @@ -1104,9 +1417,6 @@ void hqlQueries() { 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) " + // @@ -1315,7 +1625,7 @@ void hqlQueries() { assertQuery("select longest.duration " + // "from Phone p " + // "left join lateral (" + // - " select c.duration as duration " + // + "select c.duration as duration " + // " from p.calls c" + // " order by c.duration desc" + // " limit 1 " + // @@ -1435,9 +1745,6 @@ void hqlQueries() { "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) " + //