diff --git a/pom.xml b/pom.xml
index bf04cdab4a9..1d5414c9374 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,6 +30,7 @@
16
+ 4.11.1
3.0.3
6.1.4.Final
2.7.1
diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml
index 01e6d605033..42204b71ae7 100644
--- a/spring-data-jpa/pom.xml
+++ b/spring-data-jpa/pom.xml
@@ -73,6 +73,12 @@
+
+ org.antlr
+ antlr4-runtime
+ ${antlr}
+
+
org.aspectj
aspectjweaver
@@ -247,8 +253,8 @@
org.jacoco
@@ -344,6 +350,45 @@
+
+ org.antlr
+ antlr4-maven-plugin
+ ${antlr}
+
+
+
+ antlr4
+
+ generate-sources
+
+ true
+
+
+
+
+
+
+ com.google.code.maven-replacer-plugin
+ maven-replacer-plugin
+ 1.4.1
+
+
+ process-sources
+
+ replace
+
+
+
+
+
+ target/generated-sources/antlr4/**/*.java
+
+
+ public class=class,public interface=interface
+
+
+
+
maven-compiler-plugin
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
new file mode 100644
index 00000000000..4f07719d7ec
--- /dev/null
+++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4
@@ -0,0 +1,1011 @@
+/*
+ * Copyright 2011-2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+grammar Hql;
+
+@header {
+/**
+ * HQL per https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#query-language
+ *
+ * This is a mixture of Hibernate 6.1's BNF and missing bits of grammar. There are gaps and inconsistencies in the
+ * BNF itself, explained by other fragments of their spec. Additionally, alternate labels are used to provide easier
+ * management of complex rules in the generated Visitor. Finally, there are labels applied to rule elements (op=('+'|'-')
+ * to simplify the processing.
+ *
+ * @author Greg Turnquist
+ * @since 3.1
+ */
+}
+
+/*
+ Parser rules
+ */
+
+start
+ : ql_statement EOF
+ ;
+
+ql_statement
+ : selectStatement
+ | updateStatement
+ | deleteStatement
+ | insertStatement
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-select
+selectStatement
+ : queryExpression
+ ;
+
+queryExpression
+ : orderedQuery (setOperator orderedQuery)*
+ ;
+
+orderedQuery
+ : (query | '(' queryExpression ')') queryOrder?
+ ;
+
+query
+ : selectClause fromClause? whereClause? (groupByClause havingClause?)? # SelectQuery
+ | fromClause whereClause? (groupByClause havingClause?)? selectClause? # FromQuery
+ ;
+
+queryOrder
+ : orderByClause limitClause? offsetClause? fetchClause?
+ ;
+
+fromClause
+ : FROM entityWithJoins (',' entityWithJoins)*
+ ;
+
+entityWithJoins
+ : fromRoot (joinSpecifier)*
+ ;
+
+joinSpecifier
+ : join
+ | crossJoin
+ | jpaCollectionJoin
+ ;
+
+fromRoot
+ : entityName variable?
+ | LATERAL? '(' subquery ')' variable?
+ ;
+
+join
+ : joinType JOIN FETCH? joinTarget joinRestriction? // Spec BNF says joinType isn't optional, but text says that it is.
+ ;
+
+joinTarget
+ : path variable? # JoinPath
+ | LATERAL? '(' subquery ')' variable? # JoinSubquery
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-update
+updateStatement
+ : UPDATE VERSIONED? targetEntity setClause whereClause?
+ ;
+
+targetEntity
+ : entityName variable?
+ ;
+
+setClause
+ : SET assignment (',' assignment)*
+ ;
+
+assignment
+ : simplePath '=' expressionOrPredicate
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-delete
+deleteStatement
+ : DELETE FROM? targetEntity whereClause?
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-insert
+insertStatement
+ : INSERT INTO? targetEntity targetFields (queryExpression | valuesList)
+ ;
+
+// Already defined underneath updateStatement
+//targetEntity
+// : entityName variable?
+// ;
+
+targetFields
+ : '(' simplePath (',' simplePath)* ')'
+ ;
+
+valuesList
+ : VALUES values (',' values)*
+ ;
+
+values
+ : '(' expression (',' expression)* ')'
+ ;
+
+projectedItem
+ : (expression | instantiation) alias?
+ ;
+
+instantiation
+ : NEW instantiationTarget '(' instantiationArguments ')'
+ ;
+
+alias
+ : AS? identifier // spec says IDENTIFIER but clearly does NOT mean a reserved word
+ ;
+
+groupedItem
+ : identifier
+ | INTEGER_LITERAL
+ | expression
+ ;
+
+sortedItem
+ : sortExpression sortDirection? nullsPrecedence?
+ ;
+
+sortExpression
+ : identifier
+ | INTEGER_LITERAL
+ | expression
+ ;
+
+sortDirection
+ : ASC
+ | DESC
+ ;
+
+nullsPrecedence
+ : NULLS (FIRST | LAST)
+ ;
+
+limitClause
+ : LIMIT parameterOrIntegerLiteral
+ ;
+
+offsetClause
+ : OFFSET parameterOrIntegerLiteral (ROW | ROWS)?
+ ;
+
+fetchClause
+ : FETCH (FIRST | NEXT) (parameterOrIntegerLiteral | parameterOrNumberLiteral '%') (ROW | ROWS) (ONLY | WITH TIES)
+ ;
+
+/*******************
+ Gaps in the spec.
+ *******************/
+
+subquery
+ : queryExpression
+ ;
+
+selectClause
+ : SELECT DISTINCT? selectionList
+ ;
+
+selectionList
+ : selection (',' selection)*
+ ;
+
+selection
+ : selectExpression variable?
+ ;
+
+selectExpression
+ : instantiation
+ | mapEntrySelection
+ | jpaSelectObjectSyntax
+ | expressionOrPredicate
+ ;
+
+mapEntrySelection
+ : ENTRY '(' path ')'
+ ;
+
+/**
+ * Deprecated syntax dating back to EJB-QL prior to EJB 3, required by JPA, never documented in Hibernate
+ */
+jpaSelectObjectSyntax
+ : OBJECT '(' identifier ')'
+ ;
+
+whereClause
+ : WHERE predicate (',' predicate)*
+ ;
+
+joinType
+ : INNER?
+ | (LEFT | RIGHT | FULL)? OUTER?
+ | CROSS
+ ;
+
+crossJoin
+ : CROSS JOIN entityName variable?
+ ;
+
+joinRestriction
+ : (ON | WITH) predicate
+ ;
+
+// Deprecated syntax dating back to EJB-QL prior to EJB 3, required by JPA, never documented in Hibernate
+jpaCollectionJoin
+ : ',' IN '(' path ')' variable?
+ ;
+
+groupByClause
+ : GROUP BY groupedItem (',' groupedItem)*
+ ;
+
+orderByClause
+ : ORDER BY projectedItem (',' projectedItem)*
+ ;
+
+havingClause
+ : HAVING predicate (',' predicate)*
+ ;
+
+setOperator
+ : UNION ALL?
+ | INTERSECT ALL?
+ | EXCEPT ALL?
+ ;
+
+// Literals
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-literals
+literal
+ : NULL
+ | booleanLiteral
+ | stringLiteral
+ | numericLiteral
+ | dateTimeLiteral
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-boolean-literals
+booleanLiteral
+ : TRUE
+ | 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
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-datetime-literals
+dateTimeLiteral
+ : LOCAL_DATE
+ | LOCAL_TIME
+ | LOCAL_DATETIME
+ | CURRENT_DATE
+ | CURRENT_TIME
+ | CURRENT_TIMESTAMP
+ | OFFSET_DATETIME
+ | (LOCAL | CURRENT) DATE
+ | (LOCAL | CURRENT) TIME
+ | (LOCAL | CURRENT | OFFSET) DATETIME
+ | INSTANT
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-duration-literals
+// TBD
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-binary-literals
+// TBD
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-enum-literals
+// TBD
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-java-constants
+// TBD
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-entity-name-literals
+// TBD
+
+// Expressions
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-expressions
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-concatenation
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-numeric-arithmetic
+expression
+ : '(' expression ')' # GroupedExpression
+ | '(' expressionOrPredicate (',' expressionOrPredicate)+ ')' # TupleExpression
+ | '(' subquery ')' # SubqueryExpression
+ | primaryExpression # PlainPrimaryExpression
+ | op=('+' | '-') numericLiteral # SignedNumericLiteral
+ | op=('+' | '-') expression # SignedExpression
+ | expression op=('*' | '/') expression # MultiplicationExpression
+ | expression op=('+' | '-') expression # AdditionExpression
+ | expression '||' expression # HqlConcatenationExpression
+ ;
+
+primaryExpression
+ : caseList # CaseExpression
+ | literal # LiteralExpression
+ | parameter # ParameterExpression
+ | 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
+identificationVariable
+ : identifier
+ | simplePath
+ ;
+
+path
+ : treatedPath pathContinutation?
+ | generalPathFragment
+ ;
+
+generalPathFragment
+ : simplePath indexedPathAccessFragment?
+ ;
+
+indexedPathAccessFragment
+ : '[' expression ']' ('.' generalPathFragment)?
+ ;
+
+simplePath
+ : identifier simplePathElement*
+ ;
+
+simplePathElement
+ : '.' identifier
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-case-expressions
+caseList
+ : simpleCaseExpression
+ | searchedCaseExpression
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-simple-case-expressions
+simpleCaseExpression
+ : CASE expressionOrPredicate caseWhenExpressionClause+ (ELSE expressionOrPredicate)? END
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-searched-case-expressions
+searchedCaseExpression
+ : CASE caseWhenPredicateClause+ (ELSE expressionOrPredicate)? END
+ ;
+
+caseWhenExpressionClause
+ : WHEN expression THEN expressionOrPredicate
+ ;
+
+caseWhenPredicateClause
+ : WHEN predicate THEN expressionOrPredicate
+ ;
+
+// Functions
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-exp-functions
+function
+ : functionName '(' (functionArguments | ASTERISK)? ')' pathContinutation? filterClause? withinGroup? # GenericFunction
+ | functionName '(' subquery ')' # FunctionWithSubquery
+ | castFunction # CastFunctionInvocation
+ | extractFunction # ExtractFunctionInvocation
+ | trimFunction # TrimFunctionInvocation
+ | everyFunction # EveryFunctionInvocation
+ | anyFunction # AnyFunctionInvocation
+ | treatedPath # TreatedPathInvocation
+ ;
+
+functionArguments
+ : DISTINCT? expressionOrPredicate (',' expressionOrPredicate)*
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-filter
+filterClause
+ : FILTER '(' whereClause ')'
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-orderedset
+withinGroup
+ : WITHIN GROUP '(' orderByClause ')'
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-functions
+castFunction
+ : CAST '(' expression AS identifier ')'
+ ;
+
+extractFunction
+ : EXTRACT '(' expression FROM expression ')'
+ | dateTimeFunction '(' expression ')'
+ ;
+
+trimFunction
+ : TRIM '(' (LEADING | TRAILING | BOTH)? stringLiteral? FROM? expression ')'
+ ;
+
+dateTimeFunction
+ : d=(YEAR
+ | MONTH
+ | DAY
+ | WEEK
+ | QUARTER
+ | HOUR
+ | MINUTE
+ | SECOND
+ | NANOSECOND
+ | EPOCH)
+ ;
+
+everyFunction
+ : every=(EVERY | ALL) '(' predicate ')'
+ | every=(EVERY | ALL) '(' subquery ')'
+ | every=(EVERY | ALL) (ELEMENTS | INDICES) '(' simplePath ')'
+ ;
+
+anyFunction
+ : any=(ANY | SOME) '(' predicate ')'
+ | any=(ANY | SOME) '(' subquery ')'
+ | any=(ANY | SOME) (ELEMENTS | INDICES) '(' simplePath ')'
+ ;
+
+// 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
+ : '(' predicate ')' # GroupedPredicate
+ | dealingWithNullExpression # NullExpressionPredicate
+ | inExpression # InPredicate
+ | betweenExpression # BetweenPredicate
+ | relationalExpression # RelationalPredicate
+ | stringPatternMatching # LikePredicate
+ | existsExpression # ExistsPredicate
+ | collectionExpression # CollectionPredicate
+ | NOT predicate # NotPredicate
+ | predicate AND predicate # AndPredicate
+ | predicate OR predicate # OrPredicate
+ | expression # ExpressionPredicate
+ ;
+
+expressionOrPredicate
+ : expression
+ | predicate
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-relational-comparisons
+relationalExpression
+ : expression op=('=' | '>' | '>=' | '<' | '<=' | '<>' ) expression
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-between-predicate
+betweenExpression
+ : expression NOT? BETWEEN expression AND expression
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-null-predicate
+dealingWithNullExpression
+ : expression IS NOT? NULL
+ | expression IS NOT? DISTINCT FROM expression
+ ;
+
+// 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 character)?
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-elements-indices
+// TBD
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-in-predicate
+inExpression
+ : expression NOT? IN inList
+ ;
+
+inList
+ : (ELEMENTS | INDICES) '(' simplePath ')'
+ | '(' subquery ')'
+ | parameter
+ | '(' (expressionOrPredicate (',' expressionOrPredicate)*)? ')'
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-exists-predicate
+// TBD
+existsExpression
+ : EXISTS (ELEMENTS | INDICES) '(' simplePath ')'
+ | EXISTS expression
+ ;
+
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-collection-operators
+collectionExpression
+ : expression IS NOT? EMPTY
+ | expression NOT? MEMBER OF path
+ ;
+
+// Projection
+// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-select-new
+instantiationTarget
+ : LIST
+ | MAP
+ | simplePath
+ ;
+
+instantiationArguments
+ : instantiationArgument (',' instantiationArgument)*
+ ;
+
+instantiationArgument
+ : (expressionOrPredicate | instantiation) variable?
+ ;
+
+// Low level parsing rules
+
+parameterOrIntegerLiteral
+ : parameter
+ | INTEGER_LITERAL
+ ;
+
+parameterOrNumberLiteral
+ : parameter
+ | numericLiteral
+ ;
+
+variable
+ : AS identifier
+ | reservedWord
+ ;
+
+parameter
+ : prefix=':' identifier
+ | prefix='?' (INTEGER_LITERAL | spelExpression)?
+ ;
+
+entityName
+ : identifier ('.' identifier)*
+ ;
+
+identifier
+ : reservedWord
+ | spelExpression
+ ;
+
+spelExpression
+ : prefix='#{#' identificationVariable ('.' identificationVariable)* '}' // #{#entityName}
+ | prefix='#{#[' INTEGER_LITERAL ']}' // #{[0]}
+ | prefix='#{' identificationVariable '(' ( stringLiteral | '[' INTEGER_LITERAL ']' )? ')}' // #{escape([0])} | #{escapeCharacter()}
+ ;
+
+
+character
+ : CHARACTER
+ ;
+
+functionName
+ : reservedWord
+ ;
+
+reservedWord
+ : IDENTIFICATION_VARIABLE
+ | f=(ALL
+ | AND
+ | ANY
+ | AS
+ | ASC
+ | AVG
+ | BETWEEN
+ | BOTH
+ | BREADTH
+ | BY
+ | CASE
+ | CAST
+ | COLLATE
+ | COUNT
+ | CROSS
+ | CUBE
+ | CURRENT
+ | CURRENT_DATE
+ | CURRENT_INSTANT
+ | CURRENT_TIME
+ | CURRENT_TIMESTAMP
+ | CYCLE
+ | DATE
+ | DATETIME
+ | DAY
+ | DEFAULT
+ | DELETE
+ | DEPTH
+ | DESC
+ | DISTINCT
+ | ELEMENT
+ | ELEMENTS
+ | ELSE
+ | EMPTY
+ | END
+ | ENTRY
+ | EPOCH
+ | ERROR
+ | ESCAPE
+ | EVERY
+ | EXCEPT
+ | EXCLUDE
+ | EXISTS
+ | EXTRACT
+ | FETCH
+ | FILTER
+ | FIRST
+ | FOLLOWING
+ | FOR
+ | FORMAT
+ | FROM
+// | FULL
+ | FUNCTION
+ | GROUP
+ | GROUPS
+ | HAVING
+ | HOUR
+ | ID
+ | IGNORE
+ | ILIKE
+ | IN
+ | INDEX
+ | INDICES
+// | INNER
+ | INSERT
+ | INSTANT
+ | INTERSECT
+ | INTO
+ | IS
+ | JOIN
+ | KEY
+ | LAST
+ | LEADING
+// | LEFT
+ | LIKE
+ | LIMIT
+ | LIST
+ | LISTAGG
+ | LOCAL
+ | LOCAL_DATE
+ | LOCAL_DATETIME
+ | LOCAL_TIME
+ | MAP
+ | MATERIALIZED
+ | MAX
+ | MAXELEMENT
+ | MAXINDEX
+ | MEMBER
+ | MICROSECOND
+ | MILLISECOND
+ | MIN
+ | MINELEMENT
+ | MININDEX
+ | MINUTE
+ | MONTH
+ | NANOSECOND
+ | NATURALID
+ | NEW
+ | NEXT
+ | NO
+ | NOT
+ | NULLS
+ | OBJECT
+ | OF
+ | OFFSET
+ | OFFSET_DATETIME
+ | ON
+ | ONLY
+ | OR
+ | ORDER
+ | OTHERS
+// | OUTER
+ | OVER
+ | OVERFLOW
+ | OVERLAY
+ | PAD
+ | PARTITION
+ | PERCENT
+ | PLACING
+ | POSITION
+ | PRECEDING
+ | QUARTER
+ | RANGE
+ | RESPECT
+// | RIGHT
+ | ROLLUP
+ | ROW
+ | ROWS
+ | SEARCH
+ | SECOND
+ | SELECT
+ | SET
+ | SIZE
+ | SOME
+ | SUBSTRING
+ | SUM
+ | THEN
+ | TIES
+ | TIME
+ | TIMESTAMP
+ | TIMEZONE_HOUR
+ | TIMEZONE_MINUTE
+ | TO
+ | TRAILING
+ | TREAT
+ | TRIM
+ | TRUNC
+ | TRUNCATE
+ | TYPE
+ | UNBOUNDED
+ | UNION
+ | UPDATE
+ | USING
+ | VALUE
+ | VALUES
+ | VERSION
+ | VERSIONED
+ | WEEK
+ | WHEN
+ | WHERE
+ | WITH
+ | WITHIN
+ | WITHOUT
+ | YEAR)
+ ;
+
+/*
+ Lexer rules
+ */
+
+
+WS : [ \t\r\n] -> skip ;
+
+// Build up case-insentive tokens
+
+fragment A: 'a' | 'A';
+fragment B: 'b' | 'B';
+fragment C: 'c' | 'C';
+fragment D: 'd' | 'D';
+fragment E: 'e' | 'E';
+fragment F: 'f' | 'F';
+fragment G: 'g' | 'G';
+fragment H: 'h' | 'H';
+fragment I: 'i' | 'I';
+fragment J: 'j' | 'J';
+fragment K: 'k' | 'K';
+fragment L: 'l' | 'L';
+fragment M: 'm' | 'M';
+fragment N: 'n' | 'N';
+fragment O: 'o' | 'O';
+fragment P: 'p' | 'P';
+fragment Q: 'q' | 'Q';
+fragment R: 'r' | 'R';
+fragment S: 's' | 'S';
+fragment T: 't' | 'T';
+fragment U: 'u' | 'U';
+fragment V: 'v' | 'V';
+fragment W: 'w' | 'W';
+fragment X: 'x' | 'X';
+fragment Y: 'y' | 'Y';
+fragment Z: 'z' | 'Z';
+
+// The following are reserved identifiers:
+
+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;
+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;
+COUNT : C O U N T;
+CROSS : C R O S S;
+CUBE : C U B E;
+CURRENT : C U R R E N T;
+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;
+CYCLE : C Y C L E;
+DATE : D A T E;
+DATETIME : D A T E T I M E ;
+DAY : D A Y;
+DEFAULT : D E F A U L T;
+DELETE : D E L E T E;
+DEPTH : D E P T H;
+DESC : D E S C;
+DISTINCT : D I S T I N C T;
+ELEMENT : E L E M E N T;
+ELEMENTS : E L E M E N T S;
+ELSE : E L S E;
+EMPTY : E M P T Y;
+END : E N D;
+ENTRY : E N T R Y;
+EPOCH : E P O C H;
+ERROR : E R R O R;
+ESCAPE : E S C A P E;
+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;
+FROM : F R O M;
+FULL : F U L L;
+FUNCTION : F U N C T I O N;
+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;
+INDEX : I N D E X;
+INDICES : I N D I C E S;
+INNER : I N N E R;
+INSERT : I N S E R T;
+INSTANT : I N S T A N T;
+INTERSECT : I N T E R S E C T;
+INTO : I N T O;
+IS : I S;
+JOIN : J O I N;
+KEY : K E Y;
+LAST : L A S T;
+LATERAL : L A T E R A L;
+LEADING : L E A D I N G;
+LEFT : L E F T;
+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;
+LOCAL_TIME : L O C A L '_' T I M E;
+MAP : M A P;
+MATERIALIZED : M A T E R I A L I Z E D;
+MAX : M A X;
+MAXELEMENT : M A X E L E M E N T;
+MAXINDEX : M A X I N D E X;
+MEMBER : M E M B E R;
+MICROSECOND : M I C R O S E C O N D;
+MILLISECOND : M I L L I S E C O N D;
+MIN : M I N;
+MINELEMENT : M I N E L E M E N T;
+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;
+NULL : N U L L;
+NULLS : N U L L S;
+OBJECT : O B J E C T;
+OF : O F;
+OFFSET : O F F S E T;
+OFFSET_DATETIME : O F F S E T '_' D A T E T I M E;
+ON : O N;
+ONLY : O N L Y;
+OR : O R;
+ORDER : O R D E R;
+OTHERS : O T H E R S;
+OUTER : O U T E R;
+OVER : O V E R;
+OVERFLOW : O V E R F L O W;
+OVERLAY : O V E R L A Y;
+PAD : P A D;
+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;
+RESPECT : R E S P E C T;
+RIGHT : R I G H T;
+ROLLUP : R O L L U P;
+ROUND : R O U N D;
+ROW : R O W;
+ROWS : R O W S;
+SEARCH : S E A R C H;
+SECOND : S E C O N D;
+SELECT : S E L E C T;
+SET : S E T;
+SIGN : S I G N;
+SIZE : S I Z E;
+SOME : S O M E;
+SUBSTRING : S U B S T R I N G;
+SUM : S U M;
+THEN : T H E N;
+TIES : T I E S;
+TIME : T I M E;
+TIMESTAMP : T I M E S T A M P;
+TIMEZONE_HOUR : T I M E Z O N E '_' H O U R;
+TIMEZONE_MINUTE : T I M E Z O N E '_' M I N U T E;
+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;
+UNBOUNDED : U N B O U N D E D;
+UNION : U N I O N;
+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;
+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;
+
+fragment INTEGER_NUMBER : ('0' .. '9')+ ;
+fragment FLOAT_NUMBER : INTEGER_NUMBER+ '.'? INTEGER_NUMBER* (E [+-]? INTEGER_NUMBER)? ;
+
+CHARACTER : '\'' (~ ('\'' | '\\' )) '\'' ;
+STRINGLITERAL : '\'' ('\'' '\'' | ~('\'' | '\\'))* '\'' ;
+JAVASTRINGLITERAL : '"' ( ('\\' [btnfr"']) | ~('"'))* '"';
+INTEGER_LITERAL : INTEGER_NUMBER (L | B I)? ;
+FLOAT_LITERAL : FLOAT_NUMBER (D | F | B D)?;
+HEXLITERAL : '0' X ('0' .. '9' | A | B | C | D | E)+ ;
+
+IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '$' | '_')* ;
+
diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4
new file mode 100644
index 00000000000..52d49eddb96
--- /dev/null
+++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4
@@ -0,0 +1,844 @@
+/*
+ * Copyright 2011-2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+grammar Jpql;
+
+@header {
+/**
+ * JPQL per https://jakarta.ee/specifications/persistence/3.1/jakarta-persistence-spec-3.1.html#bnf
+ *
+ * This is JPA 3.1 BNF for JPQL. There are gaps and inconsistencies in the BNF itself, explained by other fragments of the spec.
+ *
+ * @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#bnf
+ * @author Greg Turnquist
+ * @since 3.1
+ */
+}
+
+/*
+ Parser rules
+ */
+
+start
+ : ql_statement EOF
+ ;
+
+ql_statement
+ : select_statement
+ | update_statement
+ | delete_statement
+ ;
+
+select_statement
+ : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)?
+ ;
+
+update_statement
+ : update_clause (where_clause)?
+ ;
+
+delete_statement
+ : delete_clause (where_clause)?
+ ;
+
+from_clause
+ : FROM identification_variable_declaration (',' (identification_variable_declaration | collection_member_declaration))*
+ ;
+
+identification_variable_declaration
+ : range_variable_declaration (join | fetch_join)*
+ ;
+
+range_variable_declaration
+ : entity_name (AS)? identification_variable
+ ;
+
+join
+ : join_spec join_association_path_expression (AS)? identification_variable (join_condition)?
+ ;
+
+fetch_join
+ : join_spec FETCH join_association_path_expression
+ ;
+
+join_spec
+ : ((LEFT (OUTER)?) | INNER)? JOIN
+ ;
+
+join_condition
+ : ON conditional_expression
+ ;
+
+join_association_path_expression
+ : join_collection_valued_path_expression
+ | join_single_valued_path_expression
+ | TREAT '(' join_collection_valued_path_expression AS subtype ')'
+ | TREAT '(' join_single_valued_path_expression AS subtype ')'
+ ;
+
+join_collection_valued_path_expression
+ : identification_variable '.' (single_valued_embeddable_object_field '.')* collection_valued_field
+ ;
+
+join_single_valued_path_expression
+ : identification_variable '.' (single_valued_embeddable_object_field '.')* single_valued_object_field
+ ;
+
+collection_member_declaration
+ : IN '(' collection_valued_path_expression ')' (AS)? identification_variable
+ ;
+
+qualified_identification_variable
+ : map_field_identification_variable
+ | ENTRY '(' identification_variable ')'
+ ;
+
+map_field_identification_variable
+ : KEY '(' identification_variable ')'
+ | VALUE '(' identification_variable ')'
+ ;
+
+single_valued_path_expression
+ : qualified_identification_variable
+ | TREAT '(' qualified_identification_variable AS subtype ')'
+ | state_field_path_expression
+ | single_valued_object_path_expression
+ ;
+
+general_identification_variable
+ : identification_variable
+ | map_field_identification_variable
+ ;
+
+general_subpath
+ : simple_subpath
+ | treated_subpath ('.' single_valued_object_field)*
+ ;
+
+simple_subpath
+ : general_identification_variable
+ | general_identification_variable ('.' single_valued_object_field)*
+ ;
+
+treated_subpath
+ : TREAT '(' general_subpath AS subtype ')'
+ ;
+
+state_field_path_expression
+ : general_subpath '.' state_field
+ ;
+
+state_valued_path_expression
+ : state_field_path_expression
+ | general_identification_variable
+ ;
+
+single_valued_object_path_expression
+ : general_subpath '.' single_valued_object_field
+ ;
+
+collection_valued_path_expression
+ : general_subpath '.' collection_value_field // BNF at end of spec has a typo
+ ;
+
+update_clause
+ : UPDATE entity_name ((AS)? identification_variable)? SET update_item (',' update_item)*
+ ;
+
+update_item
+ : (identification_variable '.')? (single_valued_embeddable_object_field '.')* (state_field | single_valued_object_field) '=' new_value
+ ;
+
+new_value
+ : scalar_expression
+ | simple_entity_expression
+ | NULL
+ ;
+
+delete_clause
+ : DELETE FROM entity_name ((AS)? identification_variable)?
+ ;
+
+select_clause
+ : SELECT (DISTINCT)? select_item (',' select_item)*
+ ;
+
+select_item
+ : select_expression ((AS)? result_variable)?
+ ;
+
+select_expression
+ : single_valued_path_expression
+ | scalar_expression
+ | aggregate_expression
+ | identification_variable
+ | OBJECT '(' identification_variable ')'
+ | constructor_expression
+ ;
+
+constructor_expression
+ : NEW constructor_name '(' constructor_item (',' constructor_item)* ')'
+ ;
+
+constructor_item
+ : single_valued_path_expression
+ | scalar_expression
+ | aggregate_expression
+ | identification_variable
+ ;
+
+aggregate_expression
+ : (AVG | MAX | MIN | SUM) '(' (DISTINCT)? state_valued_path_expression ')'
+ | COUNT '(' (DISTINCT)? (identification_variable | state_valued_path_expression | single_valued_object_path_expression) ')'
+ | function_invocation
+ ;
+
+where_clause
+ : WHERE conditional_expression
+ ;
+
+groupby_clause
+ : GROUP BY groupby_item (',' groupby_item)*
+ ;
+
+groupby_item
+ : single_valued_path_expression
+ | identification_variable
+ ;
+
+having_clause
+ : HAVING conditional_expression
+ ;
+
+orderby_clause
+ : ORDER BY orderby_item (',' orderby_item)*
+ ;
+
+// TODO Error in spec BNF, correctly shown elsewhere in spec.
+orderby_item
+ : (state_field_path_expression | general_identification_variable | result_variable ) (ASC | DESC)?
+ ;
+
+subquery
+ : simple_select_clause subquery_from_clause (where_clause)? (groupby_clause)? (having_clause)?
+ ;
+
+subquery_from_clause
+ : FROM subselect_identification_variable_declaration (',' (subselect_identification_variable_declaration | collection_member_declaration))*
+ ;
+
+subselect_identification_variable_declaration
+ : identification_variable_declaration
+ | derived_path_expression (AS)? identification_variable (join)*
+ | derived_collection_member_declaration
+ ;
+
+derived_path_expression
+ : general_derived_path '.' single_valued_object_field
+ | general_derived_path '.' collection_valued_field
+ ;
+
+general_derived_path
+ : simple_derived_path
+ | treated_derived_path ('.' single_valued_object_field)*
+ ;
+
+simple_derived_path
+ : superquery_identification_variable ('.' single_valued_object_field)*
+ ;
+
+treated_derived_path
+ : TREAT '(' general_derived_path AS subtype ')'
+ ;
+
+derived_collection_member_declaration
+ : IN superquery_identification_variable '.' (single_valued_object_field '.')* collection_valued_field
+ ;
+
+simple_select_clause
+ : SELECT (DISTINCT)? simple_select_expression
+ ;
+
+simple_select_expression
+ : single_valued_path_expression
+ | scalar_expression
+ | aggregate_expression
+ | identification_variable
+ ;
+
+scalar_expression
+ : arithmetic_expression
+ | string_expression
+ | enum_expression
+ | datetime_expression
+ | boolean_expression
+ | case_expression
+ | entity_type_expression
+ ;
+
+conditional_expression
+ : conditional_term
+ | conditional_expression OR conditional_term
+ ;
+
+conditional_term
+ : conditional_factor
+ | conditional_term AND conditional_factor
+ ;
+
+conditional_factor
+ : (NOT)? conditional_primary
+ ;
+
+conditional_primary
+ : simple_cond_expression
+ | '(' conditional_expression ')'
+ ;
+
+simple_cond_expression
+ : comparison_expression
+ | between_expression
+ | in_expression
+ | like_expression
+ | null_comparison_expression
+ | empty_collection_comparison_expression
+ | collection_member_expression
+ | exists_expression
+ ;
+
+between_expression
+ : arithmetic_expression (NOT)? BETWEEN arithmetic_expression AND arithmetic_expression
+ | string_expression (NOT)? BETWEEN string_expression AND string_expression
+ | datetime_expression (NOT)? BETWEEN datetime_expression AND datetime_expression
+ ;
+
+in_expression
+ : (state_valued_path_expression | type_discriminator) (NOT)? IN (('(' in_item (',' in_item)* ')') | ( '(' subquery ')') | collection_valued_input_parameter)
+ ;
+
+in_item
+ : literal
+ | single_valued_input_parameter
+ ;
+
+like_expression
+ : string_expression (NOT)? LIKE pattern_value (ESCAPE escape_character)?
+ ;
+
+null_comparison_expression
+ : (single_valued_path_expression | input_parameter) IS (NOT)? NULL
+ ;
+
+empty_collection_comparison_expression
+ : collection_valued_path_expression IS (NOT)? EMPTY
+ ;
+
+collection_member_expression
+ : entity_or_value_expression (NOT)? MEMBER (OF)? collection_valued_path_expression
+ ;
+
+entity_or_value_expression
+ : single_valued_object_path_expression
+ | state_field_path_expression
+ | simple_entity_or_value_expression
+ ;
+
+simple_entity_or_value_expression
+ : identification_variable
+ | input_parameter
+ | literal
+ ;
+
+exists_expression
+ : (NOT)? EXISTS '(' subquery ')'
+ ;
+
+all_or_any_expression
+ : (ALL | ANY | SOME) '(' subquery ')'
+ ;
+
+comparison_expression
+ : string_expression comparison_operator (string_expression | all_or_any_expression)
+ | boolean_expression op=('=' | '<>') (boolean_expression | all_or_any_expression)
+ | enum_expression op=('=' | '<>') (enum_expression | all_or_any_expression)
+ | datetime_expression comparison_operator (datetime_expression | all_or_any_expression)
+ | entity_expression op=('=' | '<>') (entity_expression | all_or_any_expression)
+ | arithmetic_expression comparison_operator (arithmetic_expression | all_or_any_expression)
+ | entity_type_expression op=('=' | '<>') entity_type_expression
+ ;
+
+comparison_operator
+ : op='='
+ | op='>'
+ | op='>='
+ | op='<'
+ | op='<='
+ | op='<>'
+ ;
+
+arithmetic_expression
+ : arithmetic_term
+ | arithmetic_expression op=('+' | '-') arithmetic_term
+ ;
+
+arithmetic_term
+ : arithmetic_factor
+ | arithmetic_term op=('*' | '/') arithmetic_factor
+ ;
+
+arithmetic_factor
+ : op=('+' | '-')? arithmetic_primary
+ ;
+
+arithmetic_primary
+ : state_valued_path_expression
+ | numeric_literal
+ | '(' arithmetic_expression ')'
+ | input_parameter
+ | functions_returning_numerics
+ | aggregate_expression
+ | case_expression
+ | function_invocation
+ | '(' subquery ')'
+ ;
+
+string_expression
+ : state_valued_path_expression
+ | string_literal
+ | input_parameter
+ | functions_returning_strings
+ | aggregate_expression
+ | case_expression
+ | function_invocation
+ | '(' subquery ')'
+ ;
+
+datetime_expression
+ : state_valued_path_expression
+ | input_parameter
+ | functions_returning_datetime
+ | aggregate_expression
+ | case_expression
+ | function_invocation
+ | date_time_timestamp_literal
+ | '(' subquery ')'
+ ;
+
+boolean_expression
+ : state_valued_path_expression
+ | boolean_literal
+ | input_parameter
+ | case_expression
+ | function_invocation
+ | '(' subquery ')'
+ ;
+
+enum_expression
+ : state_valued_path_expression
+ | enum_literal
+ | input_parameter
+ | case_expression
+ | '(' subquery ')'
+ ;
+
+entity_expression
+ : single_valued_object_path_expression
+ | simple_entity_expression
+ ;
+
+simple_entity_expression
+ : identification_variable
+ | input_parameter
+ ;
+
+entity_type_expression
+ : type_discriminator
+ | entity_type_literal
+ | input_parameter
+ ;
+
+type_discriminator
+ : TYPE '(' (general_identification_variable | single_valued_object_path_expression | input_parameter) ')'
+ ;
+
+functions_returning_numerics
+ : LENGTH '(' string_expression ')'
+ | LOCATE '(' string_expression ',' string_expression (',' arithmetic_expression)? ')'
+ | ABS '(' arithmetic_expression ')'
+ | CEILING '(' arithmetic_expression ')'
+ | EXP '(' arithmetic_expression ')'
+ | FLOOR '(' arithmetic_expression ')'
+ | LN '(' arithmetic_expression ')'
+ | SIGN '(' arithmetic_expression ')'
+ | SQRT '(' arithmetic_expression ')'
+ | MOD '(' arithmetic_expression ',' arithmetic_expression ')'
+ | POWER '(' arithmetic_expression ',' arithmetic_expression ')'
+ | ROUND '(' arithmetic_expression ',' arithmetic_expression ')'
+ | SIZE '(' collection_valued_path_expression ')'
+ | INDEX '(' identification_variable ')'
+ | extract_datetime_field
+ ;
+
+functions_returning_datetime
+ : CURRENT_DATE
+ | CURRENT_TIME
+ | CURRENT_TIMESTAMP
+ | LOCAL DATE
+ | LOCAL TIME
+ | LOCAL DATETIME
+ | extract_datetime_part
+ ;
+
+functions_returning_strings
+ : CONCAT '(' string_expression ',' string_expression (',' string_expression)* ')'
+ | SUBSTRING '(' string_expression ',' arithmetic_expression (',' arithmetic_expression)? ')'
+ | TRIM '(' ((trim_specification)? (trim_character)? FROM)? string_expression ')'
+ | LOWER '(' string_expression ')'
+ | UPPER '(' string_expression ')'
+ ;
+
+trim_specification
+ : LEADING
+ | TRAILING
+ | BOTH
+ ;
+
+
+function_invocation
+ : FUNCTION '(' function_name (',' function_arg)* ')'
+ ;
+
+extract_datetime_field
+ : EXTRACT '(' datetime_field FROM datetime_expression ')'
+ ;
+
+datetime_field
+ : identification_variable
+ ;
+
+extract_datetime_part
+ : EXTRACT '(' datetime_part FROM datetime_expression ')'
+ ;
+
+datetime_part
+ : identification_variable
+ ;
+
+function_arg
+ : literal
+ | state_valued_path_expression
+ | input_parameter
+ | scalar_expression
+ ;
+
+case_expression
+ : general_case_expression
+ | simple_case_expression
+ | coalesce_expression
+ | nullif_expression
+ ;
+
+general_case_expression
+ : CASE when_clause (when_clause)* ELSE scalar_expression END
+ ;
+
+when_clause
+ : WHEN conditional_expression THEN scalar_expression
+ ;
+
+simple_case_expression
+ : CASE case_operand simple_when_clause (simple_when_clause)* ELSE scalar_expression END
+ ;
+
+case_operand
+ : state_valued_path_expression
+ | type_discriminator
+ ;
+
+simple_when_clause
+ : WHEN scalar_expression THEN scalar_expression
+ ;
+
+coalesce_expression
+ : COALESCE '(' scalar_expression (',' scalar_expression)+ ')'
+ ;
+
+nullif_expression
+ : NULLIF '(' scalar_expression ',' scalar_expression ')'
+ ;
+
+/*******************
+ Gaps in the spec.
+ *******************/
+
+trim_character
+ : CHARACTER
+ | character_valued_input_parameter
+ ;
+
+identification_variable
+ : IDENTIFICATION_VARIABLE
+ | ORDER // Gap in the spec requires supporting 'Order' as an entity name
+ | COUNT // Gap in the spec requires supporting 'count' as a possible name
+ | KEY // Gap in the sepc requires supported 'key' as a possible name
+ | spel_expression // we use various SpEL expressions in our queries
+ ;
+
+constructor_name
+ : state_field_path_expression
+ ;
+
+literal
+ : STRINGLITERAL
+ | INTLITERAL
+ | FLOATLITERAL
+ | boolean_literal
+ | entity_type_literal
+ ;
+
+input_parameter
+ : '?' INTLITERAL
+ | ':' identification_variable
+ ;
+
+pattern_value
+ : string_expression
+ ;
+
+date_time_timestamp_literal
+ : STRINGLITERAL
+ ;
+
+entity_type_literal
+ : identification_variable
+ ;
+
+escape_character
+ : CHARACTER
+ | character_valued_input_parameter //
+ ;
+
+numeric_literal
+ : INTLITERAL
+ | FLOATLITERAL
+ ;
+
+boolean_literal
+ : TRUE
+ | FALSE
+ ;
+
+enum_literal
+ : state_field_path_expression
+ ;
+
+string_literal
+ : CHARACTER
+ | STRINGLITERAL
+ ;
+
+single_valued_embeddable_object_field
+ : identification_variable
+ ;
+
+subtype
+ : identification_variable
+ ;
+
+collection_valued_field
+ : identification_variable
+ ;
+
+single_valued_object_field
+ : identification_variable
+ ;
+
+state_field
+ : identification_variable
+ ;
+
+collection_value_field
+ : identification_variable
+ ;
+
+entity_name
+ : identification_variable
+ | identification_variable ('.' identification_variable)* // Hibernate sometimes expands the entity name to FQDN when using named queries
+ ;
+
+result_variable
+ : identification_variable
+ ;
+
+superquery_identification_variable
+ : identification_variable
+ ;
+
+collection_valued_input_parameter
+ : input_parameter
+ ;
+
+single_valued_input_parameter
+ : input_parameter
+ ;
+
+function_name
+ : string_literal
+ ;
+
+spel_expression
+ : prefix='#{#' identification_variable ('.' identification_variable)* '}' // #{#entityName}
+ | prefix='#{#[' INTLITERAL ']}' // #{[0]}
+ | prefix='#{' identification_variable '(' ( string_literal | '[' INTLITERAL ']' )? ')}' // #{escape([0])} | #{escapeCharacter()}
+ ;
+
+character_valued_input_parameter
+ : CHARACTER
+ | input_parameter
+ ;
+
+/*
+ Lexer rules
+ */
+
+
+WS : [ \t\r\n] -> skip ;
+
+// Build up case-insentive tokens
+
+fragment A: 'a' | 'A';
+fragment B: 'b' | 'B';
+fragment C: 'c' | 'C';
+fragment D: 'd' | 'D';
+fragment E: 'e' | 'E';
+fragment F: 'f' | 'F';
+fragment G: 'g' | 'G';
+fragment H: 'h' | 'H';
+fragment I: 'i' | 'I';
+fragment J: 'j' | 'J';
+fragment K: 'k' | 'K';
+fragment L: 'l' | 'L';
+fragment M: 'm' | 'M';
+fragment N: 'n' | 'N';
+fragment O: 'o' | 'O';
+fragment P: 'p' | 'P';
+fragment Q: 'q' | 'Q';
+fragment R: 'r' | 'R';
+fragment S: 's' | 'S';
+fragment T: 't' | 'T';
+fragment U: 'u' | 'U';
+fragment V: 'v' | 'V';
+fragment W: 'w' | 'W';
+fragment X: 'x' | 'X';
+fragment Y: 'y' | 'Y';
+fragment Z: 'z' | 'Z';
+
+// The following are reserved identifiers:
+
+ABS : A B S;
+ALL : A L L;
+AND : A N D;
+ANY : A N Y;
+AS : A S;
+ASC : A S C;
+AVG : A V G;
+BETWEEN : B E T W E E N;
+BOTH : B O T H;
+BY : B Y;
+CASE : C A S E;
+CEILING : C E I L I N G;
+COALESCE : C O A L E S C E;
+CONCAT : C O N C A T;
+COUNT : C O U N T;
+CURRENT_DATE : C U R R E N T '_' D A T E;
+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;
+DATE : D A T E;
+DATETIME : D A T E T I M E ;
+DELETE : D E L E T E;
+DESC : D E S C;
+DISTINCT : D I S T I N C T;
+END : E N D;
+ELSE : E L S E;
+EMPTY : E M P T Y;
+ENTRY : E N T R Y;
+ESCAPE : E S C A P 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;
+FLOOR : F L O O R;
+FROM : F R O M;
+FUNCTION : F U N C T I O N;
+GROUP : G R O U P;
+HAVING : H A V I N G;
+IN : I N;
+INDEX : I N D E X;
+INNER : I N N E R;
+IS : I S;
+JOIN : J O I N;
+KEY : K E Y;
+LEADING : L E A D I N G;
+LEFT : L E F T;
+LENGTH : L E N G T H;
+LIKE : L I K E;
+LN : L N;
+LOCAL : L O C A L;
+LOCATE : L O C A T E;
+LOWER : L O W E R;
+MAX : M A X;
+MEMBER : M E M B E R;
+MIN : M I N;
+MOD : M O D;
+NEW : N E W;
+NOT : N O T;
+NULL : N U L L;
+NULLIF : N U L L I F;
+OBJECT : O B J E C T;
+OF : O F;
+ON : O N;
+OR : O R;
+ORDER : O R D E R;
+OUTER : O U T E R;
+POWER : P O W E R;
+ROUND : R O U N D;
+SELECT : S E L E C T;
+SET : S E T;
+SIGN : S I G N;
+SIZE : S I Z E;
+SOME : S O M E;
+SQRT : S Q R T;
+SUBSTRING : S U B S T R I N G;
+SUM : S U M;
+THEN : T H E N;
+TIME : T I M E;
+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;
+TYPE : T Y P E;
+UPDATE : U P D A T E;
+UPPER : U P P E R;
+VALUE : V A L U E;
+WHEN : W H E N;
+WHERE : W H E R E;
+
+
+CHARACTER : '\'' (~ ('\'' | '\\')) '\'' ;
+IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '$' | '_')* ;
+STRINGLITERAL : '\'' (~ ('\'' | '\\'))* '\'' ;
+FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E '0' .. '9')* ;
+INTLITERAL : ('0' .. '9')+ ;
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlParsingStrategy.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlParsingStrategy.java
new file mode 100644
index 00000000000..8a1af44ad00
--- /dev/null
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlParsingStrategy.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2022-2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.query;
+
+import java.util.List;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.springframework.data.domain.Sort;
+import org.springframework.lang.Nullable;
+
+/**
+ * Implements the various parsing operations using {@link HqlTransformingVisitor} as well as {@link HqlUtils}.
+ *
+ * @author Greg Turnquist
+ * @since 3.1
+ */
+class HqlParsingStrategy implements QueryParsingStrategy {
+
+ private final DeclaredQuery query;
+
+ @Nullable private Sort sort;
+
+ HqlParsingStrategy(DeclaredQuery query, @Nullable Sort sort) {
+
+ this.query = query;
+ this.sort = sort;
+ }
+
+ HqlParsingStrategy(String query, @Nullable Sort sort) {
+ this(DeclaredQuery.of(query, false), sort);
+ }
+
+ HqlParsingStrategy(DeclaredQuery query) {
+ this(query, null);
+ }
+
+ HqlParsingStrategy(String query) {
+ this(DeclaredQuery.of(query, false), null);
+ }
+
+ @Override
+ public DeclaredQuery getDeclaredQuery() {
+ return query;
+ }
+
+ @Override
+ public ParserRuleContext parse() {
+ return HqlUtils.parseWithFastFailure(getQuery());
+ }
+
+ @Override
+ public List applySorting(ParserRuleContext parsedQuery) {
+ return new HqlTransformingVisitor(sort).visit(parsedQuery);
+ }
+
+ @Override
+ public List count(ParserRuleContext parsedQuery) {
+ return new HqlTransformingVisitor(true).visit(parsedQuery);
+ }
+
+ @Override
+ public String findAlias(ParserRuleContext parsedQuery) {
+
+ HqlTransformingVisitor transformVisitor = new HqlTransformingVisitor();
+ transformVisitor.visit(parsedQuery);
+ return transformVisitor.getAlias();
+ }
+
+ @Override
+ public List projection(ParserRuleContext parsedQuery) {
+
+ HqlTransformingVisitor transformVisitor = new HqlTransformingVisitor();
+ transformVisitor.visit(parsedQuery);
+ return transformVisitor.getProjection();
+ }
+
+ @Override
+ public boolean hasConstructor(ParserRuleContext parsedQuery) {
+
+ HqlTransformingVisitor transformVisitor = new HqlTransformingVisitor();
+ transformVisitor.visit(parsedQuery);
+ return transformVisitor.hasConstructorExpression();
+ }
+
+ @Override
+ public void setSort(Sort sort) {
+ this.sort = sort;
+ }
+}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlTransformingVisitor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlTransformingVisitor.java
new file mode 100644
index 00000000000..bbcc57dbbd0
--- /dev/null
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlTransformingVisitor.java
@@ -0,0 +1,2215 @@
+/*
+ * Copyright 2022-2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.query;
+
+import static org.springframework.data.jpa.repository.query.QueryParsingToken.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.springframework.data.domain.Sort;
+import org.springframework.lang.Nullable;
+
+/**
+ * An ANTLR visitor that transforms a parsed HQL query.
+ *
+ * @author Greg Turnquist
+ * @since 3.1
+ */
+class HqlTransformingVisitor extends HqlBaseVisitor> {
+
+ private Sort sort;
+ private boolean countQuery;
+
+ private String alias = "";
+
+ private List projection = null;
+
+ private boolean hasConstructorExpression = false;
+
+ HqlTransformingVisitor() {
+ this(null, false);
+ }
+
+ HqlTransformingVisitor(@Nullable Sort sort) {
+ this(sort, false);
+ }
+
+ HqlTransformingVisitor(boolean countQuery) {
+ this(null, countQuery);
+ }
+
+ private HqlTransformingVisitor(@Nullable Sort sort, boolean countQuery) {
+
+ this.sort = sort;
+ this.countQuery = countQuery;
+ }
+
+ public String getAlias() {
+ return this.alias;
+ }
+
+ public List getProjection() {
+ return this.projection;
+ }
+
+ public boolean hasConstructorExpression() {
+ return this.hasConstructorExpression;
+ }
+
+ /**
+ * Is this a {@literal selectState} (main select statement) or a {@literal subquery}?
+ *
+ * @return boolean
+ */
+ private static boolean isSelectStatement(ParserRuleContext ctx) {
+
+ if (ctx instanceof HqlParser.SelectStatementContext) {
+ return true;
+ } else if (ctx instanceof HqlParser.SubqueryContext) {
+ return false;
+ } else {
+ return isSelectStatement(ctx.getParent());
+ }
+ }
+
+ @Override
+ public List visitStart(HqlParser.StartContext ctx) {
+ return visit(ctx.ql_statement());
+ }
+
+ @Override
+ public List visitQl_statement(HqlParser.Ql_statementContext ctx) {
+
+ if (ctx.selectStatement() != null) {
+ return visit(ctx.selectStatement());
+ } else if (ctx.updateStatement() != null) {
+ return visit(ctx.updateStatement());
+ } else if (ctx.deleteStatement() != null) {
+ return visit(ctx.deleteStatement());
+ } else if (ctx.insertStatement() != null) {
+ return visit(ctx.insertStatement());
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitSelectStatement(HqlParser.SelectStatementContext ctx) {
+ return visit(ctx.queryExpression());
+ }
+
+ @Override
+ public List visitQueryExpression(HqlParser.QueryExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.orderedQuery(0)));
+
+ for (int i = 1; i < ctx.orderedQuery().size(); i++) {
+
+ tokens.addAll(visit(ctx.setOperator(i - 1)));
+ tokens.addAll(visit(ctx.orderedQuery(i)));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitOrderedQuery(HqlParser.OrderedQueryContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.query() != null) {
+ tokens.addAll(visit(ctx.query()));
+ } else if (ctx.queryExpression() != null) {
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.queryExpression()));
+ tokens.add(new QueryParsingToken(")", ctx));
+ }
+
+ if (!this.countQuery && isSelectStatement(ctx)) {
+
+ if (ctx.queryOrder() != null) {
+ tokens.addAll(visit(ctx.queryOrder()));
+ }
+
+ if (this.sort != null && this.sort.isSorted()) {
+
+ if (ctx.queryOrder() != null) {
+
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(",", ctx));
+ } else {
+
+ SPACE(tokens);
+ tokens.add(new QueryParsingToken("order by", ctx));
+ }
+
+ this.sort.forEach(order -> {
+
+ if (order.isIgnoreCase()) {
+ tokens.add(new QueryParsingToken("lower(", ctx, false));
+ }
+ tokens.add(new QueryParsingToken(() -> this.alias + "." + order.getProperty(), ctx, true));
+ if (order.isIgnoreCase()) {
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(")", ctx, true));
+ }
+ tokens.add(new QueryParsingToken(order.isDescending() ? "desc" : "asc", ctx, false));
+ tokens.add(new QueryParsingToken(",", ctx));
+ });
+ CLIP(tokens);
+ }
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSelectQuery(HqlParser.SelectQueryContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.selectClause() != null) {
+ tokens.addAll(visit(ctx.selectClause()));
+ }
+
+ if (ctx.fromClause() != null) {
+ tokens.addAll(visit(ctx.fromClause()));
+ }
+
+ if (ctx.whereClause() != null) {
+ tokens.addAll(visit(ctx.whereClause()));
+ }
+
+ if (ctx.groupByClause() != null) {
+ tokens.addAll(visit(ctx.groupByClause()));
+ }
+
+ if (ctx.havingClause() != null) {
+ tokens.addAll(visit(ctx.havingClause()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitFromQuery(HqlParser.FromQueryContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.fromClause() != null) {
+ tokens.addAll(visit(ctx.fromClause()));
+ }
+
+ if (ctx.whereClause() != null) {
+ tokens.addAll(visit(ctx.whereClause()));
+ }
+
+ if (ctx.groupByClause() != null) {
+ tokens.addAll(visit(ctx.groupByClause()));
+ }
+
+ if (ctx.havingClause() != null) {
+ tokens.addAll(visit(ctx.havingClause()));
+ }
+
+ if (ctx.selectClause() != null) {
+ tokens.addAll(visit(ctx.selectClause()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitQueryOrder(HqlParser.QueryOrderContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.orderByClause()));
+
+ if (ctx.limitClause() != null) {
+ tokens.addAll(visit(ctx.limitClause()));
+ }
+ if (ctx.offsetClause() != null) {
+ tokens.addAll(visit(ctx.offsetClause()));
+ }
+ if (ctx.fetchClause() != null) {
+ tokens.addAll(visit(ctx.fetchClause()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitFromClause(HqlParser.FromClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.FROM().getText(), ctx));
+
+ ctx.entityWithJoins().forEach(entityWithJoinsContext -> {
+ tokens.addAll(visit(entityWithJoinsContext));
+ tokens.add(new QueryParsingToken(",", entityWithJoinsContext));
+ });
+ CLIP(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitEntityWithJoins(HqlParser.EntityWithJoinsContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.fromRoot()));
+
+ ctx.joinSpecifier().forEach(joinSpecifierContext -> {
+ tokens.addAll(visit(joinSpecifierContext));
+ });
+
+ return tokens;
+ }
+
+ @Override
+ public List visitJoinSpecifier(HqlParser.JoinSpecifierContext ctx) {
+
+ if (ctx.join() != null) {
+ return visit(ctx.join());
+ } else if (ctx.crossJoin() != null) {
+ return visit(ctx.crossJoin());
+ } else if (ctx.jpaCollectionJoin() != null) {
+ return visit(ctx.jpaCollectionJoin());
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitFromRoot(HqlParser.FromRootContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.entityName() != null) {
+
+ tokens.addAll(visit(ctx.entityName()));
+
+ if (ctx.variable() != null) {
+ tokens.addAll(visit(ctx.variable()));
+
+ if (this.alias.equals("")) {
+ this.alias = tokens.get(tokens.size() - 1).getToken();
+ }
+ }
+ } else if (ctx.subquery() != null) {
+
+ tokens.add(new QueryParsingToken(ctx.LATERAL().getText(), ctx));
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.subquery()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ if (ctx.variable() != null) {
+ tokens.addAll(visit(ctx.variable()));
+
+ if (this.alias.equals("")) {
+ this.alias = tokens.get(tokens.size() - 1).getToken();
+ }
+ }
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitJoin(HqlParser.JoinContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.joinType()));
+ tokens.add(new QueryParsingToken(ctx.JOIN().getText(), ctx));
+
+ if (ctx.FETCH() != null) {
+ tokens.add(new QueryParsingToken(ctx.FETCH().getText(), ctx));
+ }
+
+ tokens.addAll(visit(ctx.joinTarget()));
+
+ if (ctx.joinRestriction() != null) {
+ tokens.addAll(visit(ctx.joinRestriction()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitJoinPath(HqlParser.JoinPathContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.path()));
+
+ if (ctx.variable() != null) {
+ tokens.addAll(visit(ctx.variable()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitJoinSubquery(HqlParser.JoinSubqueryContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.LATERAL() != null) {
+ tokens.add(new QueryParsingToken(ctx.LATERAL().getText(), ctx));
+ }
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.subquery()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ if (ctx.variable() != null) {
+ tokens.addAll(visit(ctx.variable()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitUpdateStatement(HqlParser.UpdateStatementContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.UPDATE().getText(), ctx));
+
+ if (ctx.VERSIONED() != null) {
+ tokens.add(new QueryParsingToken(ctx.VERSIONED().getText(), ctx));
+ }
+
+ tokens.addAll(visit(ctx.targetEntity()));
+ tokens.addAll(visit(ctx.setClause()));
+
+ if (ctx.whereClause() != null) {
+ tokens.addAll(visit(ctx.whereClause()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitTargetEntity(HqlParser.TargetEntityContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.entityName()));
+
+ if (ctx.variable() != null) {
+ tokens.addAll(visit(ctx.variable()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSetClause(HqlParser.SetClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.SET().getText(), ctx));
+
+ ctx.assignment().forEach(assignmentContext -> {
+ tokens.addAll(visit(assignmentContext));
+ tokens.add(new QueryParsingToken(",", assignmentContext));
+ });
+ CLIP(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitAssignment(HqlParser.AssignmentContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.simplePath()));
+ tokens.add(new QueryParsingToken("=", ctx));
+ tokens.addAll(visit(ctx.expressionOrPredicate()));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitDeleteStatement(HqlParser.DeleteStatementContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.DELETE().getText(), ctx));
+
+ if (ctx.FROM() != null) {
+ tokens.add(new QueryParsingToken(ctx.FROM().getText(), ctx));
+ }
+
+ tokens.addAll(visit(ctx.targetEntity()));
+
+ if (ctx.whereClause() != null) {
+ tokens.addAll(visit(ctx.whereClause()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitInsertStatement(HqlParser.InsertStatementContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.INSERT().getText(), ctx));
+
+ if (ctx.INTO() != null) {
+ tokens.add(new QueryParsingToken(ctx.INTO().getText(), ctx));
+ }
+
+ tokens.addAll(visit(ctx.targetEntity()));
+ tokens.addAll(visit(ctx.targetFields()));
+
+ if (ctx.queryExpression() != null) {
+ tokens.addAll(visit(ctx.queryExpression()));
+ } else if (ctx.valuesList() != null) {
+ tokens.addAll(visit(ctx.valuesList()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitTargetFields(HqlParser.TargetFieldsContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+
+ ctx.simplePath().forEach(simplePathContext -> {
+ tokens.addAll(visit(simplePathContext));
+ tokens.add(new QueryParsingToken(",", simplePathContext));
+ });
+ CLIP(tokens);
+
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitValuesList(HqlParser.ValuesListContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.VALUES().getText(), ctx));
+
+ ctx.values().forEach(valuesContext -> {
+ tokens.addAll(visit(valuesContext));
+ tokens.add(new QueryParsingToken(",", valuesContext));
+ });
+ CLIP(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitValues(HqlParser.ValuesContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+
+ ctx.expression().forEach(expressionContext -> {
+ tokens.addAll(visit(expressionContext));
+ tokens.add(new QueryParsingToken(",", expressionContext));
+ });
+ CLIP(tokens);
+
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitProjectedItem(HqlParser.ProjectedItemContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.expression() != null) {
+ tokens.addAll(visit(ctx.expression()));
+ } else if (ctx.instantiation() != null) {
+ tokens.addAll(visit(ctx.instantiation()));
+ }
+
+ if (ctx.alias() != null) {
+ tokens.addAll(visit(ctx.alias()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitInstantiation(HqlParser.InstantiationContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ this.hasConstructorExpression = true;
+
+ tokens.add(new QueryParsingToken(ctx.NEW().getText(), ctx));
+ tokens.addAll(visit(ctx.instantiationTarget()));
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.instantiationArguments()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitAlias(HqlParser.AliasContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.AS() != null) {
+ tokens.add(new QueryParsingToken(ctx.AS().getText(), ctx));
+ }
+
+ tokens.addAll(visit(ctx.identifier()));
+
+ if (this.alias.equals("")) {
+ this.alias = tokens.get(tokens.size() - 1).getToken();
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitGroupedItem(HqlParser.GroupedItemContext ctx) {
+
+ if (ctx.identifier() != null) {
+ return visit(ctx.identifier());
+ } else if (ctx.INTEGER_LITERAL() != null) {
+ return List.of(new QueryParsingToken(ctx.INTEGER_LITERAL().getText(), ctx));
+ } else if (ctx.expression() != null) {
+ return visit(ctx.expression());
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitSortedItem(HqlParser.SortedItemContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.sortExpression()));
+
+ if (ctx.sortDirection() != null) {
+ tokens.addAll(visit(ctx.sortDirection()));
+ }
+
+ if (ctx.nullsPrecedence() != null) {
+ tokens.addAll(visit(ctx.nullsPrecedence()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSortExpression(HqlParser.SortExpressionContext ctx) {
+
+ if (ctx.identifier() != null) {
+ return visit(ctx.identifier());
+ } else if (ctx.INTEGER_LITERAL() != null) {
+ return List.of(new QueryParsingToken(ctx.INTEGER_LITERAL().getText(), ctx));
+ } else if (ctx.expression() != null) {
+ return visit(ctx.expression());
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitSortDirection(HqlParser.SortDirectionContext ctx) {
+
+ if (ctx.ASC() != null) {
+ return List.of(new QueryParsingToken(ctx.ASC().getText(), ctx));
+ } else if (ctx.DESC() != null) {
+ return List.of(new QueryParsingToken(ctx.DESC().getText(), ctx));
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitNullsPrecedence(HqlParser.NullsPrecedenceContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.NULLS().getText(), ctx));
+
+ if (ctx.FIRST() != null) {
+ tokens.add(new QueryParsingToken(ctx.FIRST().getText(), ctx));
+ } else if (ctx.LAST() != null) {
+ tokens.add(new QueryParsingToken(ctx.LAST().getText(), ctx));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitLimitClause(HqlParser.LimitClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.LIMIT().getText(), ctx));
+ tokens.addAll(visit(ctx.parameterOrIntegerLiteral()));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitOffsetClause(HqlParser.OffsetClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.OFFSET().getText(), ctx));
+ tokens.addAll(visit(ctx.parameterOrIntegerLiteral()));
+
+ if (ctx.ROW() != null) {
+ tokens.add(new QueryParsingToken(ctx.ROW().getText(), ctx));
+ } else if (ctx.ROWS() != null) {
+ tokens.add(new QueryParsingToken(ctx.ROWS().getText(), ctx));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitFetchClause(HqlParser.FetchClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.FETCH().getText(), ctx));
+
+ if (ctx.FIRST() != null) {
+ tokens.add(new QueryParsingToken(ctx.FIRST().getText(), ctx));
+ } else if (ctx.NEXT() != null) {
+ tokens.add(new QueryParsingToken(ctx.NEXT().getText(), ctx));
+ }
+
+ if (ctx.parameterOrIntegerLiteral() != null) {
+ tokens.addAll(visit(ctx.parameterOrIntegerLiteral()));
+ } else if (ctx.parameterOrNumberLiteral() != null) {
+
+ tokens.addAll(visit(ctx.parameterOrNumberLiteral()));
+ tokens.add(new QueryParsingToken("%", ctx));
+ }
+
+ if (ctx.ROW() != null) {
+ tokens.add(new QueryParsingToken(ctx.ROW().getText(), ctx));
+ } else if (ctx.ROWS() != null) {
+ tokens.add(new QueryParsingToken(ctx.ROWS().getText(), ctx));
+ }
+
+ if (ctx.ONLY() != null) {
+ tokens.add(new QueryParsingToken(ctx.ONLY().getText(), ctx));
+ } else if (ctx.WITH() != null) {
+
+ tokens.add(new QueryParsingToken(ctx.WITH().getText(), ctx));
+ tokens.add(new QueryParsingToken(ctx.TIES().getText(), ctx));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSubquery(HqlParser.SubqueryContext ctx) {
+ return visit(ctx.queryExpression());
+ }
+
+ @Override
+ public List visitSelectClause(HqlParser.SelectClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.SELECT().getText(), ctx));
+
+ if (this.countQuery && isSelectStatement(ctx)) {
+ tokens.add(new QueryParsingToken("count(", ctx, false));
+ }
+
+ if (ctx.DISTINCT() != null) {
+ tokens.add(new QueryParsingToken(ctx.DISTINCT().getText(), ctx));
+ }
+
+ List selectionListTokens = visit(ctx.selectionList());
+
+ if (this.countQuery && isSelectStatement(ctx)) {
+
+ if (ctx.DISTINCT() != null) {
+
+ if (selectionListTokens.stream().anyMatch(hqlToken -> hqlToken.getToken().contains("new"))) {
+ // constructor
+ tokens.add(new QueryParsingToken(() -> this.alias, ctx));
+ } else {
+ // keep all the select items to distinct against
+ tokens.addAll(selectionListTokens);
+ }
+ } else {
+ tokens.add(new QueryParsingToken(() -> this.alias, ctx));
+ }
+
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(")", ctx));
+ } else {
+ tokens.addAll(selectionListTokens);
+ }
+
+ this.projection = selectionListTokens;
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSelectionList(HqlParser.SelectionListContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ ctx.selection().forEach(selectionContext -> {
+ tokens.addAll(visit(selectionContext));
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(",", selectionContext));
+ });
+ CLIP(tokens);
+ SPACE(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSelection(HqlParser.SelectionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.selectExpression()));
+
+ if (ctx.variable() != null) {
+ tokens.addAll(visit(ctx.variable()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSelectExpression(HqlParser.SelectExpressionContext ctx) {
+
+ if (ctx.instantiation() != null) {
+ return visit(ctx.instantiation());
+ } else if (ctx.mapEntrySelection() != null) {
+ return visit(ctx.mapEntrySelection());
+ } else if (ctx.jpaSelectObjectSyntax() != null) {
+ return visit(ctx.jpaSelectObjectSyntax());
+ } else if (ctx.expressionOrPredicate() != null) {
+ return visit(ctx.expressionOrPredicate());
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitMapEntrySelection(HqlParser.MapEntrySelectionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.ENTRY().getText(), ctx));
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.path()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitJpaSelectObjectSyntax(HqlParser.JpaSelectObjectSyntaxContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.OBJECT().getText(), ctx));
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.identifier()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitWhereClause(HqlParser.WhereClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.WHERE().getText(), ctx));
+
+ ctx.predicate().forEach(predicateContext -> {
+ tokens.addAll(visit(predicateContext));
+ tokens.add(new QueryParsingToken(",", predicateContext));
+ });
+ CLIP(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitJoinType(HqlParser.JoinTypeContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.INNER() != null) {
+ tokens.add(new QueryParsingToken(ctx.INNER().getText(), ctx));
+ }
+ if (ctx.LEFT() != null) {
+ tokens.add(new QueryParsingToken(ctx.LEFT().getText(), ctx));
+ }
+ if (ctx.RIGHT() != null) {
+ tokens.add(new QueryParsingToken(ctx.RIGHT().getText(), ctx));
+ }
+ if (ctx.FULL() != null) {
+ tokens.add(new QueryParsingToken(ctx.FULL().getText(), ctx));
+ }
+ if (ctx.OUTER() != null) {
+ tokens.add(new QueryParsingToken(ctx.OUTER().getText(), ctx));
+ }
+ if (ctx.CROSS() != null) {
+ tokens.add(new QueryParsingToken(ctx.CROSS().getText(), ctx));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitCrossJoin(HqlParser.CrossJoinContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.CROSS().getText(), ctx));
+ tokens.add(new QueryParsingToken(ctx.JOIN().getText(), ctx));
+ tokens.addAll(visit(ctx.entityName()));
+
+ if (ctx.variable() != null) {
+ tokens.addAll(visit(ctx.variable()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitJoinRestriction(HqlParser.JoinRestrictionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.ON() != null) {
+ tokens.add(new QueryParsingToken(ctx.ON().getText(), ctx));
+ } else if (ctx.WITH() != null) {
+ tokens.add(new QueryParsingToken(ctx.WITH().getText(), ctx));
+ }
+
+ tokens.addAll(visit(ctx.predicate()));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitJpaCollectionJoin(HqlParser.JpaCollectionJoinContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(",", ctx));
+ tokens.add(new QueryParsingToken(ctx.IN().getText(), ctx));
+ tokens.addAll(visit(ctx.path()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ if (ctx.variable() != null) {
+ tokens.addAll(visit(ctx.variable()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitGroupByClause(HqlParser.GroupByClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.GROUP().getText(), ctx));
+ tokens.add(new QueryParsingToken(ctx.BY().getText(), ctx));
+
+ ctx.groupedItem().forEach(groupedItemContext -> {
+ tokens.addAll(visit(groupedItemContext));
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(",", groupedItemContext));
+ });
+ CLIP(tokens);
+ SPACE(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitOrderByClause(HqlParser.OrderByClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.ORDER().getText(), ctx));
+ tokens.add(new QueryParsingToken(ctx.BY().getText(), ctx));
+
+ ctx.projectedItem().forEach(projectedItemContext -> {
+ tokens.addAll(visit(projectedItemContext));
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(",", projectedItemContext));
+ });
+ CLIP(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitHavingClause(HqlParser.HavingClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.HAVING().getText(), ctx));
+
+ ctx.predicate().forEach(predicateContext -> {
+ tokens.addAll(visit(predicateContext));
+ tokens.add(new QueryParsingToken(",", predicateContext));
+ });
+ CLIP(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSetOperator(HqlParser.SetOperatorContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.UNION() != null) {
+ tokens.add(new QueryParsingToken(ctx.UNION().getText(), ctx));
+ } else if (ctx.INTERSECT() != null) {
+ tokens.add(new QueryParsingToken(ctx.INTERSECT().getText(), ctx));
+ } else if (ctx.EXCEPT() != null) {
+ tokens.add(new QueryParsingToken(ctx.EXCEPT().getText(), ctx));
+ }
+
+ if (ctx.ALL() != null) {
+ tokens.add(new QueryParsingToken(ctx.ALL().getText(), ctx));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitLiteral(HqlParser.LiteralContext ctx) {
+
+ if (ctx.NULL() != null) {
+ return List.of(new QueryParsingToken(ctx.NULL().getText(), ctx));
+ } else if (ctx.booleanLiteral() != null) {
+ return visit(ctx.booleanLiteral());
+ } else if (ctx.stringLiteral() != null) {
+ return visit(ctx.stringLiteral());
+ } else if (ctx.numericLiteral() != null) {
+ return visit(ctx.numericLiteral());
+ } else if (ctx.dateTimeLiteral() != null) {
+ return visit(ctx.dateTimeLiteral());
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitBooleanLiteral(HqlParser.BooleanLiteralContext ctx) {
+
+ if (ctx.TRUE() != null) {
+ return List.of(new QueryParsingToken(ctx.TRUE().getText(), ctx));
+ } else if (ctx.FALSE() != null) {
+ return List.of(new QueryParsingToken(ctx.FALSE().getText(), ctx));
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitStringLiteral(HqlParser.StringLiteralContext ctx) {
+
+ if (ctx.STRINGLITERAL() != null) {
+ return List.of(new QueryParsingToken(ctx.STRINGLITERAL().getText(), ctx));
+ } else if (ctx.CHARACTER() != null) {
+ return List.of(new QueryParsingToken(ctx.CHARACTER().getText(), ctx));
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitNumericLiteral(HqlParser.NumericLiteralContext ctx) {
+
+ if (ctx.INTEGER_LITERAL() != null) {
+ return List.of(new QueryParsingToken(ctx.INTEGER_LITERAL().getText(), ctx));
+ } else if (ctx.FLOAT_LITERAL() != null) {
+ return List.of(new QueryParsingToken(ctx.FLOAT_LITERAL().getText(), ctx));
+ } else if (ctx.HEXLITERAL() != null) {
+ return List.of(new QueryParsingToken(ctx.HEXLITERAL().getText(), ctx));
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitDateTimeLiteral(HqlParser.DateTimeLiteralContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.LOCAL_DATE() != null) {
+ tokens.add(new QueryParsingToken(ctx.LOCAL_DATE().getText(), ctx));
+ } else if (ctx.LOCAL_TIME() != null) {
+ tokens.add(new QueryParsingToken(ctx.LOCAL_TIME().getText(), ctx));
+ } else if (ctx.LOCAL_DATETIME() != null) {
+ tokens.add(new QueryParsingToken(ctx.LOCAL_DATETIME().getText(), ctx));
+ } else if (ctx.CURRENT_DATE() != null) {
+ tokens.add(new QueryParsingToken(ctx.CURRENT_DATE().getText(), ctx));
+ } else if (ctx.CURRENT_TIME() != null) {
+ tokens.add(new QueryParsingToken(ctx.CURRENT_TIME().getText(), ctx));
+ } else if (ctx.CURRENT_TIMESTAMP() != null) {
+ tokens.add(new QueryParsingToken(ctx.CURRENT_TIMESTAMP().getText(), ctx));
+ } else if (ctx.OFFSET_DATETIME() != null) {
+ tokens.add(new QueryParsingToken(ctx.OFFSET_DATETIME().getText(), ctx));
+ } else {
+
+ if (ctx.LOCAL() != null) {
+ tokens.add(new QueryParsingToken(ctx.LOCAL().getText(), ctx));
+ } else if (ctx.CURRENT() != null) {
+ tokens.add(new QueryParsingToken(ctx.CURRENT().getText(), ctx));
+ } else if (ctx.OFFSET() != null) {
+ tokens.add(new QueryParsingToken(ctx.OFFSET().getText(), ctx));
+ }
+
+ if (ctx.DATE() != null) {
+ tokens.add(new QueryParsingToken(ctx.DATE().getText(), ctx));
+ } else if (ctx.TIME() != null) {
+ tokens.add(new QueryParsingToken(ctx.TIME().getText(), ctx));
+ } else if (ctx.DATETIME() != null) {
+ tokens.add(new QueryParsingToken(ctx.DATETIME().getText(), ctx));
+ }
+
+ if (ctx.INSTANT() != null) {
+ tokens.add(new QueryParsingToken(ctx.INSTANT().getText(), ctx));
+ }
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitPlainPrimaryExpression(HqlParser.PlainPrimaryExpressionContext ctx) {
+ return visit(ctx.primaryExpression());
+ }
+
+ @Override
+ public List visitTupleExpression(HqlParser.TupleExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+
+ ctx.expressionOrPredicate().forEach(expressionOrPredicateContext -> {
+ tokens.addAll(visit(expressionOrPredicateContext));
+ tokens.add(new QueryParsingToken(",", expressionOrPredicateContext));
+ });
+ CLIP(tokens);
+
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitHqlConcatenationExpression(HqlParser.HqlConcatenationExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.expression(0)));
+ tokens.add(new QueryParsingToken("||", ctx));
+ tokens.addAll(visit(ctx.expression(1)));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitGroupedExpression(HqlParser.GroupedExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.expression()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitAdditionExpression(HqlParser.AdditionExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.expression(0)));
+ tokens.add(new QueryParsingToken(ctx.op.getText(), ctx));
+ tokens.addAll(visit(ctx.expression(1)));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSignedNumericLiteral(HqlParser.SignedNumericLiteralContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.op.getText(), ctx));
+ tokens.addAll(visit(ctx.numericLiteral()));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitMultiplicationExpression(HqlParser.MultiplicationExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.expression(0)));
+ tokens.add(new QueryParsingToken(ctx.op.getText(), ctx));
+ tokens.addAll(visit(ctx.expression(1)));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSubqueryExpression(HqlParser.SubqueryExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.subquery()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSignedExpression(HqlParser.SignedExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.op.getText(), ctx));
+ tokens.addAll(visit(ctx.expression()));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitCaseExpression(HqlParser.CaseExpressionContext ctx) {
+ return visit(ctx.caseList());
+ }
+
+ @Override
+ public List visitLiteralExpression(HqlParser.LiteralExpressionContext ctx) {
+ return visit(ctx.literal());
+ }
+
+ @Override
+ public List visitParameterExpression(HqlParser.ParameterExpressionContext ctx) {
+ return visit(ctx.parameter());
+ }
+
+ @Override
+ public List visitFunctionExpression(HqlParser.FunctionExpressionContext ctx) {
+ return visit(ctx.function());
+ }
+
+ @Override
+ public List visitGeneralPathExpression(HqlParser.GeneralPathExpressionContext ctx) {
+ return visit(ctx.generalPathFragment());
+ }
+
+ @Override
+ public List visitIdentificationVariable(HqlParser.IdentificationVariableContext ctx) {
+
+ if (ctx.identifier() != null) {
+ return visit(ctx.identifier());
+ } else if (ctx.simplePath() != null) {
+ return visit(ctx.simplePath());
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitPath(HqlParser.PathContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.treatedPath() != null) {
+
+ tokens.addAll(visit(ctx.treatedPath()));
+
+ if (ctx.pathContinutation() != null) {
+ tokens.addAll(visit(ctx.pathContinutation()));
+ }
+ } else if (ctx.generalPathFragment() != null) {
+ tokens.addAll(visit(ctx.generalPathFragment()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitGeneralPathFragment(HqlParser.GeneralPathFragmentContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.simplePath()));
+
+ if (ctx.indexedPathAccessFragment() != null) {
+ tokens.addAll(visit(ctx.indexedPathAccessFragment()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitIndexedPathAccessFragment(HqlParser.IndexedPathAccessFragmentContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken("[", ctx, false));
+ tokens.addAll(visit(ctx.expression()));
+ tokens.add(new QueryParsingToken("]", ctx));
+
+ if (ctx.generalPathFragment() != null) {
+
+ tokens.add(new QueryParsingToken(".", ctx, false));
+ tokens.addAll(visit(ctx.generalPathFragment()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSimplePath(HqlParser.SimplePathContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.identifier()));
+
+ ctx.simplePathElement().forEach(simplePathElementContext -> {
+ tokens.addAll(visit(simplePathElementContext));
+ });
+
+ tokens.forEach(hqlToken -> hqlToken.setSpace(false));
+ SPACE(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSimplePathElement(HqlParser.SimplePathElementContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(".", ctx, false));
+ tokens.addAll(visit(ctx.identifier()));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitCaseList(HqlParser.CaseListContext ctx) {
+
+ if (ctx.simpleCaseExpression() != null) {
+ return visit(ctx.simpleCaseExpression());
+ } else if (ctx.searchedCaseExpression() != null) {
+ return visit(ctx.searchedCaseExpression());
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitSimpleCaseExpression(HqlParser.SimpleCaseExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.CASE().getText(), ctx));
+ tokens.addAll(visit(ctx.expressionOrPredicate(0)));
+
+ ctx.caseWhenExpressionClause().forEach(caseWhenExpressionClauseContext -> {
+ tokens.addAll(visit(caseWhenExpressionClauseContext));
+ });
+
+ if (ctx.ELSE() != null) {
+
+ tokens.add(new QueryParsingToken(ctx.ELSE().getText(), ctx));
+ tokens.addAll(visit(ctx.expressionOrPredicate(1)));
+ }
+
+ tokens.add(new QueryParsingToken(ctx.END().getText(), ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSearchedCaseExpression(HqlParser.SearchedCaseExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.CASE().getText(), ctx));
+
+ ctx.caseWhenPredicateClause().forEach(caseWhenPredicateClauseContext -> {
+ tokens.addAll(visit(caseWhenPredicateClauseContext));
+ });
+
+ if (ctx.ELSE() != null) {
+
+ tokens.add(new QueryParsingToken(ctx.ELSE().getText(), ctx));
+ tokens.addAll(visit(ctx.expressionOrPredicate()));
+ }
+
+ tokens.add(new QueryParsingToken(ctx.END().getText(), ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitCaseWhenExpressionClause(HqlParser.CaseWhenExpressionClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.WHEN().getText(), ctx));
+ tokens.addAll(visit(ctx.expression()));
+ tokens.add(new QueryParsingToken(ctx.THEN().getText(), ctx));
+ tokens.addAll(visit(ctx.expressionOrPredicate()));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitCaseWhenPredicateClause(HqlParser.CaseWhenPredicateClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.WHEN().getText(), ctx));
+ tokens.addAll(visit(ctx.predicate()));
+ tokens.add(new QueryParsingToken(ctx.THEN().getText(), ctx));
+ tokens.addAll(visit(ctx.expressionOrPredicate()));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitGenericFunction(HqlParser.GenericFunctionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.functionName()));
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken("(", ctx, false));
+
+ if (ctx.functionArguments() != null) {
+ tokens.addAll(visit(ctx.functionArguments()));
+ } else if (ctx.ASTERISK() != null) {
+ tokens.add(new QueryParsingToken(ctx.ASTERISK().getText(), ctx));
+ }
+
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ if (ctx.pathContinutation() != null) {
+ tokens.addAll(visit(ctx.pathContinutation()));
+ }
+
+ if (ctx.filterClause() != null) {
+ tokens.addAll(visit(ctx.filterClause()));
+ }
+
+ if (ctx.withinGroup() != null) {
+ tokens.addAll(visit(ctx.withinGroup()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitFunctionWithSubquery(HqlParser.FunctionWithSubqueryContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.functionName()));
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.subquery()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitCastFunctionInvocation(HqlParser.CastFunctionInvocationContext ctx) {
+ return visit(ctx.castFunction());
+ }
+
+ @Override
+ public List visitExtractFunctionInvocation(HqlParser.ExtractFunctionInvocationContext ctx) {
+ return visit(ctx.extractFunction());
+ }
+
+ @Override
+ public List visitTrimFunctionInvocation(HqlParser.TrimFunctionInvocationContext ctx) {
+ return visit(ctx.trimFunction());
+ }
+
+ @Override
+ public List visitEveryFunctionInvocation(HqlParser.EveryFunctionInvocationContext ctx) {
+ return visit(ctx.everyFunction());
+ }
+
+ @Override
+ public List visitAnyFunctionInvocation(HqlParser.AnyFunctionInvocationContext ctx) {
+ return visit(ctx.anyFunction());
+ }
+
+ @Override
+ public List visitTreatedPathInvocation(HqlParser.TreatedPathInvocationContext ctx) {
+ return visit(ctx.treatedPath());
+ }
+
+ @Override
+ public List visitFunctionArguments(HqlParser.FunctionArgumentsContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.DISTINCT() != null) {
+ tokens.add(new QueryParsingToken(ctx.DISTINCT().getText(), ctx));
+ }
+
+ ctx.expressionOrPredicate().forEach(expressionOrPredicateContext -> {
+ tokens.addAll(visit(expressionOrPredicateContext));
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(",", expressionOrPredicateContext));
+ });
+ CLIP(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitFilterClause(HqlParser.FilterClauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.FILTER().getText(), ctx));
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.whereClause()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitWithinGroup(HqlParser.WithinGroupContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.WITHIN().getText(), ctx));
+ tokens.add(new QueryParsingToken(ctx.GROUP().getText(), ctx));
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.orderByClause()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitCastFunction(HqlParser.CastFunctionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.CAST().getText(), ctx));
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.expression()));
+ tokens.add(new QueryParsingToken(ctx.AS().getText(), ctx));
+ tokens.addAll(visit(ctx.identifier()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitExtractFunction(HqlParser.ExtractFunctionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.EXTRACT() != null) {
+
+ tokens.add(new QueryParsingToken(ctx.EXTRACT().getText(), ctx));
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.expression(0)));
+ tokens.add(new QueryParsingToken(ctx.FROM().getText(), ctx));
+ tokens.addAll(visit(ctx.expression(1)));
+ tokens.add(new QueryParsingToken(")", ctx));
+ } else if (ctx.dateTimeFunction() != null) {
+
+ tokens.addAll(visit(ctx.dateTimeFunction()));
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.expression(0)));
+ tokens.add(new QueryParsingToken(")", ctx));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitTrimFunction(HqlParser.TrimFunctionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.TRIM().getText(), ctx));
+ tokens.add(new QueryParsingToken("(", ctx, false));
+
+ if (ctx.LEADING() != null) {
+ tokens.add(new QueryParsingToken(ctx.LEADING().getText(), ctx));
+ } else if (ctx.TRAILING() != null) {
+ tokens.add(new QueryParsingToken(ctx.TRAILING().getText(), ctx));
+ } else if (ctx.BOTH() != null) {
+ tokens.add(new QueryParsingToken(ctx.BOTH().getText(), ctx));
+ }
+
+ if (ctx.stringLiteral() != null) {
+ tokens.addAll(visit(ctx.stringLiteral()));
+ }
+
+ if (ctx.FROM() != null) {
+ tokens.add(new QueryParsingToken(ctx.FROM().getText(), ctx));
+ }
+
+ tokens.addAll(visit(ctx.expression()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitDateTimeFunction(HqlParser.DateTimeFunctionContext ctx) {
+ return List.of(new QueryParsingToken(ctx.d.getText(), ctx));
+ }
+
+ @Override
+ public List visitEveryFunction(HqlParser.EveryFunctionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.every.getText(), ctx));
+
+ if (ctx.ELEMENTS() != null) {
+ tokens.add(new QueryParsingToken(ctx.ELEMENTS().getText(), ctx));
+ } else if (ctx.INDICES() != null) {
+ tokens.add(new QueryParsingToken(ctx.INDICES().getText(), ctx));
+ }
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+
+ if (ctx.predicate() != null) {
+ tokens.addAll(visit(ctx.predicate()));
+ } else if (ctx.subquery() != null) {
+ tokens.addAll(visit(ctx.subquery()));
+ } else if (ctx.simplePath() != null) {
+ tokens.addAll(visit(ctx.simplePath()));
+ }
+
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitAnyFunction(HqlParser.AnyFunctionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.any.getText(), ctx));
+
+ if (ctx.ELEMENTS() != null) {
+ tokens.add(new QueryParsingToken(ctx.ELEMENTS().getText(), ctx));
+ } else if (ctx.INDICES() != null) {
+ tokens.add(new QueryParsingToken(ctx.INDICES().getText(), ctx));
+ }
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+
+ if (ctx.predicate() != null) {
+ tokens.addAll(visit(ctx.predicate()));
+ } else if (ctx.subquery() != null) {
+ tokens.addAll(visit(ctx.subquery()));
+ } else if (ctx.simplePath() != null) {
+ tokens.addAll(visit(ctx.simplePath()));
+ }
+
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitTreatedPath(HqlParser.TreatedPathContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.TREAT().getText(), ctx));
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.path()));
+ tokens.add(new QueryParsingToken(ctx.AS().getText(), ctx));
+ tokens.addAll(visit(ctx.simplePath()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ if (ctx.pathContinutation() != null) {
+ tokens.addAll(visit(ctx.pathContinutation()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitPathContinutation(HqlParser.PathContinutationContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(".", ctx, false));
+ tokens.addAll(visit(ctx.simplePath()));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitNullExpressionPredicate(HqlParser.NullExpressionPredicateContext ctx) {
+ return visit(ctx.dealingWithNullExpression());
+ }
+
+ @Override
+ public List visitBetweenPredicate(HqlParser.BetweenPredicateContext ctx) {
+ return visit(ctx.betweenExpression());
+ }
+
+ @Override
+ public List visitOrPredicate(HqlParser.OrPredicateContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.predicate(0)));
+ tokens.add(new QueryParsingToken(ctx.OR().getText(), ctx));
+ tokens.addAll(visit(ctx.predicate(1)));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitRelationalPredicate(HqlParser.RelationalPredicateContext ctx) {
+ return visit(ctx.relationalExpression());
+ }
+
+ @Override
+ public List visitExistsPredicate(HqlParser.ExistsPredicateContext ctx) {
+ return visit(ctx.existsExpression());
+ }
+
+ @Override
+ public List visitCollectionPredicate(HqlParser.CollectionPredicateContext ctx) {
+ return visit(ctx.collectionExpression());
+ }
+
+ @Override
+ public List visitAndPredicate(HqlParser.AndPredicateContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.predicate(0)));
+ tokens.add(new QueryParsingToken(ctx.AND().getText(), ctx));
+ tokens.addAll(visit(ctx.predicate(1)));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitGroupedPredicate(HqlParser.GroupedPredicateContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.predicate()));
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitLikePredicate(HqlParser.LikePredicateContext ctx) {
+ return visit(ctx.stringPatternMatching());
+ }
+
+ @Override
+ public List visitInPredicate(HqlParser.InPredicateContext ctx) {
+ return visit(ctx.inExpression());
+ }
+
+ @Override
+ public List visitNotPredicate(HqlParser.NotPredicateContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.NOT().getText(), ctx));
+ tokens.addAll(visit(ctx.predicate()));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitExpressionPredicate(HqlParser.ExpressionPredicateContext ctx) {
+ return visit(ctx.expression());
+ }
+
+ @Override
+ public List visitExpressionOrPredicate(HqlParser.ExpressionOrPredicateContext ctx) {
+
+ if (ctx.expression() != null) {
+ return visit(ctx.expression());
+ } else if (ctx.predicate() != null) {
+ return visit(ctx.predicate());
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitRelationalExpression(HqlParser.RelationalExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.expression(0)));
+ tokens.add(new QueryParsingToken(ctx.op.getText(), ctx));
+ tokens.addAll(visit(ctx.expression(1)));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitBetweenExpression(HqlParser.BetweenExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.expression(0)));
+
+ if (ctx.NOT() != null) {
+ tokens.add(new QueryParsingToken(ctx.NOT().getText(), ctx));
+ }
+
+ tokens.add(new QueryParsingToken(ctx.BETWEEN().getText(), ctx));
+ tokens.addAll(visit(ctx.expression(1)));
+ tokens.add(new QueryParsingToken(ctx.AND().getText(), ctx));
+ tokens.addAll(visit(ctx.expression(2)));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitDealingWithNullExpression(HqlParser.DealingWithNullExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.expression(0)));
+ tokens.add(new QueryParsingToken(ctx.IS().getText(), ctx));
+
+ if (ctx.NOT() != null) {
+ tokens.add(new QueryParsingToken(ctx.NOT().getText(), ctx));
+ }
+
+ if (ctx.NULL() != null) {
+ tokens.add(new QueryParsingToken(ctx.NULL().getText(), ctx));
+ } else if (ctx.DISTINCT() != null) {
+
+ tokens.add(new QueryParsingToken(ctx.DISTINCT().getText(), ctx));
+ tokens.add(new QueryParsingToken(ctx.FROM().getText(), ctx));
+ tokens.addAll(visit(ctx.expression(1)));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitStringPatternMatching(HqlParser.StringPatternMatchingContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.expression(0)));
+
+ if (ctx.NOT() != null) {
+ tokens.add(new QueryParsingToken(ctx.NOT().getText(), ctx));
+ }
+
+ if (ctx.LIKE() != null) {
+ tokens.add(new QueryParsingToken(ctx.LIKE().getText(), ctx));
+ } else if (ctx.ILIKE() != null) {
+ tokens.add(new QueryParsingToken(ctx.ILIKE().getText(), ctx));
+ }
+
+ tokens.addAll(visit(ctx.expression(1)));
+
+ if (ctx.ESCAPE() != null) {
+
+ tokens.add(new QueryParsingToken(ctx.ESCAPE().getText(), ctx));
+ tokens.addAll(visit(ctx.character()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitInExpression(HqlParser.InExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.expression()));
+
+ if (ctx.NOT() != null) {
+ tokens.add(new QueryParsingToken(ctx.NOT().getText(), ctx));
+ }
+
+ tokens.add(new QueryParsingToken(ctx.IN().getText(), ctx));
+ tokens.addAll(visit(ctx.inList()));
+
+ return tokens;
+ }
+
+ @Override
+ public List visitInList(HqlParser.InListContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.simplePath() != null) {
+
+ if (ctx.ELEMENTS() != null) {
+ tokens.add(new QueryParsingToken(ctx.ELEMENTS().getText(), ctx));
+ } else if (ctx.INDICES() != null) {
+ tokens.add(new QueryParsingToken(ctx.INDICES().getText(), ctx));
+ }
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.simplePath()));
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(")", ctx));
+ } else if (ctx.subquery() != null) {
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.subquery()));
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(")", ctx));
+ } else if (ctx.parameter() != null) {
+ tokens.addAll(visit(ctx.parameter()));
+ } else if (ctx.expressionOrPredicate() != null) {
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+
+ ctx.expressionOrPredicate().forEach(expressionOrPredicateContext -> {
+ tokens.addAll(visit(expressionOrPredicateContext));
+ tokens.add(new QueryParsingToken(",", expressionOrPredicateContext));
+ });
+ CLIP(tokens);
+
+ tokens.add(new QueryParsingToken(")", ctx));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitExistsExpression(HqlParser.ExistsExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.simplePath() != null) {
+
+ tokens.add(new QueryParsingToken(ctx.EXISTS().getText(), ctx));
+
+ if (ctx.ELEMENTS() != null) {
+ tokens.add(new QueryParsingToken(ctx.ELEMENTS().getText(), ctx));
+ } else if (ctx.INDICES() != null) {
+ tokens.add(new QueryParsingToken(ctx.INDICES().getText(), ctx));
+ }
+
+ tokens.add(new QueryParsingToken("(", ctx, false));
+ tokens.addAll(visit(ctx.simplePath()));
+ tokens.add(new QueryParsingToken(")", ctx));
+
+ } else if (ctx.expression() != null) {
+
+ tokens.add(new QueryParsingToken(ctx.EXISTS().getText(), ctx));
+ tokens.addAll(visit(ctx.expression()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitCollectionExpression(HqlParser.CollectionExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.expression()));
+
+ if (ctx.IS() != null) {
+
+ tokens.add(new QueryParsingToken(ctx.IS().getText(), ctx));
+
+ if (ctx.NOT() != null) {
+ tokens.add(new QueryParsingToken(ctx.NOT().getText(), ctx));
+ }
+
+ tokens.add(new QueryParsingToken(ctx.EMPTY().getText(), ctx));
+ } else if (ctx.MEMBER() != null) {
+
+ if (ctx.NOT() != null) {
+ tokens.add(new QueryParsingToken(ctx.NOT().getText(), ctx));
+ }
+
+ tokens.add(new QueryParsingToken(ctx.MEMBER().getText(), ctx));
+ tokens.add(new QueryParsingToken(ctx.OF().getText(), ctx));
+ tokens.addAll(visit(ctx.path()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitInstantiationTarget(HqlParser.InstantiationTargetContext ctx) {
+
+ if (ctx.LIST() != null) {
+ return List.of(new QueryParsingToken(ctx.LIST().getText(), ctx));
+ } else if (ctx.MAP() != null) {
+ return List.of(new QueryParsingToken(ctx.MAP().getText(), ctx));
+ } else if (ctx.simplePath() != null) {
+
+ List tokens = visit(ctx.simplePath());
+ NOSPACE(tokens);
+ return tokens;
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitInstantiationArguments(HqlParser.InstantiationArgumentsContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ ctx.instantiationArgument().forEach(instantiationArgumentContext -> {
+ tokens.addAll(visit(instantiationArgumentContext));
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(",", instantiationArgumentContext));
+ });
+ CLIP(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitInstantiationArgument(HqlParser.InstantiationArgumentContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.expressionOrPredicate() != null) {
+ tokens.addAll(visit(ctx.expressionOrPredicate()));
+ } else if (ctx.instantiation() != null) {
+ tokens.addAll(visit(ctx.instantiation()));
+ }
+
+ if (ctx.variable() != null) {
+ tokens.addAll(visit(ctx.variable()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitParameterOrIntegerLiteral(HqlParser.ParameterOrIntegerLiteralContext ctx) {
+
+ if (ctx.parameter() != null) {
+ return visit(ctx.parameter());
+ } else if (ctx.INTEGER_LITERAL() != null) {
+ return List.of(new QueryParsingToken(ctx.INTEGER_LITERAL().getText(), ctx));
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitParameterOrNumberLiteral(HqlParser.ParameterOrNumberLiteralContext ctx) {
+
+ if (ctx.parameter() != null) {
+ return visit(ctx.parameter());
+ } else if (ctx.numericLiteral() != null) {
+ return visit(ctx.numericLiteral());
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitVariable(HqlParser.VariableContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.identifier() != null) {
+
+ tokens.add(new QueryParsingToken(ctx.AS().getText(), ctx));
+ tokens.addAll(visit(ctx.identifier()));
+ } else if (ctx.reservedWord() != null) {
+ tokens.addAll(visit(ctx.reservedWord()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitParameter(HqlParser.ParameterContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.prefix.getText().equals(":")) {
+
+ tokens.add(new QueryParsingToken(":", ctx, false));
+ tokens.addAll(visit(ctx.identifier()));
+ } else if (ctx.prefix.getText().equals("?")) {
+
+ tokens.add(new QueryParsingToken("?", ctx, false));
+
+ if (ctx.INTEGER_LITERAL() != null) {
+ tokens.add(new QueryParsingToken(ctx.INTEGER_LITERAL().getText(), ctx));
+ } else if (ctx.spelExpression() != null) {
+ tokens.addAll(visit(ctx.spelExpression()));
+ }
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitEntityName(HqlParser.EntityNameContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ ctx.identifier().forEach(identifierContext -> {
+ tokens.addAll(visit(identifierContext));
+ tokens.add(new QueryParsingToken(".", identifierContext));
+ });
+ CLIP(tokens);
+
+ return tokens;
+ }
+
+ @Override
+ public List visitIdentifier(HqlParser.IdentifierContext ctx) {
+
+ if (ctx.reservedWord() != null) {
+ return visit(ctx.reservedWord());
+ } else if (ctx.spelExpression() != null) {
+ return visit(ctx.spelExpression());
+ } else {
+ return List.of();
+ }
+ }
+
+ @Override
+ public List visitSpelExpression(HqlParser.SpelExpressionContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.prefix.equals("#{#")) { // #{#entityName}
+
+ tokens.add(new QueryParsingToken(ctx.prefix.getText(), ctx));
+ ctx.identificationVariable().forEach(identificationVariableContext -> {
+ tokens.addAll(visit(identificationVariableContext));
+ tokens.add(new QueryParsingToken(".", identificationVariableContext));
+ });
+ CLIP(tokens);
+ tokens.add(new QueryParsingToken("}", ctx));
+
+ } else if (ctx.prefix.equals("#{#[")) { // #{[0]}
+
+ tokens.add(new QueryParsingToken(ctx.prefix.getText(), ctx));
+ tokens.add(new QueryParsingToken(ctx.INTEGER_LITERAL().getText(), ctx));
+ tokens.add(new QueryParsingToken("]}", ctx));
+
+ } else if (ctx.prefix.equals("#{")) {// #{escape([0])} or #{escape('foo')}
+
+ tokens.add(new QueryParsingToken(ctx.prefix.getText(), ctx));
+ tokens.addAll(visit(ctx.identificationVariable(0)));
+ tokens.add(new QueryParsingToken("(", ctx));
+
+ if (ctx.stringLiteral() != null) {
+ tokens.addAll(visit(ctx.stringLiteral()));
+ } else if (ctx.INTEGER_LITERAL() != null) {
+
+ tokens.add(new QueryParsingToken("[", ctx));
+ tokens.add(new QueryParsingToken(ctx.INTEGER_LITERAL().getText(), ctx));
+ tokens.add(new QueryParsingToken("]", ctx));
+ }
+
+ tokens.add(new QueryParsingToken(")}", ctx));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitCharacter(HqlParser.CharacterContext ctx) {
+ return List.of(new QueryParsingToken(ctx.CHARACTER().getText(), ctx));
+ }
+
+ @Override
+ public List visitFunctionName(HqlParser.FunctionNameContext ctx) {
+ return visit(ctx.reservedWord());
+ }
+
+ @Override
+ public List visitReservedWord(HqlParser.ReservedWordContext ctx) {
+
+ if (ctx.IDENTIFICATION_VARIABLE() != null) {
+ return List.of(new QueryParsingToken(ctx.IDENTIFICATION_VARIABLE().getText(), ctx));
+ } else {
+ return List.of(new QueryParsingToken(ctx.f.getText(), ctx));
+ }
+ }
+}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlUtils.java
new file mode 100644
index 00000000000..5e5627a99f1
--- /dev/null
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2022-2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.query;
+
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+
+/**
+ * Methods to parse an HQL query.
+ *
+ * @author Greg Turnquist
+ * @since 3.1
+ */
+class HqlUtils {
+
+ /**
+ * Parse the provided {@literal query}.
+ *
+ * @param query
+ * @param failFast
+ */
+ static HqlParser.StartContext parse(String query, boolean failFast) {
+
+ HqlLexer lexer = new HqlLexer(CharStreams.fromString(query));
+ HqlParser parser = new HqlParser(new CommonTokenStream(lexer));
+
+ if (failFast) {
+ parser.addErrorListener(new QueryParsingSyntaxErrorListener());
+ }
+
+ return parser.start();
+ }
+
+ /**
+ * Shortcut to parse the {@literal query} and fail fast.
+ *
+ * @param query
+ */
+ static HqlParser.StartContext parseWithFastFailure(String query) {
+ return parse(query, true);
+ }
+}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlParsingStrategy.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlParsingStrategy.java
new file mode 100644
index 00000000000..a63ba02976e
--- /dev/null
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlParsingStrategy.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2022-2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.query;
+
+import java.util.List;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.springframework.data.domain.Sort;
+import org.springframework.lang.Nullable;
+
+/**
+ * Implements the various parsing operations using {@link JpqlTransformingVisitor} and {@link JpqlUtils}.
+ *
+ * @author Greg Turnquist
+ * @since 3.1
+ */
+class JpqlParsingStrategy implements QueryParsingStrategy {
+
+ private final DeclaredQuery query;
+
+ @Nullable private Sort sort;
+
+ JpqlParsingStrategy(DeclaredQuery query, @Nullable Sort sort) {
+
+ this.query = query;
+ this.sort = sort;
+ }
+
+ JpqlParsingStrategy(String query, @Nullable Sort sort) {
+ this(DeclaredQuery.of(query, false), sort);
+ }
+
+ JpqlParsingStrategy(DeclaredQuery query) {
+ this(query, null);
+ }
+
+ JpqlParsingStrategy(String query) {
+ this(DeclaredQuery.of(query, false), null);
+ }
+
+ @Override
+ public DeclaredQuery getDeclaredQuery() {
+ return query;
+ }
+
+ @Override
+ public ParserRuleContext parse() {
+ return JpqlUtils.parseWithFastFailure(getQuery());
+ }
+
+ @Override
+ public List applySorting(ParserRuleContext parsedQuery) {
+ return new JpqlTransformingVisitor(sort).visit(parsedQuery);
+ }
+
+ @Override
+ public List count(ParserRuleContext parsedQuery) {
+ return new JpqlTransformingVisitor(true).visit(parsedQuery);
+ }
+
+ @Override
+ public String findAlias(ParserRuleContext parsedQuery) {
+
+ JpqlTransformingVisitor transformVisitor = new JpqlTransformingVisitor();
+ transformVisitor.visit(parsedQuery);
+ return transformVisitor.getAlias();
+ }
+
+ @Override
+ public List projection(ParserRuleContext parsedQuery) {
+
+ JpqlTransformingVisitor transformVisitor = new JpqlTransformingVisitor();
+ transformVisitor.visit(parsedQuery);
+ return transformVisitor.getProjection();
+ }
+
+ @Override
+ public boolean hasConstructor(ParserRuleContext parsedQuery) {
+
+ JpqlTransformingVisitor transformVisitor = new JpqlTransformingVisitor();
+ transformVisitor.visit(parsedQuery);
+ return transformVisitor.hasConstructorExpression();
+ }
+
+ @Override
+ public void setSort(Sort sort) {
+ this.sort = sort;
+ }
+}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlTransformingVisitor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlTransformingVisitor.java
new file mode 100644
index 00000000000..a6134f2457b
--- /dev/null
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlTransformingVisitor.java
@@ -0,0 +1,2410 @@
+/*
+ * Copyright 2022-2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.query;
+
+import static org.springframework.data.jpa.repository.query.QueryParsingToken.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.lang.Nullable;
+
+/**
+ * An ANTLR visitor that transforms a parsed JPQL query.
+ *
+ * @author Greg Turnquist
+ * @since 3.1
+ */
+class JpqlTransformingVisitor extends JpqlBaseVisitor> {
+
+ @Nullable private Sort sort;
+ private boolean countQuery;
+
+ private String alias = "";
+
+ private List projection = null;
+
+ private boolean hasConstructorExpression = false;
+
+ JpqlTransformingVisitor() {
+ this(null, false);
+ }
+
+ JpqlTransformingVisitor(@Nullable Sort sort) {
+ this(sort, false);
+ }
+
+ JpqlTransformingVisitor(boolean countQuery) {
+ this(null, countQuery);
+ }
+
+ private JpqlTransformingVisitor(@Nullable Sort sort, boolean countQuery) {
+
+ this.sort = sort;
+ this.countQuery = countQuery;
+ }
+
+ public String getAlias() {
+ return this.alias;
+ }
+
+ public List getProjection() {
+ return this.projection;
+ }
+
+ public boolean hasConstructorExpression() {
+ return this.hasConstructorExpression;
+ }
+
+ @Override
+ public List visitStart(JpqlParser.StartContext ctx) {
+ return visit(ctx.ql_statement());
+ }
+
+ @Override
+ public List visitQl_statement(JpqlParser.Ql_statementContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ if (ctx.select_statement() != null) {
+ tokens.addAll(visit(ctx.select_statement()));
+ } else if (ctx.update_statement() != null) {
+ tokens.addAll(visit(ctx.update_statement()));
+ } else if (ctx.delete_statement() != null) {
+ tokens.addAll(visit(ctx.delete_statement()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitSelect_statement(JpqlParser.Select_statementContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.select_clause()));
+ tokens.addAll(visit(ctx.from_clause()));
+
+ if (ctx.where_clause() != null) {
+ tokens.addAll(visit(ctx.where_clause()));
+ }
+
+ if (ctx.groupby_clause() != null) {
+ tokens.addAll(visit(ctx.groupby_clause()));
+ }
+
+ if (ctx.having_clause() != null) {
+ tokens.addAll(visit(ctx.having_clause()));
+ }
+
+ if (!this.countQuery) {
+
+ if (ctx.orderby_clause() != null) {
+ tokens.addAll(visit(ctx.orderby_clause()));
+ }
+
+ if (this.sort != null && this.sort.isSorted()) {
+
+ if (ctx.orderby_clause() != null) {
+
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(",", ctx));
+ } else {
+
+ SPACE(tokens);
+ tokens.add(new QueryParsingToken("order by", ctx));
+ }
+
+ this.sort.forEach(order -> {
+
+ if (order.isIgnoreCase()) {
+ tokens.add(new QueryParsingToken("lower(", ctx, false));
+ }
+ tokens.add(new QueryParsingToken(() -> this.alias + "." + order.getProperty(), ctx, true));
+ if (order.isIgnoreCase()) {
+ NOSPACE(tokens);
+ tokens.add(new QueryParsingToken(")", ctx, true));
+ }
+ tokens.add(new QueryParsingToken(order.isDescending() ? "desc" : "asc", ctx, false));
+ tokens.add(new QueryParsingToken(",", ctx));
+ });
+ CLIP(tokens);
+ }
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitUpdate_statement(JpqlParser.Update_statementContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.update_clause()));
+
+ if (ctx.where_clause() != null) {
+ tokens.addAll(visit(ctx.where_clause()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitDelete_statement(JpqlParser.Delete_statementContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.addAll(visit(ctx.delete_clause()));
+
+ if (ctx.where_clause() != null) {
+ tokens.addAll(visit(ctx.where_clause()));
+ }
+
+ return tokens;
+ }
+
+ @Override
+ public List visitFrom_clause(JpqlParser.From_clauseContext ctx) {
+
+ List tokens = new ArrayList<>();
+
+ tokens.add(new QueryParsingToken(ctx.FROM().getText(), ctx, true));
+
+ ctx.identification_variable_declaration().forEach(identificationVariableDeclarationContext -> {
+ tokens.addAll(visit(identificationVariableDeclarationContext));
+ });
+
+ return tokens;
+ }
+
+ @Override
+ public List visitIdentification_variable_declaration(
+ JpqlParser.Identification_variable_declarationContext ctx) {
+
+ List