diff --git a/Jenkinsfile b/Jenkinsfile index f87caaa0aa..a088fae34e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -58,50 +58,6 @@ pipeline { } parallel { - stage("test: hibernate 6.2 (LTS)") { - agent { - label 'data' - } - options { timeout(time: 30, unit: 'MINUTES')} - environment { - ARTIFACTORY = credentials("${p['artifactory.credentials']}") - DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}") - TESTCONTAINERS_IMAGE_SUBSTITUTOR = 'org.springframework.data.jpa.support.ProxyImageNameSubstitutor' - } - steps { - script { - docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) { - docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) { - sh "PROFILE=all-dbs,hibernate-62 " + - "JENKINS_USER_NAME=${p['jenkins.user.name']} " + - "ci/test.sh" - } - } - } - } - } - stage("test: baseline (hibernate 6.6 snapshots)") { - agent { - label 'data' - } - options { timeout(time: 30, unit: 'MINUTES')} - environment { - ARTIFACTORY = credentials("${p['artifactory.credentials']}") - DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}") - TESTCONTAINERS_IMAGE_SUBSTITUTOR = 'org.springframework.data.jpa.support.ProxyImageNameSubstitutor' - } - steps { - script { - docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) { - docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) { - sh "PROFILE=all-dbs,hibernate-66-snapshots " + - "JENKINS_USER_NAME=${p['jenkins.user.name']} " + - "ci/test.sh" - } - } - } - } - } stage("test: java.next (next)") { agent { label 'data' diff --git a/pom.xml b/pom.xml index ade6f22b06..4fc11f8452 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT pom Spring Data JPA Parent @@ -28,16 +28,13 @@ 4.13.0 - 4.0.4 - 4.0.5-SNAPSHOT - 6.6.2.Final - 6.2.31.Final - 6.6.3-SNAPSHOT - 7.0.0.Beta1 + 5.0.0-B04 + 5.0.0-SNAPSHOT + 7.0.0.Beta1 7.0.0-SNAPSHOT 2.7.4

2.3.232

- 3.1.0 + 3.2.0 5.0 9.1.0 42.7.4 @@ -47,7 +44,6 @@ org.hibernate reuseReports -
@@ -58,43 +54,6 @@ - - hibernate-62 - - ${hibernate-62} - - - - hibernate-66-snapshots - - ${hibernate-66-snapshots} - - - - sonatype-oss - https://oss.sonatype.org/content/repositories/snapshots - - false - - - - - - hibernate-70 - - ${hibernate-70} - 3.2.0-M2 - - - - sonatype-oss - https://oss.sonatype.org/content/repositories/snapshots - - false - - - - hibernate-70-snapshots diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index 0bdf2c8e7e..b5fa9ea78d 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index af5244a230..4d826679e0 100644 --- a/spring-data-jpa-distribution/pom.xml +++ b/spring-data-jpa-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index ae5a4738dc..5dd4b50cbb 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jpa - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.0-GH-3673-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 index 3ed025efb5..ede622152e 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 @@ -43,7 +43,7 @@ ql_statement ; select_statement - : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (setOperator select_statement)* + : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)? ; setOperator @@ -52,6 +52,10 @@ setOperator | EXCEPT ALL? ; +set_fuction + : setOperator select_statement + ; + update_statement : update_clause (where_clause)? ; @@ -211,6 +215,7 @@ constructor_item | scalar_expression | aggregate_expression | identification_variable + | literal ; aggregate_expression @@ -309,6 +314,7 @@ scalar_expression | datetime_expression | boolean_expression | case_expression + | cast_function | entity_type_expression ; @@ -450,6 +456,7 @@ string_expression | case_expression | function_invocation | '(' subquery ')' + | string_expression '||' string_expression ; datetime_expression @@ -534,6 +541,9 @@ functions_returning_strings | TRIM '(' ((trim_specification)? (trim_character)? FROM)? string_expression ')' | LOWER '(' string_expression ')' | UPPER '(' string_expression ')' + | REPLACE '(' string_expression ',' string_expression ',' string_expression ')' + | LEFT '(' string_expression ',' arithmetic_expression ')' + | RIGHT '(' string_expression ',' arithmetic_expression ')' ; trim_specification @@ -543,7 +553,7 @@ trim_specification ; cast_function - : CAST '(' single_valued_path_expression identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')' + : CAST '(' single_valued_path_expression (identification_variable)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')' ; function_invocation @@ -609,6 +619,14 @@ nullif_expression : NULLIF '(' scalar_expression ',' scalar_expression ')' ; +type_literal + : STRING + | INTEGER + | LONG + | FLOAT + | DOUBLE + ; + /******************* Gaps in the spec. *******************/ @@ -621,6 +639,7 @@ trim_character identification_variable : IDENTIFICATION_VARIABLE | f=(COUNT + | AS | DATE | FROM | INNER @@ -630,11 +649,13 @@ identification_variable | ORDER | OUTER | POWER + | RIGHT | FLOOR | SIGN | TIME | TYPE | VALUE) + | type_literal ; constructor_name @@ -643,6 +664,7 @@ constructor_name literal : STRINGLITERAL + | JAVASTRINGLITERAL | INTLITERAL | FLOATLITERAL | LONGLITERAL @@ -812,6 +834,8 @@ reserved_word |ORDER |OUTER |POWER + |REPLACE + |RIGHT |ROUND |SELECT |SET @@ -894,6 +918,7 @@ 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; +DOUBLE : D O U B L E; END : E N D; ELSE : E L S E; EMPTY : E M P T Y; @@ -906,6 +931,7 @@ EXTRACT : E X T R A C T; FALSE : F A L S E; FETCH : F E T C H; FIRST : F I R S T; +FLOAT : F L O A T; FLOOR : F L O O R; FROM : F R O M; FUNCTION : F U N C T I O N; @@ -914,6 +940,7 @@ HAVING : H A V I N G; IN : I N; INDEX : I N D E X; INNER : I N N E R; +INTEGER : I N T E G E R; INTERSECT : I N T E R S E C T; IS : I S; JOIN : J O I N; @@ -926,6 +953,7 @@ LIKE : L I K E; LN : L N; LOCAL : L O C A L; LOCATE : L O C A T E; +LONG : L O N G; LOWER : L O W E R; MAX : M A X; MEMBER : M E M B E R; @@ -944,6 +972,8 @@ ORDER : O R D E R; OUTER : O U T E R; POWER : P O W E R; REGEXP : R E G E X P; +REPLACE : R E P L A C E; +RIGHT : R I G H T; ROUND : R O U N D; SELECT : S E L E C T; SET : S E T; @@ -951,6 +981,7 @@ SIGN : S I G N; SIZE : S I Z E; SOME : S O M E; SQRT : S Q R T; +STRING : S T R I N G; SUBSTRING : S U B S T R I N G; SUM : S U M; THEN : T H E N; @@ -970,9 +1001,9 @@ WHERE : W H E R E; EQUAL : '=' ; NOT_EQUAL : '<>' | '!=' ; - CHARACTER : '\'' (~ ('\'' | '\\')) '\'' ; IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ; +JAVASTRINGLITERAL : '"' ( ('\\' [btnfr"']) | ~('"'))* '"'; STRINGLITERAL : '\'' (~ ('\'' | '\\')|'\\')* '\'' ; FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E ('0' .. '9')+)* (F|D)?; INTLITERAL : ('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 index e87d523b12..87c93f1394 100644 --- 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 @@ -43,7 +43,17 @@ ql_statement ; select_statement - : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? + : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)? + ; + +setOperator + : UNION ALL? + | INTERSECT ALL? + | EXCEPT ALL? + ; + +set_fuction + : setOperator select_statement ; update_statement @@ -77,7 +87,7 @@ join ; fetch_join - : join_spec FETCH join_association_path_expression + : join_spec FETCH join_association_path_expression AS? identification_variable? join_condition? ; join_spec @@ -297,6 +307,7 @@ scalar_expression | datetime_expression | boolean_expression | case_expression + | cast_function | entity_type_expression ; @@ -422,6 +433,7 @@ arithmetic_primary | functions_returning_numerics | aggregate_expression | case_expression + | cast_function | function_invocation | '(' subquery ')' ; @@ -435,6 +447,7 @@ string_expression | case_expression | function_invocation | '(' subquery ')' + | string_expression '||' string_expression ; datetime_expression @@ -519,6 +532,9 @@ functions_returning_strings | TRIM '(' ((trim_specification)? (trim_character)? FROM)? string_expression ')' | LOWER '(' string_expression ')' | UPPER '(' string_expression ')' + | REPLACE '(' string_expression ',' string_expression ',' string_expression ')' + | LEFT '(' string_expression ',' arithmetic_expression ')' + | RIGHT '(' string_expression ',' arithmetic_expression ')' ; trim_specification @@ -527,6 +543,9 @@ trim_specification | BOTH ; +cast_function + : CAST '(' single_valued_path_expression (identification_variable)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')' + ; function_invocation : FUNCTION '(' function_name (',' function_arg)* ')' @@ -591,6 +610,7 @@ nullif_expression : NULLIF '(' scalar_expression ',' scalar_expression ')' ; + /******************* Gaps in the spec. *******************/ @@ -603,6 +623,7 @@ trim_character identification_variable : IDENTIFICATION_VARIABLE | f=(COUNT + | AS | DATE | FROM | INNER @@ -612,11 +633,13 @@ identification_variable | ORDER | OUTER | POWER + | RIGHT | FLOOR | SIGN | TIME | TYPE | VALUE) + | type_literal ; constructor_name @@ -644,6 +667,9 @@ pattern_value date_time_timestamp_literal : STRINGLITERAL + | DATELITERAL + | TIMELITERAL + | TIMESTAMPLITERAL ; entity_type_literal @@ -661,6 +687,14 @@ numeric_literal | LONGLITERAL ; +type_literal + : STRING + | INTEGER + | LONG + | FLOAT + | DOUBLE + ; + boolean_literal : TRUE | FALSE @@ -792,6 +826,8 @@ reserved_word |ORDER |OUTER |POWER + |REPLACE + |RIGHT |ROUND |SELECT |SET @@ -861,6 +897,7 @@ BETWEEN : B E T W E E N; BOTH : B O T H; BY : B Y; CASE : C A S E; +CAST : C A S T; CEILING : C E I L I N G; COALESCE : C O A L E S C E; CONCAT : C O N C A T; @@ -873,17 +910,20 @@ 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; +DOUBLE : D O U B L E; 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; +EXCEPT : E X C E P T; 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; FIRST : F I R S T; +FLOAT : F L O A T; FLOOR : F L O O R; FROM : F R O M; FUNCTION : F U N C T I O N; @@ -892,6 +932,8 @@ HAVING : H A V I N G; IN : I N; INDEX : I N D E X; INNER : I N N E R; +INTEGER : I N T E G E R; +INTERSECT : I N T E R S E C T; IS : I S; JOIN : J O I N; KEY : K E Y; @@ -903,6 +945,7 @@ LIKE : L I K E; LN : L N; LOCAL : L O C A L; LOCATE : L O C A T E; +LONG : L O N G; LOWER : L O W E R; MAX : M A X; MEMBER : M E M B E R; @@ -920,6 +963,9 @@ OR : O R; ORDER : O R D E R; OUTER : O U T E R; POWER : P O W E R; +REGEXP : R E G E X P; +REPLACE : R E P L A C E; +RIGHT : R I G H T; ROUND : R O U N D; SELECT : S E L E C T; SET : S E T; @@ -927,6 +973,7 @@ SIGN : S I G N; SIZE : S I Z E; SOME : S O M E; SQRT : S Q R T; +STRING : S T R I N G; SUBSTRING : S U B S T R I N G; SUM : S U M; THEN : T H E N; @@ -936,6 +983,7 @@ TREAT : T R E A T; TRIM : T R I M; TRUE : T R U E; TYPE : T Y P E; +UNION : U N I O N; UPDATE : U P D A T E; UPPER : U P P E R; VALUE : V A L U E; @@ -947,8 +995,11 @@ NOT_EQUAL : '<>' | '!=' ; CHARACTER : '\'' (~ ('\'' | '\\')) '\'' ; IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ; -STRINGLITERAL : '\'' (~ ('\'' | '\\'))* '\'' ; +STRINGLITERAL : '\'' (~ ('\'' | '\\')|'\\')* '\'' ; JAVASTRINGLITERAL : '"' ( ('\\' [btnfr"']) | ~('"'))* '"'; FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E ('0' .. '9')+)* (F|D)?; INTLITERAL : ('0' .. '9')+ ; -LONGLITERAL : ('0' .. '9')+L ; +LONGLITERAL : ('0' .. '9')+ L; +DATELITERAL : '{' D STRINGLITERAL '}'; +TIMELITERAL : '{' T STRINGLITERAL '}'; +TIMESTAMPLITERAL : '{' T S STRINGLITERAL '}'; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java index 6cc365619f..058da4c4f5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java @@ -17,13 +17,11 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.MappedSuperclass; -import jakarta.persistence.Temporal; -import jakarta.persistence.TemporalType; import java.io.Serializable; +import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.Date; import java.util.Optional; import org.springframework.data.domain.Auditable; @@ -45,14 +43,12 @@ public abstract class AbstractAuditable extends Abst @ManyToOne // private @Nullable U createdBy; - @Temporal(TemporalType.TIMESTAMP) // - private @Nullable Date createdDate; + private @Nullable Instant createdDate; @ManyToOne // private @Nullable U lastModifiedBy; - @Temporal(TemporalType.TIMESTAMP) // - private @Nullable Date lastModifiedDate; + private @Nullable Instant lastModifiedDate; @Override public Optional getCreatedBy() { @@ -67,12 +63,12 @@ public void setCreatedBy(U createdBy) { @Override public Optional getCreatedDate() { return null == createdDate ? Optional.empty() - : Optional.of(LocalDateTime.ofInstant(createdDate.toInstant(), ZoneId.systemDefault())); + : Optional.of(LocalDateTime.ofInstant(createdDate, ZoneId.systemDefault())); } @Override public void setCreatedDate(LocalDateTime createdDate) { - this.createdDate = Date.from(createdDate.atZone(ZoneId.systemDefault()).toInstant()); + this.createdDate = createdDate.atZone(ZoneId.systemDefault()).toInstant(); } @Override @@ -88,11 +84,11 @@ public void setLastModifiedBy(U lastModifiedBy) { @Override public Optional getLastModifiedDate() { return null == lastModifiedDate ? Optional.empty() - : Optional.of(LocalDateTime.ofInstant(lastModifiedDate.toInstant(), ZoneId.systemDefault())); + : Optional.of(LocalDateTime.ofInstant(lastModifiedDate, ZoneId.systemDefault())); } @Override public void setLastModifiedDate(LocalDateTime lastModifiedDate) { - this.lastModifiedDate = Date.from(lastModifiedDate.atZone(ZoneId.systemDefault()).toInstant()); + this.lastModifiedDate = lastModifiedDate.atZone(ZoneId.systemDefault()).toInstant(); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java index a28bf8a390..99b29715ec 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java @@ -281,14 +281,14 @@ public , U> Path dot(A attribute) { * @return */ public

, U> Path dot(P attribute) { - return new Path(add(attribute)); + return new Path<>(add(attribute)); } private List> add(Attribute attribute) { Assert.notNull(attribute, "Attribute must not be null"); - List> newAttributes = new ArrayList>(attributes.size() + 1); + List> newAttributes = new ArrayList<>(attributes.size() + 1); newAttributes.addAll(attributes); newAttributes.add(attribute); return newAttributes; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java index 501d3a6444..d2bd3039ad 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java @@ -57,13 +57,9 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty> annotations = new HashSet<>(); - annotations.add(OneToMany.class); - annotations.add(OneToOne.class); - annotations.add(ManyToMany.class); - annotations.add(ManyToOne.class); + Set> annotations; - ASSOCIATION_ANNOTATIONS = Collections.unmodifiableSet(annotations); + ASSOCIATION_ANNOTATIONS = Set.of(OneToMany.class, OneToOne.class, ManyToMany.class, ManyToOne.class); annotations = new HashSet<>(); annotations.add(Id.class); @@ -107,7 +103,7 @@ public JpaPersistentPropertyImpl(JpaMetamodel metamodel, Property property, this.associationTargetType = detectAssociationTargetType(); this.updateable = detectUpdatability(); - this.isIdProperty = Lazy.of(() -> ID_ANNOTATIONS.stream().anyMatch(it -> isAnnotationPresent(it)) // + this.isIdProperty = Lazy.of(() -> ID_ANNOTATIONS.stream().anyMatch(this::isAnnotationPresent) // || metamodel.isSingleIdAttribute(getOwner().getType(), getName(), getType())); this.isEntity = Lazy.of(() -> metamodel.isMappedType(getActualType())); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/package-info.java new file mode 100644 index 0000000000..4f85f48a62 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/package-info.java @@ -0,0 +1,5 @@ +/** + * JPA specific support projection support. + */ +@org.springframework.lang.NonNullApi +package org.springframework.data.jpa.projection; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Temporal.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Temporal.java index 7668ccd33f..245983b766 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Temporal.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Temporal.java @@ -30,10 +30,12 @@ * * @author Thomas Darimont * @author Oliver Gierke + * @deprecated since 4.0. Please use {@literal java.time} types instead. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @Documented +@Deprecated(since = "4.0", forRemoval = true) public @interface Temporal { /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java index 9c83985546..657778b260 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java @@ -93,9 +93,7 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri return query.deriveCountQuery(method.getCountQueryProjection()); }); - this.countParameterBinder = Lazy.of(() -> { - return this.createBinder(this.countQuery.get()); - }); + this.countParameterBinder = Lazy.of(() -> this.createBinder(this.countQuery.get())); this.queryRewriter = queryRewriter; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java index aec9763fa0..2d458b91fa 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java @@ -43,7 +43,7 @@ class EqlCountQueryTransformer extends EqlQueryRenderer { } @Override - public QueryRendererBuilder visitSelect_statement(EqlParser.Select_statementContext ctx) { + public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -93,7 +93,7 @@ public QueryTokenStream visitSelect_clause(EqlParser.Select_clauseContext ctx) { return builder; } - private QueryRendererBuilder getDistinctCountSelection(QueryTokenStream selectionListbuilder) { + private QueryTokenStream getDistinctCountSelection(QueryTokenStream selectionListbuilder) { QueryRendererBuilder nested = new QueryRendererBuilder(); CountSelectionTokenStream countSelection = CountSelectionTokenStream.create(selectionListbuilder); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java index 17e6e51a55..43f66ee2cb 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java @@ -23,6 +23,7 @@ import org.antlr.v4.runtime.tree.ParseTree; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.util.ObjectUtils; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders an EQL query without making any changes. @@ -77,30 +78,8 @@ public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext builder.appendExpression(visit(ctx.orderby_clause())); } - for (int i = 0; i < ctx.setOperator().size(); i++) { - - builder.appendExpression(visit(ctx.setOperator(i))); - builder.appendExpression(visit(ctx.select_statement(i))); - } - - return builder; - } - - @Override - public QueryTokenStream visitSetOperator(EqlParser.SetOperatorContext ctx) { - - QueryRendererBuilder builder = QueryRenderer.builder(); - - if (ctx.UNION() != null) { - builder.append(QueryTokens.expression(ctx.UNION())); - } else if (ctx.INTERSECT() != null) { - builder.append(QueryTokens.expression(ctx.INTERSECT())); - } else if (ctx.EXCEPT() != null) { - builder.append(QueryTokens.expression(ctx.EXCEPT())); - } - - if (ctx.ALL() != null) { - builder.append(QueryTokens.expression(ctx.ALL())); + if (ctx.set_fuction() != null) { + builder.appendExpression(visit(ctx.set_fuction())); } return builder; @@ -184,13 +163,8 @@ public QueryTokenStream visitIdentification_variable_declaration( QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(visit(ctx.range_variable_declaration())); - - ctx.join().forEach(joinContext -> { - builder.append(visit(joinContext)); - }); - ctx.fetch_join().forEach(fetchJoinContext -> { - builder.append(visit(fetchJoinContext)); - }); + builder.appendExpression(QueryTokenStream.concat(ctx.join(), this::visit, TOKEN_SPACE)); + builder.appendExpression(QueryTokenStream.concat(ctx.fetch_join(), this::visit, TOKEN_SPACE)); return builder; } @@ -226,9 +200,11 @@ public QueryTokenStream visitJoin(EqlParser.JoinContext ctx) { if (ctx.AS() != null) { builder.append(QueryTokens.expression(ctx.AS())); } + if (ctx.identification_variable() != null) { builder.appendExpression(visit(ctx.identification_variable())); } + if (ctx.join_condition() != null) { builder.appendExpression(visit(ctx.join_condition())); } @@ -291,8 +267,7 @@ public QueryTokenStream visitJoin_condition(EqlParser.Join_conditionContext ctx) } @Override - public QueryTokenStream visitJoin_association_path_expression( - EqlParser.Join_association_path_expressionContext ctx) { + public QueryTokenStream visitJoin_association_path_expression(EqlParser.Join_association_path_expressionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -304,31 +279,25 @@ public QueryTokenStream visitJoin_association_path_expression( builder.appendExpression(visit(ctx.join_single_valued_path_expression())); } } else { - if (ctx.join_collection_valued_path_expression() != null) { + QueryRendererBuilder nested = QueryRenderer.builder(); - QueryRendererBuilder nested = QueryRenderer.builder(); + if (ctx.join_collection_valued_path_expression() != null) { - nested.append(QueryTokens.token(ctx.TREAT())); - nested.append(TOKEN_OPEN_PAREN); - nested.appendInline(visit(ctx.join_collection_valued_path_expression())); + nested.appendExpression(visit(ctx.join_collection_valued_path_expression())); nested.append(QueryTokens.expression(ctx.AS())); - nested.appendInline(visit(ctx.subtype())); - nested.append(TOKEN_CLOSE_PAREN); + nested.appendExpression(visit(ctx.subtype())); - builder.appendExpression(nested); } else if (ctx.join_single_valued_path_expression() != null) { - QueryRendererBuilder nested = QueryRenderer.builder(); - - nested.append(QueryTokens.token(ctx.TREAT())); - nested.append(TOKEN_OPEN_PAREN); - nested.appendInline(visit(ctx.join_single_valued_path_expression())); + nested.appendExpression(visit(ctx.join_single_valued_path_expression())); nested.append(QueryTokens.expression(ctx.AS())); - nested.appendInline(visit(ctx.subtype())); - nested.append(TOKEN_CLOSE_PAREN); - - builder.appendExpression(nested); + nested.appendExpression(visit(ctx.subtype())); } + + builder.append(QueryTokens.token(ctx.TREAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); } return builder; @@ -450,8 +419,7 @@ public QueryTokenStream visitSingle_valued_path_expression(EqlParser.Single_valu } @Override - public QueryTokenStream visitGeneral_identification_variable( - EqlParser.General_identification_variableContext ctx) { + public QueryTokenStream visitGeneral_identification_variable(EqlParser.General_identification_variableContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -494,12 +462,15 @@ public QueryTokenStream visitSimple_subpath(EqlParser.Simple_subpathContext ctx) public QueryTokenStream visitTreated_subpath(EqlParser.Treated_subpathContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.general_subpath())); + nested.append(QueryTokens.expression(ctx.AS())); + nested.appendExpression(visit(ctx.subtype())); builder.append(QueryTokens.token(ctx.TREAT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.general_subpath())); - builder.append(QueryTokens.expression(ctx.AS())); - builder.appendInline(visit(ctx.subtype())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -612,7 +583,7 @@ public QueryTokenStream visitNew_value(EqlParser.New_valueContext ctx) { } else if (ctx.simple_entity_expression() != null) { return visit(ctx.simple_entity_expression()); } else if (ctx.NULL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.NULL())); + return QueryTokenStream.ofToken(ctx.NULL()); } else { return QueryRenderer.builder(); } @@ -719,19 +690,19 @@ public QueryTokenStream visitConstructor_expression(EqlParser.Constructor_expres @Override public QueryTokenStream visitConstructor_item(EqlParser.Constructor_itemContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.single_valued_path_expression() != null) { - builder.append(visit(ctx.single_valued_path_expression())); + return visit(ctx.single_valued_path_expression()); } else if (ctx.scalar_expression() != null) { - builder.append(visit(ctx.scalar_expression())); + return visit(ctx.scalar_expression()); } else if (ctx.aggregate_expression() != null) { - builder.append(visit(ctx.aggregate_expression())); + return visit(ctx.aggregate_expression()); } else if (ctx.identification_variable() != null) { - builder.append(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); + } else if (ctx.literal() != null) { + return visit(ctx.literal()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -852,15 +823,15 @@ public QueryTokenStream visitOrderby_item(EqlParser.Orderby_itemContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_field_path_expression() != null) { - builder.append(visit(ctx.state_field_path_expression())); + builder.appendExpression(visit(ctx.state_field_path_expression())); } else if (ctx.general_identification_variable() != null) { - builder.append(visit(ctx.general_identification_variable())); + builder.appendExpression(visit(ctx.general_identification_variable())); } else if (ctx.result_variable() != null) { - builder.append(visit(ctx.result_variable())); + builder.appendExpression(visit(ctx.result_variable())); } else if (ctx.string_expression() != null) { - builder.append(visit(ctx.string_expression())); + builder.appendExpression(visit(ctx.string_expression())); } else if (ctx.scalar_expression() != null) { - builder.append(visit(ctx.scalar_expression())); + builder.appendExpression(visit(ctx.scalar_expression())); } if (ctx.ASC() != null) { @@ -882,12 +853,44 @@ public QueryTokenStream visitNullsPrecedence(EqlParser.NullsPrecedenceContext ct QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(TOKEN_NULLS); + builder.append(QueryTokens.expression(ctx.NULLS())); if (ctx.FIRST() != null) { - builder.append(TOKEN_FIRST); + builder.append(QueryTokens.expression(ctx.FIRST())); } else if (ctx.LAST() != null) { - builder.append(TOKEN_LAST); + builder.append(QueryTokens.expression(ctx.LAST())); + } + + return builder; + } + + @Override + public QueryTokenStream visitSet_fuction(EqlParser.Set_fuctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.setOperator() != null) { + builder.append(visit(ctx.setOperator())); + } + + builder.appendExpression(visit(ctx.select_statement())); + + return builder; + } + + @Override + public QueryTokenStream visitSetOperator(EqlParser.SetOperatorContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.INTERSECT() != null) { + builder.append(QueryTokens.expression(ctx.INTERSECT())); + } else if (ctx.UNION() != null) { + builder.append(QueryTokens.expression(ctx.UNION())); + } else if (ctx.EXCEPT() != null) { + builder.append(QueryTokens.expression(ctx.EXCEPT())); + } else if (ctx.ALL() != null) { + builder.append(QueryTokens.expression(ctx.ALL())); } return builder; @@ -992,81 +995,82 @@ public QueryTokenStream visitSimple_select_expression(EqlParser.Simple_select_ex @Override public QueryTokenStream visitScalar_expression(EqlParser.Scalar_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.arithmetic_expression() != null) { - builder.append(visit(ctx.arithmetic_expression())); + return visit(ctx.arithmetic_expression()); } else if (ctx.string_expression() != null) { - builder.append(visit(ctx.string_expression())); + return visit(ctx.string_expression()); } else if (ctx.enum_expression() != null) { - builder.append(visit(ctx.enum_expression())); + return visit(ctx.enum_expression()); } else if (ctx.datetime_expression() != null) { - builder.append(visit(ctx.datetime_expression())); + return visit(ctx.datetime_expression()); } else if (ctx.boolean_expression() != null) { - builder.append(visit(ctx.boolean_expression())); + return visit(ctx.boolean_expression()); } else if (ctx.case_expression() != null) { - builder.append(visit(ctx.case_expression())); + return visit(ctx.case_expression()); } else if (ctx.entity_type_expression() != null) { - builder.append(visit(ctx.entity_type_expression())); + return visit(ctx.entity_type_expression()); + } else if (ctx.cast_function() != null) { + return (visit(ctx.cast_function())); } - return builder; + return QueryTokenStream.empty(); } @Override public QueryTokenStream visitConditional_expression(EqlParser.Conditional_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.conditional_expression() != null) { - builder.append(visit(ctx.conditional_expression())); + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.conditional_expression())); builder.append(QueryTokens.expression(ctx.OR())); - builder.append(visit(ctx.conditional_term())); + builder.appendExpression(visit(ctx.conditional_term())); + + return builder; } else { - builder.append(visit(ctx.conditional_term())); + return visit(ctx.conditional_term()); } - - return builder; } @Override public QueryTokenStream visitConditional_term(EqlParser.Conditional_termContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.conditional_term() != null) { - builder.append(visit(ctx.conditional_term())); + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.conditional_term())); builder.append(QueryTokens.expression(ctx.AND())); - builder.append(visit(ctx.conditional_factor())); + builder.appendExpression(visit(ctx.conditional_factor())); + + return builder; } else { - builder.append(visit(ctx.conditional_factor())); + return visit(ctx.conditional_factor()); } - - return builder; } @Override public QueryTokenStream visitConditional_factor(EqlParser.Conditional_factorContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.NOT() != null) { + QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(QueryTokens.expression(ctx.NOT())); + builder.appendExpression(visit(ctx.conditional_primary())); + return builder; } - builder.append(visit(ctx.conditional_primary())); - - return builder; + return visit(ctx.conditional_primary()); } @Override public QueryTokenStream visitConditional_primary(EqlParser.Conditional_primaryContext ctx) { + if (ctx.simple_cond_expression() != null) { + return visit(ctx.simple_cond_expression()); + } + QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.simple_cond_expression() != null) { - builder.append(visit(ctx.simple_cond_expression())); - } else if (ctx.conditional_expression() != null) { + if (ctx.conditional_expression() != null) { builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.conditional_expression())); @@ -1079,27 +1083,25 @@ public QueryTokenStream visitConditional_primary(EqlParser.Conditional_primaryCo @Override public QueryTokenStream visitSimple_cond_expression(EqlParser.Simple_cond_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.comparison_expression() != null) { - builder.append(visit(ctx.comparison_expression())); + return visit(ctx.comparison_expression()); } else if (ctx.between_expression() != null) { - builder.append(visit(ctx.between_expression())); + return visit(ctx.between_expression()); } else if (ctx.in_expression() != null) { - builder.append(visit(ctx.in_expression())); + return visit(ctx.in_expression()); } else if (ctx.like_expression() != null) { - builder.append(visit(ctx.like_expression())); + return visit(ctx.like_expression()); } else if (ctx.null_comparison_expression() != null) { - builder.append(visit(ctx.null_comparison_expression())); + return visit(ctx.null_comparison_expression()); } else if (ctx.empty_collection_comparison_expression() != null) { - builder.append(visit(ctx.empty_collection_comparison_expression())); + return visit(ctx.empty_collection_comparison_expression()); } else if (ctx.collection_member_expression() != null) { - builder.append(visit(ctx.collection_member_expression())); + return visit(ctx.collection_member_expression()); } else if (ctx.exists_expression() != null) { - builder.append(visit(ctx.exists_expression())); + return visit(ctx.exists_expression()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -1109,7 +1111,7 @@ public QueryTokenStream visitBetween_expression(EqlParser.Between_expressionCont if (ctx.arithmetic_expression(0) != null) { - builder.append(visit(ctx.arithmetic_expression(0))); + builder.appendExpression(visit(ctx.arithmetic_expression(0))); if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); @@ -1135,7 +1137,7 @@ public QueryTokenStream visitBetween_expression(EqlParser.Between_expressionCont } else if (ctx.datetime_expression(0) != null) { - builder.append(visit(ctx.datetime_expression(0))); + builder.appendExpression(visit(ctx.datetime_expression(0))); if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); @@ -1156,10 +1158,10 @@ public QueryTokenStream visitIn_expression(EqlParser.In_expressionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - builder.append(visit(ctx.state_valued_path_expression())); + builder.appendExpression(visit(ctx.state_valued_path_expression())); } if (ctx.type_discriminator() != null) { - builder.append(visit(ctx.type_discriminator())); + builder.appendExpression(visit(ctx.type_discriminator())); } if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); @@ -1172,7 +1174,6 @@ public QueryTokenStream visitIn_expression(EqlParser.In_expressionContext ctx) { builder.append(TOKEN_OPEN_PAREN); builder.appendInline(QueryTokenStream.concat(ctx.in_item(), this::visit, TOKEN_COMMA)); - builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.subquery() != null) { @@ -1189,15 +1190,13 @@ public QueryTokenStream visitIn_expression(EqlParser.In_expressionContext ctx) { @Override public QueryTokenStream visitIn_item(EqlParser.In_itemContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.literal() != null) { - builder.append(visit(ctx.literal())); + return visit(ctx.literal()); } else if (ctx.single_valued_input_parameter() != null) { - builder.append(visit(ctx.single_valued_input_parameter())); + return visit(ctx.single_valued_input_parameter()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -1205,7 +1204,8 @@ public QueryTokenStream visitLike_expression(EqlParser.Like_expressionContext ct QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.string_expression())); + builder.appendExpression(visit(ctx.string_expression())); + if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); } @@ -1227,11 +1227,11 @@ public QueryTokenStream visitNull_comparison_expression(EqlParser.Null_compariso QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.single_valued_path_expression() != null) { - builder.append(visit(ctx.single_valued_path_expression())); + builder.appendExpression(visit(ctx.single_valued_path_expression())); } else if (ctx.input_parameter() != null) { - builder.append(visit(ctx.input_parameter())); + builder.appendExpression(visit(ctx.input_parameter())); } else if (ctx.nullif_expression() != null) { - builder.append(visit(ctx.nullif_expression())); + builder.appendExpression(visit(ctx.nullif_expression())); } if (ctx.op != null) { @@ -1253,7 +1253,7 @@ public QueryTokenStream visitEmpty_collection_comparison_expression( QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.collection_valued_path_expression())); + builder.appendExpression(visit(ctx.collection_valued_path_expression())); builder.append(QueryTokens.expression(ctx.IS())); if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); @@ -1268,7 +1268,7 @@ public QueryTokenStream visitCollection_member_expression(EqlParser.Collection_m QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.entity_or_value_expression())); + builder.appendExpression(visit(ctx.entity_or_value_expression())); if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); } @@ -1284,34 +1284,30 @@ public QueryTokenStream visitCollection_member_expression(EqlParser.Collection_m @Override public QueryTokenStream visitEntity_or_value_expression(EqlParser.Entity_or_value_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.single_valued_object_path_expression() != null) { - builder.append(visit(ctx.single_valued_object_path_expression())); + return visit(ctx.single_valued_object_path_expression()); } else if (ctx.state_field_path_expression() != null) { - builder.append(visit(ctx.state_field_path_expression())); + return visit(ctx.state_field_path_expression()); } else if (ctx.simple_entity_or_value_expression() != null) { - builder.append(visit(ctx.simple_entity_or_value_expression())); + return visit(ctx.simple_entity_or_value_expression()); } - return builder; + return QueryTokenStream.empty(); } @Override public QueryTokenStream visitSimple_entity_or_value_expression( EqlParser.Simple_entity_or_value_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.identification_variable() != null) { - builder.append(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); } else if (ctx.input_parameter() != null) { - builder.append(visit(ctx.input_parameter())); + return visit(ctx.input_parameter()); } else if (ctx.literal() != null) { - builder.append(visit(ctx.literal())); + return visit(ctx.literal()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -1356,13 +1352,13 @@ public QueryTokenStream visitStringComparison(EqlParser.StringComparisonContext QueryRendererBuilder builder = QueryRenderer.builder(); - builder.appendInline(visit(ctx.string_expression(0))); - builder.append(visit(ctx.comparison_operator())); + builder.appendExpression(visit(ctx.string_expression(0))); + builder.appendExpression(visit(ctx.comparison_operator())); if (ctx.string_expression(1) != null) { - builder.append(visit(ctx.string_expression(1))); + builder.appendExpression(visit(ctx.string_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1377,9 +1373,9 @@ public QueryTokenStream visitBooleanComparison(EqlParser.BooleanComparisonContex builder.append(QueryTokens.ventilated(ctx.op)); if (ctx.boolean_expression(1) != null) { - builder.append(visit(ctx.boolean_expression(1))); + builder.appendExpression(visit(ctx.boolean_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1399,9 +1395,9 @@ public QueryTokenStream visitEnumComparison(EqlParser.EnumComparisonContext ctx) builder.append(QueryTokens.ventilated(ctx.op)); if (ctx.enum_expression(1) != null) { - builder.append(visit(ctx.enum_expression(1))); + builder.appendExpression(visit(ctx.enum_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1416,9 +1412,9 @@ public QueryTokenStream visitDatetimeComparison(EqlParser.DatetimeComparisonCont builder.append(QueryTokens.ventilated(ctx.comparison_operator().op)); if (ctx.datetime_expression(1) != null) { - builder.append(visit(ctx.datetime_expression(1))); + builder.appendExpression(visit(ctx.datetime_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1433,9 +1429,9 @@ public QueryTokenStream visitEntityComparison(EqlParser.EntityComparisonContext builder.append(QueryTokens.expression(ctx.op)); if (ctx.entity_expression(1) != null) { - builder.append(visit(ctx.entity_expression(1))); + builder.appendExpression(visit(ctx.entity_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1446,13 +1442,13 @@ public QueryTokenStream visitArithmeticComparison(EqlParser.ArithmeticComparison QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.arithmetic_expression(0))); - builder.append(visit(ctx.comparison_operator())); + builder.appendExpression(visit(ctx.arithmetic_expression(0))); + builder.appendExpression(visit(ctx.comparison_operator())); if (ctx.arithmetic_expression(1) != null) { - builder.append(visit(ctx.arithmetic_expression(1))); + builder.appendExpression(visit(ctx.arithmetic_expression(1))); } else { - builder.append(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } return builder; @@ -1465,7 +1461,7 @@ public QueryTokenStream visitEntityTypeComparison(EqlParser.EntityTypeComparison builder.appendInline(visit(ctx.entity_type_expression(0))); builder.append(QueryTokens.ventilated(ctx.op)); - builder.append(visit(ctx.entity_type_expression(1))); + builder.appendExpression(visit(ctx.entity_type_expression(1))); return builder; } @@ -1484,42 +1480,37 @@ public QueryTokenStream visitRegexpComparison(EqlParser.RegexpComparisonContext @Override public QueryTokenStream visitComparison_operator(EqlParser.Comparison_operatorContext ctx) { - return QueryRendererBuilder.from(QueryTokens.ventilated(ctx.op)); + return QueryTokenStream.ofToken(ctx.op); } @Override public QueryTokenStream visitArithmetic_expression(EqlParser.Arithmetic_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.arithmetic_expression() != null) { + QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(visit(ctx.arithmetic_expression())); - builder.append(QueryTokens.expression(ctx.op)); + builder.append(QueryTokens.ventilated(ctx.op)); builder.append(visit(ctx.arithmetic_term())); + return builder; } else { - builder.append(visit(ctx.arithmetic_term())); + return visit(ctx.arithmetic_term()); } - - return builder; } @Override public QueryTokenStream visitArithmetic_term(EqlParser.Arithmetic_termContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.arithmetic_term() != null) { - + QueryRendererBuilder builder = QueryRenderer.builder(); builder.appendInline(visit(ctx.arithmetic_term())); builder.append(QueryTokens.ventilated(ctx.op)); builder.append(visit(ctx.arithmetic_factor())); + return builder; } else { - builder.append(visit(ctx.arithmetic_factor())); + return visit(ctx.arithmetic_factor()); } - - return builder; } @Override @@ -1530,7 +1521,8 @@ public QueryTokenStream visitArithmetic_factor(EqlParser.Arithmetic_factorContex if (ctx.op != null) { builder.append(QueryTokens.token(ctx.op)); } - builder.appendInline(visit(ctx.arithmetic_primary())); + + builder.append(visit(ctx.arithmetic_primary())); return builder; } @@ -1595,6 +1587,11 @@ public QueryTokenStream visitString_expression(EqlParser.String_expressionContex builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.subquery())); builder.append(TOKEN_CLOSE_PAREN); + } else if (!ObjectUtils.isEmpty(ctx.string_expression())) { + + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_DOUBLE_PIPE); + builder.appendExpression(visit(ctx.string_expression(1))); } return builder; @@ -1682,45 +1679,39 @@ public QueryTokenStream visitEnum_expression(EqlParser.Enum_expressionContext ct @Override public QueryTokenStream visitEntity_expression(EqlParser.Entity_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.single_valued_object_path_expression() != null) { - builder.append(visit(ctx.single_valued_object_path_expression())); + return visit(ctx.single_valued_object_path_expression()); } else if (ctx.simple_entity_expression() != null) { - builder.append(visit(ctx.simple_entity_expression())); + return visit(ctx.simple_entity_expression()); } - return builder; + return QueryTokenStream.empty(); } @Override public QueryTokenStream visitSimple_entity_expression(EqlParser.Simple_entity_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.identification_variable() != null) { - builder.append(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); } else if (ctx.input_parameter() != null) { - builder.append(visit(ctx.input_parameter())); + return visit(ctx.input_parameter()); } - return builder; + return QueryTokenStream.empty(); } @Override public QueryTokenStream visitEntity_type_expression(EqlParser.Entity_type_expressionContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.type_discriminator() != null) { - builder.append(visit(ctx.type_discriminator())); + return visit(ctx.type_discriminator()); } else if (ctx.entity_type_literal() != null) { - builder.append(visit(ctx.entity_type_literal())); + return visit(ctx.entity_type_literal()); } else if (ctx.input_parameter() != null) { - builder.append(visit(ctx.input_parameter())); + return visit(ctx.input_parameter()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -1895,7 +1886,7 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret builder.append(QueryTokens.token(ctx.SUBSTRING())); builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.string_expression(0))); + builder.append(visit(ctx.string_expression(0))); builder.append(TOKEN_COMMA); builder.appendInline(QueryTokenStream.concat(ctx.arithmetic_expression(), this::visit, TOKEN_COMMA)); builder.append(TOKEN_CLOSE_PAREN); @@ -1903,16 +1894,19 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret builder.append(QueryTokens.token(ctx.TRIM())); builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); if (ctx.trim_specification() != null) { - builder.appendExpression(visit(ctx.trim_specification())); + nested.appendExpression(visit(ctx.trim_specification())); } if (ctx.trim_character() != null) { - builder.appendExpression(visit(ctx.trim_character())); + nested.appendExpression(visit(ctx.trim_character())); } if (ctx.FROM() != null) { - builder.append(QueryTokens.expression(ctx.FROM())); + nested.append(QueryTokens.expression(ctx.FROM())); } - builder.appendInline(visit(ctx.string_expression(0))); + nested.append(visit(ctx.string_expression(0))); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LOWER() != null) { @@ -1924,7 +1918,30 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret builder.append(QueryTokens.token(ctx.UPPER())); builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.string_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.LEFT() != null) { + builder.append(QueryTokens.token(ctx.LEFT())); + builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.RIGHT() != null) { + builder.append(QueryTokens.token(ctx.RIGHT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.REPLACE() != null) { + builder.append(QueryTokens.token(ctx.REPLACE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.string_expression(1))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.string_expression(2))); builder.append(TOKEN_CLOSE_PAREN); } @@ -1935,11 +1952,11 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret public QueryTokenStream visitTrim_specification(EqlParser.Trim_specificationContext ctx) { if (ctx.LEADING() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.LEADING())); + return QueryTokenStream.ofToken(ctx.LEADING()); } else if (ctx.TRAILING() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.TRAILING())); + return QueryTokenStream.ofToken(ctx.TRAILING()); } else { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.BOTH())); + return QueryTokenStream.ofToken(ctx.BOTH()); } } @@ -1952,9 +1969,9 @@ public QueryTokenStream visitCast_function(EqlParser.Cast_functionContext ctx) { builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.single_valued_path_expression())); builder.append(TOKEN_SPACE); - builder.appendInline(visit(ctx.identification_variable())); + builder.appendInline(QueryTokenStream.concat(ctx.identification_variable(), this::visit, TOKEN_SPACE)); - if (ctx.numeric_literal() != null) { + if (!ObjectUtils.isEmpty(ctx.numeric_literal())) { builder.append(TOKEN_OPEN_PAREN); builder.appendInline(QueryTokenStream.concat(ctx.numeric_literal(), this::visit, TOKEN_COMMA)); @@ -1991,12 +2008,15 @@ public QueryTokenStream visitFunction_invocation(EqlParser.Function_invocationCo public QueryTokenStream visitExtract_datetime_field(EqlParser.Extract_datetime_fieldContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.datetime_field())); + nested.append(QueryTokens.expression(ctx.FROM())); + nested.appendExpression(visit(ctx.datetime_expression())); builder.append(QueryTokens.token(ctx.EXTRACT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendExpression(visit(ctx.datetime_field())); - builder.append(QueryTokens.expression(ctx.FROM())); - builder.appendInline(visit(ctx.datetime_expression())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -2011,12 +2031,15 @@ public QueryTokenStream visitDatetime_field(EqlParser.Datetime_fieldContext ctx) public QueryTokenStream visitExtract_datetime_part(EqlParser.Extract_datetime_partContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.datetime_part())); + nested.append(QueryTokens.expression(ctx.FROM())); + nested.appendExpression(visit(ctx.datetime_expression())); builder.append(QueryTokens.token(ctx.EXTRACT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendExpression(visit(ctx.datetime_part())); - builder.append(QueryTokens.expression(ctx.FROM())); - builder.appendInline(visit(ctx.datetime_expression())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -2055,16 +2078,21 @@ public QueryTokenStream visitCase_expression(EqlParser.Case_expressionContext ct } } + @Override + public QueryRendererBuilder visitType_literal(EqlParser.Type_literalContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + ctx.children.forEach(it -> builder.append(QueryTokens.expression(it.getText()))); + return builder; + } + @Override public QueryTokenStream visitGeneral_case_expression(EqlParser.General_case_expressionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(QueryTokens.expression(ctx.CASE())); - - ctx.when_clause().forEach(whenClauseContext -> { - builder.appendExpression(visit(whenClauseContext)); - }); + builder.appendExpression(QueryTokenStream.concat(ctx.when_clause(), this::visit, TOKEN_SPACE)); builder.append(QueryTokens.expression(ctx.ELSE())); builder.appendExpression(visit(ctx.scalar_expression())); @@ -2079,9 +2107,9 @@ public QueryTokenStream visitWhen_clause(EqlParser.When_clauseContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(QueryTokens.expression(ctx.WHEN())); - builder.append(visit(ctx.conditional_expression())); + builder.appendExpression(visit(ctx.conditional_expression())); builder.append(QueryTokens.expression(ctx.THEN())); - builder.append(visit(ctx.scalar_expression())); + builder.appendExpression(visit(ctx.scalar_expression())); return builder; } @@ -2092,14 +2120,11 @@ public QueryTokenStream visitSimple_case_expression(EqlParser.Simple_case_expres QueryRendererBuilder builder = QueryRenderer.builder(); builder.append(QueryTokens.expression(ctx.CASE())); - builder.append(visit(ctx.case_operand())); - - ctx.simple_when_clause().forEach(simpleWhenClauseContext -> { - builder.append(visit(simpleWhenClauseContext)); - }); + builder.appendExpression(visit(ctx.case_operand())); + builder.appendExpression(QueryTokenStream.concat(ctx.simple_when_clause(), this::visit, TOKEN_SPACE)); builder.append(QueryTokens.expression(ctx.ELSE())); - builder.append(visit(ctx.scalar_expression())); + builder.appendExpression(visit(ctx.scalar_expression())); builder.append(QueryTokens.expression(ctx.END())); return builder; @@ -2160,11 +2185,11 @@ public QueryTokenStream visitNullif_expression(EqlParser.Nullif_expressionContex public QueryTokenStream visitTrim_character(EqlParser.Trim_characterContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.character_valued_input_parameter() != null) { return visit(ctx.character_valued_input_parameter()); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @@ -2172,11 +2197,13 @@ public QueryTokenStream visitTrim_character(EqlParser.Trim_characterContext ctx) public QueryTokenStream visitIdentification_variable(EqlParser.Identification_variableContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.IDENTIFICATION_VARIABLE())); + return QueryTokenStream.ofToken(ctx.IDENTIFICATION_VARIABLE()); + } else if (ctx.type_literal() != null) { + return visit(ctx.type_literal()); } else if (ctx.f != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.f)); + return QueryTokenStream.ofToken(ctx.f); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @@ -2188,23 +2215,23 @@ public QueryTokenStream visitConstructor_name(EqlParser.Constructor_nameContext @Override public QueryTokenStream visitLiteral(EqlParser.LiteralContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); - if (ctx.STRINGLITERAL() != null) { - builder.append(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); + } else if (ctx.JAVASTRINGLITERAL() != null) { + return QueryTokenStream.ofToken(ctx.JAVASTRINGLITERAL()); } else if (ctx.INTLITERAL() != null) { - builder.append(QueryTokens.expression(ctx.INTLITERAL())); + return QueryTokenStream.ofToken(ctx.INTLITERAL()); } else if (ctx.FLOATLITERAL() != null) { - builder.append(QueryTokens.expression(ctx.FLOATLITERAL())); + return QueryTokenStream.ofToken(ctx.FLOATLITERAL()); } else if (ctx.LONGLITERAL() != null) { - builder.append(QueryTokens.expression(ctx.LONGLITERAL())); + return QueryTokenStream.ofToken(ctx.LONGLITERAL()); } else if (ctx.boolean_literal() != null) { - builder.append(visit(ctx.boolean_literal())); + return visit(ctx.boolean_literal()); } else if (ctx.entity_type_literal() != null) { - builder.append(visit(ctx.entity_type_literal())); + return visit(ctx.entity_type_literal()); } - return builder; + return QueryTokenStream.empty(); } @Override @@ -2239,13 +2266,13 @@ public QueryTokenStream visitPattern_value(EqlParser.Pattern_valueContext ctx) { public QueryTokenStream visitDate_time_timestamp_literal(EqlParser.Date_time_timestamp_literalContext ctx) { if (ctx.STRINGLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); } else if (ctx.DATELITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.DATELITERAL())); + return QueryTokenStream.ofToken(ctx.DATELITERAL()); } else if (ctx.TIMELITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.TIMELITERAL())); + return QueryTokenStream.ofToken(ctx.TIMELITERAL()); } else if (ctx.TIMESTAMPLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.TIMESTAMPLITERAL())); + return QueryTokenStream.ofToken(ctx.TIMESTAMPLITERAL()); } else { return QueryRenderer.builder(); } @@ -2258,20 +2285,20 @@ public QueryTokenStream visitEntity_type_literal(EqlParser.Entity_type_literalCo @Override public QueryTokenStream visitEscape_character(EqlParser.Escape_characterContext ctx) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } @Override public QueryTokenStream visitNumeric_literal(EqlParser.Numeric_literalContext ctx) { if (ctx.INTLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.INTLITERAL())); + return QueryTokenStream.ofToken(ctx.INTLITERAL()); } else if (ctx.FLOATLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.FLOATLITERAL())); + return QueryTokenStream.ofToken(ctx.FLOATLITERAL()); } else if (ctx.LONGLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.LONGLITERAL())); + return QueryTokenStream.ofToken(ctx.LONGLITERAL()); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @@ -2279,11 +2306,11 @@ public QueryTokenStream visitNumeric_literal(EqlParser.Numeric_literalContext ct public QueryTokenStream visitBoolean_literal(EqlParser.Boolean_literalContext ctx) { if (ctx.TRUE() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.TRUE())); + return QueryTokenStream.ofToken(ctx.TRUE()); } else if (ctx.FALSE() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.FALSE())); + return QueryTokenStream.ofToken(ctx.FALSE()); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @@ -2296,11 +2323,11 @@ public QueryTokenStream visitEnum_literal(EqlParser.Enum_literalContext ctx) { public QueryTokenStream visitString_literal(EqlParser.String_literalContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.STRINGLITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @@ -2337,7 +2364,7 @@ public QueryTokenStream visitCollection_value_field(EqlParser.Collection_value_f @Override public QueryTokenStream visitEntity_name(EqlParser.Entity_nameContext ctx) { - return QueryTokenStream.concat(ctx.reserved_word(), this::visit, QueryRenderer::inline, TOKEN_DOT); + return QueryTokenStream.concat(ctx.reserved_word(), this::visit, TOKEN_DOT); } @Override @@ -2368,26 +2395,25 @@ public QueryTokenStream visitFunction_name(EqlParser.Function_nameContext ctx) { } @Override - public QueryTokenStream visitCharacter_valued_input_parameter( - EqlParser.Character_valued_input_parameterContext ctx) { + public QueryTokenStream visitCharacter_valued_input_parameter(EqlParser.Character_valued_input_parameterContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.input_parameter() != null) { return visit(ctx.input_parameter()); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } @Override public QueryTokenStream visitReserved_word(EqlParser.Reserved_wordContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.IDENTIFICATION_VARIABLE())); + return QueryTokenStream.ofToken(ctx.IDENTIFICATION_VARIABLE()); } else if (ctx.f != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.f)); + return QueryTokenStream.ofToken(ctx.f); } else { - return QueryRenderer.builder(); + return QueryTokenStream.empty(); } } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java index ed14e9afdf..23e75a20ee 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java @@ -23,7 +23,6 @@ import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed EQL query by applying @@ -50,7 +49,7 @@ class EqlSortedQueryTransformer extends EqlQueryRenderer { } @Override - public QueryRendererBuilder visitSelect_statement(EqlParser.Select_statementContext ctx) { + public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -69,18 +68,16 @@ public QueryRendererBuilder visitSelect_statement(EqlParser.Select_statementCont builder.appendExpression(visit(ctx.having_clause())); } - doVisitOrderBy(builder, ctx, ObjectUtils.isEmpty(ctx.setOperator()) ? this.sort : Sort.unsorted()); - - for (int i = 0; i < ctx.setOperator().size(); i++) { - - builder.appendExpression(visit(ctx.setOperator(i))); - builder.appendExpression(visit(ctx.select_statement(i))); + if (ctx.set_fuction() != null) { + builder.appendExpression(visit(ctx.set_fuction())); + } else { + doVisitOrderBy(builder, ctx); } return builder; } - private void doVisitOrderBy(QueryRendererBuilder builder, EqlParser.Select_statementContext ctx, Sort sort) { + private void doVisitOrderBy(QueryRendererBuilder builder, EqlParser.Select_statementContext ctx) { if (ctx.orderby_clause() != null) { QueryTokenStream existingOrder = visit(ctx.orderby_clause()); @@ -113,7 +110,7 @@ public QueryTokenStream visitSelect_item(EqlParser.Select_itemContext ctx) { QueryTokenStream tokens = super.visitSelect_item(ctx); if (ctx.result_variable() != null && !tokens.isEmpty()) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; @@ -125,7 +122,7 @@ public QueryTokenStream visitJoin(EqlParser.JoinContext ctx) { QueryTokenStream tokens = super.visitJoin(ctx); if (!tokens.isEmpty()) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java index 311fdce1d3..3d6ef20a95 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java @@ -107,7 +107,7 @@ public QueryTokenStream visitQueryExpression(HqlParser.QueryExpressionContext ct @Override public QueryTokenStream visitWithClause(HqlParser.WithClauseContext ctx) { - QueryRendererBuilder builder = QueryRendererBuilder.from(TOKEN_WITH); + QueryRendererBuilder builder = QueryRendererBuilder.builder(TOKEN_WITH); builder.append(QueryTokenStream.concatExpressions(ctx.cte(), this::visit, TOKEN_COMMA)); return builder; @@ -668,7 +668,7 @@ public QueryTokenStream visitGroupedItem(HqlParser.GroupedItemContext ctx) { if (ctx.identifier() != null) { return visit(ctx.identifier()); } else if (ctx.INTEGER_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.INTEGER_LITERAL())); + return QueryTokenStream.ofToken(ctx.INTEGER_LITERAL()); } else if (ctx.expression() != null) { return visit(ctx.expression()); } else { @@ -700,7 +700,7 @@ public QueryTokenStream visitSortExpression(HqlParser.SortExpressionContext ctx) if (ctx.identifier() != null) { return visit(ctx.identifier()); } else if (ctx.INTEGER_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.INTEGER_LITERAL())); + return QueryTokenStream.ofToken(ctx.INTEGER_LITERAL()); } else if (ctx.expression() != null) { return visit(ctx.expression()); } else { @@ -712,9 +712,9 @@ public QueryTokenStream visitSortExpression(HqlParser.SortExpressionContext ctx) public QueryTokenStream visitSortDirection(HqlParser.SortDirectionContext ctx) { if (ctx.ASC() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.ASC())); + return QueryTokenStream.ofToken(ctx.ASC()); } else if (ctx.DESC() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.DESC())); + return QueryTokenStream.ofToken(ctx.DESC()); } else { return QueryTokenStream.empty(); } @@ -778,9 +778,9 @@ public QueryTokenStream visitFetchClause(HqlParser.FetchClauseContext ctx) { } if (ctx.parameterOrIntegerLiteral() != null) { - builder.append(visit(ctx.parameterOrIntegerLiteral())); + builder.appendExpression(visit(ctx.parameterOrIntegerLiteral())); } else if (ctx.parameterOrNumberLiteral() != null) { - builder.append(visit(ctx.parameterOrNumberLiteral())); + builder.appendExpression(visit(ctx.parameterOrNumberLiteral())); } if (ctx.ROW() != null) { @@ -1029,13 +1029,13 @@ public QueryTokenStream visitSetOperator(HqlParser.SetOperatorContext ctx) { public QueryTokenStream visitLiteral(HqlParser.LiteralContext ctx) { if (ctx.NULL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.NULL())); + return QueryTokenStream.ofToken(ctx.NULL()); } else if (ctx.booleanLiteral() != null) { return visit(ctx.booleanLiteral()); } else if (ctx.JAVA_STRING_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.JAVA_STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.JAVA_STRING_LITERAL()); } else if (ctx.STRING_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.STRING_LITERAL()); } else if (ctx.numericLiteral() != null) { return visit(ctx.numericLiteral()); } else if (ctx.dateTimeLiteral() != null) { @@ -1051,9 +1051,9 @@ public QueryTokenStream visitLiteral(HqlParser.LiteralContext ctx) { public QueryTokenStream visitBooleanLiteral(HqlParser.BooleanLiteralContext ctx) { if (ctx.TRUE() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.TRUE())); + return QueryTokenStream.ofToken(ctx.TRUE()); } else if (ctx.FALSE() != null) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.FALSE())); + return QueryTokenStream.ofToken(ctx.FALSE()); } else { return QueryTokenStream.empty(); } @@ -1063,19 +1063,19 @@ public QueryTokenStream visitBooleanLiteral(HqlParser.BooleanLiteralContext ctx) public QueryTokenStream visitNumericLiteral(HqlParser.NumericLiteralContext ctx) { if (ctx.INTEGER_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + return QueryTokenStream.ofToken(ctx.INTEGER_LITERAL()); } else if (ctx.LONG_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.LONG_LITERAL())); + return QueryTokenStream.ofToken(ctx.LONG_LITERAL()); } else if (ctx.BIG_INTEGER_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.BIG_INTEGER_LITERAL())); + return QueryTokenStream.ofToken(ctx.BIG_INTEGER_LITERAL()); } else if (ctx.FLOAT_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.FLOAT_LITERAL())); + return QueryTokenStream.ofToken(ctx.FLOAT_LITERAL()); } else if (ctx.DOUBLE_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.DOUBLE_LITERAL())); + return QueryTokenStream.ofToken(ctx.DOUBLE_LITERAL()); } else if (ctx.BIG_DECIMAL_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.BIG_DECIMAL_LITERAL())); + return QueryTokenStream.ofToken(ctx.BIG_DECIMAL_LITERAL()); } else if (ctx.HEX_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.HEX_LITERAL())); + return QueryTokenStream.ofToken(ctx.HEX_LITERAL()); } else { return QueryTokenStream.empty(); } @@ -1130,25 +1130,25 @@ public QueryTokenStream visitDateTimeLiteral(HqlParser.DateTimeLiteralContext ct public QueryTokenStream visitDatetimeField(HqlParser.DatetimeFieldContext ctx) { if (ctx.YEAR() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.YEAR())); + return QueryTokenStream.ofToken(ctx.YEAR()); } else if (ctx.MONTH() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.MONTH())); + return QueryTokenStream.ofToken(ctx.MONTH()); } else if (ctx.DAY() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.DAY())); + return QueryTokenStream.ofToken(ctx.DAY()); } else if (ctx.WEEK() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.WEEK())); + return QueryTokenStream.ofToken(ctx.WEEK()); } else if (ctx.QUARTER() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.QUARTER())); + return QueryTokenStream.ofToken(ctx.QUARTER()); } else if (ctx.HOUR() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.HOUR())); + return QueryTokenStream.ofToken(ctx.HOUR()); } else if (ctx.MINUTE() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.MINUTE())); + return QueryTokenStream.ofToken(ctx.MINUTE()); } else if (ctx.SECOND() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.SECOND())); + return QueryTokenStream.ofToken(ctx.SECOND()); } else if (ctx.NANOSECOND() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.NANOSECOND())); + return QueryTokenStream.ofToken(ctx.NANOSECOND()); } else if (ctx.EPOCH() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.EPOCH())); + return QueryTokenStream.ofToken(ctx.EPOCH()); } else { return QueryTokenStream.empty(); } @@ -1226,7 +1226,7 @@ public QueryTokenStream visitTimeZoneField(HqlParser.TimeZoneFieldContext ctx) { @Override public QueryTokenStream visitDateOrTimeField(HqlParser.DateOrTimeFieldContext ctx) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.DATE() != null ? ctx.DATE() : ctx.TIME())); + return QueryTokenStream.ofToken(ctx.DATE() != null ? ctx.DATE() : ctx.TIME()); } @Override @@ -1239,11 +1239,7 @@ public QueryTokenStream visitBinaryLiteral(HqlParser.BinaryLiteralContext ctx) { } else if (ctx.HEX_LITERAL() != null) { builder.append(TOKEN_OPEN_BRACE); - - builder.append(QueryTokenStream.concat(ctx.HEX_LITERAL(), it -> { - return QueryRendererBuilder.from(QueryTokens.token(it)); - }, TOKEN_COMMA)); - + builder.append(QueryTokenStream.concat(ctx.HEX_LITERAL(), QueryTokenStream::ofToken, TOKEN_COMMA)); builder.append(TOKEN_CLOSE_BRACE); } @@ -1390,7 +1386,7 @@ public QueryTokenStream visitToDurationExpression(HqlParser.ToDurationExpression QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.expression())); + builder.appendExpression(visit(ctx.expression())); builder.appendExpression(visit(ctx.datetimeField())); return builder; @@ -1401,7 +1397,7 @@ public QueryTokenStream visitFromDurationExpression(HqlParser.FromDurationExpres QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.expression())); + builder.appendExpression(visit(ctx.expression())); builder.append(QueryTokens.expression(ctx.BY())); builder.appendExpression(visit(ctx.datetimeField())); @@ -1812,12 +1808,12 @@ public QueryTokenStream visitPadFunction(HqlParser.PadFunctionContext ctx) { @Override public QueryTokenStream visitPadSpecification(HqlParser.PadSpecificationContext ctx) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.LEADING() != null ? ctx.LEADING() : ctx.TRAILING())); + return QueryTokenStream.ofToken(ctx.LEADING() != null ? ctx.LEADING() : ctx.TRAILING()); } @Override public QueryTokenStream visitPadCharacter(HqlParser.PadCharacterContext ctx) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.STRING_LITERAL()); } @Override @@ -1829,12 +1825,16 @@ public QueryTokenStream visitPadLength(HqlParser.PadLengthContext ctx) { public QueryTokenStream visitPositionFunction(HqlParser.PositionFunctionContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); builder.append(QueryTokens.token(ctx.POSITION())); builder.append(TOKEN_OPEN_PAREN); - builder.append(visit(ctx.positionFunctionPatternArgument())); - builder.append(QueryTokens.expression(ctx.IN())); - builder.appendInline(visit(ctx.positionFunctionStringArgument())); + + nested.appendExpression(visit(ctx.positionFunctionPatternArgument())); + nested.append(QueryTokens.expression(ctx.IN())); + nested.append(visit(ctx.positionFunctionStringArgument())); + + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -1857,7 +1857,7 @@ public QueryTokenStream visitOverlayFunction(HqlParser.OverlayFunctionContext ct builder.append(QueryTokens.token(ctx.OVERLAY())); builder.append(TOKEN_OPEN_PAREN); - builder.append(visit(ctx.overlayFunctionStringArgument())); + builder.appendExpression(visit(ctx.overlayFunctionStringArgument())); builder.append(QueryTokens.expression(ctx.PLACING())); builder.append(visit(ctx.overlayFunctionReplacementArgument())); builder.append(QueryTokens.expression(ctx.FROM())); @@ -2091,7 +2091,7 @@ public QueryTokenStream visitRollup(HqlParser.RollupContext ctx) { @Override public QueryTokenStream visitFormat(HqlParser.FormatContext ctx) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.STRING_LITERAL()); } @Override @@ -2153,7 +2153,7 @@ public QueryTokenStream visitJpaNonstandardFunctionName(HqlParser.JpaNonstandard return visit(ctx.identifier()); } - return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.STRING_LITERAL()); } @Override @@ -2652,10 +2652,7 @@ public QueryTokenStream visitSimpleCaseExpression(HqlParser.SimpleCaseExpression builder.append(QueryTokens.expression(ctx.CASE())); builder.append(visit(ctx.expressionOrPredicate(0))); - - ctx.caseWhenExpressionClause().forEach(caseWhenExpressionClauseContext -> { - builder.append(visit(caseWhenExpressionClauseContext)); - }); + builder.appendExpression(QueryTokenStream.concat(ctx.caseWhenExpressionClause(), this::visit, TOKEN_SPACE)); if (ctx.ELSE() != null) { @@ -2898,7 +2895,7 @@ public QueryTokenStream visitCastTarget(HqlParser.CastTargetContext ctx) { @Override public QueryTokenStream visitCastTargetType(HqlParser.CastTargetTypeContext ctx) { - return QueryRendererBuilder.from(QueryTokens.expression(ctx.fullTargetName)); + return QueryTokenStream.from(QueryTokens.token(ctx.fullTargetName)); } @Override @@ -2915,7 +2912,7 @@ public QueryTokenStream visitExtractFunction(HqlParser.ExtractFunctionContext ct nested.appendExpression(visit(ctx.extractField())); nested.append(QueryTokens.expression(ctx.FROM())); - nested.append(visit(ctx.expression())); + nested.appendExpression(visit(ctx.expression())); builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); @@ -3005,7 +3002,7 @@ public QueryTokenStream visitTrimSpecification(HqlParser.TrimSpecificationContex public QueryTokenStream visitTrimCharacter(HqlParser.TrimCharacterContext ctx) { if (ctx.STRING_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + return QueryTokenStream.ofToken(ctx.STRING_LITERAL()); } return visit(ctx.parameter()); @@ -3155,11 +3152,11 @@ public QueryTokenStream visitToOneFkReference(HqlParser.ToOneFkReferenceContext public QueryTokenStream visitElementValueQuantifier(HqlParser.ElementValueQuantifierContext ctx) { if (ctx.ELEMENT() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.ELEMENT())); + return QueryTokenStream.ofToken(ctx.ELEMENT()); } if (ctx.VALUE() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.VALUE())); + return QueryTokenStream.ofToken(ctx.VALUE()); } return QueryTokenStream.empty(); @@ -3169,11 +3166,11 @@ public QueryTokenStream visitElementValueQuantifier(HqlParser.ElementValueQuanti public QueryTokenStream visitIndexKeyQuantifier(HqlParser.IndexKeyQuantifierContext ctx) { if (ctx.INDEX() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.INDEX())); + return QueryTokenStream.ofToken(ctx.INDEX()); } if (ctx.KEY() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.KEY())); + return QueryTokenStream.ofToken(ctx.KEY()); } return QueryTokenStream.empty(); @@ -3517,11 +3514,10 @@ public QueryTokenStream visitExistsExpression(HqlParser.ExistsExpressionContext public QueryTokenStream visitInstantiationTarget(HqlParser.InstantiationTargetContext ctx) { if (ctx.LIST() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.LIST())); + return QueryTokenStream.ofToken(ctx.LIST()); } else if (ctx.MAP() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.MAP())); + return QueryTokenStream.ofToken(ctx.MAP()); } else if (ctx.simplePath() != null) { - return visit(ctx.simplePath()); } else { return QueryTokenStream.empty(); @@ -3557,7 +3553,7 @@ public QueryTokenStream visitParameterOrIntegerLiteral(HqlParser.ParameterOrInte if (ctx.parameter() != null) { return visit(ctx.parameter()); } else if (ctx.INTEGER_LITERAL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + return QueryTokenStream.ofToken(ctx.INTEGER_LITERAL()); } else { return QueryTokenStream.empty(); } @@ -3623,15 +3619,15 @@ public QueryTokenStream visitIdentifier(HqlParser.IdentifierContext ctx) { if (ctx.nakedIdentifier() != null) { return visit(ctx.nakedIdentifier()); } else if (ctx.FULL() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.FULL())); + return QueryTokenStream.ofToken(ctx.FULL()); } else if (ctx.LEFT() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.LEFT())); + return QueryTokenStream.ofToken(ctx.LEFT()); } else if (ctx.INNER() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.INNER())); + return QueryTokenStream.ofToken(ctx.INNER()); } else if (ctx.OUTER() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.OUTER())); + return QueryTokenStream.ofToken(ctx.OUTER()); } else if (ctx.RIGHT() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.RIGHT())); + return QueryTokenStream.ofToken(ctx.RIGHT()); } return QueryTokenStream.empty(); @@ -3641,11 +3637,11 @@ public QueryTokenStream visitIdentifier(HqlParser.IdentifierContext ctx) { public QueryTokenStream visitNakedIdentifier(HqlParser.NakedIdentifierContext ctx) { if (ctx.IDENTIFIER() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.IDENTIFIER())); + return QueryTokenStream.ofToken(ctx.IDENTIFIER()); } else if (ctx.QUOTED_IDENTIFIER() != null) { - return QueryRendererBuilder.from(QueryTokens.token(ctx.QUOTED_IDENTIFIER())); + return QueryTokenStream.ofToken(ctx.QUOTED_IDENTIFIER()); } else { - return QueryRendererBuilder.from(QueryTokens.token(ctx.f)); + return QueryTokenStream.ofToken(ctx.f); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java index b6b8853f93..256e4a443e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java @@ -87,7 +87,7 @@ public QueryTokenStream visitJoinPath(HqlParser.JoinPathContext ctx) { QueryTokenStream tokens = super.visitJoinPath(ctx); if (ctx.variable() != null && !isSubquery(ctx)) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; @@ -99,7 +99,7 @@ public QueryTokenStream visitJoinSubquery(HqlParser.JoinSubqueryContext ctx) { QueryTokenStream tokens = super.visitJoinSubquery(ctx); if (ctx.variable() != null && !tokens.isEmpty() && !isSubquery(ctx)) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; @@ -111,7 +111,7 @@ public QueryTokenStream visitVariable(HqlParser.VariableContext ctx) { QueryTokenStream tokens = super.visitVariable(ctx); if (ctx.identifier() != null && !tokens.isEmpty() && !isSubquery(ctx)) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java index 1686148603..c7a76502f9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java @@ -98,7 +98,8 @@ public JSqlParserQueryEnhancer(DeclaredQuery query) { /** * Parses a query string with JSqlParser. * - * @param query the query to parse + * @param sql the query to parse + * @param classOfT the query to parse * @return the parsed query */ static T parseStatement(String sql, Class classOfT) { @@ -502,7 +503,7 @@ private static boolean onlyASingleColumnProjection(List> projectio * */ enum ParsedType { - DELETE, UPDATE, SELECT, INSERT, MERGE, OTHER; + DELETE, UPDATE, SELECT, INSERT, MERGE, OTHER } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java index d40895ab78..9f42ebf970 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java @@ -88,6 +88,8 @@ public boolean hasLimitingParameters() { public static class JpaParameter extends Parameter { private final @Nullable Temporal annotation; + + @SuppressWarnings("deprecation") private @Nullable TemporalType temporalType; /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java index 82482cd99c..b6dd20cd47 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java @@ -16,7 +16,6 @@ package org.springframework.data.jpa.repository.query; import jakarta.persistence.EntityManager; -import jakarta.persistence.NoResultException; import jakarta.persistence.Query; import jakarta.persistence.StoredProcedureQuery; @@ -87,13 +86,7 @@ public Object execute(AbstractJpaQuery query, JpaParametersParameterAccessor acc Assert.notNull(query, "AbstractJpaQuery must not be null"); Assert.notNull(accessor, "JpaParametersParameterAccessor must not be null"); - Object result; - - try { - result = doExecute(query, accessor); - } catch (NoResultException e) { - return null; - } + Object result = doExecute(query, accessor); if (result == null) { return null; @@ -221,7 +214,7 @@ static class SingleEntityExecution extends JpaQueryExecution { @Override protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) { - return query.createQuery(accessor).getSingleResult(); + return query.createQuery(accessor).getSingleResultOrNull(); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java index 8ca7a007df..affcee14c8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java @@ -143,7 +143,8 @@ private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStra * * @param em must not be {@literal null}. * @param queryMethodFactory must not be {@literal null}. - * @param evaluationContextProvider must not be {@literal null}. + * @param delegate must not be {@literal null}. + * @param queryRewriterProvider must not be {@literal null}. */ public DeclaredQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory, ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java index 2adac83e9b..faad242941 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java @@ -42,7 +42,7 @@ class JpqlCountQueryTransformer extends JpqlQueryRenderer { } @Override - public QueryRenderer.QueryRendererBuilder visitSelect_statement(JpqlParser.Select_statementContext ctx) { + public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); @@ -58,6 +58,9 @@ public QueryRenderer.QueryRendererBuilder visitSelect_statement(JpqlParser.Selec if (ctx.having_clause() != null) { builder.appendExpression(visit(ctx.having_clause())); } + if (ctx.set_fuction() != null) { + builder.appendExpression(visit(ctx.set_fuction())); + } return builder; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java index 287b397384..6c1817946b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java @@ -848,9 +848,7 @@ public String getAlias(Origin source) { */ public String getAlias(Origin source) { - return aliases.computeIfAbsent(source, it -> JpqlQueryBuilder.getAlias(source.getName(), s -> { - return !aliases.containsValue(s); - }, () -> "join_" + (counter++))); + return aliases.computeIfAbsent(source, it -> JpqlQueryBuilder.getAlias(source.getName(), s -> !aliases.containsValue(s), () -> "join_" + (counter++))); } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java index f743d50669..593e25e579 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java @@ -24,7 +24,10 @@ import org.springframework.data.jpa.repository.query.JpqlParser.NullsPrecedenceContext; import org.springframework.data.jpa.repository.query.JpqlParser.Reserved_wordContext; +import org.springframework.data.jpa.repository.query.JpqlParser.Set_fuctionContext; +import org.springframework.data.jpa.repository.query.JpqlParser.Type_literalContext; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.util.ObjectUtils; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders a JPQL query without making any changes. @@ -79,6 +82,10 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext builder.appendExpression(visit(ctx.orderby_clause())); } + if (ctx.set_fuction() != null) { + builder.appendExpression(visit(ctx.set_fuction())); + } + return builder; } @@ -146,15 +153,10 @@ public QueryTokenStream visitIdentification_variable_declaration( JpqlParser.Identification_variable_declarationContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.appendExpression(visit(ctx.range_variable_declaration())); - ctx.join().forEach(joinContext -> { - builder.append(visit(joinContext)); - }); - - ctx.fetch_join().forEach(fetchJoinContext -> { - builder.append(visit(fetchJoinContext)); - }); + builder.append(visit(ctx.range_variable_declaration())); + builder.appendExpression(QueryTokenStream.concat(ctx.join(), this::visit, TOKEN_SPACE)); + builder.appendExpression(QueryTokenStream.concat(ctx.fetch_join(), this::visit, TOKEN_SPACE)); return builder; } @@ -180,14 +182,19 @@ public QueryTokenStream visitJoin(JpqlParser.JoinContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.join_spec())); - builder.append(visit(ctx.join_association_path_expression())); + builder.appendExpression(visit(ctx.join_spec())); + builder.appendExpression(visit(ctx.join_association_path_expression())); + if (ctx.AS() != null) { builder.append(QueryTokens.expression(ctx.AS())); } - builder.append(visit(ctx.identification_variable())); + + if (ctx.identification_variable() != null) { + builder.appendExpression(visit(ctx.identification_variable())); + } + if (ctx.join_condition() != null) { - builder.append(visit(ctx.join_condition())); + builder.appendExpression(visit(ctx.join_condition())); } return builder; @@ -198,9 +205,19 @@ public QueryTokenStream visitFetch_join(JpqlParser.Fetch_joinContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(visit(ctx.join_spec())); + builder.appendExpression(visit(ctx.join_spec())); builder.append(QueryTokens.expression(ctx.FETCH())); - builder.append(visit(ctx.join_association_path_expression())); + builder.appendExpression(visit(ctx.join_association_path_expression())); + + if (ctx.AS() != null) { + builder.append(QueryTokens.expression(ctx.AS())); + } + if (ctx.identification_variable() != null) { + builder.appendExpression(visit(ctx.identification_variable())); + } + if (ctx.join_condition() != null) { + builder.appendExpression(visit(ctx.join_condition())); + } return builder; } @@ -251,23 +268,25 @@ public QueryTokenStream visitJoin_association_path_expression( builder.appendExpression(visit(ctx.join_single_valued_path_expression())); } } else { + QueryRendererBuilder nested = QueryRenderer.builder(); + if (ctx.join_collection_valued_path_expression() != null) { - builder.append(QueryTokens.token(ctx.TREAT())); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.join_collection_valued_path_expression())); - builder.append(QueryTokens.expression(ctx.AS())); - builder.appendInline(visit(ctx.subtype())); - builder.append(TOKEN_CLOSE_PAREN); + nested.appendExpression(visit(ctx.join_collection_valued_path_expression())); + nested.append(QueryTokens.expression(ctx.AS())); + nested.appendExpression(visit(ctx.subtype())); + } else if (ctx.join_single_valued_path_expression() != null) { - builder.append(QueryTokens.token(ctx.TREAT())); - builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.join_single_valued_path_expression())); - builder.append(QueryTokens.expression(ctx.AS())); - builder.appendInline(visit(ctx.subtype())); - builder.append(TOKEN_CLOSE_PAREN); + nested.appendExpression(visit(ctx.join_single_valued_path_expression())); + nested.append(QueryTokens.expression(ctx.AS())); + nested.appendExpression(visit(ctx.subtype())); } + + builder.append(QueryTokens.token(ctx.TREAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); } return builder; @@ -427,12 +446,15 @@ public QueryTokenStream visitSimple_subpath(JpqlParser.Simple_subpathContext ctx public QueryTokenStream visitTreated_subpath(JpqlParser.Treated_subpathContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.general_subpath())); + nested.append(QueryTokens.expression(ctx.AS())); + nested.appendExpression(visit(ctx.subtype())); builder.append(QueryTokens.token(ctx.TREAT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendInline(visit(ctx.general_subpath())); - builder.append(QueryTokens.expression(ctx.AS())); - builder.appendInline(visit(ctx.subtype())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -544,7 +566,7 @@ public QueryTokenStream visitNew_value(JpqlParser.New_valueContext ctx) { } else if (ctx.simple_entity_expression() != null) { return visit(ctx.simple_entity_expression()); } else if (ctx.NULL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.NULL())); + return QueryTokenStream.ofToken(ctx.NULL()); } else { return QueryTokenStream.empty(); } @@ -791,8 +813,7 @@ public QueryTokenStream visitOrderby_item(JpqlParser.Orderby_itemContext ctx) { if (ctx.ASC() != null) { builder.append(QueryTokens.expression(ctx.ASC())); - } - if (ctx.DESC() != null) { + } else if (ctx.DESC() != null) { builder.append(QueryTokens.expression(ctx.DESC())); } @@ -808,12 +829,44 @@ public QueryTokenStream visitNullsPrecedence(NullsPrecedenceContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(TOKEN_NULLS); + builder.append(QueryTokens.expression(ctx.NULLS())); if (ctx.FIRST() != null) { - builder.append(TOKEN_FIRST); + builder.append(QueryTokens.expression(ctx.FIRST())); } else if (ctx.LAST() != null) { - builder.append(TOKEN_LAST); + builder.append(QueryTokens.expression(ctx.LAST())); + } + + return builder; + } + + @Override + public QueryTokenStream visitSet_fuction(Set_fuctionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.setOperator() != null) { + builder.append(visit(ctx.setOperator())); + } + + builder.appendExpression(visit(ctx.select_statement())); + + return builder; + } + + @Override + public QueryTokenStream visitSetOperator(JpqlParser.SetOperatorContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.INTERSECT() != null) { + builder.append(QueryTokens.expression(ctx.INTERSECT())); + } else if (ctx.UNION() != null) { + builder.append(QueryTokens.expression(ctx.UNION())); + } else if (ctx.EXCEPT() != null) { + builder.append(QueryTokens.expression(ctx.EXCEPT())); + } else if (ctx.ALL() != null) { + builder.append(QueryTokens.expression(ctx.ALL())); } return builder; @@ -931,6 +984,8 @@ public QueryTokenStream visitScalar_expression(JpqlParser.Scalar_expressionConte return visit(ctx.case_expression()); } else if (ctx.entity_type_expression() != null) { return visit(ctx.entity_type_expression()); + } else if (ctx.cast_function() != null) { + return (visit(ctx.cast_function())); } return QueryTokenStream.empty(); @@ -1154,9 +1209,11 @@ public QueryTokenStream visitNull_comparison_expression(JpqlParser.Null_comparis } builder.append(QueryTokens.expression(ctx.IS())); + if (ctx.NOT() != null) { builder.append(QueryTokens.expression(ctx.NOT())); } + builder.append(QueryTokens.expression(ctx.NULL())); return builder; @@ -1339,7 +1396,7 @@ public QueryTokenStream visitComparison_expression(JpqlParser.Comparison_express @Override public QueryTokenStream visitComparison_operator(JpqlParser.Comparison_operatorContext ctx) { - return QueryRenderer.from(QueryTokens.token(ctx.op)); + return QueryTokenStream.ofToken(ctx.op); } @Override @@ -1410,6 +1467,8 @@ public QueryTokenStream visitArithmetic_primary(JpqlParser.Arithmetic_primaryCon builder.append(visit(ctx.aggregate_expression())); } else if (ctx.case_expression() != null) { builder.append(visit(ctx.case_expression())); + } else if (ctx.cast_function() != null) { + builder.append(visit(ctx.cast_function())); } else if (ctx.function_invocation() != null) { builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { @@ -1446,6 +1505,11 @@ public QueryTokenStream visitString_expression(JpqlParser.String_expressionConte builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.subquery())); builder.append(TOKEN_CLOSE_PAREN); + } else if (!ObjectUtils.isEmpty(ctx.string_expression())) { + + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_DOUBLE_PIPE); + builder.appendExpression(visit(ctx.string_expression(1))); } return builder; @@ -1689,6 +1753,8 @@ public QueryTokenStream visitFunctions_returning_numerics(JpqlParser.Functions_r builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.identification_variable())); builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.extract_datetime_field() != null) { + builder.append(visit(ctx.extract_datetime_field())); } return builder; @@ -1716,6 +1782,8 @@ public QueryTokenStream visitFunctions_returning_datetime(JpqlParser.Functions_r } else if (ctx.DATETIME() != null) { builder.append(QueryTokens.expression(ctx.DATETIME())); } + } else if (ctx.extract_datetime_part() != null) { + builder.append(visit(ctx.extract_datetime_part())); } return builder; @@ -1737,22 +1805,26 @@ public QueryTokenStream visitFunctions_returning_strings(JpqlParser.Functions_re builder.append(QueryTokens.token(ctx.SUBSTRING())); builder.append(TOKEN_OPEN_PAREN); builder.append(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); builder.appendInline(QueryTokenStream.concat(ctx.arithmetic_expression(), this::visit, TOKEN_COMMA)); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.TRIM() != null) { builder.append(QueryTokens.token(ctx.TRIM())); builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); if (ctx.trim_specification() != null) { - builder.appendExpression(visit(ctx.trim_specification())); + nested.appendExpression(visit(ctx.trim_specification())); } if (ctx.trim_character() != null) { - builder.appendExpression(visit(ctx.trim_character())); + nested.appendExpression(visit(ctx.trim_character())); } if (ctx.FROM() != null) { - builder.append(QueryTokens.expression(ctx.FROM())); + nested.append(QueryTokens.expression(ctx.FROM())); } - builder.append(visit(ctx.string_expression(0))); + nested.append(visit(ctx.string_expression(0))); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LOWER() != null) { @@ -1766,6 +1838,29 @@ public QueryTokenStream visitFunctions_returning_strings(JpqlParser.Functions_re builder.append(TOKEN_OPEN_PAREN); builder.append(visit(ctx.string_expression(0))); builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.LEFT() != null) { + builder.append(QueryTokens.token(ctx.LEFT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.RIGHT() != null) { + builder.append(QueryTokens.token(ctx.RIGHT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); + } else if (ctx.REPLACE() != null) { + builder.append(QueryTokens.token(ctx.REPLACE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.string_expression(1))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.string_expression(2))); + builder.append(TOKEN_CLOSE_PAREN); } return builder; @@ -1775,14 +1870,36 @@ public QueryTokenStream visitFunctions_returning_strings(JpqlParser.Functions_re public QueryTokenStream visitTrim_specification(JpqlParser.Trim_specificationContext ctx) { if (ctx.LEADING() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.LEADING())); + return QueryTokenStream.ofToken(ctx.LEADING()); } else if (ctx.TRAILING() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.TRAILING())); + return QueryTokenStream.ofToken(ctx.TRAILING()); } else { - return QueryRenderer.from(QueryTokens.expression(ctx.BOTH())); + return QueryTokenStream.ofToken(ctx.BOTH()); } } + @Override + public QueryTokenStream visitCast_function(JpqlParser.Cast_functionContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(QueryTokens.token(ctx.CAST())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.single_valued_path_expression())); + builder.append(TOKEN_SPACE); + builder.appendInline(QueryTokenStream.concat(ctx.identification_variable(), this::visit, TOKEN_SPACE)); + + if (!ObjectUtils.isEmpty(ctx.numeric_literal())) { + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryTokenStream.concat(ctx.numeric_literal(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); + } + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + @Override public QueryTokenStream visitFunction_invocation(JpqlParser.Function_invocationContext ctx) { @@ -1804,12 +1921,15 @@ public QueryTokenStream visitFunction_invocation(JpqlParser.Function_invocationC public QueryTokenStream visitExtract_datetime_field(JpqlParser.Extract_datetime_fieldContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.datetime_field())); + nested.append(QueryTokens.expression(ctx.FROM())); + nested.appendExpression(visit(ctx.datetime_expression())); - builder.append(QueryTokens.expression(ctx.EXTRACT())); + builder.append(QueryTokens.token(ctx.EXTRACT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendExpression(visit(ctx.datetime_field())); - builder.append(QueryTokens.expression(ctx.FROM())); - builder.appendInline(visit(ctx.datetime_expression())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -1824,12 +1944,15 @@ public QueryTokenStream visitDatetime_field(JpqlParser.Datetime_fieldContext ctx public QueryTokenStream visitExtract_datetime_part(JpqlParser.Extract_datetime_partContext ctx) { QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.datetime_part())); + nested.append(QueryTokens.expression(ctx.FROM())); + nested.appendExpression(visit(ctx.datetime_expression())); - builder.append(QueryTokens.expression(ctx.EXTRACT())); + builder.append(QueryTokens.token(ctx.EXTRACT())); builder.append(TOKEN_OPEN_PAREN); - builder.appendExpression(visit(ctx.datetime_part())); - builder.append(QueryTokens.expression(ctx.FROM())); - builder.append(visit(ctx.datetime_expression())); + builder.appendInline(nested); builder.append(TOKEN_CLOSE_PAREN); return builder; @@ -1868,6 +1991,14 @@ public QueryTokenStream visitCase_expression(JpqlParser.Case_expressionContext c } } + @Override + public QueryRendererBuilder visitType_literal(Type_literalContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + ctx.children.forEach(it -> builder.append(QueryTokens.expression(it.getText()))); + return builder; + } + @Override public QueryTokenStream visitGeneral_case_expression(JpqlParser.General_case_expressionContext ctx) { @@ -1953,7 +2084,7 @@ public QueryTokenStream visitNullif_expression(JpqlParser.Nullif_expressionConte QueryRendererBuilder builder = QueryRenderer.builder(); - builder.append(QueryTokens.expression(ctx.NULLIF())); + builder.append(QueryTokens.token(ctx.NULLIF())); builder.append(TOKEN_OPEN_PAREN); builder.appendInline(visit(ctx.scalar_expression(0))); builder.append(TOKEN_COMMA); @@ -1967,7 +2098,7 @@ public QueryTokenStream visitNullif_expression(JpqlParser.Nullif_expressionConte public QueryTokenStream visitTrim_character(JpqlParser.Trim_characterContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.character_valued_input_parameter() != null) { return visit(ctx.character_valued_input_parameter()); } else { @@ -1979,9 +2110,11 @@ public QueryTokenStream visitTrim_character(JpqlParser.Trim_characterContext ctx public QueryTokenStream visitIdentification_variable(JpqlParser.Identification_variableContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.IDENTIFICATION_VARIABLE())); + return QueryTokenStream.ofToken(ctx.IDENTIFICATION_VARIABLE()); + } else if (ctx.type_literal() != null) { + return visit(ctx.type_literal()); } else if (ctx.f != null) { - return QueryRenderer.from(QueryTokens.token(ctx.f)); + return QueryTokenStream.ofToken(ctx.f); } else { return QueryTokenStream.empty(); } @@ -1996,15 +2129,15 @@ public QueryTokenStream visitConstructor_name(JpqlParser.Constructor_nameContext public QueryTokenStream visitLiteral(JpqlParser.LiteralContext ctx) { if (ctx.STRINGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); } else if (ctx.JAVASTRINGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.JAVASTRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.JAVASTRINGLITERAL()); } else if (ctx.INTLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.INTLITERAL())); + return QueryTokenStream.ofToken(ctx.INTLITERAL()); } else if (ctx.FLOATLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.FLOATLITERAL())); + return QueryTokenStream.ofToken(ctx.FLOATLITERAL()); } else if (ctx.LONGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.LONGLITERAL())); + return QueryTokenStream.ofToken(ctx.LONGLITERAL()); } else if (ctx.boolean_literal() != null) { return visit(ctx.boolean_literal()); } else if (ctx.entity_type_literal() != null) { @@ -2039,7 +2172,18 @@ public QueryTokenStream visitPattern_value(JpqlParser.Pattern_valueContext ctx) @Override public QueryTokenStream visitDate_time_timestamp_literal(JpqlParser.Date_time_timestamp_literalContext ctx) { - return QueryRenderer.from(QueryTokens.expression(ctx.STRINGLITERAL())); + + if (ctx.STRINGLITERAL() != null) { + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); + } else if (ctx.DATELITERAL() != null) { + return QueryTokenStream.ofToken(ctx.DATELITERAL()); + } else if (ctx.TIMELITERAL() != null) { + return QueryTokenStream.ofToken(ctx.TIMELITERAL()); + } else if (ctx.TIMESTAMPLITERAL() != null) { + return QueryTokenStream.ofToken(ctx.TIMESTAMPLITERAL()); + } else { + return QueryRenderer.builder(); + } } @Override @@ -2049,18 +2193,18 @@ public QueryTokenStream visitEntity_type_literal(JpqlParser.Entity_type_literalC @Override public QueryTokenStream visitEscape_character(JpqlParser.Escape_characterContext ctx) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } @Override public QueryTokenStream visitNumeric_literal(JpqlParser.Numeric_literalContext ctx) { if (ctx.INTLITERAL() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.INTLITERAL())); + return QueryTokenStream.ofToken(ctx.INTLITERAL()); } else if (ctx.FLOATLITERAL() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.FLOATLITERAL())); + return QueryTokenStream.ofToken(ctx.FLOATLITERAL()); } else if (ctx.LONGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.LONGLITERAL())); + return QueryTokenStream.ofToken(ctx.LONGLITERAL()); } else { return QueryTokenStream.empty(); } @@ -2070,9 +2214,9 @@ public QueryTokenStream visitNumeric_literal(JpqlParser.Numeric_literalContext c public QueryTokenStream visitBoolean_literal(JpqlParser.Boolean_literalContext ctx) { if (ctx.TRUE() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.TRUE())); + return QueryTokenStream.ofToken(ctx.TRUE()); } else if (ctx.FALSE() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.FALSE())); + return QueryTokenStream.ofToken(ctx.FALSE()); } else { return QueryTokenStream.empty(); } @@ -2087,9 +2231,9 @@ public QueryTokenStream visitEnum_literal(JpqlParser.Enum_literalContext ctx) { public QueryTokenStream visitString_literal(JpqlParser.String_literalContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.STRINGLITERAL() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.STRINGLITERAL())); + return QueryTokenStream.ofToken(ctx.STRINGLITERAL()); } else { return QueryTokenStream.empty(); } @@ -2128,7 +2272,7 @@ public QueryTokenStream visitCollection_value_field(JpqlParser.Collection_value_ @Override public QueryTokenStream visitEntity_name(JpqlParser.Entity_nameContext ctx) { - return QueryTokenStream.concat(ctx.reserved_word(), this::visitReserved_word, TOKEN_DOT); + return QueryTokenStream.concat(ctx.reserved_word(), this::visit, TOKEN_DOT); } @Override @@ -2163,7 +2307,7 @@ public QueryTokenStream visitCharacter_valued_input_parameter( JpqlParser.Character_valued_input_parameterContext ctx) { if (ctx.CHARACTER() != null) { - return QueryRenderer.from(QueryTokens.expression(ctx.CHARACTER())); + return QueryTokenStream.ofToken(ctx.CHARACTER()); } else if (ctx.input_parameter() != null) { return visit(ctx.input_parameter()); } else { @@ -2174,9 +2318,9 @@ public QueryTokenStream visitCharacter_valued_input_parameter( @Override public QueryTokenStream visitReserved_word(Reserved_wordContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return QueryRenderer.from(QueryTokens.token(ctx.IDENTIFICATION_VARIABLE())); + return QueryTokenStream.ofToken(ctx.IDENTIFICATION_VARIABLE()); } else if (ctx.f != null) { - return QueryRenderer.from(QueryTokens.token(ctx.f)); + return QueryTokenStream.ofToken(ctx.f); } else { return QueryTokenStream.empty(); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java index a545171bbf..196fbcff1e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java @@ -67,7 +67,11 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext builder.appendExpression(visit(ctx.having_clause())); } - doVisitOrderBy(builder, ctx); + if(ctx.set_fuction() != null) { + builder.appendExpression(visit(ctx.set_fuction())); + } else { + doVisitOrderBy(builder, ctx); + } return builder; } @@ -105,7 +109,7 @@ public QueryTokenStream visitSelect_item(JpqlParser.Select_itemContext ctx) { QueryTokenStream tokens = super.visitSelect_item(ctx); if (ctx.result_variable() != null && !tokens.isEmpty()) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; @@ -117,9 +121,10 @@ public QueryTokenStream visitJoin(JpqlParser.JoinContext ctx) { QueryTokenStream tokens = super.visitJoin(ctx); if (!tokens.isEmpty()) { - transformerSupport.registerAlias(tokens.getLast()); + transformerSupport.registerAlias(tokens.getRequiredLast()); } return tokens; } + } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java index 5d4d8acb5f..a9f38dcebc 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java @@ -138,7 +138,7 @@ public Sort createSort(Sort sort, JpaEntityInformation entity) { } /** - * Reverse scrolling variant applying {@link Direction#Backward}. In reverse scrolling, we need to flip directions for + * Reverse scrolling variant applying {@link Direction#BACKWARD}. In reverse scrolling, we need to flip directions for * the actual query so that we do not get everything from the top position and apply the limit but rather flip the * sort direction, apply the limit and then reverse the result to restore the actual sort order. */ diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java index 259343e9c7..ecd4d63ee0 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java @@ -653,7 +653,7 @@ static MethodInvocationArgument ofParameter(@Nullable String name, @Nullable Int /** * Creates a {@link MethodInvocationArgument} object for {@code position}. * - * @param position the parameter position (1-based) from the method invocation. + * @param parameter the parameter from the method invocation. * @return {@link MethodInvocationArgument} object for {@code position}. */ static MethodInvocationArgument ofParameter(Parameter parameter) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java index de3256baa3..9fd7817a7e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java @@ -273,17 +273,12 @@ public Object prepare(@Nullable Object value) { if (String.class.equals(parameterType) && !noWildcards) { - switch (type) { - case STARTING_WITH: - return String.format("%s%%", escape.escape(value.toString())); - case ENDING_WITH: - return String.format("%%%s", escape.escape(value.toString())); - case CONTAINING: - case NOT_CONTAINING: - return String.format("%%%s%%", escape.escape(value.toString())); - default: - return value; - } + return switch (type) { + case STARTING_WITH -> String.format("%s%%", escape.escape(value.toString())); + case ENDING_WITH -> String.format("%%%s", escape.escape(value.toString())); + case CONTAINING, NOT_CONTAINING -> String.format("%%%s%%", escape.escape(value.toString())); + default -> value; + }; } return Collection.class.isAssignableFrom(parameterType) // diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java index 4844060aa3..4b5f530d0b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java @@ -93,7 +93,6 @@ static QueryParameterSetterFactory forSynthetic() { * * @param parser must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}. - * @param parameters must not be {@literal null}. * @return a {@link QueryParameterSetterFactory} that can handle * {@link org.springframework.expression.spel.standard.SpelExpression}s. */ @@ -170,7 +169,6 @@ private static class ExpressionBasedQueryParameterSetterFactory extends QueryPar /** * @param parser must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}. - * @param parameters must not be {@literal null}. */ ExpressionBasedQueryParameterSetterFactory(ValueExpressionParser parser, ValueEvaluationContextProvider evaluationContextProvider) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java index 87fe53e050..6d116a33c9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java @@ -20,9 +20,9 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.function.Function; import java.util.stream.Stream; +import org.springframework.lang.Nullable; import org.springframework.util.CompositeIterator; /** @@ -44,9 +44,6 @@ abstract class QueryRenderer implements QueryTokenStream { /** * Creates a QueryRenderer from a {@link QueryToken}. - * - * @param token - * @return */ static QueryRenderer from(QueryToken token) { return QueryRenderer.from(Collections.singletonList(token)); @@ -54,9 +51,6 @@ static QueryRenderer from(QueryToken token) { /** * Creates a QueryRenderer from a collection of {@link QueryToken}. - * - * @param tokens - * @return */ static QueryRenderer from(Collection tokens) { List tokensToUse = new ArrayList<>(Math.max(tokens.size(), 32)); @@ -66,9 +60,6 @@ static QueryRenderer from(Collection tokens) { /** * Creates a QueryRenderer from a {@link QueryTokenStream}. - * - * @param tokens - * @return */ static QueryRenderer from(QueryTokenStream tokens) { @@ -85,8 +76,6 @@ static QueryRenderer from(QueryTokenStream tokens) { /** * Creates a new empty {@link QueryRenderer}. - * - * @return */ public static QueryRenderer empty() { return EmptyQueryRenderer.INSTANCE; @@ -94,8 +83,6 @@ public static QueryRenderer empty() { /** * Creates a new {@link QueryRendererBuilder}. - * - * @return */ static QueryRendererBuilder builder() { return new QueryRendererBuilder(); @@ -144,14 +131,11 @@ static String render(Iterable tokenStream) { results.append(token.value()); } - return results.toString(); + return results != null ? results.toString() : ""; } /** * Append a {@link QueryRenderer} to create a composed renderer. - * - * @param tokens - * @return */ QueryRenderer append(QueryTokenStream tokens) { @@ -180,7 +164,7 @@ public String toString() { return render(); } - public static QueryRenderer expression(QueryTokenStream tokenStream) { + public static QueryRenderer ofExpression(QueryTokenStream tokenStream) { if (tokenStream instanceof QueryRendererBuilder builder) { tokenStream = builder.current; @@ -258,9 +242,6 @@ String render() { /** * Append a {@link QueryRenderer} to create a composed renderer. - * - * @param tokens - * @return */ QueryRenderer append(QueryTokenStream tokens) { @@ -290,6 +271,7 @@ QueryRenderer append(QueryTokenStream tokens) { } @Override + @Nullable public QueryToken getLast() { for (int i = nested.size() - 1; i > -1; i--) { @@ -386,11 +368,13 @@ public List toList() { } @Override + @Nullable public QueryToken getFirst() { return tokens.isEmpty() ? null : tokens.get(0); } @Override + @Nullable public QueryToken getLast() { return tokens.isEmpty() ? null : tokens.get(tokens.size() - 1); } @@ -407,7 +391,7 @@ public boolean isEmpty() { @Override public boolean isExpression() { - return !tokens.isEmpty() && getLast().isExpression(); + return !tokens.isEmpty() && getRequiredLast().isExpression(); } /** @@ -418,7 +402,7 @@ public boolean isExpression() { */ static String render(Object tokens) { - if (tokens instanceof Collection tpr) { + if (tokens instanceof Collection tpr) { return render(tpr); } @@ -454,11 +438,13 @@ public Iterator iterator() { } @Override + @Nullable public QueryToken getFirst() { return tokens.getFirst(); } @Override + @Nullable public QueryToken getLast() { return tokens.getLast(); } @@ -475,7 +461,7 @@ public boolean isEmpty() { @Override public boolean isExpression() { - return !tokens.isEmpty() && tokens.getLast().isExpression(); + return !tokens.isEmpty() && tokens.getRequiredLast().isExpression(); } } @@ -486,68 +472,13 @@ static class QueryRendererBuilder implements QueryTokenStream { protected QueryRenderer current = QueryRenderer.empty(); - /** - * Compose a {@link QueryRendererBuilder} from a collection of inline elements that can be mapped to - * {@link QueryRendererBuilder}. - * - * @param elements - * @param visitor - * @param separator - * @return - * @param - */ - public static QueryRendererBuilder concat(Collection elements, Function visitor, - QueryToken separator) { - return concat(elements, visitor, QueryRendererBuilder::toInline, separator); - } - - /** - * Compose a {@link QueryRendererBuilder} from a collection of expression elements that can be mapped to - * {@link QueryRendererBuilder}. - * - * @param elements - * @param visitor - * @param separator - * @return - * @param - */ - public static QueryRendererBuilder concatExpressions(Collection elements, - Function visitor, QueryToken separator) { - return concat(elements, visitor, QueryRendererBuilder::toExpression, separator); - } - - /** - * Compose a {@link QueryRendererBuilder} from a collection of elements that can be mapped to - * {@link QueryRendererBuilder}. - * - * @param elements - * @param visitor - * @param postProcess post-processing function to convert {@link QueryRendererBuilder} into {@link QueryRenderer}. - * @param separator - * @return - * @param - */ - public static QueryRendererBuilder concat(Collection elements, Function visitor, - Function postProcess, QueryToken separator) { - - QueryRendererBuilder builder = new QueryRendererBuilder(); - for (T element : elements) { - if (!builder.isEmpty()) { - builder.append(separator); - } - builder.append(postProcess.apply(visitor.apply(element))); - } - - return builder; - } - /** * Create and initialize a QueryRendererBuilder from a {@link QueryTokens.SimpleQueryToken}. * * @param token * @return */ - public static QueryRendererBuilder from(QueryToken token) { + public static QueryRendererBuilder builder(QueryToken token) { return new QueryRendererBuilder().append(token); } @@ -627,7 +558,7 @@ QueryRendererBuilder appendExpression(QueryTokenStream tokens) { return this; } - current = current.append(QueryRenderer.expression(tokens)); + current = current.append(QueryRenderer.ofExpression(tokens)); return this; } @@ -643,11 +574,13 @@ public Stream stream() { } @Override + @Nullable public QueryToken getFirst() { return current.getFirst(); } @Override + @Nullable public QueryToken getLast() { return current.getLast(); } @@ -657,11 +590,6 @@ public boolean isExpression() { return current.isExpression(); } - /** - * Return whet the builder is empty. - * - * @return - */ @Override public boolean isEmpty() { return current.isEmpty(); @@ -686,19 +614,6 @@ public QueryRenderer build() { return current; } - QueryRenderer toExpression() { - - if (current instanceof ExpressionRenderer) { - return current; - } - - return QueryRenderer.expression(current); - } - - public QueryRenderer toInline() { - return new InlineRenderer(current); - } - } private static class InlineRenderer extends QueryRenderer { @@ -730,11 +645,13 @@ public Iterator iterator() { } @Override + @Nullable public QueryToken getFirst() { return delegate.getFirst(); } @Override + @Nullable public QueryToken getLast() { return delegate.getLast(); } @@ -784,11 +701,13 @@ public Iterator iterator() { } @Override + @Nullable public QueryToken getFirst() { return delegate.getFirst(); } @Override + @Nullable public QueryToken getLast() { return delegate.getLast(); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java index 233711d797..9b97836ab9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java @@ -16,10 +16,14 @@ package org.springframework.data.jpa.repository.query; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.function.Function; -import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.TerminalNode; + import org.springframework.data.util.Streamable; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; @@ -35,13 +39,35 @@ interface QueryTokenStream extends Streamable { /** * Creates an empty stream. - * - * @return */ static QueryTokenStream empty() { return EmptyQueryTokenStream.INSTANCE; } + /** + * Creates a QueryTokenStream from a {@link QueryToken}. + * @since 4.0 + */ + static QueryTokenStream from(QueryToken token) { + return QueryRenderer.from(Collections.singletonList(token)); + } + + /** + * Creates an token QueryRenderer from an AST {@link TerminalNode}. + * @since 4.0 + */ + static QueryTokenStream ofToken(TerminalNode node) { + return from(QueryTokens.token(node)); + } + + /** + * Creates an token QueryRenderer from an AST {@link Token}. + * @since 4.0 + */ + static QueryTokenStream ofToken(Token node) { + return from(QueryTokens.token(node)); + } + /** * Compose a {@link QueryTokenStream} from a collection of inline elements. * @@ -55,10 +81,6 @@ static QueryTokenStream concat(Collection elements, Function QueryTokenStream justAs(Collection elements, Function converter) { - return concat(elements, it-> QueryRendererBuilder.from(converter.apply(it)), QueryRenderer::inline, QueryTokens.TOKEN_SPACE); - } - /** * Compose a {@link QueryTokenStream} from a collection of expression elements. * @@ -69,7 +91,7 @@ static QueryTokenStream justAs(Collection elements, Function QueryTokenStream concatExpressions(Collection elements, Function visitor, QueryToken separator) { - return concat(elements, visitor, QueryRenderer::expression, separator); + return concat(elements, visitor, QueryRenderer::ofExpression, separator); } /** @@ -127,6 +149,21 @@ default QueryToken getFirst() { return it.hasNext() ? it.next() : null; } + /** + * @return the required first query token or throw {@link java.util.NoSuchElementException} if empty. + * @since 4.0 + */ + default QueryToken getRequiredFirst() { + + QueryToken first = getFirst(); + + if (first == null) { + throw new NoSuchElementException("No token in the stream"); + } + + return first; + } + /** * @return the last query token or {@code null} if empty. */ @@ -135,6 +172,21 @@ default QueryToken getLast() { return CollectionUtils.lastElement(toList()); } + /** + * @return the required last query token or throw {@link java.util.NoSuchElementException} if empty. + * @since 4.0 + */ + default QueryToken getRequiredLast() { + + QueryToken last = getLast(); + + if (last == null) { + throw new NoSuchElementException("No token in the stream"); + } + + return last; + } + /** * @return {@code true} if this stream represents a query expression. */ diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java index 80df0b3300..cfbaf20ba8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java @@ -31,7 +31,6 @@ class QueryTokens { /** * Commonly use tokens. */ - static final QueryToken TOKEN_NONE = token(""); static final QueryToken TOKEN_COMMA = token(", "); static final QueryToken TOKEN_SPACE = token(" "); static final QueryToken TOKEN_DOT = token("."); @@ -56,15 +55,9 @@ class QueryTokens { static final QueryToken TOKEN_WITH = expression("WITH"); static final QueryToken TOKEN_NOT = expression("NOT"); static final QueryToken TOKEN_MATERIALIZED = expression("materialized"); - static final QueryToken TOKEN_NULLS = expression("NULLS"); - static final QueryToken TOKEN_FIRST = expression("FIRST"); - static final QueryToken TOKEN_LAST = expression("LAST"); /** * Creates a {@link QueryToken token} from an ANTLR {@link TerminalNode}. - * - * @param node - * @return */ static QueryToken token(TerminalNode node) { return token(node.getText()); @@ -72,9 +65,6 @@ static QueryToken token(TerminalNode node) { /** * Creates a {@link QueryToken token} from an ANTLR {@link Token}. - * - * @param token - * @return */ static QueryToken token(Token token) { return token(token.getText()); @@ -82,9 +72,6 @@ static QueryToken token(Token token) { /** * Creates a {@link QueryToken token} from a string {@code token}. - * - * @param token - * @return */ static QueryToken token(String token) { return new SimpleQueryToken(token); @@ -92,9 +79,6 @@ static QueryToken token(String token) { /** * Creates a ventilated token that is embedded in spaces. - * - * @param token - * @return */ static QueryToken ventilated(Token token) { return new SimpleQueryToken(" " + token.getText() + " "); @@ -102,9 +86,6 @@ static QueryToken ventilated(Token token) { /** * Creates a {@link QueryToken expression} from an ANTLR {@link TerminalNode}. - * - * @param node - * @return */ static QueryToken expression(TerminalNode node) { return expression(node.getText()); @@ -112,9 +93,6 @@ static QueryToken expression(TerminalNode node) { /** * Creates a {@link QueryToken expression} from an ANTLR {@link Token}. - * - * @param token - * @return */ static QueryToken expression(Token token) { return expression(token.getText()); @@ -122,9 +100,6 @@ static QueryToken expression(Token token) { /** * Creates a {@link QueryToken token} from a string {@code expression}. - * - * @param expression - * @return */ static QueryToken expression(String expression) { return new ExpressionToken(expression); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index e625c9460f..302da71f18 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -29,6 +29,7 @@ import jakarta.persistence.criteria.From; import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Nulls; import jakarta.persistence.criteria.Path; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.Attribute.PersistentAttributeType; @@ -292,9 +293,8 @@ public static String applySorting(String query, Sort sort, @Nullable String alia Set selectionAliases = getFunctionAliases(query); selectionAliases.addAll(getFieldAliases(query)); - String orderClauses = sort.stream() - .map(order -> getOrderClause(joinAliases, selectionAliases, alias, order)) - .collect(Collectors.joining(", ")); + String orderClauses = sort.stream().map(order -> getOrderClause(joinAliases, selectionAliases, alias, order)) + .collect(Collectors.joining(", ")); builder.append(orderClauses); @@ -753,18 +753,25 @@ private static jakarta.persistence.criteria.Order toJpaOrder(Order order, From expression = toExpressionRecursively(from, property); - if (order.getNullHandling() != Sort.NullHandling.NATIVE) { - throw new UnsupportedOperationException("Applying Null Precedence using Criteria Queries is not yet supported."); - } + Nulls nulls = toNulls(order.getNullHandling()); if (order.isIgnoreCase() && String.class.equals(expression.getJavaType())) { Expression upper = cb.lower((Expression) expression); - return order.isAscending() ? cb.asc(upper) : cb.desc(upper); + return order.isAscending() ? cb.asc(upper, nulls) : cb.desc(upper, nulls); } else { - return order.isAscending() ? cb.asc(expression) : cb.desc(expression); + return order.isAscending() ? cb.asc(expression, nulls) : cb.desc(expression, nulls); } } + private static Nulls toNulls(Sort.NullHandling nullHandling) { + + return switch (nullHandling) { + case NULLS_LAST -> Nulls.LAST; + case NULLS_FIRST -> Nulls.FIRST; + case NATIVE -> Nulls.NONE; + }; + } + static Expression toExpressionRecursively(From from, PropertyPath property) { return toExpressionRecursively(from, property, false); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java index 16fa3c30e0..b044191b54 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java @@ -84,23 +84,13 @@ private void validateQuery(String query, String errorMessage, Object... argument return; } - EntityManager validatingEm = null; - - try { - validatingEm = getEntityManager().getEntityManagerFactory().createEntityManager(); - validatingEm.createQuery(query); - - } catch (RuntimeException e) { - - // Needed as there's ambiguities in how an invalid query string shall be expressed by the persistence provider - // https://java.net/projects/jpa-spec/lists/jsr338-experts/archive/2012-07/message/17 - throw new IllegalArgumentException(String.format(errorMessage, arguments), e); - - } finally { - - if (validatingEm != null) { - validatingEm.close(); - } - } + try (EntityManager validatingEm = getEntityManager().getEntityManagerFactory().createEntityManager()) { + validatingEm.createQuery(query); + } catch (RuntimeException e) { + + // Needed as there's ambiguities in how an invalid query string shall be expressed by the persistence provider + // https://java.net/projects/jpa-spec/lists/jsr338-experts/archive/2012-07/message/17 + throw new IllegalArgumentException(String.format(errorMessage, arguments), e); + } } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java index 471ede0403..e531262ce1 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java @@ -304,25 +304,17 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que : ParameterOrigin.ofExpression(expression); BindingIdentifier targetBinding = queryParameter; - Function bindingFactory; - switch (ParameterBindingType.of(typeSource)) { + Function bindingFactory = switch (ParameterBindingType.of(typeSource)) { + case LIKE -> { - case LIKE: + Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2)); + yield (identifier) -> new LikeParameterBinding(identifier, origin, likeType); + } + case IN -> (identifier) -> new InParameterBinding(identifier, origin); // fall-through we don't need a special parameter queryParameter for the given parameter. + default -> (identifier) -> new ParameterBinding(identifier, origin); + }; - Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2)); - bindingFactory = (identifier) -> new LikeParameterBinding(identifier, origin, likeType); - break; - - case IN: - bindingFactory = (identifier) -> new InParameterBinding(identifier, origin); - break; - - case AS_IS: // fall-through we don't need a special parameter queryParameter for the given parameter. - default: - bindingFactory = (identifier) -> new ParameterBinding(identifier, origin); - } - - if (origin.isExpression()) { + if (origin.isExpression()) { parameterBindings.register(bindingFactory.apply(queryParameter)); } else { targetBinding = parameterBindings.register(queryParameter, origin, bindingFactory); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 1036ce24dd..457f4a1104 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -19,7 +19,6 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.LockModeType; -import jakarta.persistence.NoResultException; import jakarta.persistence.Parameter; import jakarta.persistence.Query; import jakarta.persistence.TypedQuery; @@ -433,12 +432,7 @@ public Page findAll(Pageable pageable) { @Override public Optional findOne(Specification spec) { - - try { - return Optional.of(getQuery(spec, Sort.unsorted()).setMaxResults(2).getSingleResult()); - } catch (NoResultException e) { - return Optional.empty(); - } + return Optional.ofNullable(getQuery(spec, Sort.unsorted()).setMaxResults(2).getSingleResultOrNull()); } @Override @@ -540,13 +534,10 @@ private R doFindBy(Specification spec, Class domainClass, @Override public Optional findOne(Example example) { - try { - return Optional - .of(getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), Sort.unsorted()) - .setMaxResults(2).getSingleResult()); - } catch (NoResultException e) { - return Optional.empty(); - } + TypedQuery query = getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), + Sort.unsorted()).setMaxResults(2); + + return Optional.ofNullable(query.getSingleResultOrNull()); } @Override diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java index 8415d67959..06f454e24c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java @@ -193,7 +193,7 @@ private Set scanForMappingFileLocations() { * @param uri * @return */ - private static String getResourcePath(URI uri) throws IOException { + private static String getResourcePath(URI uri) { if (uri.isOpaque()) { // e.g. jar:file:/foo/lib/somelib.jar!/com/acme/orm.xml diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractAuditingViaJavaConfigRepositoriesTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractAuditingViaJavaConfigRepositoriesTests.java index 7a5fb05c0d..a8cec7ab1e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractAuditingViaJavaConfigRepositoriesTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/AbstractAuditingViaJavaConfigRepositoriesTests.java @@ -20,9 +20,9 @@ import jakarta.persistence.EntityManager; +import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.Date; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -54,6 +54,7 @@ * @author Oliver Gierke * @author Jens Schauder * @author Krzysztof Krason + * @author Christoph Strobl */ @ExtendWith(SpringExtension.class) @Transactional @@ -111,13 +112,13 @@ void shouldAllowUseOfDynamicSpelParametersInUpdateQueries() { em.detach(thomas); em.detach(auditor); - FixedDate.INSTANCE.setDate(new Date()); + FixedDate.INSTANCE.setDate(Instant.now()); SampleSecurityContextHolder.getCurrent().setPrincipal(thomas); auditableUserRepository.updateAllNamesToUpperCase(); // DateTime now = new DateTime(FixedDate.INSTANCE.getDate()); - LocalDateTime now = LocalDateTime.ofInstant(FixedDate.INSTANCE.getDate().toInstant(), ZoneId.systemDefault()); + LocalDateTime now = LocalDateTime.ofInstant(FixedDate.INSTANCE.getDate(), ZoneId.systemDefault()); List users = auditableUserRepository.findAll(); for (AuditableUser user : users) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureIntegrationTests.java index b3c76d25b1..40f3c87379 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/procedures/PostgresStoredProcedureIntegrationTests.java @@ -106,7 +106,8 @@ void testNamedOutputParameter() { new Employee(4, "Gabriel")); } - @DisabledOnHibernate("6") + @DisabledOnHibernate(value = "7", + disabledReason = "class org.hibernate.metamodel.model.domain.internal.EntityTypeImpl cannot be cast to class org.hibernate.query.OutputableType (org.hibernate.metamodel.model.domain.internal.EntityTypeImpl and org.hibernate.query.OutputableType are in unnamed module of loader 'app')") @Test // 2256 void testSingleEntityFromResultSet() { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java index bbfbffe1ab..02ac17b66d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java @@ -20,14 +20,16 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.data.jpa.repository.query.QueryRenderer.TokenRenderer; /** * Tests built around examples of EQL found in the EclipseLink's docs at * https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL
- * With the exception of {@literal MOD} which is defined as {@literal MOD(arithmetic_expression , arithmetic_expression)}, - * but shown in tests as {@literal MOD(arithmetic_expression ? arithmetic_expression)}. - *
+ * With the exception of {@literal MOD} which is defined as + * {@literal MOD(arithmetic_expression , arithmetic_expression)}, but shown in tests as + * {@literal MOD(arithmetic_expression ? arithmetic_expression)}.
* IMPORTANT: Purely verifies the parser without any transformations. * * @author Greg Turnquist @@ -101,6 +103,7 @@ void joinFetch() { assertQuery("SELECT e FROM Employee e JOIN FETCH e.address"); assertQuery("SELECT e FROM Employee e JOIN FETCH e.address a ORDER BY a.city"); + assertQuery("SELECT e FROM Employee e JOIN FETCH e.address AS a ORDER BY a.city"); } @Test @@ -121,6 +124,21 @@ void subselectsInFromClause() { "SELECT e, c.city FROM Employee e, (SELECT DISTINCT a.city FROM Address a) c WHERE e.address.city = c.city"); } + @Test // GH-3277 + void numericLiterals() { + + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234"); + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); + } + + @Test // GH-3308 + void newWithStrings() { + assertQuery("select new com.example.demo.SampleObject(se.id, se.sampleValue, \"java\") from SampleEntity se"); + } + @Test void orderByClause() { @@ -414,4 +432,53 @@ void isNullAndIsNotNull() { assertQuery("SELECT e FROM Employee e WHERE (e.active IS NOT null OR e.active = true)"); assertQuery("SELECT e FROM Employee e WHERE (e.active IS NOT NULL OR e.active = true)"); } + + @Test // GH-3496 + void lateralShouldBeAValidParameter() { + + assertQuery("select e from Employee e where e.lateral = :_lateral"); + assertQuery("select te from TestEntity te where te.lateral = :lateral"); + } + + @Test // GH-3136 + void intersect() { + + assertQuery(""" + SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode1 + INTERSECT SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode2 + """); + } + + @Test // GH-3136 + void except() { + + assertQuery(""" + SELECT e FROM Employee e + EXCEPT SELECT e FROM Employee e WHERE e.salary > e.manager.salary + """); + } + + @ParameterizedTest // GH-3136 + @ValueSource(strings = { "STRING", "INTEGER", "FLOAT", "DOUBLE" }) + void cast(String targetType) { + assertQuery("SELECT CAST(e.salary AS %s) FROM Employee e".formatted(targetType)); + } + + @ParameterizedTest // GH-3136 + @ValueSource(strings = { "LEFT", "RIGHT" }) + void leftRightStringFunctions(String keyword) { + assertQuery("SELECT %s(e.name, 3) FROM Employee e".formatted(keyword)); + } + + @Test // GH-3136 + void replaceStringFunctions() { + assertQuery("SELECT REPLACE(e.name, 'o', 'a') FROM Employee e"); + assertQuery("SELECT REPLACE(e.name, ' ', '_') FROM Employee e"); + } + + @Test // GH-3136 + void stringConcatWithPipes() { + assertQuery("SELECT e.firstname || e.lastname AS name FROM Employee e"); + } + } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java index 91a4bb761e..7588619f23 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java @@ -1017,6 +1017,59 @@ void powerShouldBeLegalInAQuery() { assertQuery("select e.power.id from MyEntity e"); } + @Test // GH-3136 + void doublePipeShouldBeValidAsAStringConcatOperator() { + + assertQuery(""" + select e.name || ' ' || e.title + from Employee e + """); + } + + @Test // GH-3136 + void combinedSelectStatementsShouldWork() { + + assertQuery(""" + select e from Employee e where e.last_name = 'Baggins' + intersect + select e from Employee e where e.first_name = 'Samwise' + union + select e from Employee e where e.home = 'The Shire' + except + select e from Employee e where e.home = 'Isengard' + """); + } + + @Disabled + @Test // GH-3136 + void additionalStringOperationsShouldWork() { + + assertQuery(""" + select + replace(e.name, 'Baggins', 'Proudfeet'), + left(e.role, 4), + right(e.home, 5), + cast(e.distance_from_home, int) + from Employee e + """); + } + + @Test // GH-3136 + void orderByWithNullsFirstOrLastShouldWork() { + + assertQuery(""" + select a + from Element a + order by mutationAm desc nulls first + """); + + assertQuery(""" + select a + from Element a + order by mutationAm desc nulls last + """); + } + @ParameterizedTest // GH-3342 @ValueSource(strings = { "select 1 from User u", "select -1 from User u", "select +1 from User u", "select +1 * -100 from User u", "select count(u) * -0.7f from User u", @@ -1040,10 +1093,4 @@ void entityNameWithPackageContainingReservedWord(String reservedWord) { assertQuery(source); } - @Test // GH-3496 - void lateralShouldBeAValidParameter() { - - assertQuery("select e from Employee e where e.lateral = :_lateral"); - assertQuery("select te from TestEntity te where te.lateral = :lateral"); - } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlSpecificationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlSpecificationTests.java index b6ed4fc8b8..81a104c953 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlSpecificationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlSpecificationTests.java @@ -281,7 +281,7 @@ void fromClauseDowncastingExample1() { assertQuery(""" SELECT b.name, b.ISBN FROM Order o JOIN TREAT(o.product AS Book) b - """); + """); } @Test @@ -290,7 +290,7 @@ void fromClauseDowncastingExample2() { assertQuery(""" SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) lp WHERE lp.budget > 1000 - """); + """); } /** @@ -305,7 +305,7 @@ void fromClauseDowncastingExample3_SPEC_BUG() { WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE "cost overrun" - """); + """); } @Test @@ -316,7 +316,7 @@ void fromClauseDowncastingExample3fixed() { WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE 'cost overrun' - """); + """); } @Test @@ -326,7 +326,39 @@ void fromClauseDowncastingExample4() { SELECT e FROM Employee e WHERE TREAT(e AS Exempt).vacationDays > 10 OR TREAT(e AS Contractor).hours > 100 - """); + """); + } + + @Test // GH-3136 + void substring() { + + assertQuery("select substring(c.number, 1, 2) " + // + "from Call c"); + + assertQuery("select substring(c.number, 1) " + // + "from Call c"); + } + + @Test // GH-3136 + void currentDateFunctions() { + + assertQuery("select CURRENT_DATE " + // + "from Call c "); + + assertQuery("select CURRENT_TIME " + // + "from Call c "); + + assertQuery("select CURRENT_TIMESTAMP " + // + "from Call c "); + + assertQuery("select LOCAL_DATE " + // + "from Call c "); + + assertQuery("select LOCAL_TIME " + // + "from Call c "); + + assertQuery("select LOCAL_DATETIME " + // + "from Call c "); } @Test @@ -390,7 +422,7 @@ void allExample() { WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department) - """); + """); } @Test @@ -402,7 +434,7 @@ void existsSubSelectExample2() { WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) - """); + """); } @Test @@ -470,7 +502,7 @@ void updateCaseExample1() { WHEN e.rating = 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test @@ -483,7 +515,7 @@ void updateCaseExample2() { WHEN 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test @@ -523,7 +555,7 @@ void theRest() { SELECT e FROM Employee e WHERE TYPE(e) IN (Exempt, Contractor) - """); + """); } @Test diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java index 067a3adbe4..5eb578b498 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java @@ -1646,42 +1646,36 @@ void hqlQueries() { @Test // GH-2962 void orderByWithNullsFirstOrLastShouldWork() { - assertThatNoException().isThrownBy(() -> { - parseWithoutChanges(""" - select a, - case - when a.geaendertAm is null then a.erstelltAm - else a.geaendertAm end as mutationAm - from Element a - where a.erstelltDurch = :variable - order by mutationAm desc nulls first - """); - }); - - assertThatNoException().isThrownBy(() -> { - parseWithoutChanges(""" - select a, - case - when a.geaendertAm is null then a.erstelltAm - else a.geaendertAm end as mutationAm - from Element a - where a.erstelltDurch = :variable - order by mutationAm desc nulls last - """); - }); + assertQuery(""" + select a, + case + when a.geaendertAm is null then a.erstelltAm + else a.geaendertAm end as mutationAm + from Element a + where a.erstelltDurch = :variable + order by mutationAm desc nulls first + """); + + assertQuery(""" + select a, + case + when a.geaendertAm is null then a.erstelltAm + else a.geaendertAm end as mutationAm + from Element a + where a.erstelltDurch = :variable + order by mutationAm desc nulls last + """); } @Test // GH-2964 void roundFunctionShouldWorkLikeAnyOtherFunction() { - assertThatNoException().isThrownBy(() -> { - parseWithoutChanges(""" - select round(count(ri) * 100 / max(ri.receipt.positions), 0) as perc - from StockOrderItem oi - right join StockReceiptItem ri - on ri.article = oi.article - """); - }); + assertQuery(""" + select round(count(ri) * 100 / max(ri.receipt.positions), 0) as perc + from StockOrderItem oi + right join StockReceiptItem ri + on ri.article = oi.article + """); } @Test // GH-3711 @@ -1831,6 +1825,42 @@ void powerShouldBeLegalInAQuery() { assertQuery("select e.power.id from MyEntity e"); } + @Test // GH-3136 + void doublePipeShouldBeValidAsAStringConcatOperator() { + + assertQuery(""" + select e.name || ' ' || e.title + from Employee e + """); + } + + @Test // GH-3136 + void additionalStringOperationsShouldWork() { + + assertQuery(""" + select + replace(e.name, 'Baggins', 'Proudfeet'), + left(e.role, 4), + right(e.home, 5), + cast(e.distance_from_home, int) + from Employee e + """); + } + + @Test // GH-3136 + void combinedSelectStatementsShouldWork() { + + assertQuery(""" + select e from Employee e where e.last_name = 'Baggins' + intersect + select e from Employee e where e.first_name = 'Samwise' + union + select e from Employee e where e.home = 'The Shire' + except + select e from Employee e where e.home = 'Isengard' + """); + } + @Test // GH-3219 void extractFunctionShouldSupportAdditionalExtensions() { @@ -1883,4 +1913,16 @@ void entityNameWithPackageContainingReservedWord(String reservedWord) { String source = "select new com.company.%s.thing.stuff.ClassName(e.id) from Experience e".formatted(reservedWord); assertQuery(source); } + + @ParameterizedTest // GH-3136 + @ValueSource(strings = {"LEFT", "RIGHT"}) + void leftRightStringFunctions(String keyword) { + assertQuery("SELECT %s(e.name, 3) FROM Employee e".formatted(keyword)); + } + + @Test // GH-3136 + void replaceStringFunctions() { + assertQuery("SELECT REPLACE(e.name, 'o', 'a') FROM Employee e"); + assertQuery("SELECT REPLACE(e.name, ' ', '_') FROM Employee e"); + } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java index aadf2c2589..b0ec7cbb1a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java @@ -20,6 +20,8 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; /** * Test to verify compliance of {@link JpqlParser} with standard SQL. Other than {@link JpqlSpecificationTests} tests in @@ -27,6 +29,7 @@ * suffix. * * @author Christoph Strobl + * @author Mark Paluch */ class JpqlComplianceTests { @@ -55,6 +58,52 @@ private String reduceWhitespace(String original) { .trim(); } + @Test + void selectQueries() { + + assertQuery("Select e FROM Employee e WHERE e.salary > 100000"); + assertQuery("Select e FROM Employee e WHERE e.id = :id"); + assertQuery("Select MAX(e.salary) FROM Employee e"); + assertQuery("Select e.firstName FROM Employee e"); + assertQuery("Select e.firstName, e.lastName FROM Employee e"); + } + + @Test + void selectClause() { + + assertQuery("SELECT COUNT(e) FROM Employee e"); + assertQuery("SELECT MAX(e.salary) FROM Employee e"); + assertQuery("SELECT NEW com.acme.reports.EmpReport(e.firstName, e.lastName, e.salary) FROM Employee e"); + } + + @Test + void fromClause() { + + assertQuery("SELECT e FROM Employee e"); + assertQuery("SELECT e, a FROM Employee e, MailingAddress a WHERE e.address = a.address"); + assertQuery("SELECT e FROM com.acme.Employee e"); + } + + @Test + void join() { + + assertQuery("SELECT e FROM Employee e JOIN e.address a WHERE a.city = :city"); + assertQuery("SELECT e FROM Employee e JOIN e.projects p JOIN e.projects p2 WHERE p.name = :p1 AND p2.name = :p2"); + } + + @Test + void joinFetch() { + + assertQuery("SELECT e FROM Employee e JOIN FETCH e.address"); + assertQuery("SELECT e FROM Employee e JOIN FETCH e.address a ORDER BY a.city"); + assertQuery("SELECT e FROM Employee e JOIN FETCH e.address AS a ORDER BY a.city"); + } + + @Test + void leftJoin() { + assertQuery("SELECT e FROM Employee e LEFT JOIN e.address a ORDER BY a.city"); + } + @Test // GH-3277 void numericLiterals() { @@ -70,4 +119,210 @@ void newWithStrings() { assertQuery("select new com.example.demo.SampleObject(se.id, se.sampleValue, \"java\") from SampleEntity se"); } + @Test + void orderByClause() { + + assertQuery("SELECT e FROM Employee e ORDER BY e.lastName ASC, e.firstName ASC"); // Typo in EQL document + assertQuery("SELECT e FROM Employee e LEFT JOIN e.manager m ORDER BY m.lastName NULLS FIRST"); + assertQuery("SELECT e FROM Employee e ORDER BY e.address"); + } + + @Test + void groupByClause() { + + assertQuery("SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city"); + assertQuery("SELECT e, COUNT(p) FROM Employee e LEFT JOIN e.projects p GROUP BY e"); + } + + @Test + void havingClause() { + assertQuery( + "SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city HAVING AVG(e.salary) > 100000"); + } + + @Test // GH-3136 + void union() { + + assertQuery(""" + SELECT MAX(e.salary) FROM Employee e WHERE e.address.city = :city1 + UNION SELECT MAX(e.salary) FROM Employee e WHERE e.address.city = :city2 + """); + assertQuery(""" + SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode1 + INTERSECT SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode2 + """); + assertQuery(""" + SELECT e FROM Employee e + EXCEPT SELECT e FROM Employee e WHERE e.salary > e.manager.salary + """); + } + + @Test + void whereClause() { + // TBD + } + + @Test + void updateQueries() { + assertQuery("UPDATE Employee e SET e.salary = 60000 WHERE e.salary = 50000"); + } + + @Test + void deleteQueries() { + assertQuery("DELETE FROM Employee e WHERE e.department IS NULL"); + } + + @Test + void literals() { + + assertQuery("SELECT e FROM Employee e WHERE e.name = 'Bob'"); + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234"); + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); + assertQuery("SELECT e FROM Employee e WHERE e.active = TRUE"); + assertQuery("SELECT e FROM Employee e WHERE e.startDate = {d'2012-01-03'}"); + assertQuery("SELECT e FROM Employee e WHERE e.startTime = {t'09:00:00'}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts'2012-01-03 09:00:00.000000001'}"); + assertQuery("SELECT e FROM Employee e WHERE e.gender = org.acme.Gender.MALE"); + assertQuery("UPDATE Employee e SET e.manager = NULL WHERE e.manager = :manager"); + } + + @Test + void functionsInSelect() { + + assertQuery("SELECT e.salary - 1000 FROM Employee e"); + assertQuery("SELECT e.salary + 1000 FROM Employee e"); + assertQuery("SELECT e.salary * 2 FROM Employee e"); + assertQuery("SELECT e.salary * 2.0 FROM Employee e"); + assertQuery("SELECT e.salary / 2 FROM Employee e"); + assertQuery("SELECT e.salary / 2.0 FROM Employee e"); + assertQuery("SELECT ABS(e.salary - e.manager.salary) FROM Employee e"); + assertQuery( + "select e from Employee e where case e.firstName when 'Bob' then 'Robert' when 'Jill' then 'Gillian' else '' end = 'Robert'"); + assertQuery( + "select case when e.firstName = 'Bob' then 'Robert' when e.firstName = 'Jill' then 'Gillian' else '' end from Employee e where e.firstName = 'Bob' or e.firstName = 'Jill'"); + assertQuery( + "select e from Employee e where case when e.firstName = 'Bob' then 'Robert' when e.firstName = 'Jill' then 'Gillian' else '' end = 'Robert'"); + assertQuery("SELECT COALESCE(e.salary, 0) FROM Employee e"); + assertQuery("SELECT CONCAT(e.firstName, ' ', e.lastName) FROM Employee e"); + assertQuery("SELECT e.name, CURRENT_DATE FROM Employee e"); + assertQuery("SELECT e.name, CURRENT_TIME FROM Employee e"); + assertQuery("SELECT e.name, CURRENT_TIMESTAMP FROM Employee e"); + assertQuery("SELECT LENGTH(e.lastName) FROM Employee e"); + assertQuery("SELECT LOWER(e.lastName) FROM Employee e"); + assertQuery("SELECT MOD(e.hoursWorked, 8) FROM Employee e"); + assertQuery("SELECT NULLIF(e.salary, 0) FROM Employee e"); + assertQuery("SELECT SQRT(o.RESULT) FROM Output o"); + assertQuery("SELECT SUBSTRING(e.lastName, 0, 2) FROM Employee e"); + assertQuery( + "SELECT TRIM(TRAILING FROM e.lastName), TRIM(e.lastName), TRIM(LEADING '-' FROM e.lastName) FROM Employee e"); + assertQuery("SELECT UPPER(e.lastName) FROM Employee e"); + assertQuery("SELECT CAST(e.salary NUMERIC(10, 2)) FROM Employee e"); + assertQuery("SELECT EXTRACT(YEAR FROM e.startDate) FROM Employee e"); + } + + @Test + void functionsInWhere() { + + assertQuery("SELECT e FROM Employee e WHERE e.salary - 1000 > 0"); + assertQuery("SELECT e FROM Employee e WHERE e.salary + 1000 > 0"); + assertQuery("SELECT e FROM Employee e WHERE e.salary * 2 > 0"); + assertQuery("SELECT e FROM Employee e WHERE e.salary * 2.0 > 0.0"); + assertQuery("SELECT e FROM Employee e WHERE e.salary / 2 > 0"); + assertQuery("SELECT e FROM Employee e WHERE e.salary / 2.0 > 0.0"); + assertQuery("SELECT e FROM Employee e WHERE ABS(e.salary - e.manager.salary) > 0"); + assertQuery("SELECT e FROM Employee e WHERE COALESCE(e.salary, 0) > 0"); + assertQuery("SELECT e FROM Employee e WHERE CONCAT(e.firstName, ' ', e.lastName) = 'Bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE CURRENT_DATE > CURRENT_TIME"); + assertQuery("SELECT e FROM Employee e WHERE CURRENT_TIME > CURRENT_TIMESTAMP"); + assertQuery("SELECT e FROM Employee e WHERE LENGTH(e.lastName) > 0"); + assertQuery("SELECT e FROM Employee e WHERE LOWER(e.lastName) = 'bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE MOD(e.hoursWorked, 8) > 0"); + assertQuery("SELECT e FROM Employee e WHERE SQRT(o.RESULT) > 0.0"); + assertQuery("SELECT e FROM Employee e WHERE SUBSTRING(e.lastName, 0, 2) = 'Bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE TRIM(TRAILING FROM e.lastName) = 'Bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE TRIM(e.lastName) = 'Bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE TRIM(LEADING '-' FROM e.lastName) = 'Bilbo'"); + assertQuery("SELECT e FROM Employee e WHERE UPPER(e.lastName) = 'BILBO'"); + assertQuery("SELECT e FROM Employee e WHERE CAST(e.salary NUMERIC(10, 2)) > 0.0"); + assertQuery("SELECT e FROM Employee e WHERE EXTRACT(YEAR FROM e.startDate) = '2023'"); + } + + @Test + void specialOperators() { + + assertQuery("SELECT toDo FROM Employee e JOIN e.toDoList toDo WHERE INDEX(toDo) = 1"); + assertQuery("SELECT p FROM Employee e JOIN e.priorities p WHERE KEY(p) = 'high'"); + assertQuery("SELECT e FROM Employee e WHERE SIZE(e.managedEmployees) < 2"); + assertQuery("SELECT e FROM Employee e WHERE e.managedEmployees IS EMPTY"); + assertQuery("SELECT e FROM Employee e WHERE 'write code' MEMBER OF e.responsibilities"); + assertQuery("SELECT p FROM Project p WHERE TYPE(p) = LargeProject"); + + /** + * NOTE: The following query has been altered to properly align with EclipseLink test code despite NOT matching + * their ref docs. See https://github.com/eclipse-ee4j/eclipselink/issues/1949 for more details. + */ + assertQuery("SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) p WHERE p.budget > 1000000"); + + assertQuery("SELECT p FROM Phone p WHERE FUNCTION('TO_NUMBER', p.areaCode) > 613"); + } + + @Test // GH-3314 + void isNullAndIsNotNull() { + + assertQuery("SELECT e FROM Employee e WHERE (e.active IS null OR e.active = true)"); + assertQuery("SELECT e FROM Employee e WHERE (e.active IS NULL OR e.active = true)"); + assertQuery("SELECT e FROM Employee e WHERE (e.active IS NOT null OR e.active = true)"); + assertQuery("SELECT e FROM Employee e WHERE (e.active IS NOT NULL OR e.active = true)"); + } + + @Test // GH-3496 + void lateralShouldBeAValidParameter() { + + assertQuery("select e from Employee e where e.lateral = :_lateral"); + assertQuery("select te from TestEntity te where te.lateral = :lateral"); + } + + @Test // GH-3136 + void intersect() { + + assertQuery(""" + SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode1 + INTERSECT SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode2 + """); + } + + @Test // GH-3136 + void except() { + + assertQuery(""" + SELECT e FROM Employee e + EXCEPT SELECT e FROM Employee e WHERE e.salary > e.manager.salary + """); + } + + @ParameterizedTest // GH-3136 + @ValueSource(strings = { "STRING", "INTEGER", "FLOAT", "DOUBLE" }) + void cast(String targetType) { + assertQuery("SELECT CAST(e.salary AS %s) FROM Employee e".formatted(targetType)); + } + + @ParameterizedTest // GH-3136 + @ValueSource(strings = { "LEFT", "RIGHT" }) + void leftRightStringFunctions(String keyword) { + assertQuery("SELECT %s(e.name, 3) FROM Employee e".formatted(keyword)); + } + + @Test // GH-3136 + void replaceStringFunctions() { + assertQuery("SELECT REPLACE(e.name, 'o', 'a') FROM Employee e"); + assertQuery("SELECT REPLACE(e.name, ' ', '_') FROM Employee e"); + } + + @Test // GH-3136 + void stringConcatWithPipes() { + assertQuery("SELECT e.firstname || e.lastname AS name FROM Employee e"); + } + } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java index c50f07c596..3ec9ab7c9b 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java @@ -1010,6 +1010,59 @@ void powerShouldBeLegalInAQuery() { assertQuery("select e.power.id from MyEntity e"); } + @Test // GH-3136 + void doublePipeShouldBeValidAsAStringConcatOperator() { + + assertQuery(""" + select e.name || ' ' || e.title + from Employee e + """); + } + + @Test // GH-3136 + void combinedSelectStatementsShouldWork() { + + assertQuery(""" + select e from Employee e where e.last_name = 'Baggins' + intersect + select e from Employee e where e.first_name = 'Samwise' + union + select e from Employee e where e.home = 'The Shire' + except + select e from Employee e where e.home = 'Isengard' + """); + } + + @Disabled + @Test // GH-3136 + void additionalStringOperationsShouldWork() { + + assertQuery(""" + select + replace(e.name, 'Baggins', 'Proudfeet'), + left(e.role, 4), + right(e.home, 5), + cast(e.distance_from_home, int) + from Employee e + """); + } + + @Test // GH-3136 + void orderByWithNullsFirstOrLastShouldWork() { + + assertQuery(""" + select a + from Element a + order by mutationAm desc nulls first + """); + + assertQuery(""" + select a + from Element a + order by mutationAm desc nulls last + """); + } + @ParameterizedTest // GH-3342 @ValueSource(strings = { "select 1 as value from User u", "select -1 as value from User u", "select +1 as value from User u", "select +1 * -100 as value from User u", diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java index c2de2ca015..8cbb80df43 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java @@ -784,6 +784,15 @@ void sortingRecognizesJoinAliases() { """); } + @Test // GH-3427 + void sortShouldBeAppendedToFullSelectOnlyInCaseOfSetOperator() { + + String source = "SELECT tb FROM Test tb WHERE (tb.type='A') UNION SELECT tb FROM Test tb WHERE (tb.type='B')"; + String target = createQueryFor(source, Sort.by("Type").ascending()); + + assertThat(target).isEqualTo("SELECT tb FROM Test tb WHERE (tb.type = 'A') UNION SELECT tb FROM Test tb WHERE (tb.type = 'B') order by tb.Type asc"); + } + static Stream queriesWithReservedWordsAsIdentifiers() { return Stream.of( // diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlSpecificationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlSpecificationTests.java index f32a9d1c75..7fae0bf044 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlSpecificationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlSpecificationTests.java @@ -21,6 +21,7 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; + import org.springframework.data.jpa.repository.query.QueryRenderer.TokenRenderer; /** @@ -333,6 +334,38 @@ OR TREAT(e AS Contractor).hours > 100 """); } + @Test // GH-3136 + void substring() { + + assertQuery("select substring(c.number, 1, 2) " + // + "from Call c"); + + assertQuery("select substring(c.number, 1) " + // + "from Call c"); + } + + @Test // GH-3136 + void currentDateFunctions() { + + assertQuery("select CURRENT_DATE " + // + "from Call c "); + + assertQuery("select CURRENT_TIME " + // + "from Call c "); + + assertQuery("select CURRENT_TIMESTAMP " + // + "from Call c "); + + assertQuery("select LOCAL_DATE " + // + "from Call c "); + + assertQuery("select LOCAL_TIME " + // + "from Call c "); + + assertQuery("select LOCAL_DATETIME " + // + "from Call c "); + } + @Test void pathExpressionsNamedParametersExample() { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java index 59eb5f8667..1c7adc781d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java @@ -32,6 +32,7 @@ import jakarta.persistence.criteria.From; import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Nulls; import jakarta.persistence.criteria.Root; import jakarta.persistence.spi.PersistenceProvider; import jakarta.persistence.spi.PersistenceProviderResolver; @@ -314,8 +315,8 @@ void toOrdersCanSortByJoinColumn() { assertThat(orders).hasSize(1); } - @Test // GH-3529 - void nullPrecedenceThroughCriteriaApiNotYetSupported() { + @Test // GH-3529, GH-3587 + void queryUtilsConsidersNullPrecedence() { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(User.class); @@ -324,8 +325,10 @@ void nullPrecedenceThroughCriteriaApiNotYetSupported() { Sort sort = Sort.by(Sort.Order.desc("manager").nullsFirst()); - assertThatExceptionOfType(UnsupportedOperationException.class) - .isThrownBy(() -> QueryUtils.toOrders(sort, join, builder)); + List orders = QueryUtils.toOrders(sort, join, builder); + for (jakarta.persistence.criteria.Order order : orders) { + assertThat(order.getNullPrecedence()).isEqualTo(Nulls.FIRST); + } } /** diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index 76896ece7a..042713219d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -544,7 +544,7 @@ List findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity List findRolesAndFirstnameBy(); - @Query(value = "FROM User u") + @Query(value = "SELECT u FROM User u") List findIdOnly(); // DATAJPA-1172 @@ -643,13 +643,13 @@ List findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity List findAllInterfaceProjectedBy(); // GH-2045, GH-425 - @Query("select concat(?1,u.id,?2) as id from #{#entityName} u") + @Query("select concat(?1,u.id,?2) as identifier from #{#entityName} u") List findAllAndSortByFunctionResultPositionalParameter( @Param("positionalParameter1") String positionalParameter1, @Param("positionalParameter2") String positionalParameter2, Sort sort); // GH-2045, GH-425 - @Query("select concat(:namedParameter1,u.id,:namedParameter2) as id from #{#entityName} u") + @Query("select concat(:namedParameter1,u.id,:namedParameter2) as identifier from #{#entityName} u") List findAllAndSortByFunctionResultNamedParameter(@Param("namedParameter1") String namedParameter1, @Param("namedParameter2") String namedParameter2, Sort sort); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/FixedDate.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/FixedDate.java index 377874e48c..fc930b94d5 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/FixedDate.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/FixedDate.java @@ -15,24 +15,25 @@ */ package org.springframework.data.jpa.util; -import java.util.Date; +import java.time.Instant; /** - * Holds a fixed {@link Date} value to use in components that have no direct connection. + * Holds a fixed {@link Instant} value to use in components that have no direct connection. * * @author Thomas Darimont + * @author Christoph Strobl */ public enum FixedDate { INSTANCE; - private Date fixedDate; + private Instant fixedDate; - public void setDate(Date date) { + public void setDate(Instant date) { this.fixedDate = date; } - public Date getDate() { + public Instant getDate() { return fixedDate; } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/TestMetaModel.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/TestMetaModel.java index a755ba222b..822365b65a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/TestMetaModel.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/TestMetaModel.java @@ -43,13 +43,13 @@ public class TestMetaModel implements Metamodel { private final Set> managedTypes; private final Lazy entityManagerFactory = Lazy.of(this::init); private final Lazy metamodel = Lazy.of(() -> entityManagerFactory.get().getMetamodel()); - private Lazy enityManager = Lazy.of(() -> entityManagerFactory.get().createEntityManager()); + private final Lazy entityManager = Lazy.of(() -> entityManagerFactory.get().createEntityManager()); - TestMetaModel(Set> managedTypes) { + private TestMetaModel(Set> managedTypes) { this("dynamic-tests", managedTypes); } - TestMetaModel(String persistenceUnit, Set> managedTypes) { + private TestMetaModel(String persistenceUnit, Set> managedTypes) { this.persistenceUnit = persistenceUnit; this.managedTypes = managedTypes; } @@ -66,6 +66,11 @@ public EntityType entity(Class cls) { return metamodel.get().entity(cls); } + @Override + public EntityType entity(String s) { + return metamodel.get().entity(s); + } + public ManagedType managedType(Class cls) { return metamodel.get().managedType(cls); } @@ -87,7 +92,7 @@ public Set> getEmbeddables() { } public EntityManager entityManager() { - return enityManager.get(); + return entityManager.get(); } EntityManagerFactory init() { diff --git a/spring-data-jpa/src/test/resources/META-INF/orm.xml b/spring-data-jpa/src/test/resources/META-INF/orm.xml index 820a9cced2..65f0ef28fe 100644 --- a/spring-data-jpa/src/test/resources/META-INF/orm.xml +++ b/spring-data-jpa/src/test/resources/META-INF/orm.xml @@ -1,8 +1,8 @@ - + diff --git a/spring-data-jpa/src/test/resources/META-INF/persistence-jmh.xml b/spring-data-jpa/src/test/resources/META-INF/persistence-jmh.xml index 60c6b5c97a..a78eb59468 100644 --- a/spring-data-jpa/src/test/resources/META-INF/persistence-jmh.xml +++ b/spring-data-jpa/src/test/resources/META-INF/persistence-jmh.xml @@ -14,9 +14,10 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + org.hibernate.jpa.HibernatePersistenceProvider org.springframework.data.jpa.domain.AbstractPersistable diff --git a/spring-data-jpa/src/test/resources/META-INF/persistence.xml b/spring-data-jpa/src/test/resources/META-INF/persistence.xml index 4f904373c3..35a8715991 100644 --- a/spring-data-jpa/src/test/resources/META-INF/persistence.xml +++ b/spring-data-jpa/src/test/resources/META-INF/persistence.xml @@ -1,5 +1,8 @@ - + org.springframework.data.jpa.domain.AbstractPersistable org.springframework.data.jpa.domain.AbstractAuditable diff --git a/spring-data-jpa/src/test/resources/META-INF/persistence2.xml b/spring-data-jpa/src/test/resources/META-INF/persistence2.xml index f4f7adb6b2..a93617de58 100644 --- a/spring-data-jpa/src/test/resources/META-INF/persistence2.xml +++ b/spring-data-jpa/src/test/resources/META-INF/persistence2.xml @@ -1,7 +1,8 @@ - + org.springframework.data.jpa.domain.sample.AnnotatedAuditableUser org.springframework.data.jpa.domain.sample.AuditableRole diff --git a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/mapping.xml b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/mapping.xml index 87f3460858..634c42b966 100644 --- a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/mapping.xml +++ b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/mapping.xml @@ -1,2 +1,5 @@ - + diff --git a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module1/module1-orm.xml b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module1/module1-orm.xml index da1ce9a7d4..634c42b966 100644 --- a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module1/module1-orm.xml +++ b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module1/module1-orm.xml @@ -1,3 +1,5 @@ - - + diff --git a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module2/module2-orm.xml b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module2/module2-orm.xml index da1ce9a7d4..634c42b966 100644 --- a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module2/module2-orm.xml +++ b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/module2/module2-orm.xml @@ -1,3 +1,5 @@ - - + diff --git a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence.xml b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence.xml index ad1460bad7..f75fea5ba3 100644 --- a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence.xml +++ b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence.xml @@ -1,5 +1,8 @@ - + foo.xml org.springframework.data.jpa.domain.sample.User diff --git a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence2.xml b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence2.xml index 962748440b..1666022d07 100644 --- a/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence2.xml +++ b/spring-data-jpa/src/test/resources/org/springframework/data/jpa/support/persistence2.xml @@ -1,5 +1,8 @@ - + bar.xml org.springframework.data.jpa.domain.sample.Role diff --git a/spring-data-jpa/src/test/resources/simple-persistence/simple-persistence.xml b/spring-data-jpa/src/test/resources/simple-persistence/simple-persistence.xml index 9caa71259a..706d5fb919 100644 --- a/spring-data-jpa/src/test/resources/simple-persistence/simple-persistence.xml +++ b/spring-data-jpa/src/test/resources/simple-persistence/simple-persistence.xml @@ -1,5 +1,8 @@ - + true