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 80e9297840..4b0bb9a14c 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 @@ -25,7 +25,9 @@ grammar Hql; * management of complex rules in the generated Visitor. Finally, there are labels applied to rule elements (op=('+'|'-') * to simplify the processing. * - * @author Greg Turnquist, Yannick Brandt + * @author Greg Turnquist + * @author Mark Paluch + * @author Yannick Brandt * @since 3.1 */ } @@ -307,9 +309,10 @@ setOperator // Literals // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-literals literal - : NULL + : STRING_LITERAL + | JAVA_STRING_LITERAL + | NULL | booleanLiteral - | stringLiteral | numericLiteral | dateTimeLiteral | binaryLiteral @@ -321,19 +324,16 @@ booleanLiteral | FALSE ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-string-literals -stringLiteral - : STRINGLITERAL - | JAVASTRINGLITERAL - | CHARACTER - ; - // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-numeric-literals numericLiteral - : INTEGER_LITERAL - | FLOAT_LITERAL - | HEXLITERAL - ; + : INTEGER_LITERAL + | LONG_LITERAL + | BIG_INTEGER_LITERAL + | FLOAT_LITERAL + | DOUBLE_LITERAL + | BIG_DECIMAL_LITERAL + | HEX_LITERAL + ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-datetime-literals dateTimeLiteral @@ -399,7 +399,7 @@ dateOrTimeField // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-binary-literals binaryLiteral : BINARY_LITERAL - | '{' HEXLITERAL (',' HEXLITERAL)* '}' + | '{' HEX_LITERAL (',' HEX_LITERAL)* '}' ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-enum-literals @@ -433,19 +433,29 @@ expression ; primaryExpression - : caseList # CaseExpression - | literal # LiteralExpression - | parameter # ParameterExpression - | function # FunctionExpression - | generalPathFragment # GeneralPathExpression + : caseList # CaseExpression + | literal # LiteralExpression + | parameter # ParameterExpression + | entityTypeReference # EntityTypeExpression + | entityIdReference # EntityIdExpression + | entityVersionReference # EntityVersionExpression + | entityNaturalIdReference # EntityNaturalIdExpression + | syntacticDomainPath pathContinuation? # SyntacticPathExpression + | function # FunctionExpression + | generalPathFragment # GeneralPathExpression ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-Datetime-arithmetic -// TBD - -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-path-expressions +/** + * A much more complicated path expression involving operators and functions + * + * A path which needs to be resolved semantically. This recognizes + * any path-like structure. Generally, the path is semantically + * interpreted by the consumer of the parse-tree. However, there + * are certain cases where we can syntactically recognize a navigable + * path; see 'syntacticNavigablePath' rule + */ path - : treatedPath pathContinutation? + : syntacticDomainPath pathContinuation? | generalPathFragment ; @@ -457,14 +467,120 @@ indexedPathAccessFragment : '[' expression ']' ('.' generalPathFragment)? ; +/** + * A simple path expression + * + * - a reference to an identification variable (not case-sensitive), + * - followed by a list of period-separated identifiers (case-sensitive) + */ simplePath : identifier simplePathElement* ; +/** + * An element of a simple path expression: a period, and an identifier (case-sensitive) + */ simplePathElement : '.' identifier ; +/** + * A continuation of a path expression "broken" by an operator or function + */ +pathContinuation + : '.' simplePath + ; + +/** + * The special function 'type()' + */ +entityTypeReference + : TYPE '(' (path | parameter) ')' + ; + +/** + * The special function 'id()' + */ +entityIdReference + : ID '(' path ')' pathContinuation? + ; + +/** + * The special function 'version()' + */ +entityVersionReference + : VERSION '(' path ')' + ; + +/** + * The special function 'naturalid()' + */ +entityNaturalIdReference + : NATURALID '(' path ')' pathContinuation? + ; + +/** + * An operator or function that may occur within a path expression + * + * Rule for cases where we syntactically know that the path is a + * "domain path" because it is one of these special cases: + * + * * TREAT( path ) + * * ELEMENTS( path ) + * * INDICES( path ) + * * VALUE( path ) + * * KEY( path ) + * * path[ selector ] + * * ARRAY_GET( embeddableArrayPath, index ).path + * * COALESCE( array1, array2 )[ selector ].path + */ +syntacticDomainPath + : treatedNavigablePath + | collectionValueNavigablePath + | mapKeyNavigablePath + | simplePath indexedPathAccessFragment + | simplePath slicedPathAccessFragment + | toOneFkReference + | function pathContinuation + | function indexedPathAccessFragment pathContinuation? + | function slicedPathAccessFragment + ; + +/** + * The slice operator to obtain elements between the lower and upper bound. + */ +slicedPathAccessFragment + : '[' expression ':' expression ']' + ; + +/** + * A 'treat()' function that "breaks" a path expression + */ +treatedNavigablePath + : TREAT '(' path AS simplePath ')' pathContinuation? + ; + +/** + * A 'value()' function that "breaks" a path expression + */ +collectionValueNavigablePath + : elementValueQuantifier '(' path ')' pathContinuation? + ; + +/** + * A 'key()' or 'index()' function that "breaks" a path expression + */ +mapKeyNavigablePath + : indexKeyQuantifier '(' path ')' pathContinuation? + ; + +/** + * The special function 'fk()' + */ +toOneFkReference + : FK '(' path ')' + ; + // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-case-expressions caseList : simpleCaseExpression @@ -511,7 +627,7 @@ function */ standardFunction : castFunction - | treatedPath + | treatedNavigablePath | extractFunction | truncFunction | formatFunction @@ -587,7 +703,7 @@ trimSpecification ; trimCharacter - : stringLiteral + : STRING_LITERAL | parameter ; @@ -604,7 +720,7 @@ padSpecification ; padCharacter - : stringLiteral + : STRING_LITERAL ; padLength @@ -756,7 +872,7 @@ rollup * see 'Dialect.appendDatetimeFormat()' */ format - : stringLiteral + : STRING_LITERAL ; /** @@ -785,7 +901,7 @@ jpaNonstandardFunction * The name of a user-defined or native database function, given as a quoted string */ jpaNonstandardFunctionName - : stringLiteral + : STRING_LITERAL | identifier ; @@ -799,7 +915,7 @@ columnFunction * The function name, followed by a parenthesized list of ','-separated expressions */ genericFunction - : genericFunctionName '(' (genericFunctionArguments | ASTERISK)? ')' pathContinutation? + : genericFunctionName '(' (genericFunctionArguments | ASTERISK)? ')' pathContinuation? nthSideClause? nullsClause? withinGroupClause? filterClause? overClause? ; @@ -984,15 +1100,6 @@ frameExclusion | EXCLUDE NO OTHERS ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-treat-type -treatedPath - : TREAT '(' path AS simplePath')' pathContinutation? - ; - -pathContinutation - : '.' simplePath - ; - // Predicates // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-conditional-expressions predicate @@ -1027,6 +1134,16 @@ elementsValuesQuantifier | VALUES ; +elementValueQuantifier + : ELEMENT + | VALUE + ; + +indexKeyQuantifier + : INDEX + | KEY + ; + indicesKeysQuantifier : INDICES | KEYS @@ -1045,7 +1162,7 @@ betweenExpression // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-like-predicate stringPatternMatching - : expression NOT? (LIKE | ILIKE) expression (ESCAPE (stringLiteral|parameter))? + : expression NOT? (LIKE | ILIKE) expression (ESCAPE (STRING_LITERAL | JAVA_STRING_LITERAL |parameter))? ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-elements-indices @@ -1097,9 +1214,12 @@ parameterOrNumberLiteral | numericLiteral ; +/** + * An identification variable (an entity alias) + */ variable : AS identifier - | reservedWord + | nakedIdentifier ; parameter @@ -1111,16 +1231,9 @@ entityName : identifier ('.' identifier)* ; -identifier - : reservedWord - ; - -functionName - : reservedWord ('.' reservedWord)* - ; - -reservedWord - : IDENTIFICATION_VARIABLE +nakedIdentifier + : IDENTIFIER + | QUOTED_IDENTIFIER | f=(ALL | AND | ANY @@ -1133,8 +1246,10 @@ reservedWord | BY | CASE | CAST - | CEILING | COLLATE + | COLUMN + | CONFLICT + | CONSTRAINT | CONTAINS | COUNT | CROSS @@ -1153,6 +1268,7 @@ reservedWord | DEPTH | DESC | DISTINCT + | DO | ELEMENT | ELEMENTS | ELSE @@ -1166,18 +1282,15 @@ reservedWord | EXCEPT | EXCLUDE | EXISTS - | EXP | EXTRACT - | FALSE | FETCH | FILTER | FIRST - | FLOOR + | FK | FOLLOWING | FOR | FORMAT | FROM - | FULL | FUNCTION | GROUP | GROUPS @@ -1187,10 +1300,9 @@ reservedWord | IGNORE | ILIKE | IN - | INCLUDES | INDEX + | INCLUDES | INDICES - | INNER | INSERT | INSTANT | INTERSECT @@ -1199,15 +1311,14 @@ reservedWord | IS | JOIN | KEY + | KEYS | LAST | LATERAL | LEADING - | LEFT | LIKE | LIMIT | LIST | LISTAGG - | LN | LOCAL | LOCAL_DATE | LOCAL_DATETIME @@ -1231,6 +1342,7 @@ reservedWord | NEXT | NO | NOT + | NOTHING | NULLS | OBJECT | OF @@ -1241,7 +1353,6 @@ reservedWord | OR | ORDER | OTHERS - | OUTER | OVER | OVERFLOW | OVERLAY @@ -1250,7 +1361,6 @@ reservedWord | PERCENT | PLACING | POSITION - | POWER | PRECEDING | QUARTER | RANGE @@ -1267,7 +1377,6 @@ reservedWord | SOME | SUBSTRING | SUM - | TRUE | THEN | TIES | TIME @@ -1295,7 +1404,17 @@ reservedWord | WITH | WITHIN | WITHOUT - | YEAR) + | YEAR + | ZONED) + ; + +identifier + : nakedIdentifier + | FULL + | INNER + | LEFT + | OUTER + | RIGHT ; /* @@ -1334,14 +1453,20 @@ fragment X: 'x' | 'X'; fragment Y: 'y' | 'Y'; fragment Z: 'z' | 'Z'; +ASTERISK : '*'; + // The following are reserved identifiers: +ID : I D; +VERSION : V E R S I O N; +VERSIONED : V E R S I O N E D; +NATURALID : N A T U R A L I D; +FK : F K; ALL : A L L; AND : A N D; ANY : A N Y; AS : A S; ASC : A S C; -ASTERISK : '*'; AVG : A V G; BETWEEN : B E T W E E N; BOTH : B O T H; @@ -1349,7 +1474,6 @@ BREADTH : B R E A D T H; BY : B Y; CASE : C A S E; CAST : C A S T; -CEILING : C E I L I N G; COLLATE : C O L L A T E; COLUMN : C O L U M N; CONFLICT : C O N F L I C T; @@ -1386,14 +1510,10 @@ EVERY : E V E R Y; EXCEPT : E X C E P T; EXCLUDE : E X C L U D E; EXISTS : E X I S T S; -EXP : E X P; EXTRACT : E X T R A C T; -FALSE : F A L S E; FETCH : F E T C H; FILTER : F I L T E R; FIRST : F I R S T; -FK : F K; -FLOOR : F L O O R; FOLLOWING : F O L L O W I N G; FOR : F O R; FORMAT : F O R M A T; @@ -1404,7 +1524,6 @@ GROUP : G R O U P; GROUPS : G R O U P S; HAVING : H A V I N G; HOUR : H O U R; -ID : I D; IGNORE : I G N O R E; ILIKE : I L I K E; IN : I N; @@ -1429,7 +1548,6 @@ LIKE : L I K E; LIMIT : L I M I T; LIST : L I S T; LISTAGG : L I S T A G G; -LN : L N; LOCAL : L O C A L; LOCAL_DATE : L O C A L '_' D A T E ; LOCAL_DATETIME : L O C A L '_' D A T E T I M E; @@ -1448,13 +1566,11 @@ MININDEX : M I N I N D E X; MINUTE : M I N U T E; MONTH : M O N T H; NANOSECOND : N A N O S E C O N D; -NATURALID : N A T U R A L I D; 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; OF : O F; @@ -1474,7 +1590,6 @@ PARTITION : P A R T I T I O N; PERCENT : P E R C E N T; PLACING : P L A C I N G; POSITION : P O S I T I O N; -POWER : P O W E R; PRECEDING : P R E C E D I N G; QUARTER : Q U A R T E R; RANGE : R A N G E; @@ -1501,7 +1616,6 @@ TO : T O; TRAILING : T R A I L I N G; TREAT : T R E A T; TRIM : T R I M; -TRUE : T R U E; TRUNC : T R U N C; TRUNCATE : T R U N C A T E; TYPE : T Y P E; @@ -1511,8 +1625,6 @@ UPDATE : U P D A T E; USING : U S I N G; VALUE : V A L U E; VALUES : V A L U E S; -VERSION : V E R S I O N; -VERSIONED : V E R S I O N E D; WEEK : W E E K; WHEN : W H E N; WHERE : W H E R E; @@ -1520,20 +1632,102 @@ WITH : W I T H; WITHIN : W I T H I N; WITHOUT : W I T H O U T; YEAR : Y E A R; +ZONED : Z O N E D; + +NULL : N U L L; +TRUE : T R U E; +FALSE : F A L S E; + +fragment +INTEGER_NUMBER + : DIGIT+ + ; + +fragment +FLOATING_POINT_NUMBER + : DIGIT+ '.' DIGIT* EXPONENT? + | '.' DIGIT+ EXPONENT? + | DIGIT+ EXPONENT + | DIGIT+ + ; + +fragment +EXPONENT : [eE] [+-]? DIGIT+; -fragment INTEGER_NUMBER : ('0' .. '9')+ ; -fragment FLOAT_NUMBER : INTEGER_NUMBER+ '.'? INTEGER_NUMBER* (E [+-]? INTEGER_NUMBER)? ; fragment HEX_DIGIT : [0-9a-fA-F]; +fragment SINGLE_QUOTE : '\''; +fragment DOUBLE_QUOTE : '"'; + +STRING_LITERAL : SINGLE_QUOTE ( SINGLE_QUOTE SINGLE_QUOTE | ~('\'') )* SINGLE_QUOTE; + +JAVA_STRING_LITERAL + : DOUBLE_QUOTE ( ESCAPE_SEQUENCE | ~('"') )* DOUBLE_QUOTE + | [jJ] SINGLE_QUOTE ( ESCAPE_SEQUENCE | ~('\'') )* SINGLE_QUOTE + | [jJ] DOUBLE_QUOTE ( ESCAPE_SEQUENCE | ~('\'') )* DOUBLE_QUOTE + ; + +INTEGER_LITERAL : INTEGER_NUMBER ('_' INTEGER_NUMBER)*; + +LONG_LITERAL : INTEGER_NUMBER ('_' INTEGER_NUMBER)* LONG_SUFFIX; + +FLOAT_LITERAL : FLOATING_POINT_NUMBER FLOAT_SUFFIX; + +DOUBLE_LITERAL : FLOATING_POINT_NUMBER DOUBLE_SUFFIX?; + +BIG_INTEGER_LITERAL : INTEGER_NUMBER BIG_INTEGER_SUFFIX; + +BIG_DECIMAL_LITERAL : FLOATING_POINT_NUMBER BIG_DECIMAL_SUFFIX; + +HEX_LITERAL : '0' [xX] HEX_DIGIT+ LONG_SUFFIX?; -CHARACTER : '\'' (~ ('\'' | '\\' )) '\'' ; -STRINGLITERAL : '\'' ('\'' '\'' | ~('\''))* '\'' ; -JAVASTRINGLITERAL : '"' ( ('\\' [btnfr"']) | ~('"'))* '"'; -INTEGER_LITERAL : INTEGER_NUMBER (L | B I)? ; -FLOAT_LITERAL : FLOAT_NUMBER (D | F | B D)?; -HEXLITERAL : '0' X HEX_DIGIT+ ; BINARY_LITERAL : [xX] '\'' HEX_DIGIT+ '\'' | [xX] '"' HEX_DIGIT+ '"' ; -IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ; +fragment +LETTER : [a-zA-Z\u0080-\ufffe_$]; + +fragment +DIGIT : [0-9]; + +fragment +LONG_SUFFIX : [lL]; + +fragment +FLOAT_SUFFIX : [fF]; + +fragment +DOUBLE_SUFFIX : [dD]; + +fragment +BIG_DECIMAL_SUFFIX : [bB] [dD]; + +fragment +BIG_INTEGER_SUFFIX : [bB] [iI]; + +// Identifiers +IDENTIFIER + : LETTER (LETTER | DIGIT)* + ; + +fragment +BACKTICK : '`'; + +fragment BACKSLASH : '\\'; + +fragment +UNICODE_ESCAPE + : 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT + ; + +fragment +ESCAPE_SEQUENCE + : BACKSLASH [btnfr"'] + | BACKSLASH UNICODE_ESCAPE + | BACKSLASH BACKSLASH + ; + +QUOTED_IDENTIFIER + : BACKTICK ( ESCAPE_SEQUENCE | '\\' BACKTICK | ~([`]) )* BACKTICK + ; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java index 0e6c5cab02..259556e542 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java @@ -15,7 +15,7 @@ */ package org.springframework.data.jpa.repository.query; -import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_COMMA; +import static org.springframework.data.jpa.repository.query.QueryTokens.*; import java.util.ArrayList; import java.util.Collections; @@ -84,7 +84,7 @@ public Void visitInstantiation(HqlParser.InstantiationContext ctx) { } private static String capturePrimaryAlias(VariableContext ctx) { - return ((ctx).reservedWord() != null ? ctx.reservedWord() : ctx.identifier().reservedWord()).getText(); + return ((ctx).nakedIdentifier() != null ? ctx.nakedIdentifier() : ctx.identifier()).getText(); } private static List captureSelectItems(List selections, 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 2ef49b95ff..311fdce1d3 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 @@ -1032,8 +1032,10 @@ public QueryTokenStream visitLiteral(HqlParser.LiteralContext ctx) { return QueryRendererBuilder.from(QueryTokens.expression(ctx.NULL())); } else if (ctx.booleanLiteral() != null) { return visit(ctx.booleanLiteral()); - } else if (ctx.stringLiteral() != null) { - return visit(ctx.stringLiteral()); + } else if (ctx.JAVA_STRING_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.expression(ctx.JAVA_STRING_LITERAL())); + } else if (ctx.STRING_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRING_LITERAL())); } else if (ctx.numericLiteral() != null) { return visit(ctx.numericLiteral()); } else if (ctx.dateTimeLiteral() != null) { @@ -1057,27 +1059,23 @@ public QueryTokenStream visitBooleanLiteral(HqlParser.BooleanLiteralContext ctx) } } - @Override - public QueryTokenStream visitStringLiteral(HqlParser.StringLiteralContext ctx) { - - if (ctx.STRINGLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRINGLITERAL())); - } else if (ctx.CHARACTER() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.CHARACTER())); - } else { - return QueryTokenStream.empty(); - } - } - @Override public QueryTokenStream visitNumericLiteral(HqlParser.NumericLiteralContext ctx) { if (ctx.INTEGER_LITERAL() != null) { return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + } else if (ctx.LONG_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.LONG_LITERAL())); + } else if (ctx.BIG_INTEGER_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.BIG_INTEGER_LITERAL())); } else if (ctx.FLOAT_LITERAL() != null) { return QueryRendererBuilder.from(QueryTokens.token(ctx.FLOAT_LITERAL())); - } else if (ctx.HEXLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.HEXLITERAL())); + } else if (ctx.DOUBLE_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.DOUBLE_LITERAL())); + } else if (ctx.BIG_DECIMAL_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.BIG_DECIMAL_LITERAL())); + } else if (ctx.HEX_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.HEX_LITERAL())); } else { return QueryTokenStream.empty(); } @@ -1238,11 +1236,11 @@ public QueryTokenStream visitBinaryLiteral(HqlParser.BinaryLiteralContext ctx) { if (ctx.BINARY_LITERAL() != null) { builder.append(QueryTokens.expression(ctx.BINARY_LITERAL())); - } else if (ctx.HEXLITERAL() != null) { + } else if (ctx.HEX_LITERAL() != null) { builder.append(TOKEN_OPEN_BRACE); - builder.append(QueryTokenStream.concat(ctx.HEXLITERAL(), it -> { + builder.append(QueryTokenStream.concat(ctx.HEX_LITERAL(), it -> { return QueryRendererBuilder.from(QueryTokens.token(it)); }, TOKEN_COMMA)); @@ -1425,6 +1423,195 @@ public QueryTokenStream visitParameterExpression(HqlParser.ParameterExpressionCo return visit(ctx.parameter()); } + @Override + public QueryTokenStream visitEntityTypeExpression(HqlParser.EntityTypeExpressionContext ctx) { + return visit(ctx.entityTypeReference()); + } + + @Override + public QueryTokenStream visitEntityIdExpression(HqlParser.EntityIdExpressionContext ctx) { + return visit(ctx.entityIdReference()); + } + + @Override + public QueryTokenStream visitEntityVersionExpression(HqlParser.EntityVersionExpressionContext ctx) { + return visit(ctx.entityVersionReference()); + } + + @Override + public QueryTokenStream visitEntityNaturalIdExpression(HqlParser.EntityNaturalIdExpressionContext ctx) { + return visit(ctx.entityNaturalIdReference()); + } + + @Override + public QueryTokenStream visitSyntacticPathExpression(HqlParser.SyntacticPathExpressionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendInline(visit(ctx.syntacticDomainPath())); + + if (ctx.pathContinuation() != null) { + builder.appendInline(visit(ctx.pathContinuation())); + } + + return builder; + } + + @Override + public QueryTokenStream visitPathContinuation(HqlParser.PathContinuationContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(TOKEN_DOT); + builder.append(visit(ctx.simplePath())); + + return builder; + } + + @Override + public QueryTokenStream visitEntityTypeReference(HqlParser.EntityTypeReferenceContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.TYPE())); + builder.append(TOKEN_OPEN_PAREN); + + if (ctx.path() != null) { + builder.appendInline(visit(ctx.path())); + } + + if (ctx.parameter() != null) { + builder.appendInline(visit(ctx.parameter())); + } + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitEntityIdReference(HqlParser.EntityIdReferenceContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.ID())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + if (ctx.pathContinuation() != null) { + builder.appendInline(visit(ctx.pathContinuation())); + } + + return builder; + } + + @Override + public QueryTokenStream visitEntityVersionReference(HqlParser.EntityVersionReferenceContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.VERSION())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitEntityNaturalIdReference(HqlParser.EntityNaturalIdReferenceContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.NATURALID())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + if (ctx.pathContinuation() != null) { + builder.appendInline(visit(ctx.pathContinuation())); + } + + return builder; + } + + @Override + public QueryTokenStream visitSyntacticDomainPath(HqlParser.SyntacticDomainPathContext ctx) { + + if (ctx.treatedNavigablePath() != null) { + return visit(ctx.treatedNavigablePath()); + } + + if (ctx.collectionValueNavigablePath() != null) { + return visit(ctx.collectionValueNavigablePath()); + } + + if (ctx.mapKeyNavigablePath() != null) { + return visit(ctx.mapKeyNavigablePath()); + } + + if (ctx.toOneFkReference() != null) { + return visit(ctx.toOneFkReference()); + } + + if (ctx.function() != null) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.function())); + + if (ctx.indexedPathAccessFragment() != null) { + builder.append(visit(ctx.indexedPathAccessFragment())); + } + + if (ctx.slicedPathAccessFragment() != null) { + builder.append(visit(ctx.slicedPathAccessFragment())); + } + + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); + } + + return builder; + } + + if (ctx.indexedPathAccessFragment() != null) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.simplePath())); + builder.append(visit(ctx.indexedPathAccessFragment())); + + return builder; + } + + if (ctx.slicedPathAccessFragment() != null) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.simplePath())); + builder.append(visit(ctx.slicedPathAccessFragment())); + + return builder; + } + + return QueryRenderer.empty(); + } + + @Override + public QueryTokenStream visitSlicedPathAccessFragment(HqlParser.SlicedPathAccessFragmentContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(TOKEN_OPEN_SQUARE_BRACKET); + builder.appendInline(visit(ctx.expression(0))); + builder.append(TOKEN_COLON); + builder.appendInline(visit(ctx.expression(1))); + builder.append(TOKEN_CLOSE_SQUARE_BRACKET); + + return builder; + } + @Override public QueryTokenStream visitFunctionExpression(HqlParser.FunctionExpressionContext ctx) { return visit(ctx.function()); @@ -1479,10 +1666,6 @@ public QueryTokenStream visitStandardFunction(HqlParser.StandardFunctionContext return visit(ctx.castFunction()); } - if (ctx.treatedPath() != null) { - return visit(ctx.treatedPath()); - } - if (ctx.extractFunction() != null) { return visit(ctx.extractFunction()); } @@ -1634,7 +1817,7 @@ public QueryTokenStream visitPadSpecification(HqlParser.PadSpecificationContext @Override public QueryTokenStream visitPadCharacter(HqlParser.PadCharacterContext ctx) { - return visit(ctx.stringLiteral()); + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); } @Override @@ -1908,7 +2091,7 @@ public QueryTokenStream visitRollup(HqlParser.RollupContext ctx) { @Override public QueryTokenStream visitFormat(HqlParser.FormatContext ctx) { - return visit(ctx.stringLiteral()); + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); } @Override @@ -1970,7 +2153,7 @@ public QueryTokenStream visitJpaNonstandardFunctionName(HqlParser.JpaNonstandard return visit(ctx.identifier()); } - return visit(ctx.stringLiteral()); + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); } @Override @@ -2378,12 +2561,12 @@ public QueryTokenStream visitPath(HqlParser.PathContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.treatedPath() != null) { + if (ctx.syntacticDomainPath() != null) { - builder.append(visit(ctx.treatedPath())); + builder.append(visit(ctx.syntacticDomainPath())); - if (ctx.pathContinutation() != null) { - builder.append(visit(ctx.pathContinutation())); + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); } } else if (ctx.generalPathFragment() != null) { builder.append(visit(ctx.generalPathFragment())); @@ -2549,8 +2732,8 @@ public QueryTokenStream visitGenericFunction(HqlParser.GenericFunctionContext ct nested.append(TOKEN_CLOSE_PAREN); builder.append(nested); - if (ctx.pathContinutation() != null) { - builder.append(visit(ctx.pathContinutation())); + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); } if (ctx.nthSideClause() != null) { @@ -2821,8 +3004,8 @@ public QueryTokenStream visitTrimSpecification(HqlParser.TrimSpecificationContex @Override public QueryTokenStream visitTrimCharacter(HqlParser.TrimCharacterContext ctx) { - if (ctx.stringLiteral() != null) { - return visit(ctx.stringLiteral()); + if (ctx.STRING_LITERAL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); } return visit(ctx.parameter()); @@ -2899,7 +3082,7 @@ public QueryTokenStream visitAnyFunction(HqlParser.AnyFunctionContext ctx) { } @Override - public QueryTokenStream visitTreatedPath(HqlParser.TreatedPathContext ctx) { + public QueryTokenStream visitTreatedNavigablePath(HqlParser.TreatedNavigablePathContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -2914,24 +3097,88 @@ public QueryTokenStream visitTreatedPath(HqlParser.TreatedPathContext ctx) { builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); - if (ctx.pathContinutation() != null) { - builder.append(visit(ctx.pathContinutation())); + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); } return builder; } @Override - public QueryTokenStream visitPathContinutation(HqlParser.PathContinutationContext ctx) { + public QueryTokenStream visitCollectionValueNavigablePath(HqlParser.CollectionValueNavigablePathContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(TOKEN_DOT); - builder.append(visit(ctx.simplePath())); + builder.append(visit(ctx.elementValueQuantifier())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); + } return builder; } + @Override + public QueryTokenStream visitMapKeyNavigablePath(HqlParser.MapKeyNavigablePathContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(visit(ctx.indexKeyQuantifier())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + if (ctx.pathContinuation() != null) { + builder.append(visit(ctx.pathContinuation())); + } + + return builder; + } + + @Override + public QueryTokenStream visitToOneFkReference(HqlParser.ToOneFkReferenceContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.FK())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitElementValueQuantifier(HqlParser.ElementValueQuantifierContext ctx) { + + if (ctx.ELEMENT() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.ELEMENT())); + } + + if (ctx.VALUE() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.VALUE())); + } + + return QueryTokenStream.empty(); + } + + @Override + public QueryTokenStream visitIndexKeyQuantifier(HqlParser.IndexKeyQuantifierContext ctx) { + + if (ctx.INDEX() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INDEX())); + } + + if (ctx.KEY() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.KEY())); + } + + return QueryTokenStream.empty(); + } + @Override public QueryTokenStream visitIsBooleanPredicate(HqlParser.IsBooleanPredicateContext ctx) { @@ -3176,8 +3423,10 @@ public QueryTokenStream visitStringPatternMatching(HqlParser.StringPatternMatchi builder.append(QueryTokens.expression(ctx.ESCAPE())); - if (ctx.stringLiteral() != null) { - builder.appendExpression(visit(ctx.stringLiteral())); + if (ctx.STRING_LITERAL() != null) { + builder.append(QueryTokens.expression(ctx.STRING_LITERAL())); + } else if (ctx.JAVA_STRING_LITERAL() != null) { + builder.append(QueryTokens.expression(ctx.JAVA_STRING_LITERAL())); } else if (ctx.parameter() != null) { builder.appendExpression(visit(ctx.parameter())); } @@ -3335,8 +3584,8 @@ public QueryTokenStream visitVariable(HqlParser.VariableContext ctx) { builder.append(QueryTokens.expression(ctx.AS())); builder.append(visit(ctx.identifier())); - } else if (ctx.reservedWord() != null) { - builder.append(visit(ctx.reservedWord())); + } else if (ctx.nakedIdentifier() != null) { + builder.append(visit(ctx.nakedIdentifier())); } return builder; @@ -3371,20 +3620,33 @@ public QueryTokenStream visitEntityName(HqlParser.EntityNameContext ctx) { @Override public QueryTokenStream visitIdentifier(HqlParser.IdentifierContext ctx) { - if (ctx.reservedWord() != null) { - return visit(ctx.reservedWord()); - } else { - return QueryTokenStream.empty(); + if (ctx.nakedIdentifier() != null) { + return visit(ctx.nakedIdentifier()); + } else if (ctx.FULL() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.FULL())); + } else if (ctx.LEFT() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.LEFT())); + } else if (ctx.INNER() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INNER())); + } else if (ctx.OUTER() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.OUTER())); + } else if (ctx.RIGHT() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.RIGHT())); } + + return QueryTokenStream.empty(); } @Override - public QueryTokenStream visitReservedWord(HqlParser.ReservedWordContext ctx) { + public QueryTokenStream visitNakedIdentifier(HqlParser.NakedIdentifierContext ctx) { - if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.IDENTIFICATION_VARIABLE())); + if (ctx.IDENTIFIER() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.IDENTIFIER())); + } else if (ctx.QUOTED_IDENTIFIER() != null) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.QUOTED_IDENTIFIER())); } else { return QueryRendererBuilder.from(QueryTokens.token(ctx.f)); } } + } 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 2f9dd07363..067a3adbe4 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,7 +37,8 @@ * * @author Greg Turnquist * @author Christoph Strobl - * @author Mark Paluch, Yannick Brandt + * @author Mark Paluch + * @author Yannick Brandt * @since 3.1 */ class HqlQueryRendererTests { @@ -179,6 +180,180 @@ void pathExpressionSyntaxExample1() { """); } + @Test // GH-3711 + void entityTypeReference() { + + assertQuery(""" + SELECT TYPE(e) + FROM Employee e + """); + + assertQuery(""" + SELECT TYPE(?0) + FROM Employee e + """); + } + + @Test // GH-3711 + void entityIdReference() { + + assertQuery(""" + SELECT ID(e) + FROM Employee e + """); + + assertQuery(""" + SELECT ID(e).foo + FROM Employee e + """); + } + + @Test // GH-3711 + void entityNaturalIdReference() { + + assertQuery(""" + SELECT NATURALID(e) + FROM Employee e + """); + + assertQuery(""" + SELECT NATURALID(e).foo + FROM Employee e + """); + } + + @Test // GH-3711 + void entityVersionReference() { + + assertQuery(""" + SELECT VERSION(e) + FROM Employee e + """); + } + + @Test // GH-3711 + void treatedNavigablePath() { + + assertQuery(""" + SELECT TREAT(e as Integer).foo + FROM Employee e + """); + } + + @Test // GH-3711 + void collectionValueNavigablePath() { + + assertQuery(""" + SELECT ELEMENT(e) + FROM Employee e + """); + + assertQuery(""" + SELECT ELEMENT(e).foo + FROM Employee e + """); + + assertQuery(""" + SELECT VALUE(e) + FROM Employee e + """); + + assertQuery(""" + SELECT VALUE(e).foo + FROM Employee e + """); + } + + @Test // GH-3711 + void mapKeyNavigablePath() { + + assertQuery(""" + SELECT KEY(e) + FROM Employee e + """); + + assertQuery(""" + SELECT KEY(e).foo + FROM Employee e + """); + + assertQuery(""" + SELECT INDEX(e) + FROM Employee e + """); + } + + @Test // GH-3711 + void toOneFkReference() { + + assertQuery(""" + SELECT FK(e) + FROM Employee e + """); + + assertQuery(""" + SELECT FK(e.foo) + FROM Employee e + """); + } + + @Test // GH-3711 + void indexedPathAccessFragment() { + + assertQuery(""" + SELECT e.names[0] + FROM Employee e + """); + + assertQuery(""" + SELECT e.payments[1].id + FROM Employee e + """); + + assertQuery(""" + SELECT some_function()[0] + FROM Employee e + """); + + assertQuery(""" + SELECT some_function()[1].id + FROM Employee e + """); + } + + @Test // GH-3711 + void slicedPathAccessFragment() { + + assertQuery(""" + SELECT e.names[0:1] + FROM Employee e + """); + + assertQuery(""" + SELECT e.payments[1:2].id + FROM Employee e + """); + + assertQuery(""" + SELECT some_function()[0:1] + FROM Employee e + """); + + assertQuery(""" + SELECT some_function()[1:2].id + FROM Employee e + """); + } + + @Test // GH-3711 + void functionPathContinuation() { + + assertQuery(""" + SELECT some_function().foo + FROM Employee e + """); + } + @Test void joinsExample1() { @@ -299,7 +474,7 @@ void fromClauseDowncastingExample1() { assertQuery(""" SELECT b.name, b.ISBN FROM Order o JOIN TREAT(o.product AS Book) b - """); + """); } @Test @@ -308,7 +483,7 @@ void fromClauseDowncastingExample2() { assertQuery(""" SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) lp WHERE lp.budget > 1000 - """); + """); } /** @@ -323,7 +498,7 @@ void fromClauseDowncastingExample3_SPEC_BUG() { WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE "cost overrun" - """); + """); } @Test @@ -334,7 +509,7 @@ void fromClauseDowncastingExample3fixed() { WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE 'cost overrun' - """); + """); } @Test @@ -344,7 +519,7 @@ void fromClauseDowncastingExample4() { SELECT e FROM Employee e WHERE TREAT(e AS Exempt).vacationDays > 10 OR TREAT(e AS Contractor).hours > 100 - """); + """); } @Test @@ -408,7 +583,7 @@ void allExample() { WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department) - """); + """); } @Test @@ -420,7 +595,7 @@ void existsSubSelectExample2() { WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) - """); + """); } @Test @@ -488,7 +663,7 @@ void updateCaseExample1() { WHEN e.rating = 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test @@ -501,7 +676,7 @@ void updateCaseExample2() { WHEN 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test @@ -541,7 +716,7 @@ void theRest() { SELECT e FROM Employee e WHERE TYPE(e) IN (Exempt, Contractor) - """); + """); } @Test @@ -1509,13 +1684,13 @@ select round(count(ri) * 100 / max(ri.receipt.positions), 0) as perc }); } - @Test + @Test // GH-3711 void ceilingFunctionShouldWork() { assertQuery("select ceiling(1.5) from Element a"); } - @Test - void lnFunctionSouldWork() { + @Test // GH-3711 + void lnFunctionShouldWork() { assertQuery("select ln(7.5) from Element a"); } @@ -1568,10 +1743,18 @@ void castFunctionWithFqdnShouldWork() { 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 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)); } + @ParameterizedTest // GH-3711 + @ValueSource(strings = { "1", "1_000", "1L", "1_000L", "1bi", "1.1f", "2.2d", "2.2bd" }) + void numberLiteralsShouldWork(String literal) { + assertQuery(String.format("SELECT %s FROM User u where u.id = %s", literal, literal)); + } + @Test // GH-3025 void binaryLiteralsShouldWork() { 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 40aa7d274b..2ee28f804a 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 @@ -924,7 +924,7 @@ where exists ( and iu = u ) and ct.id = :teamId - """, relationshipName, joinAlias, joinAlias)); + """, relationshipName, joinAlias, joinAlias)); } static Stream queriesWithReservedWordsAsIdentifiers() { @@ -933,7 +933,6 @@ static Stream queriesWithReservedWordsAsIdentifiers() { Arguments.of("right", "rt"), // Arguments.of("left", "lt"), // Arguments.of("outer", "ou"), // - Arguments.of("full", "full"), // Arguments.of("inner", "inr")); }