From 3e1c8a28a9691461c33b9c8ff1beb525b63717a2 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 23 May 2024 11:14:44 +0200 Subject: [PATCH] Refactor EQL, HQL & JPQL query rendering. This commit turns existing query transformers into introspectors using eager parsing and detection of query parts. Query transformation has also been split up into dedicated Count and Sort Query parts. To reduce duplicate code across the existing parses we introduced a reusable functional configuration. For better testing support a query assertion has been introduced to query parser tests and we adjusted invalid formatting. See: #3309 Closes: #3326 --- .../data/jpa/repository/PersonRepository.java | 4 + .../data/jpa/repository/Profiler.java | 41 + .../jpa/repository/RepositoryFinderTests.java | 8 + .../jpa/repository/query/HqlParserTests.java | 69 + .../jpa/repository/query/DeclaredQuery.java | 9 + .../query/EqlCountQueryTransformer.java | 111 + .../query/EqlQueryIntrospector.java | 98 + .../jpa/repository/query/EqlQueryParser.java | 112 +- .../repository/query/EqlQueryRenderer.java | 2140 ++++++++--------- .../repository/query/EqlQueryTransformer.java | 246 -- .../query/EqlSortedQueryTransformer.java | 132 + .../query/HqlCountQueryTransformer.java | 249 ++ .../query/HqlQueryIntrospector.java | 99 + .../jpa/repository/query/HqlQueryParser.java | 111 +- .../repository/query/HqlQueryRenderer.java | 2095 ++++++++-------- .../repository/query/HqlQueryTransformer.java | 401 --- .../query/HqlSortedQueryTransformer.java | 137 ++ .../repository/query/JpaQueryEnhancer.java | 36 +- .../jpa/repository/query/JpaQueryMethod.java | 18 +- .../jpa/repository/query/JpaQueryParser.java | 142 ++ .../query/JpaQueryParserSupport.java | 206 -- .../query/JpaQueryParsingToken.java | 195 +- .../query/JpaQueryTransformerSupport.java | 46 +- .../query/JpqlCountQueryTransformer.java | 111 + .../query/JpqlQueryIntrospector.java | 96 + .../jpa/repository/query/JpqlQueryParser.java | 113 +- .../repository/query/JpqlQueryRenderer.java | 2018 +++++++--------- .../query/JpqlQueryTransformer.java | 245 -- .../query/JpqlSortedQueryTransformer.java | 125 + .../data/jpa/repository/query/NamedQuery.java | 3 +- .../query/ParsedQueryIntrospector.java | 45 + .../jpa/repository/query/QueryRenderer.java | 464 ++++ .../repository/query/QueryTransformers.java | 5 + .../jpa/repository/query/StringQuery.java | 30 +- .../repository/query/EqlComplianceTests.java | 40 +- .../query/EqlQueryRendererTests.java | 55 +- .../query/EqlQueryTransformerTests.java | 29 +- .../query/EqlSpecificationTests.java | 202 +- .../query/HqlQueryRendererTests.java | 45 +- .../query/HqlQueryTransformerTests.java | 66 +- .../query/HqlSpecificationTests.java | 566 ++--- .../JpaQueryLookupStrategyUnitTests.java | 10 +- .../query/JpqlQueryRendererTests.java | 22 +- .../query/JpqlQueryTransformerTests.java | 34 +- .../query/JpqlSpecificationTests.java | 225 +- .../query/StringQueryUnitTests.java | 21 +- 46 files changed, 5719 insertions(+), 5556 deletions(-) create mode 100644 spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/Profiler.java create mode 100644 spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/query/HqlParserTests.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java delete mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryTransformer.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java delete mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryTransformer.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParser.java delete mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParserSupport.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java delete mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformer.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParsedQueryIntrospector.java create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java diff --git a/spring-data-jpa-performance/src/main/java/org/springframework/data/jpa/repository/PersonRepository.java b/spring-data-jpa-performance/src/main/java/org/springframework/data/jpa/repository/PersonRepository.java index 560379c0d..ea5771bce 100644 --- a/spring-data-jpa-performance/src/main/java/org/springframework/data/jpa/repository/PersonRepository.java +++ b/spring-data-jpa-performance/src/main/java/org/springframework/data/jpa/repository/PersonRepository.java @@ -17,6 +17,7 @@ package org.springframework.data.jpa.repository; import java.util.List; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.model.IPersonProjection; import org.springframework.data.jpa.model.Person; import org.springframework.data.repository.ListCrudRepository; @@ -33,6 +34,9 @@ public interface PersonRepository extends ListCrudRepository { @Query("SELECT p FROM org.springframework.data.jpa.model.Person p WHERE p.firstname = ?1") List findAllWithAnnotatedQueryByFirstname(String firstname); + @Query("SELECT p FROM org.springframework.data.jpa.model.Person p WHERE p.firstname = ?1") + List findAllWithAnnotatedQueryByFirstname(String firstname, Sort sort); + @Query(value = "SELECT * FROM person WHERE firstname = ?1", nativeQuery = true) List findAllWithNativeQueryByFirstname(String firstname); } diff --git a/spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/Profiler.java b/spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/Profiler.java new file mode 100644 index 000000000..eef1b9bae --- /dev/null +++ b/spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/Profiler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository; + +import org.springframework.data.domain.Sort; + +/** + * @author Mark Paluch + */ +public class Profiler { + + public static void main(String[] args) throws InterruptedException { + + RepositoryFinderTests tests = new RepositoryFinderTests(); + RepositoryFinderTests.BenchmarkParameters params = new RepositoryFinderTests.BenchmarkParameters(); + params.doSetup(); + + System.out.println("Ready. Waiting 10sec"); + Thread.sleep(10000); + + System.out.println("Go!"); + + while (true) { + params.repositoryProxy.findAllWithAnnotatedQueryByFirstname("first", Sort.by("firstname")); + } + + } +} diff --git a/spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/RepositoryFinderTests.java b/spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/RepositoryFinderTests.java index 993098683..322a34dfe 100644 --- a/spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/RepositoryFinderTests.java +++ b/spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/RepositoryFinderTests.java @@ -40,6 +40,8 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Timeout; import org.openjdk.jmh.annotations.Warmup; + +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.model.IPersonProjection; import org.springframework.data.jpa.model.Person; import org.springframework.data.jpa.model.Profile; @@ -151,11 +153,17 @@ public class RepositoryFinderTests { return parameters.repositoryProxy.findAllAndProjectToInterfaceByFirstname("first"); } + @Benchmark public List stringBasedQuery(BenchmarkParameters parameters) { return parameters.repositoryProxy.findAllWithAnnotatedQueryByFirstname("first"); } + @Benchmark + public List stringBasedQueryDynamicSort(BenchmarkParameters parameters) { + return parameters.repositoryProxy.findAllWithAnnotatedQueryByFirstname("first", Sort.by("firstname")); + } + @Benchmark public List stringBasedNativeQuery(BenchmarkParameters parameters) { return parameters.repositoryProxy.findAllWithNativeQueryByFirstname("first"); diff --git a/spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/query/HqlParserTests.java b/spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/query/HqlParserTests.java new file mode 100644 index 000000000..1b9e16769 --- /dev/null +++ b/spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/query/HqlParserTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import jmh.mbr.junit5.Microbenchmark; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Timeout; +import org.openjdk.jmh.annotations.Warmup; + +import org.springframework.data.domain.Sort; + +/** + * @author Mark Paluch + */ +@Microbenchmark +@Fork(1) +@Warmup(time = 2, iterations = 3) +@Measurement(time = 2) +@Timeout(time = 2) +public class HqlParserTests { + + @State(Scope.Benchmark) + public static class BenchmarkParameters { + + DeclaredQuery query; + Sort sort = Sort.by("foo"); + QueryEnhancer enhancer; + + @Setup(Level.Iteration) + public void doSetup() { + + String s = """ + SELECT e FROM Employee e JOIN e.projects p + WHERE TREAT(p AS LargeProject).budget > 1000 + OR TREAT(p AS SmallProject).name LIKE 'Persist%' + OR p.description LIKE "cost overrun" + """; + + query = DeclaredQuery.of(s, false); + enhancer = QueryEnhancerFactory.forQuery(query); + } + } + + @Benchmark + public Object measure(BenchmarkParameters parameters) { + return parameters.enhancer.applySorting(parameters.sort); + } + +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java index 4e5442440..750de16c6 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java @@ -40,6 +40,15 @@ interface DeclaredQuery { return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query, nativeQuery); } + static boolean hasNamedParameter(String query) { + + if (ObjectUtils.isEmpty(query)) { + return false; + } + + return StringQuery.hasNamedParameter(query); + } + /** * @return whether the underlying query has at least one named parameter. */ 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 new file mode 100644 index 000000000..743f30347 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java @@ -0,0 +1,111 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import java.util.List; + +import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.lang.Nullable; + +/** + * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed EQL query into a + * {@code COUNT(…)} query. + * + * @author Greg Turnquist + * @author Mark Paluch + * @since 3.4 + */ +@SuppressWarnings("ConstantValue") +class EqlCountQueryTransformer extends EqlQueryRenderer { + + private final @Nullable String countProjection; + private final @Nullable String primaryFromAlias; + + EqlCountQueryTransformer(@Nullable String countProjection, @Nullable String primaryFromAlias) { + this.countProjection = countProjection; + this.primaryFromAlias = primaryFromAlias; + } + + @Override + public QueryRendererBuilder visitSelect_statement(EqlParser.Select_statementContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.select_clause())); + builder.appendExpression(visit(ctx.from_clause())); + + if (ctx.where_clause() != null) { + builder.appendExpression(visit(ctx.where_clause())); + } + if (ctx.groupby_clause() != null) { + builder.appendExpression(visit(ctx.groupby_clause())); + } + if (ctx.having_clause() != null) { + builder.appendExpression(visit(ctx.having_clause())); + } + + return builder; + } + + @Override + public QueryRendererBuilder visitSelect_clause(EqlParser.Select_clauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(JpaQueryParsingToken.expression(ctx.SELECT())); + builder.append(TOKEN_COUNT_FUNC); + + if (countProjection != null) { + builder.append(JpaQueryParsingToken.token(countProjection)); + } + + QueryRendererBuilder nested = QueryRenderer.builder(); + + if (ctx.DISTINCT() != null) { + nested.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); + } + + if (countProjection == null) { + + if (ctx.DISTINCT() != null) { + + QueryRendererBuilder selectionListbuilder = QueryRendererBuilder.concat(ctx.select_item(), this::visit, + TOKEN_COMMA); + + List countSelection = QueryTransformers + .filterCountSelection(selectionListbuilder.build().stream().toList()); + + if (countSelection.stream().anyMatch(eqlToken -> eqlToken.getToken().contains("new"))) { + // constructor + nested.append(new JpaQueryParsingToken(primaryFromAlias)); + } else { + // keep all the select items to distinct against + nested.append(countSelection); + } + } else { + nested.append(new JpaQueryParsingToken(primaryFromAlias)); + } + } + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java new file mode 100644 index 000000000..2f094b2a2 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.lang.Nullable; + +/** + * {@link ParsedQueryIntrospector} for EQL queries. + * + * @author Mark Paluch + */ +@SuppressWarnings("UnreachableCode") +class EqlQueryIntrospector extends EqlBaseVisitor implements ParsedQueryIntrospector { + + private final EqlQueryRenderer renderer = new EqlQueryRenderer(); + + private @Nullable String primaryFromAlias = null; + private @Nullable List projection; + private boolean projectionProcessed; + private boolean hasConstructorExpression = false; + + @Override + public String getAlias() { + return primaryFromAlias; + } + + @Override + public List getProjection() { + return projection == null ? Collections.emptyList() : projection; + } + + @Override + public boolean hasConstructorExpression() { + return hasConstructorExpression; + } + + @Override + public Void visitSelect_clause(EqlParser.Select_clauseContext ctx) { + + List selections = ctx.select_item(); + List selectItemTokens = new ArrayList<>(selections.size() * 2); + + for (EqlParser.Select_itemContext selection : selections) { + + if (!selectItemTokens.isEmpty()) { + selectItemTokens.add(TOKEN_COMMA); + } + + selectItemTokens.add(JpaQueryParsingToken.token(renderer.visitSelect_item(selection).build().render())); + } + + if (!projectionProcessed) { + projection = selectItemTokens; + projectionProcessed = true; + } + + return super.visitSelect_clause(ctx); + } + + @Override + public Void visitRange_variable_declaration(EqlParser.Range_variable_declarationContext ctx) { + + if (primaryFromAlias == null) { + primaryFromAlias = ctx.identification_variable() != null ? ctx.identification_variable().getText() + : ctx.entity_name().getText(); + } + + return super.visitRange_variable_declaration(ctx); + } + + @Override + public Void visitConstructor_expression(EqlParser.Constructor_expressionContext ctx) { + + hasConstructorExpression = true; + + return super.visitConstructor_expression(ctx); + } + +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryParser.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryParser.java index 4e5dadff3..7f029c46c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryParser.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryParser.java @@ -15,118 +15,30 @@ */ package org.springframework.data.jpa.repository.query; -import java.util.List; - -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.ParserRuleContext; -import org.springframework.data.domain.Sort; -import org.springframework.lang.Nullable; - /** - * Implements the {@code EQL} parsing operations of a {@link JpaQueryParserSupport} using the ANTLR-generated - * {@link EqlParser} and {@link EqlQueryTransformer}. + * Implements the {@code EQL} parsing operations of a {@link JpaQueryParser} using the ANTLR-generated + * {@link EqlParser}. * * @author Greg Turnquist + * @author Mark Paluch * @since 3.2 */ -class EqlQueryParser extends JpaQueryParserSupport { +class EqlQueryParser extends JpaQueryParser { - EqlQueryParser(String query) { - super(query); + private EqlQueryParser(String query) { + super(parse(query, EqlLexer::new, EqlParser::new, EqlParser::start), new EqlQueryIntrospector(), + EqlSortedQueryTransformer::new, EqlCountQueryTransformer::new); } /** - * Convenience method to parse a EQL query. Will throw a {@link BadJpqlGrammarException} if the query is invalid. + * Parse a EQL query. * * @param query - * @return a parsed query, ready for postprocessing + * @return the query parser. + * @throws BadJpqlGrammarException */ - public static ParserRuleContext parseQuery(String query) { - - EqlLexer lexer = new EqlLexer(CharStreams.fromString(query)); - EqlParser parser = new EqlParser(new CommonTokenStream(lexer)); - - configureParser(query, lexer, parser); - - return parser.start(); + public static EqlQueryParser parseQuery(String query) throws BadJpqlGrammarException { + return new EqlQueryParser(query); } - /** - * Parse the query using {@link #parseQuery(String)}. - * - * @return a parsed query - */ - @Override - protected ParserRuleContext parse(String query) { - return parseQuery(query); - } - - /** - * Use the {@link EqlQueryTransformer} to transform the original query into a query with the {@link Sort} applied. - * - * @param parsedQuery - * @param sort can be {@literal null} - * @return list of {@link JpaQueryParsingToken}s - */ - @Override - protected List applySort(ParserRuleContext parsedQuery, Sort sort) { - return new EqlQueryTransformer(sort).visit(parsedQuery); - } - - /** - * Use the {@link EqlQueryTransformer} to transform the original query into a count query. - * - * @param parsedQuery - * @param countProjection - * @return list of {@link JpaQueryParsingToken}s - */ - @Override - protected List doCreateCountQuery(ParserRuleContext parsedQuery, - @Nullable String countProjection) { - return new EqlQueryTransformer(true, countProjection).visit(parsedQuery); - } - - /** - * Run the parsed query through {@link EqlQueryTransformer} to find the primary FROM clause's alias. - * - * @param parsedQuery - * @return can be {@literal null} - */ - @Override - protected String doFindAlias(ParserRuleContext parsedQuery) { - - EqlQueryTransformer transformVisitor = new EqlQueryTransformer(); - transformVisitor.visit(parsedQuery); - return transformVisitor.getAlias(); - } - - /** - * Use {@link EqlQueryTransformer} to find the projection of the query. - * - * @param parsedQuery - * @return - */ - @Override - protected List doFindProjection(ParserRuleContext parsedQuery) { - - EqlQueryTransformer transformVisitor = new EqlQueryTransformer(); - transformVisitor.visit(parsedQuery); - return transformVisitor.getProjection(); - } - - /** - * Use {@link EqlQueryTransformer} to detect if the query uses a {@code new com.example.Dto()} DTO constructor in the - * primary select clause. - * - * @param parsedQuery - * @return Guaranteed to be {@literal true} or {@literal false}. - */ - @Override - protected boolean doCheckForConstructor(ParserRuleContext parsedQuery) { - - EqlQueryTransformer transformVisitor = new EqlQueryTransformer(); - transformVisitor.visit(parsedQuery); - return transformVisitor.hasConstructorExpression(); - } } 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 f3ecafe01..14dd5ed68 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 @@ -20,6 +20,10 @@ import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken import java.util.ArrayList; import java.util.List; +import org.antlr.v4.runtime.tree.ParseTree; + +import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; + /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders an EQL query without making any changes. * @@ -28,15 +32,15 @@ import java.util.List; * @since 3.2 */ @SuppressWarnings({ "ConstantConditions", "DuplicatedCode" }) -class EqlQueryRenderer extends EqlBaseVisitor> { +class EqlQueryRenderer extends EqlBaseVisitor { @Override - public List visitStart(EqlParser.StartContext ctx) { + public QueryRendererBuilder visitStart(EqlParser.StartContext ctx) { return visit(ctx.ql_statement()); } @Override - public List visitQl_statement(EqlParser.Ql_statementContext ctx) { + public QueryRendererBuilder visitQl_statement(EqlParser.Ql_statementContext ctx) { if (ctx.select_statement() != null) { return visit(ctx.select_statement()); @@ -45,112 +49,111 @@ class EqlQueryRenderer extends EqlBaseVisitor> { } else if (ctx.delete_statement() != null) { return visit(ctx.delete_statement()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitSelect_statement(EqlParser.Select_statementContext ctx) { + public QueryRendererBuilder visitSelect_statement(EqlParser.Select_statementContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.select_clause())); - tokens.addAll(visit(ctx.from_clause())); + builder.appendExpression(visit(ctx.select_clause())); + builder.appendExpression(visit(ctx.from_clause())); if (ctx.where_clause() != null) { - tokens.addAll(visit(ctx.where_clause())); + builder.appendExpression(visit(ctx.where_clause())); } if (ctx.groupby_clause() != null) { - tokens.addAll(visit(ctx.groupby_clause())); + builder.appendExpression(visit(ctx.groupby_clause())); } if (ctx.having_clause() != null) { - tokens.addAll(visit(ctx.having_clause())); + builder.appendExpression(visit(ctx.having_clause())); } if (ctx.orderby_clause() != null) { - tokens.addAll(visit(ctx.orderby_clause())); + builder.appendExpression(visit(ctx.orderby_clause())); } for (int i = 0; i < ctx.setOperator().size(); i++) { - tokens.addAll(visit(ctx.setOperator(i))); - tokens.addAll(visit(ctx.select_statement(i))); + builder.appendExpression(visit(ctx.setOperator(i))); + builder.appendExpression(visit(ctx.select_statement(i))); } - return tokens; + return builder; } @Override - public List visitSetOperator(EqlParser.SetOperatorContext ctx) { + public QueryRendererBuilder visitSetOperator(EqlParser.SetOperatorContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.UNION() != null) { - tokens.add(new JpaQueryParsingToken(ctx.UNION())); + builder.append(JpaQueryParsingToken.expression(ctx.UNION())); } else if (ctx.INTERSECT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INTERSECT())); + builder.append(JpaQueryParsingToken.expression(ctx.INTERSECT())); } else if (ctx.EXCEPT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.EXCEPT())); + builder.append(JpaQueryParsingToken.expression(ctx.EXCEPT())); } if (ctx.ALL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ALL())); + builder.append(JpaQueryParsingToken.expression(ctx.ALL())); } - return tokens; + return builder; } @Override - public List visitUpdate_statement(EqlParser.Update_statementContext ctx) { + public QueryRendererBuilder visitUpdate_statement(EqlParser.Update_statementContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.update_clause())); + builder.appendExpression(visit(ctx.update_clause())); if (ctx.where_clause() != null) { - tokens.addAll(visit(ctx.where_clause())); + builder.appendExpression(visit(ctx.where_clause())); } - return tokens; + return builder; } @Override - public List visitDelete_statement(EqlParser.Delete_statementContext ctx) { + public QueryRendererBuilder visitDelete_statement(EqlParser.Delete_statementContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.delete_clause())); + builder.appendExpression(visit(ctx.delete_clause())); if (ctx.where_clause() != null) { - tokens.addAll(visit(ctx.where_clause())); + builder.appendExpression(visit(ctx.where_clause())); } - return tokens; + return builder; } @Override - public List visitFrom_clause(EqlParser.From_clauseContext ctx) { + public QueryRendererBuilder visitFrom_clause(EqlParser.From_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.FROM(), true)); - tokens.addAll(visit(ctx.identification_variable_declaration())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.appendInline(visit(ctx.identification_variable_declaration())); - ctx.identificationVariableDeclarationOrCollectionMemberDeclaration() - .forEach(identificationVariableDeclarationOrCollectionMemberDeclarationContext -> { - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(identificationVariableDeclarationOrCollectionMemberDeclarationContext)); - }); - SPACE(tokens); + if (!ctx.identificationVariableDeclarationOrCollectionMemberDeclaration().isEmpty()) { + builder.append(TOKEN_COMMA); + } - return tokens; + builder.appendExpression(QueryRendererBuilder + .concat(ctx.identificationVariableDeclarationOrCollectionMemberDeclaration(), this::visit, TOKEN_COMMA)); + + return builder; } @Override - public List visitIdentificationVariableDeclarationOrCollectionMemberDeclaration( + public QueryRendererBuilder visitIdentificationVariableDeclarationOrCollectionMemberDeclaration( EqlParser.IdentificationVariableDeclarationOrCollectionMemberDeclarationContext ctx) { if (ctx.identification_variable_declaration() != null) { @@ -159,563 +162,517 @@ class EqlQueryRenderer extends EqlBaseVisitor> { return visit(ctx.collection_member_declaration()); } else if (ctx.subquery() != null) { - List tokens = new ArrayList<>(); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); - tokens.addAll(visit(ctx.identification_variable())); + QueryRendererBuilder nested = QueryRenderer.builder(); + nested.append(TOKEN_OPEN_PAREN); + nested.appendInline(visit(ctx.subquery())); + nested.append(TOKEN_CLOSE_PAREN); - return tokens; + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.appendExpression(nested); + builder.appendExpression(visit(ctx.identification_variable())); + + return builder; } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitIdentification_variable_declaration( + public QueryRendererBuilder visitIdentification_variable_declaration( EqlParser.Identification_variable_declarationContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.range_variable_declaration())); + builder.append(visit(ctx.range_variable_declaration())); ctx.join().forEach(joinContext -> { - tokens.addAll(visit(joinContext)); + builder.append(visit(joinContext)); }); ctx.fetch_join().forEach(fetchJoinContext -> { - tokens.addAll(visit(fetchJoinContext)); + builder.append(visit(fetchJoinContext)); }); - return tokens; + return builder; } @Override - public List visitRange_variable_declaration(EqlParser.Range_variable_declarationContext ctx) { + public QueryRendererBuilder visitRange_variable_declaration(EqlParser.Range_variable_declarationContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.entity_name() != null) { - tokens.addAll(visit(ctx.entity_name())); + builder.appendExpression(visit(ctx.entity_name())); } else if (ctx.function_invocation() != null) { - tokens.addAll(visit(ctx.function_invocation())); + builder.appendExpression(visit(ctx.function_invocation())); } if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } - tokens.addAll(visit(ctx.identification_variable())); + builder.appendExpression(visit(ctx.identification_variable())); - return tokens; + return builder; } @Override - public List visitJoin(EqlParser.JoinContext ctx) { + public QueryRendererBuilder visitJoin(EqlParser.JoinContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.join_spec())); + builder.appendExpression(visit(ctx.join_association_path_expression())); - tokens.addAll(visit(ctx.join_spec())); - tokens.addAll(visit(ctx.join_association_path_expression())); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.appendExpression(visit(ctx.identification_variable())); } if (ctx.join_condition() != null) { - tokens.addAll(visit(ctx.join_condition())); + builder.appendExpression(visit(ctx.join_condition())); } - return tokens; + return builder; } @Override - public List visitFetch_join(EqlParser.Fetch_joinContext ctx) { + public QueryRendererBuilder visitFetch_join(EqlParser.Fetch_joinContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.join_spec())); - tokens.add(new JpaQueryParsingToken(ctx.FETCH())); - tokens.addAll(visit(ctx.join_association_path_expression())); + builder.appendExpression(visit(ctx.join_spec())); + builder.append(JpaQueryParsingToken.expression(ctx.FETCH())); + builder.appendExpression(visit(ctx.join_association_path_expression())); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.appendExpression(visit(ctx.identification_variable())); } if (ctx.join_condition() != null) { - tokens.addAll(visit(ctx.join_condition())); + builder.appendExpression(visit(ctx.join_condition())); } - return tokens; + return builder; } @Override - public List visitJoin_spec(EqlParser.Join_specContext ctx) { + public QueryRendererBuilder visitJoin_spec(EqlParser.Join_specContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.LEFT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LEFT())); + builder.append(JpaQueryParsingToken.expression(ctx.LEFT())); } if (ctx.OUTER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.OUTER())); + builder.append(JpaQueryParsingToken.expression(ctx.OUTER())); } if (ctx.INNER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INNER())); + builder.append(JpaQueryParsingToken.expression(ctx.INNER())); } if (ctx.JOIN() != null) { - tokens.add(new JpaQueryParsingToken(ctx.JOIN())); + builder.append(JpaQueryParsingToken.expression(ctx.JOIN())); } - return tokens; + return builder; } @Override - public List visitJoin_condition(EqlParser.Join_conditionContext ctx) { + public QueryRendererBuilder visitJoin_condition(EqlParser.Join_conditionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.ON())); - tokens.addAll(visit(ctx.conditional_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.ON())); + builder.appendExpression(visit(ctx.conditional_expression())); - return tokens; + return builder; } @Override - public List visitJoin_association_path_expression( + public QueryRendererBuilder visitJoin_association_path_expression( EqlParser.Join_association_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.TREAT() == null) { if (ctx.join_collection_valued_path_expression() != null) { - tokens.addAll(visit(ctx.join_collection_valued_path_expression())); + builder.appendExpression(visit(ctx.join_collection_valued_path_expression())); } else if (ctx.join_single_valued_path_expression() != null) { - tokens.addAll(visit(ctx.join_single_valued_path_expression())); + builder.appendExpression(visit(ctx.join_single_valued_path_expression())); } } else { if (ctx.join_collection_valued_path_expression() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TREAT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.join_collection_valued_path_expression())); - tokens.add(new JpaQueryParsingToken(ctx.AS())); - tokens.addAll(visit(ctx.subtype())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.append(JpaQueryParsingToken.token(ctx.TREAT())); + nested.append(TOKEN_OPEN_PAREN); + nested.appendInline(visit(ctx.join_collection_valued_path_expression())); + nested.append(JpaQueryParsingToken.expression(ctx.AS())); + nested.appendInline(visit(ctx.subtype())); + nested.append(TOKEN_CLOSE_PAREN); + + builder.appendExpression(nested); } else if (ctx.join_single_valued_path_expression() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TREAT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.join_single_valued_path_expression())); - tokens.add(new JpaQueryParsingToken(ctx.AS())); - tokens.addAll(visit(ctx.subtype())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.append(JpaQueryParsingToken.token(ctx.TREAT())); + nested.append(TOKEN_OPEN_PAREN); + nested.appendInline(visit(ctx.join_single_valued_path_expression())); + nested.append(JpaQueryParsingToken.expression(ctx.AS())); + nested.appendInline(visit(ctx.subtype())); + nested.append(TOKEN_CLOSE_PAREN); + + builder.appendExpression(nested); } } - return tokens; + return builder; } @Override - public List visitJoin_collection_valued_path_expression( + public QueryRendererBuilder visitJoin_collection_valued_path_expression( EqlParser.Join_collection_valued_path_expressionContext ctx) { - List tokens = new ArrayList<>(); - + List items = new ArrayList<>(2 + ctx.single_valued_embeddable_object_field().size()); if (ctx.identification_variable() != null) { - - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); + items.add(ctx.identification_variable()); } - ctx.single_valued_embeddable_object_field().forEach(singleValuedEmbeddableObjectFieldContext -> { - tokens.addAll(visit(singleValuedEmbeddableObjectFieldContext)); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - }); + items.addAll(ctx.single_valued_embeddable_object_field()); + items.add(ctx.collection_valued_field()); - tokens.addAll(visit(ctx.collection_valued_field())); - - return tokens; + return QueryRendererBuilder.concat(items, this::visit, TOKEN_DOT); } @Override - public List visitJoin_single_valued_path_expression( + public QueryRendererBuilder visitJoin_single_valued_path_expression( EqlParser.Join_single_valued_path_expressionContext ctx) { - List tokens = new ArrayList<>(); - + List items = new ArrayList<>(2 + ctx.single_valued_embeddable_object_field().size()); if (ctx.identification_variable() != null) { - - tokens.addAll(visit(ctx.identification_variable())); - tokens.add(TOKEN_DOT); + items.add(ctx.identification_variable()); } - ctx.single_valued_embeddable_object_field().forEach(singleValuedEmbeddableObjectFieldContext -> { - tokens.addAll(visit(singleValuedEmbeddableObjectFieldContext)); - tokens.add(TOKEN_DOT); - }); + items.addAll(ctx.single_valued_embeddable_object_field()); + items.add(ctx.single_valued_object_field()); - tokens.addAll(visit(ctx.single_valued_object_field())); - - return tokens; + return QueryRendererBuilder.concat(items, this::visit, TOKEN_DOT); } @Override - public List visitCollection_member_declaration( - EqlParser.Collection_member_declarationContext ctx) { + public QueryRendererBuilder visitCollection_member_declaration(EqlParser.Collection_member_declarationContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.IN(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.collection_valued_path_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.IN())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.collection_valued_path_expression())); + builder.append(TOKEN_CLOSE_PAREN); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } - tokens.addAll(visit(ctx.identification_variable())); + builder.appendExpression(visit(ctx.identification_variable())); - return tokens; + return builder; } @Override - public List visitQualified_identification_variable( + public QueryRendererBuilder visitQualified_identification_variable( EqlParser.Qualified_identification_variableContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.map_field_identification_variable() != null) { - tokens.addAll(visit(ctx.map_field_identification_variable())); + builder.append(visit(ctx.map_field_identification_variable())); } else if (ctx.identification_variable() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ENTRY())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.identification_variable())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.expression(ctx.ENTRY())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.identification_variable())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitMap_field_identification_variable( + public QueryRendererBuilder visitMap_field_identification_variable( EqlParser.Map_field_identification_variableContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.KEY() != null) { - tokens.add(new JpaQueryParsingToken(ctx.KEY(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.KEY())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.identification_variable())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.VALUE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.VALUE(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.VALUE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.identification_variable())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitSingle_valued_path_expression( - EqlParser.Single_valued_path_expressionContext ctx) { + public QueryRendererBuilder visitSingle_valued_path_expression(EqlParser.Single_valued_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.qualified_identification_variable() != null) { - tokens.addAll(visit(ctx.qualified_identification_variable())); + builder.append(visit(ctx.qualified_identification_variable())); } else if (ctx.qualified_identification_variable() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TREAT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.qualified_identification_variable())); - tokens.add(new JpaQueryParsingToken(ctx.AS())); - tokens.addAll(visit(ctx.subtype())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TREAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.qualified_identification_variable())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); + builder.appendInline(visit(ctx.subtype())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.state_field_path_expression() != null) { - tokens.addAll(visit(ctx.state_field_path_expression())); + builder.append(visit(ctx.state_field_path_expression())); } else if (ctx.single_valued_object_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_object_path_expression())); + builder.append(visit(ctx.single_valued_object_path_expression())); } - return tokens; + return builder; } @Override - public List visitGeneral_identification_variable( + public QueryRendererBuilder visitGeneral_identification_variable( EqlParser.General_identification_variableContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.append(visit(ctx.identification_variable())); } else if (ctx.map_field_identification_variable() != null) { - tokens.addAll(visit(ctx.map_field_identification_variable())); + builder.append(visit(ctx.map_field_identification_variable())); } - return tokens; + return builder; } @Override - public List visitGeneral_subpath(EqlParser.General_subpathContext ctx) { + public QueryRendererBuilder visitGeneral_subpath(EqlParser.General_subpathContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.simple_subpath() != null) { - tokens.addAll(visit(ctx.simple_subpath())); + builder.appendInline(visit(ctx.simple_subpath())); } else if (ctx.treated_subpath() != null) { - tokens.addAll(visit(ctx.treated_subpath())); - - ctx.single_valued_object_field().forEach(singleValuedObjectFieldContext -> { - tokens.add(TOKEN_DOT); - tokens.addAll(visit(singleValuedObjectFieldContext)); - }); + builder.appendInline(visit(ctx.treated_subpath())); + builder.appendInline(QueryRendererBuilder.concat(ctx.single_valued_object_field(), this::visit, TOKEN_DOT)); } - return tokens; + return builder; } @Override - public List visitSimple_subpath(EqlParser.Simple_subpathContext ctx) { + public QueryRendererBuilder visitSimple_subpath(EqlParser.Simple_subpathContext ctx) { - List tokens = new ArrayList<>(); + List items = new ArrayList<>(1 + ctx.single_valued_object_field().size()); + items.add(ctx.general_identification_variable()); + items.addAll(ctx.single_valued_object_field()); - tokens.addAll(visit(ctx.general_identification_variable())); - NOSPACE(tokens); - - ctx.single_valued_object_field().forEach(singleValuedObjectFieldContext -> { - tokens.add(TOKEN_DOT); - tokens.addAll(visit(singleValuedObjectFieldContext)); - NOSPACE(tokens); - }); - SPACE(tokens); - - return tokens; + return QueryRendererBuilder.concat(items, this::visit, TOKEN_DOT); } @Override - public List visitTreated_subpath(EqlParser.Treated_subpathContext ctx) { + public QueryRendererBuilder visitTreated_subpath(EqlParser.Treated_subpathContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.TREAT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.general_subpath())); - SPACE(tokens); - tokens.add(new JpaQueryParsingToken(ctx.AS())); - tokens.addAll(visit(ctx.subtype())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TREAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.general_subpath())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); + builder.appendInline(visit(ctx.subtype())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitState_field_path_expression(EqlParser.State_field_path_expressionContext ctx) { + public QueryRendererBuilder visitState_field_path_expression(EqlParser.State_field_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.general_subpath())); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - tokens.addAll(visit(ctx.state_field())); + builder.appendInline(visit(ctx.general_subpath())); + builder.append(TOKEN_DOT); + builder.appendInline(visit(ctx.state_field())); - return tokens; + return builder; } @Override - public List visitState_valued_path_expression( - EqlParser.State_valued_path_expressionContext ctx) { + public QueryRendererBuilder visitState_valued_path_expression(EqlParser.State_valued_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_field_path_expression() != null) { - tokens.addAll(visit(ctx.state_field_path_expression())); + builder.append(visit(ctx.state_field_path_expression())); } else if (ctx.general_identification_variable() != null) { - tokens.addAll(visit(ctx.general_identification_variable())); + builder.append(visit(ctx.general_identification_variable())); } - return tokens; + return builder; } @Override - public List visitSingle_valued_object_path_expression( + public QueryRendererBuilder visitSingle_valued_object_path_expression( EqlParser.Single_valued_object_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.general_subpath())); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - tokens.addAll(visit(ctx.single_valued_object_field())); + builder.appendInline(visit(ctx.general_subpath())); + builder.append(TOKEN_DOT); + builder.appendInline(visit(ctx.single_valued_object_field())); - return tokens; + return builder; } @Override - public List visitCollection_valued_path_expression( + public QueryRendererBuilder visitCollection_valued_path_expression( EqlParser.Collection_valued_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.general_subpath())); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - tokens.addAll(visit(ctx.collection_value_field())); + builder.appendInline(visit(ctx.general_subpath())); + builder.append(TOKEN_DOT); + builder.appendInline(visit(ctx.collection_value_field())); - return tokens; + return builder; } @Override - public List visitUpdate_clause(EqlParser.Update_clauseContext ctx) { + public QueryRendererBuilder visitUpdate_clause(EqlParser.Update_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.UPDATE())); - tokens.addAll(visit(ctx.entity_name())); + builder.append(JpaQueryParsingToken.expression(ctx.UPDATE())); + builder.appendExpression(visit(ctx.entity_name())); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } + if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.appendExpression(visit(ctx.identification_variable())); } - tokens.add(new JpaQueryParsingToken(ctx.SET())); + builder.append(JpaQueryParsingToken.expression(ctx.SET())); + builder.appendExpression(QueryRendererBuilder.concat(ctx.update_item(), this::visit, TOKEN_COMMA)); - ctx.update_item().forEach(updateItemContext -> { - tokens.addAll(visit(updateItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); - - return tokens; + return builder; } @Override - public List visitUpdate_item(EqlParser.Update_itemContext ctx) { + public QueryRendererBuilder visitUpdate_item(EqlParser.Update_itemContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); + List items = new ArrayList<>(3 + ctx.single_valued_embeddable_object_field().size()); if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); + items.add(ctx.identification_variable()); } - ctx.single_valued_embeddable_object_field().forEach(singleValuedEmbeddableObjectFieldContext -> { - tokens.addAll(visit(singleValuedEmbeddableObjectFieldContext)); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - }); + items.addAll(ctx.single_valued_embeddable_object_field()); if (ctx.state_field() != null) { - tokens.addAll(visit(ctx.state_field())); + items.add(ctx.state_field()); } else if (ctx.single_valued_object_field() != null) { - tokens.addAll(visit(ctx.single_valued_object_field())); + items.add(ctx.single_valued_object_field()); } - tokens.add(TOKEN_EQUALS); - tokens.addAll(visit(ctx.new_value())); + builder.appendInline(QueryRendererBuilder.concat(items, this::visit, TOKEN_DOT)); + builder.append(TOKEN_EQUALS); + builder.append(visit(ctx.new_value())); - return tokens; + return builder; } @Override - public List visitNew_value(EqlParser.New_valueContext ctx) { + public QueryRendererBuilder visitNew_value(EqlParser.New_valueContext ctx) { if (ctx.scalar_expression() != null) { return visit(ctx.scalar_expression()); } else if (ctx.simple_entity_expression() != null) { return visit(ctx.simple_entity_expression()); } else if (ctx.NULL() != null) { - return List.of(new JpaQueryParsingToken(ctx.NULL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.NULL())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitDelete_clause(EqlParser.Delete_clauseContext ctx) { + public QueryRendererBuilder visitDelete_clause(EqlParser.Delete_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(JpaQueryParsingToken.expression(ctx.DELETE())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.appendExpression(visit(ctx.entity_name())); - tokens.add(new JpaQueryParsingToken(ctx.DELETE())); - tokens.add(new JpaQueryParsingToken(ctx.FROM())); - tokens.addAll(visit(ctx.entity_name())); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.appendExpression(visit(ctx.identification_variable())); } - return tokens; + return builder; } @Override - public List visitSelect_clause(EqlParser.Select_clauseContext ctx) { + public QueryRendererBuilder visitSelect_clause(EqlParser.Select_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.SELECT())); + builder.append(JpaQueryParsingToken.expression(ctx.SELECT())); if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); } - ctx.select_item().forEach(selectItemContext -> { - tokens.addAll(visit(selectItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); + builder.appendExpression(QueryRendererBuilder.concat(ctx.select_item(), this::visit, TOKEN_COMMA)); - return tokens; + return builder; } @Override - public List visitSelect_item(EqlParser.Select_itemContext ctx) { + public QueryRendererBuilder visitSelect_item(EqlParser.Select_itemContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.select_expression())); - SPACE(tokens); + builder.appendExpression(visit(ctx.select_expression())); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } if (ctx.result_variable() != null) { - tokens.addAll(visit(ctx.result_variable())); + builder.appendExpression(visit(ctx.result_variable())); } - return tokens; + return builder; } @Override - public List visitSelect_expression(EqlParser.Select_expressionContext ctx) { + public QueryRendererBuilder visitSelect_expression(EqlParser.Select_expressionContext ctx) { if (ctx.single_valued_path_expression() != null) { return visit(ctx.single_valued_path_expression()); @@ -729,1445 +686,1349 @@ class EqlQueryRenderer extends EqlBaseVisitor> { return visit(ctx.identification_variable()); } else { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.OBJECT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.OBJECT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.identification_variable())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } } else if (ctx.constructor_expression() != null) { return visit(ctx.constructor_expression()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitConstructor_expression(EqlParser.Constructor_expressionContext ctx) { + public QueryRendererBuilder visitConstructor_expression(EqlParser.Constructor_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.NEW())); - tokens.addAll(visit(ctx.constructor_name())); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(JpaQueryParsingToken.expression(ctx.NEW())); + builder.append(visit(ctx.constructor_name())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryRendererBuilder.concat(ctx.constructor_item(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); - ctx.constructor_item().forEach(constructorItemContext -> { - tokens.addAll(visit(constructorItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - tokens.add(TOKEN_CLOSE_PAREN); - - return tokens; + return builder; } @Override - public List visitConstructor_item(EqlParser.Constructor_itemContext ctx) { + public QueryRendererBuilder visitConstructor_item(EqlParser.Constructor_itemContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.single_valued_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_path_expression())); + builder.append(visit(ctx.single_valued_path_expression())); } else if (ctx.scalar_expression() != null) { - tokens.addAll(visit(ctx.scalar_expression())); + builder.append(visit(ctx.scalar_expression())); } else if (ctx.aggregate_expression() != null) { - tokens.addAll(visit(ctx.aggregate_expression())); + builder.append(visit(ctx.aggregate_expression())); } else if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.append(visit(ctx.identification_variable())); } - return tokens; + return builder; } @Override - public List visitAggregate_expression(EqlParser.Aggregate_expressionContext ctx) { + public QueryRendererBuilder visitAggregate_expression(EqlParser.Aggregate_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.AVG() != null || ctx.MAX() != null || ctx.MIN() != null || ctx.SUM() != null) { if (ctx.AVG() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AVG(), false)); + builder.append(JpaQueryParsingToken.token(ctx.AVG())); } if (ctx.MAX() != null) { - tokens.add(new JpaQueryParsingToken(ctx.MAX(), false)); + builder.append(JpaQueryParsingToken.token(ctx.MAX())); } if (ctx.MIN() != null) { - tokens.add(new JpaQueryParsingToken(ctx.MIN(), false)); + builder.append(JpaQueryParsingToken.token(ctx.MIN())); } if (ctx.SUM() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SUM(), false)); + builder.append(JpaQueryParsingToken.token(ctx.SUM())); } - tokens.add(TOKEN_OPEN_PAREN); + builder.append(TOKEN_OPEN_PAREN); if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); } - tokens.addAll(visit(ctx.state_valued_path_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.appendInline(visit(ctx.state_valued_path_expression())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.COUNT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.COUNT(), false)); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.COUNT())); + builder.append(TOKEN_OPEN_PAREN); if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); } if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.appendInline(visit(ctx.identification_variable())); } else if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.appendInline(visit(ctx.state_valued_path_expression())); } else if (ctx.single_valued_object_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_object_path_expression())); + builder.appendInline(visit(ctx.single_valued_object_path_expression())); } - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.function_invocation() != null) { - tokens.addAll(visit(ctx.function_invocation())); + builder.append(visit(ctx.function_invocation())); } - return tokens; + return builder; } @Override - public List visitWhere_clause(EqlParser.Where_clauseContext ctx) { + public QueryRendererBuilder visitWhere_clause(EqlParser.Where_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.WHERE(), true)); - tokens.addAll(visit(ctx.conditional_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.WHERE())); + builder.appendExpression(visit(ctx.conditional_expression())); - return tokens; + return builder; } @Override - public List visitGroupby_clause(EqlParser.Groupby_clauseContext ctx) { + public QueryRendererBuilder visitGroupby_clause(EqlParser.Groupby_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.GROUP())); - tokens.add(new JpaQueryParsingToken(ctx.BY())); - ctx.groupby_item().forEach(groupbyItemContext -> { - tokens.addAll(visit(groupbyItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); + builder.append(JpaQueryParsingToken.expression(ctx.GROUP())); + builder.append(JpaQueryParsingToken.expression(ctx.BY())); + builder.appendExpression(QueryRendererBuilder.concat(ctx.groupby_item(), this::visit, TOKEN_COMMA)); - return tokens; + return builder; } @Override - public List visitGroupby_item(EqlParser.Groupby_itemContext ctx) { + public QueryRendererBuilder visitGroupby_item(EqlParser.Groupby_itemContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.single_valued_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_path_expression())); + builder.append(visit(ctx.single_valued_path_expression())); } else if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.append(visit(ctx.identification_variable())); } else if (ctx.scalar_expression() != null) { - tokens.addAll(visit(ctx.scalar_expression())); + builder.append(visit(ctx.scalar_expression())); } - return tokens; + return builder; } @Override - public List visitHaving_clause(EqlParser.Having_clauseContext ctx) { + public QueryRendererBuilder visitHaving_clause(EqlParser.Having_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.HAVING())); - tokens.addAll(visit(ctx.conditional_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.HAVING())); + builder.appendExpression(visit(ctx.conditional_expression())); - return tokens; + return builder; } @Override - public List visitOrderby_clause(EqlParser.Orderby_clauseContext ctx) { + public QueryRendererBuilder visitOrderby_clause(EqlParser.Orderby_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.ORDER())); - tokens.add(new JpaQueryParsingToken(ctx.BY())); + builder.append(JpaQueryParsingToken.expression(ctx.ORDER())); + builder.append(JpaQueryParsingToken.expression(ctx.BY())); + builder.append(QueryRendererBuilder.concat(ctx.orderby_item(), this::visit, TOKEN_COMMA)); - ctx.orderby_item().forEach(orderbyItemContext -> { - tokens.addAll(visit(orderbyItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - return tokens; + return builder; } @Override - public List visitOrderby_item(EqlParser.Orderby_itemContext ctx) { + public QueryRendererBuilder visitOrderby_item(EqlParser.Orderby_itemContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_field_path_expression() != null) { - tokens.addAll(visit(ctx.state_field_path_expression())); + builder.append(visit(ctx.state_field_path_expression())); } else if (ctx.general_identification_variable() != null) { - tokens.addAll(visit(ctx.general_identification_variable())); + builder.append(visit(ctx.general_identification_variable())); } else if (ctx.result_variable() != null) { - tokens.addAll(visit(ctx.result_variable())); + builder.append(visit(ctx.result_variable())); } else if (ctx.string_expression() != null) { - tokens.addAll(visit(ctx.string_expression())); + builder.append(visit(ctx.string_expression())); } else if (ctx.scalar_expression() != null) { - tokens.addAll(visit(ctx.scalar_expression())); + builder.append(visit(ctx.scalar_expression())); } if (ctx.ASC() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ASC())); + builder.append(JpaQueryParsingToken.expression(ctx.ASC())); } if (ctx.DESC() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DESC())); + builder.append(JpaQueryParsingToken.expression(ctx.DESC())); } if (ctx.nullsPrecedence() != null) { - tokens.addAll(visit(ctx.nullsPrecedence())); + builder.appendExpression(visit(ctx.nullsPrecedence())); } - return tokens; + return builder; } @Override - public List visitNullsPrecedence(EqlParser.NullsPrecedenceContext ctx) { + public QueryRendererBuilder visitNullsPrecedence(EqlParser.NullsPrecedenceContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_NULLS); + builder.append(TOKEN_NULLS); if (ctx.FIRST() != null) { - tokens.add(TOKEN_FIRST); + builder.append(TOKEN_FIRST); } else if (ctx.LAST() != null) { - tokens.add(TOKEN_LAST); + builder.append(TOKEN_LAST); } - return tokens; + return builder; } @Override - public List visitSubquery(EqlParser.SubqueryContext ctx) { + public QueryRendererBuilder visitSubquery(EqlParser.SubqueryContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.simple_select_clause())); - tokens.addAll(visit(ctx.subquery_from_clause())); + builder.appendExpression(visit(ctx.simple_select_clause())); + builder.appendExpression(visit(ctx.subquery_from_clause())); if (ctx.where_clause() != null) { - tokens.addAll(visit(ctx.where_clause())); + builder.appendExpression(visit(ctx.where_clause())); } if (ctx.groupby_clause() != null) { - tokens.addAll(visit(ctx.groupby_clause())); + builder.appendExpression(visit(ctx.groupby_clause())); } if (ctx.having_clause() != null) { - tokens.addAll(visit(ctx.having_clause())); + builder.appendExpression(visit(ctx.having_clause())); } - return tokens; + return builder; } @Override - public List visitSubquery_from_clause(EqlParser.Subquery_from_clauseContext ctx) { + public QueryRendererBuilder visitSubquery_from_clause(EqlParser.Subquery_from_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.FROM())); - ctx.subselect_identification_variable_declaration().forEach(subselectIdentificationVariableDeclarationContext -> { - tokens.addAll(visit(subselectIdentificationVariableDeclarationContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.appendExpression( + QueryRendererBuilder.concat(ctx.subselect_identification_variable_declaration(), this::visit, TOKEN_COMMA)); - return tokens; + return builder; } @Override - public List visitSubselect_identification_variable_declaration( + public QueryRendererBuilder visitSubselect_identification_variable_declaration( EqlParser.Subselect_identification_variable_declarationContext ctx) { return super.visitSubselect_identification_variable_declaration(ctx); } @Override - public List visitDerived_path_expression(EqlParser.Derived_path_expressionContext ctx) { + public QueryRendererBuilder visitDerived_path_expression(EqlParser.Derived_path_expressionContext ctx) { return super.visitDerived_path_expression(ctx); } @Override - public List visitGeneral_derived_path(EqlParser.General_derived_pathContext ctx) { + public QueryRendererBuilder visitGeneral_derived_path(EqlParser.General_derived_pathContext ctx) { return super.visitGeneral_derived_path(ctx); } @Override - public List visitSimple_derived_path(EqlParser.Simple_derived_pathContext ctx) { + public QueryRendererBuilder visitSimple_derived_path(EqlParser.Simple_derived_pathContext ctx) { return super.visitSimple_derived_path(ctx); } @Override - public List visitTreated_derived_path(EqlParser.Treated_derived_pathContext ctx) { + public QueryRendererBuilder visitTreated_derived_path(EqlParser.Treated_derived_pathContext ctx) { return super.visitTreated_derived_path(ctx); } @Override - public List visitDerived_collection_member_declaration( + public QueryRendererBuilder visitDerived_collection_member_declaration( EqlParser.Derived_collection_member_declarationContext ctx) { return super.visitDerived_collection_member_declaration(ctx); } @Override - public List visitSimple_select_clause(EqlParser.Simple_select_clauseContext ctx) { + public QueryRendererBuilder visitSimple_select_clause(EqlParser.Simple_select_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.SELECT())); + builder.append(JpaQueryParsingToken.expression(ctx.SELECT())); if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); } - tokens.addAll(visit(ctx.simple_select_expression())); + builder.append(visit(ctx.simple_select_expression())); - return tokens; + return builder; } @Override - public List visitSimple_select_expression(EqlParser.Simple_select_expressionContext ctx) { + public QueryRendererBuilder visitSimple_select_expression(EqlParser.Simple_select_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.single_valued_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_path_expression())); + builder.append(visit(ctx.single_valued_path_expression())); } else if (ctx.scalar_expression() != null) { - tokens.addAll(visit(ctx.scalar_expression())); + builder.append(visit(ctx.scalar_expression())); } else if (ctx.aggregate_expression() != null) { - tokens.addAll(visit(ctx.aggregate_expression())); + builder.append(visit(ctx.aggregate_expression())); } else if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.append(visit(ctx.identification_variable())); } - return tokens; + return builder; } @Override - public List visitScalar_expression(EqlParser.Scalar_expressionContext ctx) { + public QueryRendererBuilder visitScalar_expression(EqlParser.Scalar_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.arithmetic_expression() != null) { - tokens.addAll(visit(ctx.arithmetic_expression())); + builder.append(visit(ctx.arithmetic_expression())); } else if (ctx.string_expression() != null) { - tokens.addAll(visit(ctx.string_expression())); + builder.append(visit(ctx.string_expression())); } else if (ctx.enum_expression() != null) { - tokens.addAll(visit(ctx.enum_expression())); + builder.append(visit(ctx.enum_expression())); } else if (ctx.datetime_expression() != null) { - tokens.addAll(visit(ctx.datetime_expression())); + builder.append(visit(ctx.datetime_expression())); } else if (ctx.boolean_expression() != null) { - tokens.addAll(visit(ctx.boolean_expression())); + builder.append(visit(ctx.boolean_expression())); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + builder.append(visit(ctx.case_expression())); } else if (ctx.entity_type_expression() != null) { - tokens.addAll(visit(ctx.entity_type_expression())); + builder.append(visit(ctx.entity_type_expression())); } - return tokens; + return builder; } @Override - public List visitConditional_expression(EqlParser.Conditional_expressionContext ctx) { + public QueryRendererBuilder visitConditional_expression(EqlParser.Conditional_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.conditional_expression() != null) { - tokens.addAll(visit(ctx.conditional_expression())); - tokens.add(new JpaQueryParsingToken(ctx.OR())); - tokens.addAll(visit(ctx.conditional_term())); + builder.append(visit(ctx.conditional_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.OR())); + builder.append(visit(ctx.conditional_term())); } else { - tokens.addAll(visit(ctx.conditional_term())); + builder.append(visit(ctx.conditional_term())); } - return tokens; + return builder; } @Override - public List visitConditional_term(EqlParser.Conditional_termContext ctx) { + public QueryRendererBuilder visitConditional_term(EqlParser.Conditional_termContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.conditional_term() != null) { - tokens.addAll(visit(ctx.conditional_term())); - tokens.add(new JpaQueryParsingToken(ctx.AND())); - tokens.addAll(visit(ctx.conditional_factor())); + builder.append(visit(ctx.conditional_term())); + builder.append(JpaQueryParsingToken.expression(ctx.AND())); + builder.append(visit(ctx.conditional_factor())); } else { - tokens.addAll(visit(ctx.conditional_factor())); + builder.append(visit(ctx.conditional_factor())); } - return tokens; + return builder; } @Override - public List visitConditional_factor(EqlParser.Conditional_factorContext ctx) { + public QueryRendererBuilder visitConditional_factor(EqlParser.Conditional_factorContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - EqlParser.Conditional_primaryContext conditionalPrimary = ctx.conditional_primary(); - List visitedConditionalPrimary = visit(conditionalPrimary); - tokens.addAll(visitedConditionalPrimary); + builder.append(visit(ctx.conditional_primary())); - return tokens; + return builder; } @Override - public List visitConditional_primary(EqlParser.Conditional_primaryContext ctx) { + public QueryRendererBuilder visitConditional_primary(EqlParser.Conditional_primaryContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.simple_cond_expression() != null) { - tokens.addAll(visit(ctx.simple_cond_expression())); + builder.append(visit(ctx.simple_cond_expression())); } else if (ctx.conditional_expression() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.conditional_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.conditional_expression())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitSimple_cond_expression(EqlParser.Simple_cond_expressionContext ctx) { + public QueryRendererBuilder visitSimple_cond_expression(EqlParser.Simple_cond_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.comparison_expression() != null) { - tokens.addAll(visit(ctx.comparison_expression())); + builder.append(visit(ctx.comparison_expression())); } else if (ctx.between_expression() != null) { - tokens.addAll(visit(ctx.between_expression())); + builder.append(visit(ctx.between_expression())); } else if (ctx.in_expression() != null) { - tokens.addAll(visit(ctx.in_expression())); + builder.append(visit(ctx.in_expression())); } else if (ctx.like_expression() != null) { - tokens.addAll(visit(ctx.like_expression())); + builder.append(visit(ctx.like_expression())); } else if (ctx.null_comparison_expression() != null) { - tokens.addAll(visit(ctx.null_comparison_expression())); + builder.append(visit(ctx.null_comparison_expression())); } else if (ctx.empty_collection_comparison_expression() != null) { - tokens.addAll(visit(ctx.empty_collection_comparison_expression())); + builder.append(visit(ctx.empty_collection_comparison_expression())); } else if (ctx.collection_member_expression() != null) { - tokens.addAll(visit(ctx.collection_member_expression())); + builder.append(visit(ctx.collection_member_expression())); } else if (ctx.exists_expression() != null) { - tokens.addAll(visit(ctx.exists_expression())); + builder.append(visit(ctx.exists_expression())); } - return tokens; + return builder; } @Override - public List visitBetween_expression(EqlParser.Between_expressionContext ctx) { + public QueryRendererBuilder visitBetween_expression(EqlParser.Between_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.arithmetic_expression(0) != null) { - tokens.addAll(visit(ctx.arithmetic_expression(0))); + builder.append(visit(ctx.arithmetic_expression(0))); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.BETWEEN())); - tokens.addAll(visit(ctx.arithmetic_expression(1))); - tokens.add(new JpaQueryParsingToken(ctx.AND())); - tokens.addAll(visit(ctx.arithmetic_expression(2))); + builder.append(JpaQueryParsingToken.expression(ctx.BETWEEN())); + builder.appendExpression(visit(ctx.arithmetic_expression(1))); + builder.append(JpaQueryParsingToken.expression(ctx.AND())); + builder.appendExpression(visit(ctx.arithmetic_expression(2))); } else if (ctx.string_expression(0) != null) { - tokens.addAll(visit(ctx.string_expression(0))); + builder.appendExpression(visit(ctx.string_expression(0))); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.BETWEEN())); - tokens.addAll(visit(ctx.string_expression(1))); - tokens.add(new JpaQueryParsingToken(ctx.AND())); - tokens.addAll(visit(ctx.string_expression(2))); + builder.append(JpaQueryParsingToken.expression(ctx.BETWEEN())); + builder.appendExpression(visit(ctx.string_expression(1))); + builder.append(JpaQueryParsingToken.expression(ctx.AND())); + builder.appendExpression(visit(ctx.string_expression(2))); } else if (ctx.datetime_expression(0) != null) { - tokens.addAll(visit(ctx.datetime_expression(0))); + builder.append(visit(ctx.datetime_expression(0))); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.BETWEEN())); - tokens.addAll(visit(ctx.datetime_expression(1))); - tokens.add(new JpaQueryParsingToken(ctx.AND())); - tokens.addAll(visit(ctx.datetime_expression(2))); + builder.append(JpaQueryParsingToken.expression(ctx.BETWEEN())); + builder.appendExpression(visit(ctx.datetime_expression(1))); + builder.append(JpaQueryParsingToken.expression(ctx.AND())); + builder.appendExpression(visit(ctx.datetime_expression(2))); } - return tokens; + return builder; } @Override - public List visitIn_expression(EqlParser.In_expressionContext ctx) { + public QueryRendererBuilder visitIn_expression(EqlParser.In_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.append(visit(ctx.state_valued_path_expression())); } if (ctx.type_discriminator() != null) { - tokens.addAll(visit(ctx.type_discriminator())); + builder.append(visit(ctx.type_discriminator())); } if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } if (ctx.IN() != null) { - tokens.add(new JpaQueryParsingToken(ctx.IN())); + builder.append(JpaQueryParsingToken.expression(ctx.IN())); } if (ctx.in_item() != null && !ctx.in_item().isEmpty()) { - tokens.add(TOKEN_OPEN_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryRendererBuilder.concat(ctx.in_item(), this::visit, TOKEN_COMMA)); - ctx.in_item().forEach(inItemContext -> { - - tokens.addAll(visit(inItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.collection_valued_input_parameter() != null) { - tokens.addAll(visit(ctx.collection_valued_input_parameter())); + builder.append(visit(ctx.collection_valued_input_parameter())); } - return tokens; + return builder; } @Override - public List visitIn_item(EqlParser.In_itemContext ctx) { + public QueryRendererBuilder visitIn_item(EqlParser.In_itemContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.literal() != null) { - tokens.addAll(visit(ctx.literal())); + builder.append(visit(ctx.literal())); } else if (ctx.single_valued_input_parameter() != null) { - tokens.addAll(visit(ctx.single_valued_input_parameter())); + builder.append(visit(ctx.single_valued_input_parameter())); } - return tokens; + return builder; } @Override - public List visitLike_expression(EqlParser.Like_expressionContext ctx) { + public QueryRendererBuilder visitLike_expression(EqlParser.Like_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.string_expression())); + builder.append(visit(ctx.string_expression())); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.LIKE())); - tokens.addAll(visit(ctx.pattern_value())); + builder.append(JpaQueryParsingToken.expression(ctx.LIKE())); + builder.appendExpression(visit(ctx.pattern_value())); if (ctx.ESCAPE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ESCAPE())); - tokens.addAll(visit(ctx.escape_character())); + builder.append(JpaQueryParsingToken.expression(ctx.ESCAPE())); + builder.appendExpression(visit(ctx.escape_character())); } - return tokens; + return builder; } @Override - public List visitNull_comparison_expression(EqlParser.Null_comparison_expressionContext ctx) { + public QueryRendererBuilder visitNull_comparison_expression(EqlParser.Null_comparison_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.single_valued_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_path_expression())); + builder.append(visit(ctx.single_valued_path_expression())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.nullif_expression() != null) { - tokens.addAll(visit(ctx.nullif_expression())); + builder.append(visit(ctx.nullif_expression())); } if (ctx.op != null) { - tokens.add(new JpaQueryParsingToken(ctx.op.getText())); + builder.append(JpaQueryParsingToken.expression(ctx.op.getText())); } else { - tokens.add(new JpaQueryParsingToken(ctx.IS())); + builder.append(JpaQueryParsingToken.expression(ctx.IS())); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } } - tokens.add(new JpaQueryParsingToken(ctx.NULL())); + builder.append(JpaQueryParsingToken.expression(ctx.NULL())); - return tokens; + return builder; } @Override - public List visitEmpty_collection_comparison_expression( + public QueryRendererBuilder visitEmpty_collection_comparison_expression( EqlParser.Empty_collection_comparison_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.collection_valued_path_expression())); - tokens.add(new JpaQueryParsingToken(ctx.IS())); + builder.append(visit(ctx.collection_valued_path_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.IS())); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.EMPTY())); + builder.append(JpaQueryParsingToken.expression(ctx.EMPTY())); - return tokens; + return builder; } @Override - public List visitCollection_member_expression( - EqlParser.Collection_member_expressionContext ctx) { + public QueryRendererBuilder visitCollection_member_expression(EqlParser.Collection_member_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.entity_or_value_expression())); + builder.append(visit(ctx.entity_or_value_expression())); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.MEMBER())); + builder.append(JpaQueryParsingToken.expression(ctx.MEMBER())); if (ctx.OF() != null) { - tokens.add(new JpaQueryParsingToken(ctx.OF())); + builder.append(JpaQueryParsingToken.expression(ctx.OF())); } - tokens.addAll(visit(ctx.collection_valued_path_expression())); + builder.append(visit(ctx.collection_valued_path_expression())); - return tokens; + return builder; } @Override - public List visitEntity_or_value_expression(EqlParser.Entity_or_value_expressionContext ctx) { + public QueryRendererBuilder visitEntity_or_value_expression(EqlParser.Entity_or_value_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.single_valued_object_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_object_path_expression())); + builder.append(visit(ctx.single_valued_object_path_expression())); } else if (ctx.state_field_path_expression() != null) { - tokens.addAll(visit(ctx.state_field_path_expression())); + builder.append(visit(ctx.state_field_path_expression())); } else if (ctx.simple_entity_or_value_expression() != null) { - tokens.addAll(visit(ctx.simple_entity_or_value_expression())); + builder.append(visit(ctx.simple_entity_or_value_expression())); } - return tokens; + return builder; } @Override - public List visitSimple_entity_or_value_expression( + public QueryRendererBuilder visitSimple_entity_or_value_expression( EqlParser.Simple_entity_or_value_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.append(visit(ctx.identification_variable())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.literal() != null) { - tokens.addAll(visit(ctx.literal())); + builder.append(visit(ctx.literal())); } - return tokens; + return builder; } @Override - public List visitExists_expression(EqlParser.Exists_expressionContext ctx) { + public QueryRendererBuilder visitExists_expression(EqlParser.Exists_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.EXISTS())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); - return tokens; + builder.append(JpaQueryParsingToken.expression(ctx.EXISTS())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; } @Override - public List visitAll_or_any_expression(EqlParser.All_or_any_expressionContext ctx) { + public QueryRendererBuilder visitAll_or_any_expression(EqlParser.All_or_any_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.ALL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ALL())); + builder.append(JpaQueryParsingToken.expression(ctx.ALL())); } else if (ctx.ANY() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ANY())); + builder.append(JpaQueryParsingToken.expression(ctx.ANY())); } else if (ctx.SOME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SOME())); + builder.append(JpaQueryParsingToken.expression(ctx.SOME())); } - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitStringComparison(EqlParser.StringComparisonContext ctx) { + public QueryRendererBuilder visitStringComparison(EqlParser.StringComparisonContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.string_expression(0))); - tokens.addAll(visit(ctx.comparison_operator())); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(visit(ctx.comparison_operator())); if (ctx.string_expression(1) != null) { - tokens.addAll(visit(ctx.string_expression(1))); + builder.append(visit(ctx.string_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.append(visit(ctx.all_or_any_expression())); } - return tokens; + return builder; } @Override - public List visitBooleanComparison(EqlParser.BooleanComparisonContext ctx) { + public QueryRendererBuilder visitBooleanComparison(EqlParser.BooleanComparisonContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.boolean_expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.op)); + builder.appendInline(visit(ctx.boolean_expression(0))); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); if (ctx.boolean_expression(1) != null) { - tokens.addAll(visit(ctx.boolean_expression(1))); + builder.append(visit(ctx.boolean_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.append(visit(ctx.all_or_any_expression())); } - return tokens; + return builder; } @Override - public List visitDirectBooleanCheck(EqlParser.DirectBooleanCheckContext ctx) { + public QueryRendererBuilder visitDirectBooleanCheck(EqlParser.DirectBooleanCheckContext ctx) { return visit(ctx.boolean_expression()); } @Override - public List visitEnumComparison(EqlParser.EnumComparisonContext ctx) { + public QueryRendererBuilder visitEnumComparison(EqlParser.EnumComparisonContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.enum_expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.op)); + builder.appendInline(visit(ctx.enum_expression(0))); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); if (ctx.enum_expression(1) != null) { - tokens.addAll(visit(ctx.enum_expression(1))); + builder.append(visit(ctx.enum_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.append(visit(ctx.all_or_any_expression())); } - return tokens; + return builder; } @Override - public List visitDatetimeComparison(EqlParser.DatetimeComparisonContext ctx) { + public QueryRendererBuilder visitDatetimeComparison(EqlParser.DatetimeComparisonContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.datetime_expression(0))); - tokens.addAll(visit(ctx.comparison_operator())); + builder.appendInline(visit(ctx.datetime_expression(0))); + builder.append(JpaQueryParsingToken.ventilated(ctx.comparison_operator().op)); if (ctx.datetime_expression(1) != null) { - tokens.addAll(visit(ctx.datetime_expression(1))); + builder.append(visit(ctx.datetime_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.append(visit(ctx.all_or_any_expression())); } - return tokens; + return builder; } @Override - public List visitEntityComparison(EqlParser.EntityComparisonContext ctx) { + public QueryRendererBuilder visitEntityComparison(EqlParser.EntityComparisonContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.entity_expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.op)); + builder.appendExpression(visit(ctx.entity_expression(0))); + builder.append(JpaQueryParsingToken.expression(ctx.op)); if (ctx.entity_expression(1) != null) { - tokens.addAll(visit(ctx.entity_expression(1))); + builder.append(visit(ctx.entity_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.append(visit(ctx.all_or_any_expression())); } - return tokens; + return builder; } @Override - public List visitArithmeticComparison(EqlParser.ArithmeticComparisonContext ctx) { + public QueryRendererBuilder visitArithmeticComparison(EqlParser.ArithmeticComparisonContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - tokens.addAll(visit(ctx.comparison_operator())); + builder.append(visit(ctx.arithmetic_expression(0))); + builder.append(visit(ctx.comparison_operator())); if (ctx.arithmetic_expression(1) != null) { - tokens.addAll(visit(ctx.arithmetic_expression(1))); + builder.append(visit(ctx.arithmetic_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.append(visit(ctx.all_or_any_expression())); } - return tokens; + return builder; } @Override - public List visitEntityTypeComparison(EqlParser.EntityTypeComparisonContext ctx) { + public QueryRendererBuilder visitEntityTypeComparison(EqlParser.EntityTypeComparisonContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.entity_type_expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.op)); - tokens.addAll(visit(ctx.entity_type_expression(1))); + builder.appendInline(visit(ctx.entity_type_expression(0))); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); + builder.append(visit(ctx.entity_type_expression(1))); - return tokens; + return builder; } @Override - public List visitRegexpComparison(EqlParser.RegexpComparisonContext ctx) { + public QueryRendererBuilder visitRegexpComparison(EqlParser.RegexpComparisonContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.string_expression())); - tokens.add(new JpaQueryParsingToken(ctx.REGEXP())); - tokens.addAll(visit(ctx.string_literal())); + builder.appendExpression(visit(ctx.string_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.REGEXP())); + builder.appendExpression(visit(ctx.string_literal())); - return tokens; + return builder; } @Override - public List visitComparison_operator(EqlParser.Comparison_operatorContext ctx) { - return List.of(new JpaQueryParsingToken(ctx.op)); + public QueryRendererBuilder visitComparison_operator(EqlParser.Comparison_operatorContext ctx) { + return QueryRendererBuilder.from(JpaQueryParsingToken.ventilated(ctx.op)); } @Override - public List visitArithmetic_expression(EqlParser.Arithmetic_expressionContext ctx) { + public QueryRendererBuilder visitArithmetic_expression(EqlParser.Arithmetic_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.arithmetic_expression() != null) { - tokens.addAll(visit(ctx.arithmetic_expression())); - tokens.add(new JpaQueryParsingToken(ctx.op)); - tokens.addAll(visit(ctx.arithmetic_term())); + builder.append(visit(ctx.arithmetic_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.op)); + builder.append(visit(ctx.arithmetic_term())); } else { - tokens.addAll(visit(ctx.arithmetic_term())); + builder.append(visit(ctx.arithmetic_term())); } - return tokens; + return builder; } @Override - public List visitArithmetic_term(EqlParser.Arithmetic_termContext ctx) { + public QueryRendererBuilder visitArithmetic_term(EqlParser.Arithmetic_termContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.arithmetic_term() != null) { - tokens.addAll(visit(ctx.arithmetic_term())); - NOSPACE(tokens); - tokens.add(new JpaQueryParsingToken(ctx.op, false)); - tokens.addAll(visit(ctx.arithmetic_factor())); + builder.appendInline(visit(ctx.arithmetic_term())); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); + builder.append(visit(ctx.arithmetic_factor())); } else { - tokens.addAll(visit(ctx.arithmetic_factor())); + builder.append(visit(ctx.arithmetic_factor())); } - return tokens; + return builder; } @Override - public List visitArithmetic_factor(EqlParser.Arithmetic_factorContext ctx) { + public QueryRendererBuilder visitArithmetic_factor(EqlParser.Arithmetic_factorContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.op != null) { - tokens.add(new JpaQueryParsingToken(ctx.op, false)); + builder.append(JpaQueryParsingToken.token(ctx.op)); } - tokens.addAll(visit(ctx.arithmetic_primary())); + builder.appendInline(visit(ctx.arithmetic_primary())); - return tokens; + return builder; } @Override - public List visitArithmetic_primary(EqlParser.Arithmetic_primaryContext ctx) { + public QueryRendererBuilder visitArithmetic_primary(EqlParser.Arithmetic_primaryContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.append(visit(ctx.state_valued_path_expression())); } else if (ctx.numeric_literal() != null) { - tokens.addAll(visit(ctx.numeric_literal())); + builder.append(visit(ctx.numeric_literal())); } else if (ctx.arithmetic_expression() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.functions_returning_numerics() != null) { - tokens.addAll(visit(ctx.functions_returning_numerics())); + builder.append(visit(ctx.functions_returning_numerics())); } else if (ctx.aggregate_expression() != null) { - tokens.addAll(visit(ctx.aggregate_expression())); + builder.append(visit(ctx.aggregate_expression())); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + builder.append(visit(ctx.case_expression())); } else if (ctx.cast_function() != null) { - tokens.addAll(visit(ctx.cast_function())); + builder.append(visit(ctx.cast_function())); } else if (ctx.function_invocation() != null) { - tokens.addAll(visit(ctx.function_invocation())); + builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitString_expression(EqlParser.String_expressionContext ctx) { + public QueryRendererBuilder visitString_expression(EqlParser.String_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.append(visit(ctx.state_valued_path_expression())); } else if (ctx.string_literal() != null) { - tokens.addAll(visit(ctx.string_literal())); + builder.append(visit(ctx.string_literal())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.functions_returning_strings() != null) { - tokens.addAll(visit(ctx.functions_returning_strings())); + builder.append(visit(ctx.functions_returning_strings())); } else if (ctx.aggregate_expression() != null) { - tokens.addAll(visit(ctx.aggregate_expression())); + builder.append(visit(ctx.aggregate_expression())); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + builder.append(visit(ctx.case_expression())); } else if (ctx.function_invocation() != null) { - tokens.addAll(visit(ctx.function_invocation())); + builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitDatetime_expression(EqlParser.Datetime_expressionContext ctx) { + public QueryRendererBuilder visitDatetime_expression(EqlParser.Datetime_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.append(visit(ctx.state_valued_path_expression())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.functions_returning_datetime() != null) { - tokens.addAll(visit(ctx.functions_returning_datetime())); + builder.append(visit(ctx.functions_returning_datetime())); } else if (ctx.aggregate_expression() != null) { - tokens.addAll(visit(ctx.aggregate_expression())); + builder.append(visit(ctx.aggregate_expression())); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + builder.append(visit(ctx.case_expression())); } else if (ctx.function_invocation() != null) { - tokens.addAll(visit(ctx.function_invocation())); + builder.append(visit(ctx.function_invocation())); } else if (ctx.date_time_timestamp_literal() != null) { - tokens.addAll(visit(ctx.date_time_timestamp_literal())); + builder.append(visit(ctx.date_time_timestamp_literal())); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitBoolean_expression(EqlParser.Boolean_expressionContext ctx) { + public QueryRendererBuilder visitBoolean_expression(EqlParser.Boolean_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.append(visit(ctx.state_valued_path_expression())); } else if (ctx.boolean_literal() != null) { - tokens.addAll(visit(ctx.boolean_literal())); + builder.append(visit(ctx.boolean_literal())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + builder.append(visit(ctx.case_expression())); } else if (ctx.function_invocation() != null) { - tokens.addAll(visit(ctx.function_invocation())); + builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitEnum_expression(EqlParser.Enum_expressionContext ctx) { + public QueryRendererBuilder visitEnum_expression(EqlParser.Enum_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.append(visit(ctx.state_valued_path_expression())); } else if (ctx.enum_literal() != null) { - tokens.addAll(visit(ctx.enum_literal())); + builder.append(visit(ctx.enum_literal())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + builder.append(visit(ctx.case_expression())); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitEntity_expression(EqlParser.Entity_expressionContext ctx) { + public QueryRendererBuilder visitEntity_expression(EqlParser.Entity_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.single_valued_object_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_object_path_expression())); + builder.append(visit(ctx.single_valued_object_path_expression())); } else if (ctx.simple_entity_expression() != null) { - tokens.addAll(visit(ctx.simple_entity_expression())); + builder.append(visit(ctx.simple_entity_expression())); } - return tokens; + return builder; } @Override - public List visitSimple_entity_expression(EqlParser.Simple_entity_expressionContext ctx) { + public QueryRendererBuilder visitSimple_entity_expression(EqlParser.Simple_entity_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.append(visit(ctx.identification_variable())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } - return tokens; + return builder; } @Override - public List visitEntity_type_expression(EqlParser.Entity_type_expressionContext ctx) { + public QueryRendererBuilder visitEntity_type_expression(EqlParser.Entity_type_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.type_discriminator() != null) { - tokens.addAll(visit(ctx.type_discriminator())); + builder.append(visit(ctx.type_discriminator())); } else if (ctx.entity_type_literal() != null) { - tokens.addAll(visit(ctx.entity_type_literal())); + builder.append(visit(ctx.entity_type_literal())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } - return tokens; + return builder; } @Override - public List visitType_discriminator(EqlParser.Type_discriminatorContext ctx) { + public QueryRendererBuilder visitType_discriminator(EqlParser.Type_discriminatorContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.TYPE(), false)); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TYPE())); + builder.append(TOKEN_OPEN_PAREN); if (ctx.general_identification_variable() != null) { - tokens.addAll(visit(ctx.general_identification_variable())); + builder.appendInline(visit(ctx.general_identification_variable())); } else if (ctx.single_valued_object_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_object_path_expression())); + builder.appendInline(visit(ctx.single_valued_object_path_expression())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.appendInline(visit(ctx.input_parameter())); } - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitFunctions_returning_numerics( - EqlParser.Functions_returning_numericsContext ctx) { + public QueryRendererBuilder visitFunctions_returning_numerics(EqlParser.Functions_returning_numericsContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.LENGTH() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LENGTH(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.string_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.LENGTH())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LOCATE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LOCATE(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.string_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.string_expression(1))); - NOSPACE(tokens); + builder.append(JpaQueryParsingToken.token(ctx.LOCATE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.string_expression(1))); if (ctx.arithmetic_expression() != null) { - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(0))); } - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.ABS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ABS(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.ABS())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.CEILING() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CEILING(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.CEILING())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.EXP() != null) { - tokens.add(new JpaQueryParsingToken(ctx.EXP(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.EXP())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.FLOOR() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FLOOR(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.FLOOR())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LN() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LN(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.LN())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.SIGN() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SIGN(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.SIGN())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.SQRT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SQRT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.SQRT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.MOD() != null) { - tokens.add(new JpaQueryParsingToken(ctx.MOD(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.arithmetic_expression(1))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.MOD())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(1))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.POWER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.POWER(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.arithmetic_expression(1))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.POWER())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(1))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.ROUND() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ROUND(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.arithmetic_expression(1))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.ROUND())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(1))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.SIZE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SIZE(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.collection_valued_path_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.SIZE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.collection_valued_path_expression())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.INDEX() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INDEX(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.INDEX())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.identification_variable())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.extract_datetime_field() != null) { - tokens.addAll(visit(ctx.extract_datetime_field())); + builder.append(visit(ctx.extract_datetime_field())); } - return tokens; + return builder; } @Override - public List visitFunctions_returning_datetime( - EqlParser.Functions_returning_datetimeContext ctx) { + public QueryRendererBuilder visitFunctions_returning_datetime(EqlParser.Functions_returning_datetimeContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.CURRENT_DATE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CURRENT_DATE())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT_DATE())); } else if (ctx.CURRENT_TIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CURRENT_TIME())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT_TIME())); } else if (ctx.CURRENT_TIMESTAMP() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CURRENT_TIMESTAMP())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT_TIMESTAMP())); } else if (ctx.LOCAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LOCAL())); + builder.append(JpaQueryParsingToken.expression(ctx.LOCAL())); if (ctx.DATE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DATE())); + builder.append(JpaQueryParsingToken.expression(ctx.DATE())); } else if (ctx.TIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TIME())); + builder.append(JpaQueryParsingToken.expression(ctx.TIME())); } else if (ctx.DATETIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DATETIME())); + builder.append(JpaQueryParsingToken.expression(ctx.DATETIME())); } } else if (ctx.extract_datetime_part() != null) { - tokens.addAll(visit(ctx.extract_datetime_part())); + builder.append(visit(ctx.extract_datetime_part())); } - return tokens; + return builder; } @Override - public List visitFunctions_returning_strings(EqlParser.Functions_returning_stringsContext ctx) { + public QueryRendererBuilder visitFunctions_returning_strings(EqlParser.Functions_returning_stringsContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.CONCAT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CONCAT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - ctx.string_expression().forEach(stringExpressionContext -> { - tokens.addAll(visit(stringExpressionContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.CONCAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryRendererBuilder.concat(ctx.string_expression(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.SUBSTRING() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SUBSTRING(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.string_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - ctx.arithmetic_expression().forEach(arithmeticExpressionContext -> { - tokens.addAll(visit(arithmeticExpressionContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.SUBSTRING())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(QueryRendererBuilder.concat(ctx.arithmetic_expression(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.TRIM() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TRIM(), false)); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TRIM())); + builder.append(TOKEN_OPEN_PAREN); if (ctx.trim_specification() != null) { - tokens.addAll(visit(ctx.trim_specification())); + builder.appendExpression(visit(ctx.trim_specification())); } if (ctx.trim_character() != null) { - tokens.addAll(visit(ctx.trim_character())); + builder.appendExpression(visit(ctx.trim_character())); } if (ctx.FROM() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FROM())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); } - tokens.addAll(visit(ctx.string_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LOWER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LOWER(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.string_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.LOWER())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.UPPER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.UPPER(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.string_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.UPPER())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitTrim_specification(EqlParser.Trim_specificationContext ctx) { + public QueryRendererBuilder visitTrim_specification(EqlParser.Trim_specificationContext ctx) { if (ctx.LEADING() != null) { - return List.of(new JpaQueryParsingToken(ctx.LEADING())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.LEADING())); } else if (ctx.TRAILING() != null) { - return List.of(new JpaQueryParsingToken(ctx.TRAILING())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.TRAILING())); } else { - return List.of(new JpaQueryParsingToken(ctx.BOTH())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.BOTH())); } } @Override - public List visitCast_function(EqlParser.Cast_functionContext ctx) { + public QueryRendererBuilder visitCast_function(EqlParser.Cast_functionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CAST(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.single_valued_path_expression())); - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); + builder.append(JpaQueryParsingToken.token(ctx.CAST())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.single_valued_path_expression())); + builder.append(TOKEN_SPACE); + builder.appendInline(visit(ctx.identification_variable())); if (ctx.numeric_literal() != null) { - tokens.add(TOKEN_OPEN_PAREN); - ctx.numeric_literal().forEach(numericLiteralContext -> { - tokens.addAll(visit(numericLiteralContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryRendererBuilder.concat(ctx.numeric_literal(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); } - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitFunction_invocation(EqlParser.Function_invocationContext ctx) { + public QueryRendererBuilder visitFunction_invocation(EqlParser.Function_invocationContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.FUNCTION() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FUNCTION(), false)); + builder.append(JpaQueryParsingToken.token(ctx.FUNCTION())); } else if (ctx.identification_variable() != null) { - - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); + builder.appendInline(visit(ctx.identification_variable())); + } + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.function_name())); + if (!ctx.function_arg().isEmpty()) { + builder.append(TOKEN_COMMA); } - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.function_name())); - NOSPACE(tokens); - ctx.function_arg().forEach(functionArgContext -> { - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(functionArgContext)); - NOSPACE(tokens); - }); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); - return tokens; + builder.appendInline(QueryRendererBuilder.concat(ctx.function_arg(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; } @Override - public List visitExtract_datetime_field(EqlParser.Extract_datetime_fieldContext ctx) { + public QueryRendererBuilder visitExtract_datetime_field(EqlParser.Extract_datetime_fieldContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.EXTRACT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.datetime_field())); - tokens.add(new JpaQueryParsingToken(ctx.FROM())); - tokens.addAll(visit(ctx.datetime_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.EXTRACT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendExpression(visit(ctx.datetime_field())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.appendInline(visit(ctx.datetime_expression())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitDatetime_field(EqlParser.Datetime_fieldContext ctx) { + public QueryRendererBuilder visitDatetime_field(EqlParser.Datetime_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitExtract_datetime_part(EqlParser.Extract_datetime_partContext ctx) { + public QueryRendererBuilder visitExtract_datetime_part(EqlParser.Extract_datetime_partContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.EXTRACT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.datetime_part())); - tokens.add(new JpaQueryParsingToken(ctx.FROM())); - tokens.addAll(visit(ctx.datetime_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.EXTRACT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendExpression(visit(ctx.datetime_part())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.appendInline(visit(ctx.datetime_expression())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitDatetime_part(EqlParser.Datetime_partContext ctx) { + public QueryRendererBuilder visitDatetime_part(EqlParser.Datetime_partContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitFunction_arg(EqlParser.Function_argContext ctx) { + public QueryRendererBuilder visitFunction_arg(EqlParser.Function_argContext ctx) { if (ctx.literal() != null) { return visit(ctx.literal()); @@ -2181,7 +2042,7 @@ class EqlQueryRenderer extends EqlBaseVisitor> { } @Override - public List visitCase_expression(EqlParser.Case_expressionContext ctx) { + public QueryRendererBuilder visitCase_expression(EqlParser.Case_expressionContext ctx) { if (ctx.general_case_expression() != null) { return visit(ctx.general_case_expression()); @@ -2195,57 +2056,57 @@ class EqlQueryRenderer extends EqlBaseVisitor> { } @Override - public List visitGeneral_case_expression(EqlParser.General_case_expressionContext ctx) { + public QueryRendererBuilder visitGeneral_case_expression(EqlParser.General_case_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CASE())); + builder.append(JpaQueryParsingToken.expression(ctx.CASE())); ctx.when_clause().forEach(whenClauseContext -> { - tokens.addAll(visit(whenClauseContext)); + builder.appendExpression(visit(whenClauseContext)); }); - tokens.add(new JpaQueryParsingToken(ctx.ELSE())); - tokens.addAll(visit(ctx.scalar_expression())); - tokens.add(new JpaQueryParsingToken(ctx.END())); + builder.append(JpaQueryParsingToken.expression(ctx.ELSE())); + builder.appendExpression(visit(ctx.scalar_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.END())); - return tokens; + return builder; } @Override - public List visitWhen_clause(EqlParser.When_clauseContext ctx) { + public QueryRendererBuilder visitWhen_clause(EqlParser.When_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.WHEN())); - tokens.addAll(visit(ctx.conditional_expression())); - tokens.add(new JpaQueryParsingToken(ctx.THEN())); - tokens.addAll(visit(ctx.scalar_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.WHEN())); + builder.append(visit(ctx.conditional_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.THEN())); + builder.append(visit(ctx.scalar_expression())); - return tokens; + return builder; } @Override - public List visitSimple_case_expression(EqlParser.Simple_case_expressionContext ctx) { + public QueryRendererBuilder visitSimple_case_expression(EqlParser.Simple_case_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CASE())); - tokens.addAll(visit(ctx.case_operand())); + builder.append(JpaQueryParsingToken.expression(ctx.CASE())); + builder.append(visit(ctx.case_operand())); ctx.simple_when_clause().forEach(simpleWhenClauseContext -> { - tokens.addAll(visit(simpleWhenClauseContext)); + builder.append(visit(simpleWhenClauseContext)); }); - tokens.add(new JpaQueryParsingToken(ctx.ELSE())); - tokens.addAll(visit(ctx.scalar_expression())); - tokens.add(new JpaQueryParsingToken(ctx.END())); + builder.append(JpaQueryParsingToken.expression(ctx.ELSE())); + builder.append(visit(ctx.scalar_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.END())); - return tokens; + return builder; } @Override - public List visitCase_operand(EqlParser.Case_operandContext ctx) { + public QueryRendererBuilder visitCase_operand(EqlParser.Case_operandContext ctx) { if (ctx.state_valued_path_expression() != null) { return visit(ctx.state_valued_path_expression()); @@ -2255,303 +2116,278 @@ class EqlQueryRenderer extends EqlBaseVisitor> { } @Override - public List visitSimple_when_clause(EqlParser.Simple_when_clauseContext ctx) { + public QueryRendererBuilder visitSimple_when_clause(EqlParser.Simple_when_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.WHEN())); - tokens.addAll(visit(ctx.scalar_expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.THEN())); - tokens.addAll(visit(ctx.scalar_expression(1))); + builder.append(JpaQueryParsingToken.expression(ctx.WHEN())); + builder.appendExpression(visit(ctx.scalar_expression(0))); + builder.append(JpaQueryParsingToken.expression(ctx.THEN())); + builder.appendExpression(visit(ctx.scalar_expression(1))); - return tokens; + return builder; } @Override - public List visitCoalesce_expression(EqlParser.Coalesce_expressionContext ctx) { + public QueryRendererBuilder visitCoalesce_expression(EqlParser.Coalesce_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.COALESCE(), false)); - tokens.add(TOKEN_OPEN_PAREN); - ctx.scalar_expression().forEach(scalarExpressionContext -> { - tokens.addAll(visit(scalarExpressionContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.COALESCE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryRendererBuilder.concat(ctx.scalar_expression(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitNullif_expression(EqlParser.Nullif_expressionContext ctx) { + public QueryRendererBuilder visitNullif_expression(EqlParser.Nullif_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.NULLIF(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.scalar_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.scalar_expression(1))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.NULLIF())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.scalar_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.scalar_expression(1))); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitTrim_character(EqlParser.Trim_characterContext ctx) { + public QueryRendererBuilder visitTrim_character(EqlParser.Trim_characterContext ctx) { if (ctx.CHARACTER() != null) { - return List.of(new JpaQueryParsingToken(ctx.CHARACTER())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.CHARACTER())); } else if (ctx.character_valued_input_parameter() != null) { return visit(ctx.character_valued_input_parameter()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitIdentification_variable(EqlParser.Identification_variableContext ctx) { + public QueryRendererBuilder visitIdentification_variable(EqlParser.Identification_variableContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return List.of(new JpaQueryParsingToken(ctx.IDENTIFICATION_VARIABLE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.IDENTIFICATION_VARIABLE())); } else if (ctx.f != null) { - return List.of(new JpaQueryParsingToken(ctx.f)); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.f)); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitConstructor_name(EqlParser.Constructor_nameContext ctx) { - - List tokens = new ArrayList<>(); - - tokens.addAll(visit(ctx.entity_name())); - NOSPACE(tokens); - - return tokens; + public QueryRendererBuilder visitConstructor_name(EqlParser.Constructor_nameContext ctx) { + return visit(ctx.entity_name()); } @Override - public List visitLiteral(EqlParser.LiteralContext ctx) { + public QueryRendererBuilder visitLiteral(EqlParser.LiteralContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.STRINGLITERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.STRINGLITERAL())); + builder.append(JpaQueryParsingToken.expression(ctx.STRINGLITERAL())); } else if (ctx.INTLITERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INTLITERAL())); + builder.append(JpaQueryParsingToken.expression(ctx.INTLITERAL())); } else if (ctx.FLOATLITERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FLOATLITERAL())); + builder.append(JpaQueryParsingToken.expression(ctx.FLOATLITERAL())); } else if (ctx.LONGLITERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LONGLITERAL())); + builder.append(JpaQueryParsingToken.expression(ctx.LONGLITERAL())); } else if (ctx.boolean_literal() != null) { - tokens.addAll(visit(ctx.boolean_literal())); + builder.append(visit(ctx.boolean_literal())); } else if (ctx.entity_type_literal() != null) { - tokens.addAll(visit(ctx.entity_type_literal())); + builder.append(visit(ctx.entity_type_literal())); } - return tokens; + return builder; } @Override - public List visitInput_parameter(EqlParser.Input_parameterContext ctx) { + public QueryRendererBuilder visitInput_parameter(EqlParser.Input_parameterContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.INTLITERAL() != null) { - tokens.add(TOKEN_QUESTION_MARK); - tokens.add(new JpaQueryParsingToken(ctx.INTLITERAL())); + builder.append(TOKEN_QUESTION_MARK); + builder.append(JpaQueryParsingToken.expression(ctx.INTLITERAL())); } else if (ctx.identification_variable() != null) { - tokens.add(TOKEN_COLON); - tokens.addAll(visit(ctx.identification_variable())); + builder.append(TOKEN_COLON); + builder.append(visit(ctx.identification_variable())); } - return tokens; + return builder; } @Override - public List visitPattern_value(EqlParser.Pattern_valueContext ctx) { + public QueryRendererBuilder visitPattern_value(EqlParser.Pattern_valueContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.string_expression())); + builder.append(visit(ctx.string_expression())); - return tokens; + return builder; } @Override - public List visitDate_time_timestamp_literal(EqlParser.Date_time_timestamp_literalContext ctx) { + public QueryRendererBuilder visitDate_time_timestamp_literal(EqlParser.Date_time_timestamp_literalContext ctx) { if (ctx.STRINGLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.STRINGLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.STRINGLITERAL())); } else if (ctx.DATELITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.DATELITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.DATELITERAL())); } else if (ctx.TIMELITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.TIMELITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.TIMELITERAL())); } else if (ctx.TIMESTAMPLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.TIMESTAMPLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.TIMESTAMPLITERAL())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitEntity_type_literal(EqlParser.Entity_type_literalContext ctx) { + public QueryRendererBuilder visitEntity_type_literal(EqlParser.Entity_type_literalContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitEscape_character(EqlParser.Escape_characterContext ctx) { - return List.of(new JpaQueryParsingToken(ctx.CHARACTER())); + public QueryRendererBuilder visitEscape_character(EqlParser.Escape_characterContext ctx) { + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.CHARACTER())); } @Override - public List visitNumeric_literal(EqlParser.Numeric_literalContext ctx) { + public QueryRendererBuilder visitNumeric_literal(EqlParser.Numeric_literalContext ctx) { if (ctx.INTLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.INTLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.INTLITERAL())); } else if (ctx.FLOATLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.FLOATLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.FLOATLITERAL())); } else if (ctx.LONGLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.LONGLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.LONGLITERAL())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitBoolean_literal(EqlParser.Boolean_literalContext ctx) { + public QueryRendererBuilder visitBoolean_literal(EqlParser.Boolean_literalContext ctx) { if (ctx.TRUE() != null) { - return List.of(new JpaQueryParsingToken(ctx.TRUE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.TRUE())); } else if (ctx.FALSE() != null) { - return List.of(new JpaQueryParsingToken(ctx.FALSE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.FALSE())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitEnum_literal(EqlParser.Enum_literalContext ctx) { + public QueryRendererBuilder visitEnum_literal(EqlParser.Enum_literalContext ctx) { return visit(ctx.state_field_path_expression()); } @Override - public List visitString_literal(EqlParser.String_literalContext ctx) { + public QueryRendererBuilder visitString_literal(EqlParser.String_literalContext ctx) { if (ctx.CHARACTER() != null) { - return List.of(new JpaQueryParsingToken(ctx.CHARACTER())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.CHARACTER())); } else if (ctx.STRINGLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.STRINGLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.STRINGLITERAL())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitSingle_valued_embeddable_object_field( + public QueryRendererBuilder visitSingle_valued_embeddable_object_field( EqlParser.Single_valued_embeddable_object_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitSubtype(EqlParser.SubtypeContext ctx) { + public QueryRendererBuilder visitSubtype(EqlParser.SubtypeContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitCollection_valued_field(EqlParser.Collection_valued_fieldContext ctx) { + public QueryRendererBuilder visitCollection_valued_field(EqlParser.Collection_valued_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitSingle_valued_object_field(EqlParser.Single_valued_object_fieldContext ctx) { + public QueryRendererBuilder visitSingle_valued_object_field(EqlParser.Single_valued_object_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitState_field(EqlParser.State_fieldContext ctx) { + public QueryRendererBuilder visitState_field(EqlParser.State_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitCollection_value_field(EqlParser.Collection_value_fieldContext ctx) { + public QueryRendererBuilder visitCollection_value_field(EqlParser.Collection_value_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitEntity_name(EqlParser.Entity_nameContext ctx) { - - List tokens = new ArrayList<>(); - - ctx.reserved_word().forEach(identificationVariableContext -> { - tokens.addAll(visitReserved_word(identificationVariableContext)); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - }); - CLIP(tokens); - SPACE(tokens); - - return tokens; + public QueryRendererBuilder visitEntity_name(EqlParser.Entity_nameContext ctx) { + return QueryRendererBuilder.concat(ctx.reserved_word(), this::visit, QueryRendererBuilder::toInline, TOKEN_DOT); } @Override - public List visitResult_variable(EqlParser.Result_variableContext ctx) { + public QueryRendererBuilder visitResult_variable(EqlParser.Result_variableContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitSuperquery_identification_variable( + public QueryRendererBuilder visitSuperquery_identification_variable( EqlParser.Superquery_identification_variableContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitCollection_valued_input_parameter( + public QueryRendererBuilder visitCollection_valued_input_parameter( EqlParser.Collection_valued_input_parameterContext ctx) { return visit(ctx.input_parameter()); } @Override - public List visitSingle_valued_input_parameter( - EqlParser.Single_valued_input_parameterContext ctx) { + public QueryRendererBuilder visitSingle_valued_input_parameter(EqlParser.Single_valued_input_parameterContext ctx) { return visit(ctx.input_parameter()); } @Override - public List visitFunction_name(EqlParser.Function_nameContext ctx) { + public QueryRendererBuilder visitFunction_name(EqlParser.Function_nameContext ctx) { return visit(ctx.string_literal()); } @Override - public List visitCharacter_valued_input_parameter( + public QueryRendererBuilder visitCharacter_valued_input_parameter( EqlParser.Character_valued_input_parameterContext ctx) { if (ctx.CHARACTER() != null) { - return List.of(new JpaQueryParsingToken(ctx.CHARACTER())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.CHARACTER())); } else if (ctx.input_parameter() != null) { return visit(ctx.input_parameter()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitReserved_word(EqlParser.Reserved_wordContext ctx) { + public QueryRendererBuilder visitReserved_word(EqlParser.Reserved_wordContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return List.of(new JpaQueryParsingToken(ctx.IDENTIFICATION_VARIABLE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.IDENTIFICATION_VARIABLE())); } else if (ctx.f != null) { - return List.of(new JpaQueryParsingToken(ctx.f)); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.f)); } else { - return List.of(); + return QueryRenderer.builder(); } } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryTransformer.java deleted file mode 100644 index 890c5d39d..000000000 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryTransformer.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jpa.repository.query; - -import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.springframework.data.domain.Sort; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed EQL query. - * - * @author Greg Turnquist - * @since 3.2 - */ -class EqlQueryTransformer extends EqlQueryRenderer { - - // TODO: Separate input from result parameters, encapsulation... - private final Sort sort; - private final boolean countQuery; - - private final @Nullable String countProjection; - - private @Nullable String primaryFromAlias = null; - - private List projection = Collections.emptyList(); - private boolean projectionProcessed; - - private boolean hasConstructorExpression = false; - - private JpaQueryTransformerSupport transformerSupport; - - EqlQueryTransformer() { - this(Sort.unsorted(), false, null); - } - - EqlQueryTransformer(Sort sort) { - this(sort, false, null); - } - - EqlQueryTransformer(boolean countQuery, @Nullable String countProjection) { - this(Sort.unsorted(), countQuery, countProjection); - } - - private EqlQueryTransformer(Sort sort, boolean countQuery, @Nullable String countProjection) { - - Assert.notNull(sort, "Sort must not be null"); - - this.sort = sort; - this.countQuery = countQuery; - this.countProjection = countProjection; - this.transformerSupport = new JpaQueryTransformerSupport(); - } - - @Nullable - public String getAlias() { - return this.primaryFromAlias; - } - - public List getProjection() { - return this.projection; - } - - public boolean hasConstructorExpression() { - return this.hasConstructorExpression; - } - - @Override - public List visitSelect_statement(EqlParser.Select_statementContext ctx) { - - List tokens = newArrayList(); - - tokens.addAll(visit(ctx.select_clause())); - tokens.addAll(visit(ctx.from_clause())); - - if (ctx.where_clause() != null) { - tokens.addAll(visit(ctx.where_clause())); - } - - if (ctx.groupby_clause() != null) { - tokens.addAll(visit(ctx.groupby_clause())); - } - - if (ctx.having_clause() != null) { - tokens.addAll(visit(ctx.having_clause())); - } - - if (!countQuery) { - - if (ctx.orderby_clause() != null) { - tokens.addAll(visit(ctx.orderby_clause())); - } - - if (sort.isSorted()) { - - if (ctx.orderby_clause() != null) { - - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - } else { - - SPACE(tokens); - tokens.add(TOKEN_ORDER_BY); - } - - tokens.addAll(transformerSupport.generateOrderByArguments(primaryFromAlias, sort)); - } - } - - return tokens; - } - - @Override - public List visitSelect_clause(EqlParser.Select_clauseContext ctx) { - - List tokens = newArrayList(); - - tokens.add(new JpaQueryParsingToken(ctx.SELECT())); - - if (countQuery) { - tokens.add(TOKEN_COUNT_FUNC); - } - - if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); - } - - List selectItemTokens = newArrayList(); - - ctx.select_item().forEach(selectItemContext -> { - selectItemTokens.addAll(visit(selectItemContext)); - NOSPACE(selectItemTokens); - selectItemTokens.add(TOKEN_COMMA); - }); - CLIP(selectItemTokens); - SPACE(selectItemTokens); - - if (countQuery) { - - if (countProjection != null) { - tokens.add(new JpaQueryParsingToken(countProjection)); - } else { - - if (ctx.DISTINCT() != null) { - - List countSelection = QueryTransformers.filterCountSelection(selectItemTokens); - - if (countSelection.stream().anyMatch(jpqlToken -> jpqlToken.getToken().contains("new"))) { - // constructor - tokens.add(new JpaQueryParsingToken(() -> primaryFromAlias)); - } else { - // keep all the select items to distinct against - tokens.addAll(countSelection); - } - } else { - tokens.add(new JpaQueryParsingToken(() -> primaryFromAlias)); - } - } - - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); - } else { - tokens.addAll(selectItemTokens); - } - - if (!projectionProcessed) { - projection = selectItemTokens; - projectionProcessed = true; - } - - return tokens; - } - - @Override - public List visitSelect_item(EqlParser.Select_itemContext ctx) { - - List tokens = super.visitSelect_item(ctx); - - if (ctx.result_variable() != null) { - transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); - } - - return tokens; - } - - @Override - public List visitRange_variable_declaration(EqlParser.Range_variable_declarationContext ctx) { - - List tokens = newArrayList(); - - tokens.addAll(visit(ctx.entity_name())); - - if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); - } - - tokens.addAll(visit(ctx.identification_variable())); - - if (primaryFromAlias == null) { - primaryFromAlias = tokens.get(tokens.size() - 1).getToken(); - } - - return tokens; - } - - @Override - public List visitJoin(EqlParser.JoinContext ctx) { - - List tokens = super.visitJoin(ctx); - - transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); - - return tokens; - } - - @Override - public List visitConstructor_expression(EqlParser.Constructor_expressionContext ctx) { - - hasConstructorExpression = true; - - return super.visitConstructor_expression(ctx); - } - - private static ArrayList newArrayList() { - return new ArrayList<>(); - } - -} 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 new file mode 100644 index 000000000..5c954b5e7 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java @@ -0,0 +1,132 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import java.util.List; + +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed EQL query by applying + * {@link Sort}. + * + * @author Greg Turnquist + * @author Mark Paluch + * @since 3.2 + */ +@SuppressWarnings("ConstantValue") +class EqlSortedQueryTransformer extends EqlQueryRenderer { + + private final JpaQueryTransformerSupport transformerSupport = new JpaQueryTransformerSupport(); + private final Sort sort; + private final @Nullable String primaryFromAlias; + + EqlSortedQueryTransformer(Sort sort, @Nullable String primaryFromAlias) { + + Assert.notNull(sort, "Sort must not be null"); + + this.sort = sort; + this.primaryFromAlias = primaryFromAlias; + } + + @Override + public QueryRendererBuilder visitSelect_statement(EqlParser.Select_statementContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.select_clause())); + builder.appendExpression(visit(ctx.from_clause())); + + if (ctx.where_clause() != null) { + builder.appendExpression(visit(ctx.where_clause())); + } + + if (ctx.groupby_clause() != null) { + builder.appendExpression(visit(ctx.groupby_clause())); + } + + if (ctx.having_clause() != null) { + builder.appendExpression(visit(ctx.having_clause())); + } + + doVisitOrderBy(builder, ctx); + + for (int i = 0; i < ctx.setOperator().size(); i++) { + + builder.appendExpression(visit(ctx.setOperator(i))); + builder.appendExpression(visit(ctx.select_statement(i))); + } + + return builder; + } + + private void doVisitOrderBy(QueryRendererBuilder builder, EqlParser.Select_statementContext ctx) { + + if (ctx.orderby_clause() != null) { + QueryRendererBuilder existingOrder = visit(ctx.orderby_clause()); + if (sort.isSorted()) { + builder.appendInline(existingOrder); + } else { + builder.append(existingOrder); + } + } + + if (sort.isSorted()) { + + List sortBy = transformerSupport.orderBy(primaryFromAlias, sort); + + if (ctx.orderby_clause() != null) { + + QueryRendererBuilder extension = QueryRenderer.builder().append(TOKEN_COMMA).append(sortBy); + + builder.appendInline(extension); + } else { + builder.append(TOKEN_ORDER_BY); + builder.append(sortBy); + } + } + } + + @Override + public QueryRendererBuilder visitSelect_item(EqlParser.Select_itemContext ctx) { + + QueryRendererBuilder builder = super.visitSelect_item(ctx); + + if (ctx.result_variable() != null) { + List tokens = builder.build().stream().toList(); + transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); + } + + return builder; + } + + @Override + public QueryRendererBuilder visitJoin(EqlParser.JoinContext ctx) { + + QueryRendererBuilder builder = super.visitJoin(ctx); + + List tokens = builder.build().stream().toList(); + transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); + + return builder; + } + +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java new file mode 100644 index 000000000..78884bb48 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java @@ -0,0 +1,249 @@ +/* + * Copyright 2022-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import java.util.List; + +import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.lang.Nullable; + +/** + * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed HQL query into a + * {@code COUNT(…)} query. + * + * @author Greg Turnquist + * @author Christoph Strobl + * @author Mark Paluch + * @since 3.1 + */ +@SuppressWarnings("ConstantValue") +class HqlCountQueryTransformer extends HqlQueryRenderer { + + private final @Nullable String countProjection; + private final @Nullable String primaryFromAlias; + + HqlCountQueryTransformer(@Nullable String countProjection, @Nullable String primaryFromAlias) { + this.countProjection = countProjection; + this.primaryFromAlias = primaryFromAlias; + } + + @Override + public QueryRendererBuilder visitOrderedQuery(HqlParser.OrderedQueryContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.query() != null) { + builder.append(visit(ctx.query())); + } else if (ctx.queryExpression() != null) { + + QueryRendererBuilder nested = QueryRenderer.builder(); + nested.append(TOKEN_OPEN_PAREN); + nested.appendInline(visit(ctx.queryExpression())); + nested.append(TOKEN_CLOSE_PAREN); + + builder.appendExpression(nested); + } + + if (ctx.queryOrder() != null) { + builder.append(visit(ctx.queryOrder())); + } + + return builder; + } + + @Override + public QueryRendererBuilder visitFromQuery(HqlParser.FromQueryContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (!isSubquery(ctx) && ctx.selectClause() == null) { + + QueryRendererBuilder countBuilder = QueryRenderer.builder(); + countBuilder.append(TOKEN_SELECT_COUNT); + + if (countProjection != null) { + countBuilder.append(new JpaQueryParsingToken(countProjection)); + } else { + if (primaryFromAlias == null) { + countBuilder.append(TOKEN_DOUBLE_UNDERSCORE); + } else { + countBuilder.append(JpaQueryParsingToken.token(primaryFromAlias)); + } + } + + countBuilder.append(TOKEN_CLOSE_PAREN); + + builder.appendExpression(countBuilder); + } + + if (ctx.fromClause() != null) { + builder.appendExpression(visit(ctx.fromClause())); + } + + if (ctx.whereClause() != null) { + builder.appendExpression(visit(ctx.whereClause())); + } + + if (ctx.groupByClause() != null) { + builder.appendExpression(visit(ctx.groupByClause())); + } + + if (ctx.havingClause() != null) { + builder.appendExpression(visit(ctx.havingClause())); + } + + if (ctx.selectClause() != null) { + builder.appendExpression(visit(ctx.selectClause())); + } + + return builder; + } + + @Override + public QueryRendererBuilder visitFromRoot(HqlParser.FromRootContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.entityName() != null) { + + builder.appendExpression(visit(ctx.entityName())); + + if (ctx.variable() != null) { + builder.appendExpression(visit(ctx.variable())); + + } else { + + builder.append(TOKEN_AS); + builder.append(TOKEN_DOUBLE_UNDERSCORE); + } + } else if (ctx.subquery() != null) { + + if (ctx.LATERAL() != null) { + builder.append(JpaQueryParsingToken.expression(ctx.LATERAL())); + } + + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.append(TOKEN_OPEN_PAREN); + nested.appendInline(visit(ctx.subquery())); + nested.append(TOKEN_CLOSE_PAREN); + + builder.appendExpression(nested); + + if (ctx.variable() != null) { + builder.appendExpression(visit(ctx.variable())); + } + } + + return builder; + } + + @Override + public QueryRendererBuilder visitJoin(HqlParser.JoinContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.joinType())); + builder.append(JpaQueryParsingToken.expression(ctx.JOIN())); + + builder.appendExpression(visit(ctx.joinTarget())); + + if (ctx.joinRestriction() != null) { + builder.appendExpression(visit(ctx.joinRestriction())); + } + + return builder; + } + + @Override + public QueryRendererBuilder visitSelectClause(HqlParser.SelectClauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.append(JpaQueryParsingToken.expression(ctx.SELECT())); + + QueryRendererBuilder selectionListbuilder = visit(ctx.selectionList()); + + if (!isSubquery(ctx)) { + + builder.append(TOKEN_COUNT_FUNC); + + if (countProjection != null) { + builder.append(JpaQueryParsingToken.token(countProjection)); + } + + QueryRendererBuilder nested = QueryRenderer.builder(); + + if (ctx.DISTINCT() != null) { + nested.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); + } + + if (countProjection == null) { + + if (ctx.DISTINCT() != null) { + + List countSelection = QueryTransformers + .filterCountSelection(selectionListbuilder.build().stream().toList()); + + if (countSelection.stream().anyMatch(hqlToken -> hqlToken.getToken().contains("new"))) { + // constructor + nested.append(new JpaQueryParsingToken(primaryFromAlias)); + } else { + // keep all the select items to distinct against + nested.append(countSelection); + } + } else { + nested.append(new JpaQueryParsingToken(primaryFromAlias)); + } + } + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); + + } else { + + if (ctx.DISTINCT() != null) { + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); + } + + builder.append(selectionListbuilder); + } + + return builder; + } + + @Override + public QueryRendererBuilder visitQueryOrder(HqlParser.QueryOrderContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.limitClause() != null) { + builder.appendExpression(visit(ctx.limitClause())); + } + + if (ctx.offsetClause() != null) { + builder.appendExpression(visit(ctx.offsetClause())); + } + + if (ctx.fetchClause() != null) { + builder.appendExpression(visit(ctx.fetchClause())); + } + + return builder; + } + +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java new file mode 100644 index 000000000..179450f0b --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java @@ -0,0 +1,99 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.lang.Nullable; + +/** + * {@link ParsedQueryIntrospector} for HQL queries. + * + * @author Mark Paluch + */ +@SuppressWarnings({ "UnreachableCode", "ConstantValue" }) +class HqlQueryIntrospector extends HqlBaseVisitor implements ParsedQueryIntrospector { + + private final HqlQueryRenderer renderer = new HqlQueryRenderer(); + + private @Nullable String primaryFromAlias = null; + private @Nullable List projection; + private boolean projectionProcessed; + private boolean hasConstructorExpression = false; + + @Override + public String getAlias() { + return primaryFromAlias; + } + + @Override + public List getProjection() { + return projection == null ? Collections.emptyList() : projection; + } + + @Override + public boolean hasConstructorExpression() { + return hasConstructorExpression; + } + + @Override + public Void visitSelectClause(HqlParser.SelectClauseContext ctx) { + + List selections = ctx.selectionList().selection(); + List selectItemTokens = new ArrayList<>(selections.size() * 2); + + for (HqlParser.SelectionContext selection : selections) { + + if (!selectItemTokens.isEmpty()) { + selectItemTokens.add(TOKEN_COMMA); + } + + selectItemTokens.add(JpaQueryParsingToken.token(renderer.visitSelection(selection).build().render())); + } + + if (!projectionProcessed) { + projection = selectItemTokens; + projectionProcessed = true; + } + + return super.visitSelectClause(ctx); + } + + @Override + public Void visitFromRoot(HqlParser.FromRootContext ctx) { + + if (primaryFromAlias == null && ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)) { + + primaryFromAlias = (ctx.variable().reservedWord() != null ? ctx.variable().reservedWord() + : ctx.variable().identifier().reservedWord()).getText(); + } + + return null; + } + + @Override + public Void visitInstantiation(HqlParser.InstantiationContext ctx) { + + hasConstructorExpression = true; + + return super.visitInstantiation(ctx); + } + +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryParser.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryParser.java index 9842c2b6b..96b8b4aaf 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryParser.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryParser.java @@ -15,119 +15,30 @@ */ package org.springframework.data.jpa.repository.query; -import java.util.List; - -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.ParserRuleContext; -import org.springframework.data.domain.Sort; -import org.springframework.lang.Nullable; - /** - * Implements the {@code HQL} parsing operations of a {@link JpaQueryParserSupport} using the ANTLR-generated - * {@link HqlParser} and {@link HqlQueryTransformer}. + * Implements the {@code HQL} parsing operations of a {@link JpaQueryParser} using the ANTLR-generated {@link HqlParser} + * and {@link HqlSortedQueryTransformer}. * * @author Greg Turnquist * @author Mark Paluch * @since 3.1 */ -class HqlQueryParser extends JpaQueryParserSupport { +class HqlQueryParser extends JpaQueryParser { - HqlQueryParser(String query) { - super(query); + private HqlQueryParser(String query) { + super(parse(query, HqlLexer::new, HqlParser::new, HqlParser::start), new HqlQueryIntrospector(), + HqlSortedQueryTransformer::new, HqlCountQueryTransformer::new); } /** - * Convenience method to parse an HQL query. Will throw a {@link BadJpqlGrammarException} if the query is invalid. + * Parse a HQL query. * * @param query - * @return a parsed query, ready for postprocessing + * @return the query parser. + * @throws BadJpqlGrammarException */ - public static ParserRuleContext parseQuery(String query) { - - HqlLexer lexer = new HqlLexer(CharStreams.fromString(query)); - HqlParser parser = new HqlParser(new CommonTokenStream(lexer)); - - configureParser(query, lexer, parser); - - return parser.start(); + public static HqlQueryParser parseQuery(String query) throws BadJpqlGrammarException { + return new HqlQueryParser(query); } - /** - * Parse the query using {@link #parseQuery(String)}. - * - * @return a parsed query - */ - @Override - protected ParserRuleContext parse(String query) { - return parseQuery(query); - } - - /** - * Use the {@link HqlQueryTransformer} to transform the original query into a query with the {@link Sort} applied. - * - * @param parsedQuery - * @param sort can be {@literal null} - * @return list of {@link JpaQueryParsingToken}s - */ - @Override - protected List applySort(ParserRuleContext parsedQuery, Sort sort) { - return new HqlQueryTransformer(sort).visit(parsedQuery); - } - - /** - * Use the {@link HqlQueryTransformer} to transform the original query into a count query. - * - * @param parsedQuery - * @param countProjection - * @return list of {@link JpaQueryParsingToken}s - */ - @Override - protected List doCreateCountQuery(ParserRuleContext parsedQuery, - @Nullable String countProjection) { - return new HqlQueryTransformer(true, countProjection).visit(parsedQuery); - } - - /** - * Run the parsed query through {@link HqlQueryTransformer} to find the primary FROM clause's alias. - * - * @param parsedQuery - * @return can be {@literal null} - */ - @Override - protected String doFindAlias(ParserRuleContext parsedQuery) { - - HqlQueryTransformer transformVisitor = new HqlQueryTransformer(); - transformVisitor.visit(parsedQuery); - return transformVisitor.getAlias(); - } - - /** - * Use {@link HqlQueryTransformer} to find the projection of the query. - * - * @param parsedQuery - * @return - */ - @Override - protected List doFindProjection(ParserRuleContext parsedQuery) { - - HqlQueryTransformer transformVisitor = new HqlQueryTransformer(); - transformVisitor.visit(parsedQuery); - return transformVisitor.getProjection(); - } - - /** - * Use {@link HqlQueryTransformer} to detect if the query uses a {@code new com.example.Dto()} DTO constructor in the - * primary select clause. - * - * @param parsedQuery - * @return Guaranteed to be {@literal true} or {@literal false}. - */ - @Override - protected boolean doCheckForConstructor(ParserRuleContext parsedQuery) { - - HqlQueryTransformer transformVisitor = new HqlQueryTransformer(); - transformVisitor.visit(parsedQuery); - return transformVisitor.hasConstructorExpression(); - } } 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 5b8c990fe..2e86c4c9c 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 @@ -20,6 +20,11 @@ import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken import java.util.ArrayList; import java.util.List; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTree; + +import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; + /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders an HQL query without making any changes. * @@ -27,16 +32,34 @@ import java.util.List; * @author Christoph Strobl * @since 3.1 */ -@SuppressWarnings({ "ConstantConditions", "DuplicatedCode" }) -class HqlQueryRenderer extends HqlBaseVisitor> { +@SuppressWarnings({ "ConstantConditions", "DuplicatedCode", "UnreachableCode" }) +class HqlQueryRenderer extends HqlBaseVisitor { + + /** + * Is this select clause a {@literal subquery}? + * + * @return boolean + */ + static boolean isSubquery(ParserRuleContext ctx) { + + if (ctx instanceof HqlParser.SubqueryContext || ctx instanceof HqlParser.CteContext) { + return true; + } else if (ctx instanceof HqlParser.SelectStatementContext) { + return false; + } else if (ctx instanceof HqlParser.InsertStatementContext) { + return false; + } else { + return isSubquery(ctx.getParent()); + } + } @Override - public List visitStart(HqlParser.StartContext ctx) { + public QueryRendererBuilder visitStart(HqlParser.StartContext ctx) { return visit(ctx.ql_statement()); } @Override - public List visitQl_statement(HqlParser.Ql_statementContext ctx) { + public QueryRendererBuilder visitQl_statement(HqlParser.Ql_statementContext ctx) { if (ctx.selectStatement() != null) { return visit(ctx.selectStatement()); @@ -47,311 +70,271 @@ class HqlQueryRenderer extends HqlBaseVisitor> { } else if (ctx.insertStatement() != null) { return visit(ctx.insertStatement()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitSelectStatement(HqlParser.SelectStatementContext ctx) { + public QueryRendererBuilder visitSelectStatement(HqlParser.SelectStatementContext ctx) { return visit(ctx.queryExpression()); } @Override - public List visitQueryExpression(HqlParser.QueryExpressionContext ctx) { + public QueryRendererBuilder visitQueryExpression(HqlParser.QueryExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.withClause() != null) { - tokens.addAll(visit(ctx.withClause())); + builder.appendExpression(visit(ctx.withClause())); } - tokens.addAll(visit(ctx.orderedQuery(0))); + builder.append(visit(ctx.orderedQuery(0))); for (int i = 1; i < ctx.orderedQuery().size(); i++) { - tokens.addAll(visit(ctx.setOperator(i - 1))); - tokens.addAll(visit(ctx.orderedQuery(i))); + builder.append(visit(ctx.setOperator(i - 1))); + builder.append(visit(ctx.orderedQuery(i))); } - return tokens; + return builder; } @Override - public List visitWithClause(HqlParser.WithClauseContext ctx) { + public QueryRendererBuilder visitWithClause(HqlParser.WithClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRendererBuilder.from(TOKEN_WITH); + builder.append(QueryRendererBuilder.concatExpressions(ctx.cte(), this::visit, TOKEN_COMMA)); - tokens.add(TOKEN_WITH); - - ctx.cte().forEach(cteContext -> { - - tokens.addAll(visit(cteContext)); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - return tokens; + return builder; } @Override - public List visitCte(HqlParser.CteContext ctx) { + public QueryRendererBuilder visitCte(HqlParser.CteContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.identifier())); - tokens.add(TOKEN_AS); - NOSPACE(tokens); + builder.appendExpression(visit(ctx.identifier())); + builder.append(TOKEN_AS); if (ctx.NOT() != null) { - tokens.add(TOKEN_NOT); - } - if (ctx.MATERIALIZED() != null) { - tokens.add(TOKEN_MATERIALIZED); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.queryExpression())); - tokens.add(TOKEN_CLOSE_PAREN); + if (ctx.MATERIALIZED() != null) { + builder.append(TOKEN_MATERIALIZED); + } + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.queryExpression())); + builder.append(TOKEN_CLOSE_PAREN); if (ctx.searchClause() != null) { - tokens.addAll(visit(ctx.searchClause())); - } - if (ctx.cycleClause() != null) { - tokens.addAll(visit(ctx.cycleClause())); + builder.appendExpression(visit(ctx.searchClause())); } - return tokens; + if (ctx.cycleClause() != null) { + builder.appendExpression(visit(ctx.cycleClause())); + } + + return builder; } @Override - public List visitSearchClause(HqlParser.SearchClauseContext ctx) { + public QueryRendererBuilder visitSearchClause(HqlParser.SearchClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.SEARCH().getText())); + builder.append(JpaQueryParsingToken.expression(ctx.SEARCH())); if (ctx.BREADTH() != null) { - tokens.add(new JpaQueryParsingToken(ctx.BREADTH().getText())); + builder.append(JpaQueryParsingToken.expression(ctx.BREADTH())); } else if (ctx.DEPTH() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DEPTH().getText())); + builder.append(JpaQueryParsingToken.expression(ctx.DEPTH())); } - tokens.add(new JpaQueryParsingToken(ctx.FIRST().getText())); - tokens.add(new JpaQueryParsingToken(ctx.BY().getText())); - tokens.addAll(visit(ctx.searchSpecifications())); - tokens.add(new JpaQueryParsingToken(ctx.SET().getText())); - tokens.addAll(visit(ctx.identifier())); + builder.append(JpaQueryParsingToken.expression(ctx.FIRST())); + builder.append(JpaQueryParsingToken.expression(ctx.BY())); + builder.append(visit(ctx.searchSpecifications())); + builder.append(JpaQueryParsingToken.expression(ctx.SET())); + builder.append(visit(ctx.identifier())); - return tokens; + return builder; } @Override - public List visitSearchSpecifications(HqlParser.SearchSpecificationsContext ctx) { - - List tokens = new ArrayList<>(); - - ctx.searchSpecification().forEach(searchSpecificationContext -> { - - tokens.addAll(visit(searchSpecificationContext)); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - return tokens; + public QueryRendererBuilder visitSearchSpecifications(HqlParser.SearchSpecificationsContext ctx) { + return QueryRendererBuilder.concat(ctx.searchSpecification(), this::visit, TOKEN_COMMA); } @Override - public List visitSearchSpecification(HqlParser.SearchSpecificationContext ctx) { + public QueryRendererBuilder visitSearchSpecification(HqlParser.SearchSpecificationContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.identifier())); + builder.append(visit(ctx.identifier())); if (ctx.sortDirection() != null) { - tokens.addAll(visit(ctx.sortDirection())); + builder.append(visit(ctx.sortDirection())); } if (ctx.nullsPrecedence() != null) { - tokens.addAll(visit(ctx.nullsPrecedence())); + builder.append(visit(ctx.nullsPrecedence())); } - return tokens; + return builder; } @Override - public List visitCycleClause(HqlParser.CycleClauseContext ctx) { + public QueryRendererBuilder visitCycleClause(HqlParser.CycleClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CYCLE().getText())); - tokens.addAll(visit(ctx.cteAttributes())); - tokens.add(new JpaQueryParsingToken(ctx.SET().getText())); - tokens.addAll(visit(ctx.identifier(0))); + builder.append(JpaQueryParsingToken.expression(ctx.CYCLE().getText())); + builder.append(visit(ctx.cteAttributes())); + builder.append(JpaQueryParsingToken.expression(ctx.SET().getText())); + builder.append(visit(ctx.identifier(0))); if (ctx.TO() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TO().getText())); - tokens.addAll(visit(ctx.literal(0))); - tokens.add(new JpaQueryParsingToken(ctx.DEFAULT().getText())); - tokens.addAll(visit(ctx.literal(1))); + builder.append(JpaQueryParsingToken.expression(ctx.TO().getText())); + builder.append(visit(ctx.literal(0))); + builder.append(JpaQueryParsingToken.expression(ctx.DEFAULT().getText())); + builder.append(visit(ctx.literal(1))); } if (ctx.USING() != null) { - tokens.add(new JpaQueryParsingToken(ctx.USING().getText())); - tokens.addAll(visit(ctx.identifier(1))); + builder.append(JpaQueryParsingToken.expression(ctx.USING().getText())); + builder.append(visit(ctx.identifier(1))); } - return tokens; + return builder; } @Override - public List visitCteAttributes(HqlParser.CteAttributesContext ctx) { - - List tokens = new ArrayList<>(); - - ctx.identifier().forEach(identifierContext -> { - - tokens.addAll(visit(identifierContext)); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - return tokens; + public QueryRendererBuilder visitCteAttributes(HqlParser.CteAttributesContext ctx) { + return QueryRendererBuilder.concat(ctx.identifier(), this::visit, TOKEN_COMMA); } @Override - public List visitOrderedQuery(HqlParser.OrderedQueryContext ctx) { + public QueryRendererBuilder visitOrderedQuery(HqlParser.OrderedQueryContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.query() != null) { - tokens.addAll(visit(ctx.query())); + builder.append(visit(ctx.query())); } else if (ctx.queryExpression() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.queryExpression())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.queryExpression())); + builder.append(TOKEN_CLOSE_PAREN); } if (ctx.queryOrder() != null) { - tokens.addAll(visit(ctx.queryOrder())); + builder.append(visit(ctx.queryOrder())); } - return tokens; + return builder; } @Override - public List visitSelectQuery(HqlParser.SelectQueryContext ctx) { + public QueryRendererBuilder visitSelectQuery(HqlParser.SelectQueryContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.selectClause() != null) { - tokens.addAll(visit(ctx.selectClause())); + builder.appendExpression(visit(ctx.selectClause())); } if (ctx.fromClause() != null) { - tokens.addAll(visit(ctx.fromClause())); + builder.appendExpression(visit(ctx.fromClause())); } if (ctx.whereClause() != null) { - tokens.addAll(visit(ctx.whereClause())); + builder.appendExpression(visit(ctx.whereClause())); } if (ctx.groupByClause() != null) { - tokens.addAll(visit(ctx.groupByClause())); + builder.appendExpression(visit(ctx.groupByClause())); } if (ctx.havingClause() != null) { - tokens.addAll(visit(ctx.havingClause())); + builder.appendExpression(visit(ctx.havingClause())); } - return tokens; + return builder; } @Override - public List visitFromQuery(HqlParser.FromQueryContext ctx) { + public QueryRendererBuilder visitFromQuery(HqlParser.FromQueryContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.fromClause())); + builder.append(visit(ctx.fromClause())); if (ctx.whereClause() != null) { - tokens.addAll(visit(ctx.whereClause())); + builder.append(visit(ctx.whereClause())); } if (ctx.groupByClause() != null) { - tokens.addAll(visit(ctx.groupByClause())); + builder.append(visit(ctx.groupByClause())); } if (ctx.havingClause() != null) { - tokens.addAll(visit(ctx.havingClause())); + builder.append(visit(ctx.havingClause())); } if (ctx.selectClause() != null) { - tokens.addAll(visit(ctx.selectClause())); + builder.append(visit(ctx.selectClause())); } - return tokens; + return builder; } @Override - public List visitQueryOrder(HqlParser.QueryOrderContext ctx) { + public QueryRendererBuilder visitQueryOrder(HqlParser.QueryOrderContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.orderByClause())); + builder.appendExpression(visit(ctx.orderByClause())); if (ctx.limitClause() != null) { - SPACE(tokens); - tokens.addAll(visit(ctx.limitClause())); + builder.appendExpression(visit(ctx.limitClause())); } if (ctx.offsetClause() != null) { - tokens.addAll(visit(ctx.offsetClause())); + builder.appendExpression(visit(ctx.offsetClause())); } if (ctx.fetchClause() != null) { - tokens.addAll(visit(ctx.fetchClause())); + builder.appendExpression(visit(ctx.fetchClause())); } - return tokens; + return builder; } @Override - public List visitFromClause(HqlParser.FromClauseContext ctx) { + public QueryRendererBuilder visitFromClause(HqlParser.FromClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - // TODO: Read up on Framework's LeastRecentlyUsedCache - tokens.add(new JpaQueryParsingToken(ctx.FROM())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.appendExpression(QueryRendererBuilder.concat(ctx.entityWithJoins(), this::visit, TOKEN_COMMA)); - ctx.entityWithJoins().forEach(entityWithJoinsContext -> { - tokens.addAll(visit(entityWithJoinsContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); - - return tokens; + return builder; } @Override - public List visitEntityWithJoins(HqlParser.EntityWithJoinsContext ctx) { + public QueryRendererBuilder visitEntityWithJoins(HqlParser.EntityWithJoinsContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.fromRoot())); - SPACE(tokens); + builder.appendExpression(visit(ctx.fromRoot())); + builder.appendInline(QueryRendererBuilder.concatExpressions(ctx.joinSpecifier(), this::visit, TOKEN_NONE)); - ctx.joinSpecifier().forEach(joinSpecifierContext -> { - tokens.addAll(visit(joinSpecifierContext)); - }); - - return tokens; + return builder; } @Override - public List visitJoinSpecifier(HqlParser.JoinSpecifierContext ctx) { + public QueryRendererBuilder visitJoinSpecifier(HqlParser.JoinSpecifierContext ctx) { if (ctx.join() != null) { return visit(ctx.join()); @@ -360,474 +343,442 @@ class HqlQueryRenderer extends HqlBaseVisitor> { } else if (ctx.jpaCollectionJoin() != null) { return visit(ctx.jpaCollectionJoin()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitFromRoot(HqlParser.FromRootContext ctx) { + public QueryRendererBuilder visitFromRoot(HqlParser.FromRootContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.entityName() != null) { - tokens.addAll(visit(ctx.entityName())); + builder.appendExpression(visit(ctx.entityName())); if (ctx.variable() != null) { - tokens.addAll(visit(ctx.variable())); + builder.appendExpression(visit(ctx.variable())); } - NOSPACE(tokens); + } else if (ctx.subquery() != null) { if (ctx.LATERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LATERAL())); + builder.append(JpaQueryParsingToken.expression(ctx.LATERAL())); } - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - tokens.add(TOKEN_CLOSE_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.append(TOKEN_OPEN_PAREN); + nested.appendInline(visit(ctx.subquery())); + nested.append(TOKEN_CLOSE_PAREN); + + builder.appendExpression(nested); if (ctx.variable() != null) { - tokens.addAll(visit(ctx.variable())); + builder.appendExpression(visit(ctx.variable())); } } - return tokens; + return builder; } @Override - public List visitJoin(HqlParser.JoinContext ctx) { + public QueryRendererBuilder visitJoin(HqlParser.JoinContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.joinType())); - tokens.add(new JpaQueryParsingToken(ctx.JOIN())); + builder.append(visit(ctx.joinType())); + builder.append(JpaQueryParsingToken.expression(ctx.JOIN())); if (ctx.FETCH() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FETCH())); + builder.append(JpaQueryParsingToken.expression(ctx.FETCH())); } - tokens.addAll(visit(ctx.joinTarget())); + builder.append(visit(ctx.joinTarget())); if (ctx.joinRestriction() != null) { - tokens.addAll(visit(ctx.joinRestriction())); + builder.appendExpression(visit(ctx.joinRestriction())); } - return tokens; + return builder; } @Override - public List visitJoinPath(HqlParser.JoinPathContext ctx) { + public QueryRendererBuilder visitJoinPath(HqlParser.JoinPathContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.path())); + builder.appendExpression(visit(ctx.path())); if (ctx.variable() != null) { - tokens.addAll(visit(ctx.variable())); + builder.appendExpression(visit(ctx.variable())); } - return tokens; + return builder; } @Override - public List visitJoinSubquery(HqlParser.JoinSubqueryContext ctx) { + public QueryRendererBuilder visitJoinSubquery(HqlParser.JoinSubqueryContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.LATERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LATERAL())); + builder.append(JpaQueryParsingToken.expression(ctx.LATERAL())); } - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); if (ctx.variable() != null) { - tokens.addAll(visit(ctx.variable())); + builder.appendExpression(visit(ctx.variable())); } - return tokens; + return builder; } @Override - public List visitUpdateStatement(HqlParser.UpdateStatementContext ctx) { + public QueryRendererBuilder visitUpdateStatement(HqlParser.UpdateStatementContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.UPDATE())); + builder.append(JpaQueryParsingToken.expression(ctx.UPDATE())); if (ctx.VERSIONED() != null) { - tokens.add(new JpaQueryParsingToken(ctx.VERSIONED())); + builder.append(JpaQueryParsingToken.expression(ctx.VERSIONED())); } - tokens.addAll(visit(ctx.targetEntity())); - tokens.addAll(visit(ctx.setClause())); + builder.appendExpression(visit(ctx.targetEntity())); + builder.appendExpression(visit(ctx.setClause())); if (ctx.whereClause() != null) { - tokens.addAll(visit(ctx.whereClause())); + builder.appendExpression(visit(ctx.whereClause())); } - return tokens; + return builder; } @Override - public List visitTargetEntity(HqlParser.TargetEntityContext ctx) { + public QueryRendererBuilder visitTargetEntity(HqlParser.TargetEntityContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.entityName())); + builder.appendExpression(visit(ctx.entityName())); if (ctx.variable() != null) { - tokens.addAll(visit(ctx.variable())); + builder.appendExpression(visit(ctx.variable())); } - return tokens; + return builder; } @Override - public List visitSetClause(HqlParser.SetClauseContext ctx) { + public QueryRendererBuilder visitSetClause(HqlParser.SetClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.SET())); - - ctx.assignment().forEach(assignmentContext -> { - tokens.addAll(visit(assignmentContext)); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - return tokens; + builder.append(JpaQueryParsingToken.expression(ctx.SET())); + return builder.append(QueryRendererBuilder.concat(ctx.assignment(), this::visit, TOKEN_COMMA)); } @Override - public List visitAssignment(HqlParser.AssignmentContext ctx) { + public QueryRendererBuilder visitAssignment(HqlParser.AssignmentContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.simplePath())); - tokens.add(TOKEN_EQUALS); - tokens.addAll(visit(ctx.expressionOrPredicate())); + builder.append(visit(ctx.simplePath())); + builder.append(TOKEN_EQUALS); + builder.append(visit(ctx.expressionOrPredicate())); - return tokens; + return builder; } @Override - public List visitDeleteStatement(HqlParser.DeleteStatementContext ctx) { + public QueryRendererBuilder visitDeleteStatement(HqlParser.DeleteStatementContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.DELETE())); + builder.append(JpaQueryParsingToken.expression(ctx.DELETE())); if (ctx.FROM() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FROM())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); } - tokens.addAll(visit(ctx.targetEntity())); + builder.append(visit(ctx.targetEntity())); if (ctx.whereClause() != null) { - tokens.addAll(visit(ctx.whereClause())); + builder.append(visit(ctx.whereClause())); } - return tokens; + return builder; } @Override - public List visitInsertStatement(HqlParser.InsertStatementContext ctx) { + public QueryRendererBuilder visitInsertStatement(HqlParser.InsertStatementContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.INSERT())); + builder.append(JpaQueryParsingToken.expression(ctx.INSERT())); if (ctx.INTO() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INTO())); + builder.append(JpaQueryParsingToken.expression(ctx.INTO())); } - tokens.addAll(visit(ctx.targetEntity())); - tokens.addAll(visit(ctx.targetFields())); + builder.appendExpression(visit(ctx.targetEntity())); + builder.appendExpression(visit(ctx.targetFields())); if (ctx.queryExpression() != null) { - tokens.addAll(visit(ctx.queryExpression())); + builder.appendExpression(visit(ctx.queryExpression())); } else if (ctx.valuesList() != null) { - tokens.addAll(visit(ctx.valuesList())); + builder.appendExpression(visit(ctx.valuesList())); } - return tokens; + return builder; } @Override - public List visitTargetFields(HqlParser.TargetFieldsContext ctx) { + public QueryRendererBuilder visitTargetFields(HqlParser.TargetFieldsContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.append(QueryRendererBuilder.concat(ctx.simplePath(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); - ctx.simplePath().forEach(simplePathContext -> { - tokens.addAll(visit(simplePathContext)); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - tokens.add(TOKEN_CLOSE_PAREN); - - return tokens; + return builder; } @Override - public List visitValuesList(HqlParser.ValuesListContext ctx) { + public QueryRendererBuilder visitValuesList(HqlParser.ValuesListContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.VALUES())); + builder.append(JpaQueryParsingToken.expression(ctx.VALUES())); + builder.append(QueryRendererBuilder.concat(ctx.values(), this::visit, TOKEN_COMMA)); - ctx.values().forEach(valuesContext -> { - tokens.addAll(visit(valuesContext)); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - return tokens; + return builder; } @Override - public List visitValues(HqlParser.ValuesContext ctx) { + public QueryRendererBuilder visitValues(HqlParser.ValuesContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.append(QueryRendererBuilder.concat(ctx.expression(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); - ctx.expression().forEach(expressionContext -> { - tokens.addAll(visit(expressionContext)); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - tokens.add(TOKEN_CLOSE_PAREN); - - return tokens; + return builder; } @Override - public List visitInstantiation(HqlParser.InstantiationContext ctx) { + public QueryRendererBuilder visitInstantiation(HqlParser.InstantiationContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.NEW())); - tokens.addAll(visit(ctx.instantiationTarget())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.instantiationArguments())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.expression(ctx.NEW())); + builder.append(visit(ctx.instantiationTarget())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.instantiationArguments())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitAlias(HqlParser.AliasContext ctx) { + public QueryRendererBuilder visitAlias(HqlParser.AliasContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } - tokens.addAll(visit(ctx.identifier())); + builder.append(visit(ctx.identifier())); - return tokens; + return builder; } @Override - public List visitGroupedItem(HqlParser.GroupedItemContext ctx) { + public QueryRendererBuilder visitGroupedItem(HqlParser.GroupedItemContext ctx) { if (ctx.identifier() != null) { return visit(ctx.identifier()); } else if (ctx.INTEGER_LITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.INTEGER_LITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.INTEGER_LITERAL())); } else if (ctx.expression() != null) { return visit(ctx.expression()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitSortedItem(HqlParser.SortedItemContext ctx) { + public QueryRendererBuilder visitSortedItem(HqlParser.SortedItemContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.sortExpression())); + builder.appendExpression(visit(ctx.sortExpression())); if (ctx.sortDirection() != null) { - tokens.addAll(visit(ctx.sortDirection())); + builder.append(visit(ctx.sortDirection())); } if (ctx.nullsPrecedence() != null) { - tokens.addAll(visit(ctx.nullsPrecedence())); + builder.appendExpression(visit(ctx.nullsPrecedence())); } - return tokens; + return builder; } @Override - public List visitSortExpression(HqlParser.SortExpressionContext ctx) { + public QueryRendererBuilder visitSortExpression(HqlParser.SortExpressionContext ctx) { if (ctx.identifier() != null) { return visit(ctx.identifier()); } else if (ctx.INTEGER_LITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.INTEGER_LITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.INTEGER_LITERAL())); } else if (ctx.expression() != null) { return visit(ctx.expression()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitSortDirection(HqlParser.SortDirectionContext ctx) { + public QueryRendererBuilder visitSortDirection(HqlParser.SortDirectionContext ctx) { if (ctx.ASC() != null) { - return List.of(new JpaQueryParsingToken(ctx.ASC())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.ASC())); } else if (ctx.DESC() != null) { - return List.of(new JpaQueryParsingToken(ctx.DESC())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.DESC())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitNullsPrecedence(HqlParser.NullsPrecedenceContext ctx) { + public QueryRendererBuilder visitNullsPrecedence(HqlParser.NullsPrecedenceContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.NULLS())); + builder.append(JpaQueryParsingToken.expression(ctx.NULLS())); if (ctx.FIRST() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FIRST())); + builder.append(JpaQueryParsingToken.expression(ctx.FIRST())); } else if (ctx.LAST() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LAST())); + builder.append(JpaQueryParsingToken.expression(ctx.LAST())); } - return tokens; + return builder; } @Override - public List visitLimitClause(HqlParser.LimitClauseContext ctx) { + public QueryRendererBuilder visitLimitClause(HqlParser.LimitClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.LIMIT())); - tokens.addAll(visit(ctx.parameterOrIntegerLiteral())); + builder.append(JpaQueryParsingToken.expression(ctx.LIMIT())); + builder.append(visit(ctx.parameterOrIntegerLiteral())); - return tokens; + return builder; } @Override - public List visitOffsetClause(HqlParser.OffsetClauseContext ctx) { + public QueryRendererBuilder visitOffsetClause(HqlParser.OffsetClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.OFFSET())); - tokens.addAll(visit(ctx.parameterOrIntegerLiteral())); + builder.append(JpaQueryParsingToken.expression(ctx.OFFSET())); + builder.append(visit(ctx.parameterOrIntegerLiteral())); if (ctx.ROW() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ROW())); + builder.append(JpaQueryParsingToken.expression(ctx.ROW())); } else if (ctx.ROWS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ROWS())); + builder.append(JpaQueryParsingToken.expression(ctx.ROWS())); } - return tokens; + return builder; } @Override - public List visitFetchClause(HqlParser.FetchClauseContext ctx) { + public QueryRendererBuilder visitFetchClause(HqlParser.FetchClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.FETCH())); + builder.append(JpaQueryParsingToken.expression(ctx.FETCH())); if (ctx.FIRST() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FIRST())); + builder.append(JpaQueryParsingToken.expression(ctx.FIRST())); } else if (ctx.NEXT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NEXT())); + builder.append(JpaQueryParsingToken.expression(ctx.NEXT())); } if (ctx.parameterOrIntegerLiteral() != null) { - tokens.addAll(visit(ctx.parameterOrIntegerLiteral())); + builder.append(visit(ctx.parameterOrIntegerLiteral())); } else if (ctx.parameterOrNumberLiteral() != null) { - - tokens.addAll(visit(ctx.parameterOrNumberLiteral())); - tokens.add(TOKEN_PERCENT); + builder.append(visit(ctx.parameterOrNumberLiteral())); } if (ctx.ROW() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ROW())); + builder.append(JpaQueryParsingToken.expression(ctx.ROW())); } else if (ctx.ROWS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ROWS())); + builder.append(JpaQueryParsingToken.expression(ctx.ROWS())); } if (ctx.ONLY() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ONLY())); + builder.append(JpaQueryParsingToken.expression(ctx.ONLY())); } else if (ctx.WITH() != null) { - tokens.add(new JpaQueryParsingToken(ctx.WITH())); - tokens.add(new JpaQueryParsingToken(ctx.TIES())); + builder.append(JpaQueryParsingToken.expression(ctx.WITH())); + builder.append(JpaQueryParsingToken.expression(ctx.TIES())); } - return tokens; + return builder; } @Override - public List visitSubquery(HqlParser.SubqueryContext ctx) { + public QueryRendererBuilder visitSubquery(HqlParser.SubqueryContext ctx) { return visit(ctx.queryExpression()); } @Override - public List visitSelectClause(HqlParser.SelectClauseContext ctx) { + public QueryRendererBuilder visitSelectClause(HqlParser.SelectClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.SELECT())); + builder.append(JpaQueryParsingToken.expression(ctx.SELECT())); if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); } - tokens.addAll(visit(ctx.selectionList())); + builder.appendExpression(visit(ctx.selectionList())); - return tokens; + return builder; } @Override - public List visitSelectionList(HqlParser.SelectionListContext ctx) { - - List tokens = new ArrayList<>(); - - ctx.selection().forEach(selectionContext -> { - tokens.addAll(visit(selectionContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); - - return tokens; + public QueryRendererBuilder visitSelectionList(HqlParser.SelectionListContext ctx) { + return QueryRendererBuilder.concat(ctx.selection(), this::visit, TOKEN_COMMA); } @Override - public List visitSelection(HqlParser.SelectionContext ctx) { + public QueryRendererBuilder visitSelection(HqlParser.SelectionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.selectExpression())); + builder.append(visit(ctx.selectExpression())); if (ctx.variable() != null) { - tokens.addAll(visit(ctx.variable())); + builder.appendExpression(visit(ctx.variable())); } - return tokens; + return builder; } @Override - public List visitSelectExpression(HqlParser.SelectExpressionContext ctx) { + public QueryRendererBuilder visitSelectExpression(HqlParser.SelectExpressionContext ctx) { if (ctx.instantiation() != null) { return visit(ctx.instantiation()); @@ -838,209 +789,187 @@ class HqlQueryRenderer extends HqlBaseVisitor> { } else if (ctx.expressionOrPredicate() != null) { return visit(ctx.expressionOrPredicate()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitMapEntrySelection(HqlParser.MapEntrySelectionContext ctx) { + public QueryRendererBuilder visitMapEntrySelection(HqlParser.MapEntrySelectionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.ENTRY())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.path())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.expression(ctx.ENTRY())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitJpaSelectObjectSyntax(HqlParser.JpaSelectObjectSyntaxContext ctx) { + public QueryRendererBuilder visitJpaSelectObjectSyntax(HqlParser.JpaSelectObjectSyntaxContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.OBJECT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.identifier())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.OBJECT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.identifier())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitWhereClause(HqlParser.WhereClauseContext ctx) { + public QueryRendererBuilder visitWhereClause(HqlParser.WhereClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.WHERE())); + builder.append(JpaQueryParsingToken.expression(ctx.WHERE())); + builder.append(QueryRendererBuilder.concatExpressions(ctx.predicate(), this::visit, TOKEN_COMMA)); - ctx.predicate().forEach(predicateContext -> { - tokens.addAll(visit(predicateContext)); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - return tokens; + return builder; } @Override - public List visitJoinType(HqlParser.JoinTypeContext ctx) { + public QueryRendererBuilder visitJoinType(HqlParser.JoinTypeContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.INNER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INNER())); + builder.append(JpaQueryParsingToken.expression(ctx.INNER())); } if (ctx.LEFT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LEFT())); + builder.append(JpaQueryParsingToken.expression(ctx.LEFT())); } if (ctx.RIGHT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.RIGHT())); + builder.append(JpaQueryParsingToken.expression(ctx.RIGHT())); } if (ctx.FULL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FULL())); + builder.append(JpaQueryParsingToken.expression(ctx.FULL())); } if (ctx.OUTER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.OUTER())); + builder.append(JpaQueryParsingToken.expression(ctx.OUTER())); } if (ctx.CROSS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CROSS())); + builder.append(JpaQueryParsingToken.expression(ctx.CROSS())); } - return tokens; + return builder; } @Override - public List visitCrossJoin(HqlParser.CrossJoinContext ctx) { + public QueryRendererBuilder visitCrossJoin(HqlParser.CrossJoinContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CROSS())); - tokens.add(new JpaQueryParsingToken(ctx.JOIN())); - tokens.addAll(visit(ctx.entityName())); + builder.append(JpaQueryParsingToken.expression(ctx.CROSS())); + builder.append(JpaQueryParsingToken.expression(ctx.JOIN())); + builder.appendExpression(visit(ctx.entityName())); if (ctx.variable() != null) { - tokens.addAll(visit(ctx.variable())); + builder.appendExpression(visit(ctx.variable())); } - return tokens; + return builder; } @Override - public List visitJoinRestriction(HqlParser.JoinRestrictionContext ctx) { + public QueryRendererBuilder visitJoinRestriction(HqlParser.JoinRestrictionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.ON() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ON())); + builder.append(JpaQueryParsingToken.expression(ctx.ON())); } else if (ctx.WITH() != null) { - tokens.add(new JpaQueryParsingToken(ctx.WITH())); + builder.append(JpaQueryParsingToken.expression(ctx.WITH())); } - tokens.addAll(visit(ctx.predicate())); + builder.appendExpression(visit(ctx.predicate())); - return tokens; + return builder; } @Override - public List visitJpaCollectionJoin(HqlParser.JpaCollectionJoinContext ctx) { + public QueryRendererBuilder visitJpaCollectionJoin(HqlParser.JpaCollectionJoinContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_COMMA); - tokens.add(new JpaQueryParsingToken(ctx.IN(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.path())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_COMMA); + builder.append(JpaQueryParsingToken.token(ctx.IN())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.path())); + builder.append(TOKEN_CLOSE_PAREN); if (ctx.variable() != null) { - tokens.addAll(visit(ctx.variable())); + builder.appendExpression(visit(ctx.variable())); } - return tokens; + return builder; } @Override - public List visitGroupByClause(HqlParser.GroupByClauseContext ctx) { + public QueryRendererBuilder visitGroupByClause(HqlParser.GroupByClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.GROUP())); - tokens.add(new JpaQueryParsingToken(ctx.BY())); + builder.append(JpaQueryParsingToken.expression(ctx.GROUP())); + builder.append(JpaQueryParsingToken.expression(ctx.BY())); + builder.append(QueryRendererBuilder.concat(ctx.groupedItem(), this::visit, TOKEN_COMMA)); - ctx.groupedItem().forEach(groupedItemContext -> { - tokens.addAll(visit(groupedItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); - - return tokens; + return builder; } @Override - public List visitOrderByClause(HqlParser.OrderByClauseContext ctx) { + public QueryRendererBuilder visitOrderByClause(HqlParser.OrderByClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.ORDER())); - tokens.add(new JpaQueryParsingToken(ctx.BY())); + builder.append(JpaQueryParsingToken.expression(ctx.ORDER())); + builder.append(JpaQueryParsingToken.expression(ctx.BY())); - ctx.sortedItem().forEach(sortedItemContext -> { - tokens.addAll(visit(sortedItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); + QueryRendererBuilder concat = QueryRendererBuilder.concat(ctx.sortedItem(), this::visit, TOKEN_COMMA); - return tokens; + builder.appendExpression(concat); + + return builder; } @Override - public List visitHavingClause(HqlParser.HavingClauseContext ctx) { + public QueryRendererBuilder visitHavingClause(HqlParser.HavingClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.HAVING())); + builder.append(JpaQueryParsingToken.expression(ctx.HAVING())); + builder.appendExpression(QueryRendererBuilder.concat(ctx.predicate(), this::visit, TOKEN_COMMA)); - ctx.predicate().forEach(predicateContext -> { - tokens.addAll(visit(predicateContext)); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - return tokens; + return builder; } @Override - public List visitSetOperator(HqlParser.SetOperatorContext ctx) { + public QueryRendererBuilder visitSetOperator(HqlParser.SetOperatorContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.UNION() != null) { - tokens.add(new JpaQueryParsingToken(ctx.UNION())); + builder.append(JpaQueryParsingToken.expression(ctx.UNION())); } else if (ctx.INTERSECT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INTERSECT())); + builder.append(JpaQueryParsingToken.expression(ctx.INTERSECT())); } else if (ctx.EXCEPT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.EXCEPT())); + builder.append(JpaQueryParsingToken.expression(ctx.EXCEPT())); } if (ctx.ALL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ALL())); + builder.append(JpaQueryParsingToken.expression(ctx.ALL())); } - return tokens; + return builder; } @Override - public List visitLiteral(HqlParser.LiteralContext ctx) { + public QueryRendererBuilder visitLiteral(HqlParser.LiteralContext ctx) { if (ctx.NULL() != null) { - return List.of(new JpaQueryParsingToken(ctx.NULL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.NULL())); } else if (ctx.booleanLiteral() != null) { return visit(ctx.booleanLiteral()); } else if (ctx.stringLiteral() != null) { @@ -1052,1516 +981,1460 @@ class HqlQueryRenderer extends HqlBaseVisitor> { } else if (ctx.binaryLiteral() != null) { return visit(ctx.binaryLiteral()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitBooleanLiteral(HqlParser.BooleanLiteralContext ctx) { + public QueryRendererBuilder visitBooleanLiteral(HqlParser.BooleanLiteralContext ctx) { if (ctx.TRUE() != null) { - return List.of(new JpaQueryParsingToken(ctx.TRUE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.TRUE())); } else if (ctx.FALSE() != null) { - return List.of(new JpaQueryParsingToken(ctx.FALSE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.FALSE())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitStringLiteral(HqlParser.StringLiteralContext ctx) { + public QueryRendererBuilder visitStringLiteral(HqlParser.StringLiteralContext ctx) { if (ctx.STRINGLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.STRINGLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.STRINGLITERAL())); } else if (ctx.CHARACTER() != null) { - return List.of(new JpaQueryParsingToken(ctx.CHARACTER())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.CHARACTER())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitNumericLiteral(HqlParser.NumericLiteralContext ctx) { + public QueryRendererBuilder visitNumericLiteral(HqlParser.NumericLiteralContext ctx) { if (ctx.INTEGER_LITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.INTEGER_LITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.INTEGER_LITERAL())); } else if (ctx.FLOAT_LITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.FLOAT_LITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.FLOAT_LITERAL())); } else if (ctx.HEXLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.HEXLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.HEXLITERAL())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitDateTimeLiteral(HqlParser.DateTimeLiteralContext ctx) { + public QueryRendererBuilder visitDateTimeLiteral(HqlParser.DateTimeLiteralContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.LOCAL_DATE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LOCAL_DATE())); + builder.append(JpaQueryParsingToken.expression(ctx.LOCAL_DATE())); } else if (ctx.LOCAL_TIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LOCAL_TIME())); + builder.append(JpaQueryParsingToken.expression(ctx.LOCAL_TIME())); } else if (ctx.LOCAL_DATETIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LOCAL_DATETIME())); + builder.append(JpaQueryParsingToken.expression(ctx.LOCAL_DATETIME())); } else if (ctx.CURRENT_DATE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CURRENT_DATE())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT_DATE())); } else if (ctx.CURRENT_TIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CURRENT_TIME())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT_TIME())); } else if (ctx.CURRENT_TIMESTAMP() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CURRENT_TIMESTAMP())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT_TIMESTAMP())); } else if (ctx.OFFSET_DATETIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.OFFSET_DATETIME())); + builder.append(JpaQueryParsingToken.expression(ctx.OFFSET_DATETIME())); } else { if (ctx.LOCAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LOCAL())); + builder.append(JpaQueryParsingToken.expression(ctx.LOCAL())); } else if (ctx.CURRENT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CURRENT())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT())); } else if (ctx.OFFSET() != null) { - tokens.add(new JpaQueryParsingToken(ctx.OFFSET())); + builder.append(JpaQueryParsingToken.expression(ctx.OFFSET())); } if (ctx.DATE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DATE())); + builder.append(JpaQueryParsingToken.expression(ctx.DATE())); } else if (ctx.TIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TIME())); + builder.append(JpaQueryParsingToken.expression(ctx.TIME())); } else if (ctx.DATETIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DATETIME())); + builder.append(JpaQueryParsingToken.expression(ctx.DATETIME())); } if (ctx.INSTANT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INSTANT())); + builder.append(JpaQueryParsingToken.expression(ctx.INSTANT())); } } - return tokens; + return builder; } @Override - public List visitDatetimeField(HqlParser.DatetimeFieldContext ctx) { + public QueryRendererBuilder visitDatetimeField(HqlParser.DatetimeFieldContext ctx) { if (ctx.YEAR() != null) { - return List.of(new JpaQueryParsingToken(ctx.YEAR())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.YEAR())); } else if (ctx.MONTH() != null) { - return List.of(new JpaQueryParsingToken(ctx.MONTH())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.MONTH())); } else if (ctx.DAY() != null) { - return List.of(new JpaQueryParsingToken(ctx.DAY())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.DAY())); } else if (ctx.WEEK() != null) { - return List.of(new JpaQueryParsingToken(ctx.WEEK())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.WEEK())); } else if (ctx.QUARTER() != null) { - return List.of(new JpaQueryParsingToken(ctx.QUARTER())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.QUARTER())); } else if (ctx.HOUR() != null) { - return List.of(new JpaQueryParsingToken(ctx.HOUR())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.HOUR())); } else if (ctx.MINUTE() != null) { - return List.of(new JpaQueryParsingToken(ctx.MINUTE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.MINUTE())); } else if (ctx.SECOND() != null) { - return List.of(new JpaQueryParsingToken(ctx.SECOND())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.SECOND())); } else if (ctx.NANOSECOND() != null) { - return List.of(new JpaQueryParsingToken(ctx.NANOSECOND())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.NANOSECOND())); } else if (ctx.EPOCH() != null) { - return List.of(new JpaQueryParsingToken(ctx.EPOCH())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.EPOCH())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitBinaryLiteral(HqlParser.BinaryLiteralContext ctx) { + public QueryRendererBuilder visitBinaryLiteral(HqlParser.BinaryLiteralContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.BINARY_LITERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.BINARY_LITERAL())); + builder.append(JpaQueryParsingToken.expression(ctx.BINARY_LITERAL())); } else if (ctx.HEXLITERAL() != null) { - tokens.add(TOKEN_OPEN_BRACE); - ctx.HEXLITERAL().forEach(terminalNode -> { - tokens.add(new JpaQueryParsingToken(terminalNode)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - tokens.add(TOKEN_CLOSE_BRACE); + builder.append(TOKEN_OPEN_BRACE); + + builder.append(QueryRendererBuilder.concat(ctx.HEXLITERAL(), it -> { + return QueryRendererBuilder.from(JpaQueryParsingToken.token(it)); + }, TOKEN_COMMA)); + + builder.append(TOKEN_CLOSE_BRACE); } - return tokens; + return builder; } @Override - public List visitPlainPrimaryExpression(HqlParser.PlainPrimaryExpressionContext ctx) { + public QueryRendererBuilder visitPlainPrimaryExpression(HqlParser.PlainPrimaryExpressionContext ctx) { return visit(ctx.primaryExpression()); } @Override - public List visitTupleExpression(HqlParser.TupleExpressionContext ctx) { + public QueryRendererBuilder visitTupleExpression(HqlParser.TupleExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.append(QueryRendererBuilder.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); - ctx.expressionOrPredicate().forEach(expressionOrPredicateContext -> { - tokens.addAll(visit(expressionOrPredicateContext)); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - tokens.add(TOKEN_CLOSE_PAREN); - - return tokens; + return builder; } @Override - public List visitHqlConcatenationExpression(HqlParser.HqlConcatenationExpressionContext ctx) { + public QueryRendererBuilder visitHqlConcatenationExpression(HqlParser.HqlConcatenationExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression(0))); - tokens.add(TOKEN_DOUBLE_PIPE); - tokens.addAll(visit(ctx.expression(1))); + builder.appendInline(visit(ctx.expression(0))); + builder.append(TOKEN_DOUBLE_PIPE); + builder.append(visit(ctx.expression(1))); - return tokens; + return builder; } @Override - public List visitDayOfWeekExpression(HqlParser.DayOfWeekExpressionContext ctx) { + public QueryRendererBuilder visitDayOfWeekExpression(HqlParser.DayOfWeekExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.DAY())); - tokens.add(new JpaQueryParsingToken(ctx.OF())); - tokens.add(new JpaQueryParsingToken(ctx.WEEK())); + builder.append(JpaQueryParsingToken.expression(ctx.DAY())); + builder.append(JpaQueryParsingToken.expression(ctx.OF())); + builder.append(JpaQueryParsingToken.expression(ctx.WEEK())); - return tokens; + return builder; } @Override - public List visitDayOfMonthExpression(HqlParser.DayOfMonthExpressionContext ctx) { + public QueryRendererBuilder visitDayOfMonthExpression(HqlParser.DayOfMonthExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.DAY())); - tokens.add(new JpaQueryParsingToken(ctx.OF())); - tokens.add(new JpaQueryParsingToken(ctx.MONTH())); + builder.append(JpaQueryParsingToken.expression(ctx.DAY())); + builder.append(JpaQueryParsingToken.expression(ctx.OF())); + builder.append(JpaQueryParsingToken.expression(ctx.MONTH())); - return tokens; + return builder; } @Override - public List visitWeekOfYearExpression(HqlParser.WeekOfYearExpressionContext ctx) { + public QueryRendererBuilder visitWeekOfYearExpression(HqlParser.WeekOfYearExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.WEEK())); - tokens.add(new JpaQueryParsingToken(ctx.OF())); - tokens.add(new JpaQueryParsingToken(ctx.YEAR())); + builder.append(JpaQueryParsingToken.expression(ctx.WEEK())); + builder.append(JpaQueryParsingToken.expression(ctx.OF())); + builder.append(JpaQueryParsingToken.expression(ctx.YEAR())); - return tokens; + return builder; } @Override - public List visitGroupedExpression(HqlParser.GroupedExpressionContext ctx) { + public QueryRendererBuilder visitGroupedExpression(HqlParser.GroupedExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.expression())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitAdditionExpression(HqlParser.AdditionExpressionContext ctx) { + public QueryRendererBuilder visitAdditionExpression(HqlParser.AdditionExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.op)); - tokens.addAll(visit(ctx.expression(1))); + builder.appendInline(visit(ctx.expression(0))); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); + builder.appendInline(visit(ctx.expression(1))); - return tokens; + return builder; } @Override - public List visitSignedNumericLiteral(HqlParser.SignedNumericLiteralContext ctx) { + public QueryRendererBuilder visitSignedNumericLiteral(HqlParser.SignedNumericLiteralContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.op, false)); - tokens.addAll(visit(ctx.numericLiteral())); + builder.append(JpaQueryParsingToken.token(ctx.op)); + builder.append(visit(ctx.numericLiteral())); - return tokens; + return builder; } @Override - public List visitMultiplicationExpression(HqlParser.MultiplicationExpressionContext ctx) { + public QueryRendererBuilder visitMultiplicationExpression(HqlParser.MultiplicationExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression(0))); - NOSPACE(tokens); - tokens.add(new JpaQueryParsingToken(ctx.op, false)); - tokens.addAll(visit(ctx.expression(1))); + builder.appendExpression(visit(ctx.expression(0))); + builder.append(JpaQueryParsingToken.expression(ctx.op)); + builder.appendExpression(visit(ctx.expression(1))); - return tokens; + return builder; } @Override - public List visitSubqueryExpression(HqlParser.SubqueryExpressionContext ctx) { + public QueryRendererBuilder visitSubqueryExpression(HqlParser.SubqueryExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitSignedExpression(HqlParser.SignedExpressionContext ctx) { + public QueryRendererBuilder visitSignedExpression(HqlParser.SignedExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.op, false)); - tokens.addAll(visit(ctx.expression())); + builder.append(JpaQueryParsingToken.token(ctx.op)); + builder.appendInline(visit(ctx.expression())); - return tokens; + return builder; } @Override - public List visitToDurationExpression(HqlParser.ToDurationExpressionContext ctx) { + public QueryRendererBuilder visitToDurationExpression(HqlParser.ToDurationExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression())); - tokens.addAll(visit(ctx.datetimeField())); + builder.append(visit(ctx.expression())); + builder.append(visit(ctx.datetimeField())); - return tokens; + return builder; } @Override - public List visitFromDurationExpression(HqlParser.FromDurationExpressionContext ctx) { + public QueryRendererBuilder visitFromDurationExpression(HqlParser.FromDurationExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression())); - tokens.add(new JpaQueryParsingToken(ctx.BY())); - tokens.addAll(visit(ctx.datetimeField())); + builder.append(visit(ctx.expression())); + builder.append(JpaQueryParsingToken.expression(ctx.BY())); + builder.append(visit(ctx.datetimeField())); - return tokens; + return builder; } @Override - public List visitCaseExpression(HqlParser.CaseExpressionContext ctx) { + public QueryRendererBuilder visitCaseExpression(HqlParser.CaseExpressionContext ctx) { return visit(ctx.caseList()); } @Override - public List visitLiteralExpression(HqlParser.LiteralExpressionContext ctx) { + public QueryRendererBuilder visitLiteralExpression(HqlParser.LiteralExpressionContext ctx) { return visit(ctx.literal()); } @Override - public List visitParameterExpression(HqlParser.ParameterExpressionContext ctx) { + public QueryRendererBuilder visitParameterExpression(HqlParser.ParameterExpressionContext ctx) { return visit(ctx.parameter()); } @Override - public List visitFunctionExpression(HqlParser.FunctionExpressionContext ctx) { + public QueryRendererBuilder visitFunctionExpression(HqlParser.FunctionExpressionContext ctx) { return visit(ctx.function()); } @Override - public List visitGeneralPathExpression(HqlParser.GeneralPathExpressionContext ctx) { + public QueryRendererBuilder visitGeneralPathExpression(HqlParser.GeneralPathExpressionContext ctx) { return visit(ctx.generalPathFragment()); } @Override - public List visitIdentificationVariable(HqlParser.IdentificationVariableContext ctx) { + public QueryRendererBuilder visitIdentificationVariable(HqlParser.IdentificationVariableContext ctx) { if (ctx.identifier() != null) { return visit(ctx.identifier()); } else if (ctx.simplePath() != null) { return visit(ctx.simplePath()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitPath(HqlParser.PathContext ctx) { + public QueryRendererBuilder visitPath(HqlParser.PathContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.treatedPath() != null) { - tokens.addAll(visit(ctx.treatedPath())); + builder.append(visit(ctx.treatedPath())); if (ctx.pathContinutation() != null) { - NOSPACE(tokens); - tokens.addAll(visit(ctx.pathContinutation())); + builder.append(visit(ctx.pathContinutation())); } } else if (ctx.generalPathFragment() != null) { - tokens.addAll(visit(ctx.generalPathFragment())); + builder.append(visit(ctx.generalPathFragment())); } - return tokens; + return builder; } @Override - public List visitGeneralPathFragment(HqlParser.GeneralPathFragmentContext ctx) { + public QueryRendererBuilder visitGeneralPathFragment(HqlParser.GeneralPathFragmentContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.simplePath())); + builder.append(visit(ctx.simplePath())); if (ctx.indexedPathAccessFragment() != null) { - tokens.addAll(visit(ctx.indexedPathAccessFragment())); + builder.append(visit(ctx.indexedPathAccessFragment())); } - return tokens; + return builder; } @Override - public List visitIndexedPathAccessFragment(HqlParser.IndexedPathAccessFragmentContext ctx) { + public QueryRendererBuilder visitIndexedPathAccessFragment(HqlParser.IndexedPathAccessFragmentContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_OPEN_SQUARE_BRACKET); - tokens.addAll(visit(ctx.expression())); - tokens.add(TOKEN_CLOSE_SQUARE_BRACKET); + builder.append(TOKEN_OPEN_SQUARE_BRACKET); + builder.appendInline(visit(ctx.expression())); + builder.append(TOKEN_CLOSE_SQUARE_BRACKET); if (ctx.generalPathFragment() != null) { - tokens.add(TOKEN_DOT); - tokens.addAll(visit(ctx.generalPathFragment())); + builder.append(TOKEN_DOT); + builder.append(visit(ctx.generalPathFragment())); } - return tokens; + return builder; } @Override - public List visitSimplePath(HqlParser.SimplePathContext ctx) { + public QueryRendererBuilder visitSimplePath(HqlParser.SimplePathContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.identifier())); - NOSPACE(tokens); + builder.append(visit(ctx.identifier())); - ctx.simplePathElement().forEach(simplePathElementContext -> { - tokens.addAll(visit(simplePathElementContext)); - NOSPACE(tokens); - }); - SPACE(tokens); + if (!ctx.simplePathElement().isEmpty()) { + builder.append(TOKEN_DOT); + } - return tokens; + builder.append(QueryRendererBuilder.concat(ctx.simplePathElement(), this::visit, TOKEN_DOT)); + + return builder; } @Override - public List visitSimplePathElement(HqlParser.SimplePathElementContext ctx) { + public QueryRendererBuilder visitSimplePathElement(HqlParser.SimplePathElementContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_DOT); - tokens.addAll(visit(ctx.identifier())); + builder.append(visit(ctx.identifier())); - return tokens; + return builder; } @Override - public List visitCaseList(HqlParser.CaseListContext ctx) { + public QueryRendererBuilder visitCaseList(HqlParser.CaseListContext ctx) { if (ctx.simpleCaseExpression() != null) { return visit(ctx.simpleCaseExpression()); } else if (ctx.searchedCaseExpression() != null) { return visit(ctx.searchedCaseExpression()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitSimpleCaseExpression(HqlParser.SimpleCaseExpressionContext ctx) { + public QueryRendererBuilder visitSimpleCaseExpression(HqlParser.SimpleCaseExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CASE())); - tokens.addAll(visit(ctx.expressionOrPredicate(0))); + builder.append(JpaQueryParsingToken.expression(ctx.CASE())); + builder.append(visit(ctx.expressionOrPredicate(0))); ctx.caseWhenExpressionClause().forEach(caseWhenExpressionClauseContext -> { - tokens.addAll(visit(caseWhenExpressionClauseContext)); + builder.append(visit(caseWhenExpressionClauseContext)); }); if (ctx.ELSE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ELSE())); - tokens.addAll(visit(ctx.expressionOrPredicate(1))); + builder.append(JpaQueryParsingToken.expression(ctx.ELSE())); + builder.append(visit(ctx.expressionOrPredicate(1))); } - tokens.add(new JpaQueryParsingToken(ctx.END())); + builder.append(JpaQueryParsingToken.expression(ctx.END())); - return tokens; + return builder; } @Override - public List visitSearchedCaseExpression(HqlParser.SearchedCaseExpressionContext ctx) { + public QueryRendererBuilder visitSearchedCaseExpression(HqlParser.SearchedCaseExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CASE())); + builder.append(JpaQueryParsingToken.expression(ctx.CASE())); - ctx.caseWhenPredicateClause().forEach(caseWhenPredicateClauseContext -> { - tokens.addAll(visit(caseWhenPredicateClauseContext)); - }); + builder.append(QueryRendererBuilder.concatExpressions(ctx.caseWhenPredicateClause(), this::visit, TOKEN_NONE)); if (ctx.ELSE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ELSE())); - tokens.addAll(visit(ctx.expressionOrPredicate())); + builder.append(JpaQueryParsingToken.expression(ctx.ELSE())); + builder.appendExpression(visit(ctx.expressionOrPredicate())); } - tokens.add(new JpaQueryParsingToken(ctx.END())); + builder.append(JpaQueryParsingToken.expression(ctx.END())); - return tokens; + return builder; } @Override - public List visitCaseWhenExpressionClause(HqlParser.CaseWhenExpressionClauseContext ctx) { + public QueryRendererBuilder visitCaseWhenExpressionClause(HqlParser.CaseWhenExpressionClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.WHEN())); - tokens.addAll(visit(ctx.expression())); - tokens.add(new JpaQueryParsingToken(ctx.THEN())); - tokens.addAll(visit(ctx.expressionOrPredicate())); + builder.append(JpaQueryParsingToken.expression(ctx.WHEN())); + builder.appendExpression(visit(ctx.expression())); + builder.append(JpaQueryParsingToken.expression(ctx.THEN())); + builder.appendExpression(visit(ctx.expressionOrPredicate())); - return tokens; + return builder; } @Override - public List visitCaseWhenPredicateClause(HqlParser.CaseWhenPredicateClauseContext ctx) { + public QueryRendererBuilder visitCaseWhenPredicateClause(HqlParser.CaseWhenPredicateClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.WHEN())); - tokens.addAll(visit(ctx.predicate())); - tokens.add(new JpaQueryParsingToken(ctx.THEN())); - tokens.addAll(visit(ctx.expressionOrPredicate())); + builder.append(JpaQueryParsingToken.expression(ctx.WHEN())); + builder.appendExpression(visit(ctx.predicate())); + builder.append(JpaQueryParsingToken.expression(ctx.THEN())); + builder.appendExpression(visit(ctx.expressionOrPredicate())); - return tokens; + return builder; } @Override - public List visitGenericFunction(HqlParser.GenericFunctionContext ctx) { + public QueryRendererBuilder visitGenericFunction(HqlParser.GenericFunctionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); + QueryRendererBuilder nested = QueryRenderer.builder(); - tokens.addAll(visit(ctx.functionName())); - NOSPACE(tokens); - tokens.add(TOKEN_OPEN_PAREN); + nested.append(visit(ctx.functionName())); + nested.append(TOKEN_OPEN_PAREN); if (ctx.functionArguments() != null) { - tokens.addAll(visit(ctx.functionArguments())); + nested.appendInline(visit(ctx.functionArguments())); } else if (ctx.ASTERISK() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ASTERISK())); + nested.append(JpaQueryParsingToken.token(ctx.ASTERISK())); } - tokens.add(TOKEN_CLOSE_PAREN); + nested.append(TOKEN_CLOSE_PAREN); + + builder.append(nested); if (ctx.pathContinutation() != null) { - NOSPACE(tokens); - tokens.addAll(visit(ctx.pathContinutation())); + builder.appendInline(visit(ctx.pathContinutation())); } if (ctx.filterClause() != null) { - tokens.addAll(visit(ctx.filterClause())); + builder.appendExpression(visit(ctx.filterClause())); } if (ctx.withinGroup() != null) { - tokens.addAll(visit(ctx.withinGroup())); + builder.appendExpression(visit(ctx.withinGroup())); } if (ctx.overClause() != null) { - tokens.addAll(visit(ctx.overClause())); + builder.appendExpression(visit(ctx.overClause())); } - return tokens; + return builder; } @Override - public List visitFunctionWithSubquery(HqlParser.FunctionWithSubqueryContext ctx) { + public QueryRendererBuilder visitFunctionWithSubquery(HqlParser.FunctionWithSubqueryContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.functionName())); - NOSPACE(tokens); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.appendExpression(visit(ctx.functionName())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitCastFunctionInvocation(HqlParser.CastFunctionInvocationContext ctx) { + public QueryRendererBuilder visitCastFunctionInvocation(HqlParser.CastFunctionInvocationContext ctx) { return visit(ctx.castFunction()); } @Override - public List visitExtractFunctionInvocation(HqlParser.ExtractFunctionInvocationContext ctx) { + public QueryRendererBuilder visitExtractFunctionInvocation(HqlParser.ExtractFunctionInvocationContext ctx) { return visit(ctx.extractFunction()); } @Override - public List visitTrimFunctionInvocation(HqlParser.TrimFunctionInvocationContext ctx) { + public QueryRendererBuilder visitTrimFunctionInvocation(HqlParser.TrimFunctionInvocationContext ctx) { return visit(ctx.trimFunction()); } @Override - public List visitEveryFunctionInvocation(HqlParser.EveryFunctionInvocationContext ctx) { + public QueryRendererBuilder visitEveryFunctionInvocation(HqlParser.EveryFunctionInvocationContext ctx) { return visit(ctx.everyFunction()); } @Override - public List visitAnyFunctionInvocation(HqlParser.AnyFunctionInvocationContext ctx) { + public QueryRendererBuilder visitAnyFunctionInvocation(HqlParser.AnyFunctionInvocationContext ctx) { return visit(ctx.anyFunction()); } @Override - public List visitTreatedPathInvocation(HqlParser.TreatedPathInvocationContext ctx) { + public QueryRendererBuilder visitTreatedPathInvocation(HqlParser.TreatedPathInvocationContext ctx) { return visit(ctx.treatedPath()); } @Override - public List visitFunctionArguments(HqlParser.FunctionArgumentsContext ctx) { + public QueryRendererBuilder visitFunctionArguments(HqlParser.FunctionArgumentsContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); } - ctx.expressionOrPredicate().forEach(expressionOrPredicateContext -> { - tokens.addAll(visit(expressionOrPredicateContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); + builder.append(QueryRendererBuilder.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); - return tokens; + return builder; } @Override - public List visitFilterClause(HqlParser.FilterClauseContext ctx) { + public QueryRendererBuilder visitFilterClause(HqlParser.FilterClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.FILTER())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.whereClause())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.expression(ctx.FILTER())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.whereClause())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitWithinGroup(HqlParser.WithinGroupContext ctx) { + public QueryRendererBuilder visitWithinGroup(HqlParser.WithinGroupContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.WITHIN())); - tokens.add(new JpaQueryParsingToken(ctx.GROUP())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.orderByClause())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.expression(ctx.WITHIN())); + builder.append(JpaQueryParsingToken.expression(ctx.GROUP())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.orderByClause())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitOverClause(HqlParser.OverClauseContext ctx) { + public QueryRendererBuilder visitOverClause(HqlParser.OverClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.append(JpaQueryParsingToken.expression(ctx.OVER())); - tokens.add(new JpaQueryParsingToken(ctx.OVER())); - tokens.add(TOKEN_OPEN_PAREN); + QueryRendererBuilder nested = QueryRenderer.builder(); + nested.append(TOKEN_OPEN_PAREN); + + List trees = new ArrayList<>(); if (ctx.partitionClause() != null) { - tokens.addAll(visit(ctx.partitionClause())); + trees.add(ctx.partitionClause()); } if (ctx.orderByClause() != null) { - tokens.addAll(visit(ctx.orderByClause())); - SPACE(tokens); + trees.add(ctx.orderByClause()); } if (ctx.frameClause() != null) { - tokens.addAll(visit(ctx.frameClause())); + trees.add(ctx.frameClause()); } - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + nested.appendInline(QueryRendererBuilder.concatExpressions(trees, this::visit, TOKEN_NONE)); + nested.append(TOKEN_CLOSE_PAREN); - return tokens; + builder.appendInline(nested); + + return builder; } @Override - public List visitPartitionClause(HqlParser.PartitionClauseContext ctx) { + public QueryRendererBuilder visitPartitionClause(HqlParser.PartitionClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.PARTITION())); - tokens.add(new JpaQueryParsingToken(ctx.BY())); + builder.append(JpaQueryParsingToken.expression(ctx.PARTITION())); + builder.append(JpaQueryParsingToken.expression(ctx.BY())); - ctx.expression().forEach(expressionContext -> { - tokens.addAll(visit(expressionContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); + builder.append(QueryRendererBuilder.concat(ctx.expression(), this::visit, TOKEN_COMMA)); - return tokens; + return builder; } @Override - public List visitFrameClause(HqlParser.FrameClauseContext ctx) { + public QueryRendererBuilder visitFrameClause(HqlParser.FrameClauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.RANGE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.RANGE())); + builder.append(JpaQueryParsingToken.expression(ctx.RANGE())); } else if (ctx.ROWS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ROWS())); + builder.append(JpaQueryParsingToken.expression(ctx.ROWS())); } else if (ctx.GROUPS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.GROUPS())); + builder.append(JpaQueryParsingToken.expression(ctx.GROUPS())); } if (ctx.BETWEEN() != null) { - tokens.add(new JpaQueryParsingToken(ctx.BETWEEN())); + builder.append(JpaQueryParsingToken.expression(ctx.BETWEEN())); } - tokens.addAll(visit(ctx.frameStart())); + builder.appendExpression(visit(ctx.frameStart())); if (ctx.AND() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AND())); - tokens.addAll(visit(ctx.frameEnd())); + builder.append(JpaQueryParsingToken.expression(ctx.AND())); + builder.appendExpression(visit(ctx.frameEnd())); } if (ctx.frameExclusion() != null) { - tokens.addAll(visit(ctx.frameExclusion())); + builder.appendExpression(visit(ctx.frameExclusion())); } - return tokens; + return builder; } @Override - public List visitUnboundedPrecedingFrameStart( - HqlParser.UnboundedPrecedingFrameStartContext ctx) { + public QueryRendererBuilder visitUnboundedPrecedingFrameStart(HqlParser.UnboundedPrecedingFrameStartContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.UNBOUNDED())); - tokens.add(new JpaQueryParsingToken(ctx.PRECEDING())); + builder.append(JpaQueryParsingToken.expression(ctx.UNBOUNDED())); + builder.append(JpaQueryParsingToken.expression(ctx.PRECEDING())); - return tokens; + return builder; } @Override - public List visitExpressionPrecedingFrameStart( - HqlParser.ExpressionPrecedingFrameStartContext ctx) { + public QueryRendererBuilder visitExpressionPrecedingFrameStart(HqlParser.ExpressionPrecedingFrameStartContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression())); - tokens.add(new JpaQueryParsingToken(ctx.PRECEDING())); + builder.append(visit(ctx.expression())); + builder.append(JpaQueryParsingToken.expression(ctx.PRECEDING())); - return tokens; + return builder; } @Override - public List visitCurrentRowFrameStart(HqlParser.CurrentRowFrameStartContext ctx) { + public QueryRendererBuilder visitCurrentRowFrameStart(HqlParser.CurrentRowFrameStartContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CURRENT())); - tokens.add(new JpaQueryParsingToken(ctx.ROW())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT())); + builder.append(JpaQueryParsingToken.expression(ctx.ROW())); - return tokens; + return builder; } @Override - public List visitExpressionFollowingFrameStart( - HqlParser.ExpressionFollowingFrameStartContext ctx) { + public QueryRendererBuilder visitExpressionFollowingFrameStart(HqlParser.ExpressionFollowingFrameStartContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression())); - tokens.add(new JpaQueryParsingToken(ctx.FOLLOWING())); + builder.append(visit(ctx.expression())); + builder.append(JpaQueryParsingToken.expression(ctx.FOLLOWING())); - return tokens; + return builder; } @Override - public List visitCurrentRowFrameExclusion(HqlParser.CurrentRowFrameExclusionContext ctx) { + public QueryRendererBuilder visitCurrentRowFrameExclusion(HqlParser.CurrentRowFrameExclusionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.EXCLUDE())); - tokens.add(new JpaQueryParsingToken(ctx.CURRENT())); - tokens.add(new JpaQueryParsingToken(ctx.ROW())); + builder.append(JpaQueryParsingToken.expression(ctx.EXCLUDE())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT())); + builder.append(JpaQueryParsingToken.expression(ctx.ROW())); - return tokens; + return builder; } @Override - public List visitGroupFrameExclusion(HqlParser.GroupFrameExclusionContext ctx) { + public QueryRendererBuilder visitGroupFrameExclusion(HqlParser.GroupFrameExclusionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.EXCLUDE())); - tokens.add(new JpaQueryParsingToken(ctx.GROUP())); + builder.append(JpaQueryParsingToken.expression(ctx.EXCLUDE())); + builder.append(JpaQueryParsingToken.expression(ctx.GROUP())); - return tokens; + return builder; } @Override - public List visitTiesFrameExclusion(HqlParser.TiesFrameExclusionContext ctx) { + public QueryRendererBuilder visitTiesFrameExclusion(HqlParser.TiesFrameExclusionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.EXCLUDE())); - tokens.add(new JpaQueryParsingToken(ctx.TIES())); + builder.append(JpaQueryParsingToken.expression(ctx.EXCLUDE())); + builder.append(JpaQueryParsingToken.expression(ctx.TIES())); - return tokens; + return builder; } @Override - public List visitNoOthersFrameExclusion(HqlParser.NoOthersFrameExclusionContext ctx) { + public QueryRendererBuilder visitNoOthersFrameExclusion(HqlParser.NoOthersFrameExclusionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.EXCLUDE())); - tokens.add(new JpaQueryParsingToken(ctx.NO())); - tokens.add(new JpaQueryParsingToken(ctx.OTHERS())); + builder.append(JpaQueryParsingToken.expression(ctx.EXCLUDE())); + builder.append(JpaQueryParsingToken.expression(ctx.NO())); + builder.append(JpaQueryParsingToken.expression(ctx.OTHERS())); - return tokens; + return builder; } @Override - public List visitExpressionPrecedingFrameEnd(HqlParser.ExpressionPrecedingFrameEndContext ctx) { + public QueryRendererBuilder visitExpressionPrecedingFrameEnd(HqlParser.ExpressionPrecedingFrameEndContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression())); - tokens.add(new JpaQueryParsingToken(ctx.PRECEDING())); + builder.appendExpression(visit(ctx.expression())); + builder.append(JpaQueryParsingToken.expression(ctx.PRECEDING())); - return tokens; + return builder; } @Override - public List visitCurrentRowFrameEnd(HqlParser.CurrentRowFrameEndContext ctx) { + public QueryRendererBuilder visitCurrentRowFrameEnd(HqlParser.CurrentRowFrameEndContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CURRENT())); - tokens.add(new JpaQueryParsingToken(ctx.ROW())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT())); + builder.append(JpaQueryParsingToken.expression(ctx.ROW())); - return tokens; + return builder; } @Override - public List visitExpressionFollowingFrameEnd(HqlParser.ExpressionFollowingFrameEndContext ctx) { + public QueryRendererBuilder visitExpressionFollowingFrameEnd(HqlParser.ExpressionFollowingFrameEndContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression())); - tokens.add(new JpaQueryParsingToken(ctx.FOLLOWING())); + builder.appendExpression(visit(ctx.expression())); + builder.append(JpaQueryParsingToken.expression(ctx.FOLLOWING())); - return tokens; + return builder; } @Override - public List visitUnboundedFollowingFrameEnd(HqlParser.UnboundedFollowingFrameEndContext ctx) { + public QueryRendererBuilder visitUnboundedFollowingFrameEnd(HqlParser.UnboundedFollowingFrameEndContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.UNBOUNDED())); - tokens.add(new JpaQueryParsingToken(ctx.FOLLOWING())); + builder.append(JpaQueryParsingToken.expression(ctx.UNBOUNDED())); + builder.append(JpaQueryParsingToken.expression(ctx.FOLLOWING())); - return tokens; + return builder; } @Override - public List visitCastFunction(HqlParser.CastFunctionContext ctx) { + public QueryRendererBuilder visitCastFunction(HqlParser.CastFunctionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CAST(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.expression())); - tokens.add(new JpaQueryParsingToken(ctx.AS())); - tokens.addAll(visit(ctx.castTarget())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.CAST())); - return tokens; + builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); + nested.appendExpression(visit(ctx.expression())); + nested.append(JpaQueryParsingToken.expression(ctx.AS())); + nested.appendExpression(visit(ctx.castTarget())); + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; } @Override - public List visitCastTarget(HqlParser.CastTargetContext ctx) { + public QueryRendererBuilder visitCastTarget(HqlParser.CastTargetContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.castTargetType())); + builder.append(visit(ctx.castTargetType())); if (ctx.INTEGER_LITERAL() != null && !ctx.INTEGER_LITERAL().isEmpty()) { - tokens.add(TOKEN_OPEN_PAREN); + builder.append(TOKEN_OPEN_PAREN); + List tokens = new ArrayList<>(); ctx.INTEGER_LITERAL().forEach(terminalNode -> { - tokens.add(new JpaQueryParsingToken(terminalNode)); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - NOSPACE(tokens); + if (!tokens.isEmpty()) { + tokens.add(TOKEN_COMMA); + } + tokens.add(JpaQueryParsingToken.expression(terminalNode)); - tokens.add(TOKEN_CLOSE_PAREN); + }); + + builder.append(tokens); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitCastTargetType(HqlParser.CastTargetTypeContext ctx) { - return List.of(new JpaQueryParsingToken(ctx.fullTargetName)); + public QueryRendererBuilder visitCastTargetType(HqlParser.CastTargetTypeContext ctx) { + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.fullTargetName)); } @Override - public List visitExtractFunction(HqlParser.ExtractFunctionContext ctx) { + public QueryRendererBuilder visitExtractFunction(HqlParser.ExtractFunctionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.EXTRACT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.EXTRACT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.FROM())); - tokens.addAll(visit(ctx.expression(1))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.EXTRACT())); + builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); + + nested.appendExpression(visit(ctx.expression(0))); + nested.append(JpaQueryParsingToken.expression(ctx.FROM())); + nested.append(visit(ctx.expression(1))); + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.dateTimeFunction() != null) { - tokens.addAll(visit(ctx.dateTimeFunction())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.expression(0))); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(visit(ctx.dateTimeFunction())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitTrimFunction(HqlParser.TrimFunctionContext ctx) { + public QueryRendererBuilder visitTrimFunction(HqlParser.TrimFunctionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.TRIM())); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TRIM())); + builder.append(TOKEN_OPEN_PAREN); if (ctx.LEADING() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LEADING())); + builder.append(JpaQueryParsingToken.expression(ctx.LEADING())); } else if (ctx.TRAILING() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TRAILING())); + builder.append(JpaQueryParsingToken.expression(ctx.TRAILING())); } else if (ctx.BOTH() != null) { - tokens.add(new JpaQueryParsingToken(ctx.BOTH())); + builder.append(JpaQueryParsingToken.expression(ctx.BOTH())); } if (ctx.stringLiteral() != null) { - tokens.addAll(visit(ctx.stringLiteral())); + builder.append(visit(ctx.stringLiteral())); } if (ctx.FROM() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FROM())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); } - tokens.addAll(visit(ctx.expression())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.appendInline(visit(ctx.expression())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitDateTimeFunction(HqlParser.DateTimeFunctionContext ctx) { - return List.of(new JpaQueryParsingToken(ctx.d)); + public QueryRendererBuilder visitDateTimeFunction(HqlParser.DateTimeFunctionContext ctx) { + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.d)); } @Override - public List visitEveryFunction(HqlParser.EveryFunctionContext ctx) { + public QueryRendererBuilder visitEveryFunction(HqlParser.EveryFunctionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.every)); + builder.append(JpaQueryParsingToken.expression(ctx.every)); if (ctx.ELEMENTS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ELEMENTS())); + builder.append(JpaQueryParsingToken.expression(ctx.ELEMENTS())); } else if (ctx.INDICES() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INDICES())); + builder.append(JpaQueryParsingToken.expression(ctx.INDICES())); } - tokens.add(TOKEN_OPEN_PAREN); + builder.append(TOKEN_OPEN_PAREN); if (ctx.predicate() != null) { - tokens.addAll(visit(ctx.predicate())); + builder.append(visit(ctx.predicate())); } else if (ctx.subquery() != null) { - tokens.addAll(visit(ctx.subquery())); + builder.append(visit(ctx.subquery())); } else if (ctx.simplePath() != null) { - tokens.addAll(visit(ctx.simplePath())); + builder.append(visit(ctx.simplePath())); } - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitAnyFunction(HqlParser.AnyFunctionContext ctx) { + public QueryRendererBuilder visitAnyFunction(HqlParser.AnyFunctionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.any)); + builder.append(JpaQueryParsingToken.expression(ctx.any)); if (ctx.ELEMENTS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ELEMENTS())); + builder.append(JpaQueryParsingToken.expression(ctx.ELEMENTS())); } else if (ctx.INDICES() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INDICES())); + builder.append(JpaQueryParsingToken.expression(ctx.INDICES())); } - tokens.add(TOKEN_OPEN_PAREN); + builder.append(TOKEN_OPEN_PAREN); if (ctx.predicate() != null) { - tokens.addAll(visit(ctx.predicate())); + builder.append(visit(ctx.predicate())); } else if (ctx.subquery() != null) { - tokens.addAll(visit(ctx.subquery())); + builder.append(visit(ctx.subquery())); } else if (ctx.simplePath() != null) { - tokens.addAll(visit(ctx.simplePath())); + builder.append(visit(ctx.simplePath())); } - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitTreatedPath(HqlParser.TreatedPathContext ctx) { + public QueryRendererBuilder visitTreatedPath(HqlParser.TreatedPathContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.TREAT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.path())); - tokens.add(new JpaQueryParsingToken(ctx.AS())); - tokens.addAll(visit(ctx.simplePath())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TREAT())); + builder.append(TOKEN_OPEN_PAREN); + + QueryRendererBuilder nested = QueryRenderer.builder(); + nested.appendExpression(visit(ctx.path())); + nested.append(JpaQueryParsingToken.expression(ctx.AS())); + nested.append(visit(ctx.simplePath())); + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); if (ctx.pathContinutation() != null) { - NOSPACE(tokens); - tokens.addAll(visit(ctx.pathContinutation())); + builder.append(visit(ctx.pathContinutation())); } - return tokens; + return builder; } @Override - public List visitPathContinutation(HqlParser.PathContinutationContext ctx) { + public QueryRendererBuilder visitPathContinutation(HqlParser.PathContinutationContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_DOT); - tokens.addAll(visit(ctx.simplePath())); + builder.append(TOKEN_DOT); + builder.append(visit(ctx.simplePath())); - return tokens; + return builder; } @Override - public List visitNullExpressionPredicate(HqlParser.NullExpressionPredicateContext ctx) { + public QueryRendererBuilder visitNullExpressionPredicate(HqlParser.NullExpressionPredicateContext ctx) { return visit(ctx.dealingWithNullExpression()); } @Override - public List visitBetweenPredicate(HqlParser.BetweenPredicateContext ctx) { + public QueryRendererBuilder visitBetweenPredicate(HqlParser.BetweenPredicateContext ctx) { return visit(ctx.betweenExpression()); } @Override - public List visitOrPredicate(HqlParser.OrPredicateContext ctx) { + public QueryRendererBuilder visitOrPredicate(HqlParser.OrPredicateContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.predicate(0))); - tokens.add(new JpaQueryParsingToken(ctx.OR())); - tokens.addAll(visit(ctx.predicate(1))); + builder.appendExpression(visit(ctx.predicate(0))); + builder.append(JpaQueryParsingToken.expression(ctx.OR())); + builder.appendExpression(visit(ctx.predicate(1))); - return tokens; + return builder; } @Override - public List visitRelationalPredicate(HqlParser.RelationalPredicateContext ctx) { + public QueryRendererBuilder visitRelationalPredicate(HqlParser.RelationalPredicateContext ctx) { return visit(ctx.relationalExpression()); } @Override - public List visitExistsPredicate(HqlParser.ExistsPredicateContext ctx) { + public QueryRendererBuilder visitExistsPredicate(HqlParser.ExistsPredicateContext ctx) { return visit(ctx.existsExpression()); } @Override - public List visitCollectionPredicate(HqlParser.CollectionPredicateContext ctx) { + public QueryRendererBuilder visitCollectionPredicate(HqlParser.CollectionPredicateContext ctx) { return visit(ctx.collectionExpression()); } @Override - public List visitAndPredicate(HqlParser.AndPredicateContext ctx) { + public QueryRendererBuilder visitAndPredicate(HqlParser.AndPredicateContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.predicate(0))); - tokens.add(new JpaQueryParsingToken(ctx.AND())); - tokens.addAll(visit(ctx.predicate(1))); + builder.appendExpression(visit(ctx.predicate(0))); + builder.append(JpaQueryParsingToken.expression(ctx.AND())); + builder.appendExpression(visit(ctx.predicate(1))); - return tokens; + return builder; } @Override - public List visitGroupedPredicate(HqlParser.GroupedPredicateContext ctx) { + public QueryRendererBuilder visitGroupedPredicate(HqlParser.GroupedPredicateContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.predicate())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.predicate())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitLikePredicate(HqlParser.LikePredicateContext ctx) { + public QueryRendererBuilder visitLikePredicate(HqlParser.LikePredicateContext ctx) { return visit(ctx.stringPatternMatching()); } @Override - public List visitInPredicate(HqlParser.InPredicateContext ctx) { + public QueryRendererBuilder visitInPredicate(HqlParser.InPredicateContext ctx) { return visit(ctx.inExpression()); } @Override - public List visitNotPredicate(HqlParser.NotPredicateContext ctx) { + public QueryRendererBuilder visitNotPredicate(HqlParser.NotPredicateContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(TOKEN_NOT); - tokens.addAll(visit(ctx.predicate())); + builder.append(TOKEN_NOT); + builder.append(visit(ctx.predicate())); - return tokens; + return builder; } @Override - public List visitExpressionPredicate(HqlParser.ExpressionPredicateContext ctx) { + public QueryRendererBuilder visitExpressionPredicate(HqlParser.ExpressionPredicateContext ctx) { return visit(ctx.expression()); } @Override - public List visitExpressionOrPredicate(HqlParser.ExpressionOrPredicateContext ctx) { + public QueryRendererBuilder visitExpressionOrPredicate(HqlParser.ExpressionOrPredicateContext ctx) { if (ctx.expression() != null) { return visit(ctx.expression()); } else if (ctx.predicate() != null) { return visit(ctx.predicate()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitRelationalExpression(HqlParser.RelationalExpressionContext ctx) { + public QueryRendererBuilder visitRelationalExpression(HqlParser.RelationalExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.op)); - tokens.addAll(visit(ctx.expression(1))); + builder.appendInline(visit(ctx.expression(0))); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); + builder.appendInline(visit(ctx.expression(1))); - return tokens; + return builder; } @Override - public List visitBetweenExpression(HqlParser.BetweenExpressionContext ctx) { + public QueryRendererBuilder visitBetweenExpression(HqlParser.BetweenExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression(0))); + builder.appendExpression(visit(ctx.expression(0))); if (ctx.NOT() != null) { - tokens.add(TOKEN_NOT); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.BETWEEN())); - tokens.addAll(visit(ctx.expression(1))); - tokens.add(new JpaQueryParsingToken(ctx.AND())); - tokens.addAll(visit(ctx.expression(2))); + builder.append(JpaQueryParsingToken.expression(ctx.BETWEEN())); + builder.appendExpression(visit(ctx.expression(1))); + builder.append(JpaQueryParsingToken.expression(ctx.AND())); + builder.appendExpression(visit(ctx.expression(2))); - return tokens; + return builder; } @Override - public List visitDealingWithNullExpression(HqlParser.DealingWithNullExpressionContext ctx) { + public QueryRendererBuilder visitDealingWithNullExpression(HqlParser.DealingWithNullExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.IS())); + builder.appendExpression(visit(ctx.expression(0))); + builder.append(JpaQueryParsingToken.expression(ctx.IS())); if (ctx.NOT() != null) { - tokens.add(TOKEN_NOT); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } if (ctx.NULL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NULL())); + builder.append(JpaQueryParsingToken.expression(ctx.NULL())); } else if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); - tokens.add(new JpaQueryParsingToken(ctx.FROM())); - tokens.addAll(visit(ctx.expression(1))); + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.appendExpression(visit(ctx.expression(1))); } - return tokens; + return builder; } @Override - public List visitStringPatternMatching(HqlParser.StringPatternMatchingContext ctx) { + public QueryRendererBuilder visitStringPatternMatching(HqlParser.StringPatternMatchingContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression(0))); + builder.appendExpression(visit(ctx.expression(0))); if (ctx.NOT() != null) { - tokens.add(TOKEN_NOT); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } if (ctx.LIKE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LIKE())); + builder.append(JpaQueryParsingToken.expression(ctx.LIKE())); } else if (ctx.ILIKE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ILIKE())); + builder.append(JpaQueryParsingToken.expression(ctx.ILIKE())); } - tokens.addAll(visit(ctx.expression(1))); + builder.appendExpression(visit(ctx.expression(1))); if (ctx.ESCAPE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ESCAPE())); + builder.append(JpaQueryParsingToken.expression(ctx.ESCAPE())); if (ctx.stringLiteral() != null) { - tokens.addAll(visit(ctx.stringLiteral())); + builder.appendExpression(visit(ctx.stringLiteral())); } else if (ctx.parameter() != null) { - tokens.addAll(visit(ctx.parameter())); + builder.appendExpression(visit(ctx.parameter())); } } - return tokens; + return builder; } @Override - public List visitInExpression(HqlParser.InExpressionContext ctx) { + public QueryRendererBuilder visitInExpression(HqlParser.InExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression())); + builder.appendExpression(visit(ctx.expression())); if (ctx.NOT() != null) { - tokens.add(TOKEN_NOT); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.IN())); - tokens.addAll(visit(ctx.inList())); + builder.append(JpaQueryParsingToken.expression(ctx.IN())); + builder.appendExpression(visit(ctx.inList())); - return tokens; + return builder; } @Override - public List visitInList(HqlParser.InListContext ctx) { + public QueryRendererBuilder visitInList(HqlParser.InListContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.simplePath() != null) { if (ctx.ELEMENTS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ELEMENTS())); + builder.append(JpaQueryParsingToken.expression(ctx.ELEMENTS())); } else if (ctx.INDICES() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INDICES())); + builder.append(JpaQueryParsingToken.expression(ctx.INDICES())); } - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.simplePath())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.simplePath())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.parameter() != null) { - tokens.addAll(visit(ctx.parameter())); + builder.append(visit(ctx.parameter())); } else if (ctx.expressionOrPredicate() != null) { - tokens.add(TOKEN_OPEN_PAREN); - - ctx.expressionOrPredicate().forEach(expressionOrPredicateContext -> { - tokens.addAll(visit(expressionOrPredicateContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryRendererBuilder.concat(ctx.expressionOrPredicate(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitExistsExpression(HqlParser.ExistsExpressionContext ctx) { + public QueryRendererBuilder visitExistsExpression(HqlParser.ExistsExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.simplePath() != null) { - tokens.add(new JpaQueryParsingToken(ctx.EXISTS())); + builder.append(JpaQueryParsingToken.expression(ctx.EXISTS())); if (ctx.ELEMENTS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ELEMENTS())); + builder.append(JpaQueryParsingToken.expression(ctx.ELEMENTS())); } else if (ctx.INDICES() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INDICES())); + builder.append(JpaQueryParsingToken.expression(ctx.INDICES())); } - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.simplePath())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.simplePath())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.expression() != null) { - tokens.add(new JpaQueryParsingToken(ctx.EXISTS())); - tokens.addAll(visit(ctx.expression())); + builder.append(JpaQueryParsingToken.expression(ctx.EXISTS())); + builder.appendExpression(visit(ctx.expression())); } - return tokens; + return builder; } @Override - public List visitCollectionExpression(HqlParser.CollectionExpressionContext ctx) { + public QueryRendererBuilder visitCollectionExpression(HqlParser.CollectionExpressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.expression())); + builder.appendExpression(visit(ctx.expression())); if (ctx.IS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.IS())); + builder.append(JpaQueryExpression.expression(ctx.IS())); if (ctx.NOT() != null) { - tokens.add(TOKEN_NOT); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.EMPTY())); + builder.append(JpaQueryExpression.expression(ctx.EMPTY())); } else if (ctx.MEMBER() != null) { if (ctx.NOT() != null) { - tokens.add(TOKEN_NOT); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.MEMBER())); - tokens.add(new JpaQueryParsingToken(ctx.OF())); - tokens.addAll(visit(ctx.path())); + builder.append(JpaQueryExpression.expression(ctx.MEMBER())); + builder.append(JpaQueryExpression.expression(ctx.OF())); + builder.append(visit(ctx.path())); } - return tokens; + return builder; } @Override - public List visitInstantiationTarget(HqlParser.InstantiationTargetContext ctx) { + public QueryRendererBuilder visitInstantiationTarget(HqlParser.InstantiationTargetContext ctx) { if (ctx.LIST() != null) { - return List.of(new JpaQueryParsingToken(ctx.LIST())); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.LIST())); } else if (ctx.MAP() != null) { - return List.of(new JpaQueryParsingToken(ctx.MAP())); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.MAP())); } else if (ctx.simplePath() != null) { - List tokens = visit(ctx.simplePath()); - NOSPACE(tokens); - return tokens; + return visit(ctx.simplePath()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitInstantiationArguments(HqlParser.InstantiationArgumentsContext ctx) { - - List tokens = new ArrayList<>(); - - ctx.instantiationArgument().forEach(instantiationArgumentContext -> { - tokens.addAll(visit(instantiationArgumentContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - return tokens; + public QueryRendererBuilder visitInstantiationArguments(HqlParser.InstantiationArgumentsContext ctx) { + return QueryRendererBuilder.concat(ctx.instantiationArgument(), this::visit, TOKEN_COMMA); } @Override - public List visitInstantiationArgument(HqlParser.InstantiationArgumentContext ctx) { + public QueryRendererBuilder visitInstantiationArgument(HqlParser.InstantiationArgumentContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.expressionOrPredicate() != null) { - tokens.addAll(visit(ctx.expressionOrPredicate())); + builder.appendExpression(visit(ctx.expressionOrPredicate())); } else if (ctx.instantiation() != null) { - tokens.addAll(visit(ctx.instantiation())); + builder.appendExpression(visit(ctx.instantiation())); } if (ctx.variable() != null) { - tokens.addAll(visit(ctx.variable())); + builder.append(visit(ctx.variable())); } - return tokens; + return builder; } @Override - public List visitParameterOrIntegerLiteral(HqlParser.ParameterOrIntegerLiteralContext ctx) { + public QueryRendererBuilder visitParameterOrIntegerLiteral(HqlParser.ParameterOrIntegerLiteralContext ctx) { if (ctx.parameter() != null) { return visit(ctx.parameter()); } else if (ctx.INTEGER_LITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.INTEGER_LITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.INTEGER_LITERAL())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitParameterOrNumberLiteral(HqlParser.ParameterOrNumberLiteralContext ctx) { + public QueryRendererBuilder visitParameterOrNumberLiteral(HqlParser.ParameterOrNumberLiteralContext ctx) { if (ctx.parameter() != null) { return visit(ctx.parameter()); } else if (ctx.numericLiteral() != null) { return visit(ctx.numericLiteral()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitVariable(HqlParser.VariableContext ctx) { + public QueryRendererBuilder visitVariable(HqlParser.VariableContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.identifier() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); - tokens.addAll(visit(ctx.identifier())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); + builder.append(visit(ctx.identifier())); } else if (ctx.reservedWord() != null) { - tokens.addAll(visit(ctx.reservedWord())); + builder.append(visit(ctx.reservedWord())); } - return tokens; + return builder; } @Override - public List visitParameter(HqlParser.ParameterContext ctx) { + public QueryRendererBuilder visitParameter(HqlParser.ParameterContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.prefix.getText().equals(":")) { - tokens.add(TOKEN_COLON); - tokens.addAll(visit(ctx.identifier())); + builder.append(TOKEN_COLON); + builder.append(visit(ctx.identifier())); } else if (ctx.prefix.getText().equals("?")) { - tokens.add(TOKEN_QUESTION_MARK); + builder.append(TOKEN_QUESTION_MARK); if (ctx.INTEGER_LITERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INTEGER_LITERAL())); + builder.append(JpaQueryParsingToken.expression(ctx.INTEGER_LITERAL())); } } - return tokens; + return builder; } @Override - public List visitEntityName(HqlParser.EntityNameContext ctx) { - - List tokens = new ArrayList<>(); - - ctx.identifier().forEach(identifierContext -> { - tokens.addAll(visit(identifierContext)); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - }); - CLIP(tokens); - SPACE(tokens); - - return tokens; + public QueryRendererBuilder visitEntityName(HqlParser.EntityNameContext ctx) { + return QueryRendererBuilder.concat(ctx.identifier(), this::visit, TOKEN_DOT); } @Override - public List visitIdentifier(HqlParser.IdentifierContext ctx) { + public QueryRendererBuilder visitIdentifier(HqlParser.IdentifierContext ctx) { if (ctx.reservedWord() != null) { return visit(ctx.reservedWord()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitCharacter(HqlParser.CharacterContext ctx) { - return List.of(new JpaQueryParsingToken(ctx.CHARACTER())); + public QueryRendererBuilder visitCharacter(HqlParser.CharacterContext ctx) { + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.CHARACTER())); } @Override - public List visitFunctionName(HqlParser.FunctionNameContext ctx) { - - List tokens = new ArrayList<>(); - - ctx.reservedWord().forEach(reservedWordContext -> { - tokens.addAll(visit(reservedWordContext)); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - }); - CLIP(tokens); - - return tokens; + public QueryRendererBuilder visitFunctionName(HqlParser.FunctionNameContext ctx) { + return QueryRendererBuilder.concat(ctx.reservedWord(), this::visit, TOKEN_DOT); } @Override - public List visitReservedWord(HqlParser.ReservedWordContext ctx) { + public QueryRendererBuilder visitReservedWord(HqlParser.ReservedWordContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return List.of(new JpaQueryParsingToken(ctx.IDENTIFICATION_VARIABLE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.IDENTIFICATION_VARIABLE())); } else { - return List.of(new JpaQueryParsingToken(ctx.f)); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.f)); } } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryTransformer.java deleted file mode 100644 index befaccc7b..000000000 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryTransformer.java +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright 2022-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jpa.repository.query; - -import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.antlr.v4.runtime.ParserRuleContext; -import org.springframework.data.domain.Sort; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed HQL query. - * - * @author Greg Turnquist - * @author Christoph Strobl - * @since 3.1 - */ -class HqlQueryTransformer extends HqlQueryRenderer { - - // TODO: Separate input from result parameters, encapsulation... - - private final Sort sort; - private final boolean countQuery; - - private final @Nullable String countProjection; - - private @Nullable String primaryFromAlias = null; - - private List projection = Collections.emptyList(); - private boolean projectionProcessed; - - private boolean hasConstructorExpression = false; - - private JpaQueryTransformerSupport transformerSupport; - - HqlQueryTransformer() { - this(Sort.unsorted(), false, null); - } - - HqlQueryTransformer(Sort sort) { - this(sort, false, null); - } - - HqlQueryTransformer(boolean countQuery, @Nullable String countProjection) { - this(Sort.unsorted(), countQuery, countProjection); - } - - private HqlQueryTransformer(Sort sort, boolean countQuery, @Nullable String countProjection) { - - Assert.notNull(sort, "Sort must not be null"); - - this.sort = sort; - this.countQuery = countQuery; - this.countProjection = countProjection; - this.transformerSupport = new JpaQueryTransformerSupport(); - } - - @Nullable - public String getAlias() { - return this.primaryFromAlias; - } - - public List getProjection() { - return this.projection; - } - - public boolean hasConstructorExpression() { - return this.hasConstructorExpression; - } - - /** - * Is this select clause a {@literal subquery}? - * - * @return boolean - */ - private static boolean isSubquery(ParserRuleContext ctx) { - - if (ctx instanceof HqlParser.SubqueryContext || ctx instanceof HqlParser.CteContext) { - return true; - } else if (ctx instanceof HqlParser.SelectStatementContext) { - return false; - } else if (ctx instanceof HqlParser.InsertStatementContext) { - return false; - } else { - return isSubquery(ctx.getParent()); - } - } - - @Override - public List visitOrderedQuery(HqlParser.OrderedQueryContext ctx) { - - List tokens = newArrayList(); - - if (ctx.query() != null) { - tokens.addAll(visit(ctx.query())); - } else if (ctx.queryExpression() != null) { - - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.queryExpression())); - tokens.add(TOKEN_CLOSE_PAREN); - } - - if (!countQuery && !isSubquery(ctx)) { - - if (ctx.queryOrder() != null) { - tokens.addAll(visit(ctx.queryOrder())); - } - - if (sort.isSorted()) { - - if (ctx.queryOrder() != null) { - - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - } else { - - SPACE(tokens); - tokens.add(TOKEN_ORDER_BY); - } - - tokens.addAll(transformerSupport.generateOrderByArguments(primaryFromAlias, sort)); - } - } else { - - if (ctx.queryOrder() != null) { - tokens.addAll(visit(ctx.queryOrder())); - } - } - - return tokens; - } - - @Override - public List visitFromQuery(HqlParser.FromQueryContext ctx) { - - List tokens = newArrayList(); - - if (countQuery && !isSubquery(ctx) && ctx.selectClause() == null) { - - tokens.add(TOKEN_SELECT_COUNT); - - if (countProjection != null) { - tokens.add(new JpaQueryParsingToken(countProjection)); - } else { - tokens.add(new JpaQueryParsingToken(() -> primaryFromAlias, false)); - } - - tokens.add(TOKEN_CLOSE_PAREN); - } - - if (ctx.fromClause() != null) { - tokens.addAll(visit(ctx.fromClause())); - } - - if (ctx.whereClause() != null) { - tokens.addAll(visit(ctx.whereClause())); - } - - if (ctx.groupByClause() != null) { - tokens.addAll(visit(ctx.groupByClause())); - } - - if (ctx.havingClause() != null) { - tokens.addAll(visit(ctx.havingClause())); - } - - if (ctx.selectClause() != null) { - tokens.addAll(visit(ctx.selectClause())); - } - - return tokens; - } - - @Override - public List visitQueryOrder(HqlParser.QueryOrderContext ctx) { - - List tokens = newArrayList(); - - if (!countQuery) { - tokens.addAll(visit(ctx.orderByClause())); - } - - if (ctx.limitClause() != null) { - SPACE(tokens); - tokens.addAll(visit(ctx.limitClause())); - } - if (ctx.offsetClause() != null) { - tokens.addAll(visit(ctx.offsetClause())); - } - if (ctx.fetchClause() != null) { - tokens.addAll(visit(ctx.fetchClause())); - } - - return tokens; - } - - @Override - public List visitFromRoot(HqlParser.FromRootContext ctx) { - - List tokens = newArrayList(); - - if (ctx.entityName() != null) { - - tokens.addAll(visit(ctx.entityName())); - - if (ctx.variable() != null) { - tokens.addAll(visit(ctx.variable())); - - if (primaryFromAlias == null && !isSubquery(ctx)) { - primaryFromAlias = tokens.get(tokens.size() - 1).getToken(); - } - } else { - - if (countQuery) { - - tokens.add(TOKEN_AS); - tokens.add(TOKEN_DOUBLE_UNDERSCORE); - - if (primaryFromAlias == null && !isSubquery(ctx)) { - primaryFromAlias = TOKEN_DOUBLE_UNDERSCORE.getToken(); - } - } - } - } else if (ctx.subquery() != null) { - - if (ctx.LATERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LATERAL())); - } - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - tokens.add(TOKEN_CLOSE_PAREN); - - if (ctx.variable() != null) { - tokens.addAll(visit(ctx.variable())); - - if (primaryFromAlias == null && !isSubquery(ctx)) { - primaryFromAlias = tokens.get(tokens.size() - 1).getToken(); - } - } - } - - return tokens; - } - - @Override - public List visitJoin(HqlParser.JoinContext ctx) { - - List tokens = new ArrayList<>(); - - tokens.addAll(visit(ctx.joinType())); - tokens.add(new JpaQueryParsingToken(ctx.JOIN())); - - if (!countQuery) { - if (ctx.FETCH() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FETCH())); - } - } - - tokens.addAll(visit(ctx.joinTarget())); - - if (ctx.joinRestriction() != null) { - tokens.addAll(visit(ctx.joinRestriction())); - } - - return tokens; - } - - @Override - public List visitJoinPath(HqlParser.JoinPathContext ctx) { - - List tokens = super.visitJoinPath(ctx); - - if (ctx.variable() != null) { - transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); - } - - return tokens; - } - - @Override - public List visitJoinSubquery(HqlParser.JoinSubqueryContext ctx) { - - List tokens = super.visitJoinSubquery(ctx); - - if (ctx.variable() != null) { - transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); - } - - return tokens; - } - - @Override - public List visitAlias(HqlParser.AliasContext ctx) { - - List tokens = super.visitAlias(ctx); - - if (primaryFromAlias == null && !isSubquery(ctx)) { - primaryFromAlias = tokens.get(tokens.size() - 1).getToken(); - } - - return tokens; - } - - @Override - public List visitVariable(HqlParser.VariableContext ctx) { - - List tokens = super.visitVariable(ctx); - - if (ctx.identifier() != null) { - transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); - } - - return tokens; - } - - @Override - public List visitSelectClause(HqlParser.SelectClauseContext ctx) { - - List tokens = newArrayList(); - - tokens.add(new JpaQueryParsingToken(ctx.SELECT())); - - if (countQuery && !isSubquery(ctx)) { - tokens.add(TOKEN_COUNT_FUNC); - - if (countProjection != null) { - tokens.add(new JpaQueryParsingToken(countProjection)); - } - } - - if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); - } - - List selectionListTokens = visit(ctx.selectionList()); - - if (countQuery && !isSubquery(ctx)) { - - if (countProjection == null) { - - if (ctx.DISTINCT() != null) { - - List countSelection = QueryTransformers.filterCountSelection(selectionListTokens); - - if (countSelection.stream().anyMatch(hqlToken -> hqlToken.getToken().contains("new"))) { - // constructor - tokens.add(new JpaQueryParsingToken(() -> primaryFromAlias)); - } else { - // keep all the select items to distinct against - tokens.addAll(countSelection); - } - } else { - tokens.add(new JpaQueryParsingToken(() -> primaryFromAlias)); - } - } - - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); - } else { - tokens.addAll(selectionListTokens); - } - - if (!projectionProcessed && !isSubquery(ctx)) { - projection = selectionListTokens; - projectionProcessed = true; - } - - return tokens; - } - - @Override - public List visitInstantiation(HqlParser.InstantiationContext ctx) { - - hasConstructorExpression = true; - - return super.visitInstantiation(ctx); - } - - static ArrayList newArrayList() { - return new ArrayList<>(); - } - -} 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 new file mode 100644 index 000000000..458887fc7 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java @@ -0,0 +1,137 @@ +/* + * Copyright 2022-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import java.util.List; + +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed HQL query. + * + * @author Greg Turnquist + * @author Christoph Strobl + * @since 3.1 + */ +@SuppressWarnings("ConstantValue") +class HqlSortedQueryTransformer extends HqlQueryRenderer { + + private final JpaQueryTransformerSupport transformerSupport = new JpaQueryTransformerSupport(); + private final Sort sort; + private final @Nullable String primaryFromAlias; + + HqlSortedQueryTransformer(Sort sort, @Nullable String primaryFromAlias) { + + Assert.notNull(sort, "Sort must not be null"); + + this.sort = sort; + this.primaryFromAlias = primaryFromAlias; + } + + @Override + public QueryRendererBuilder visitOrderedQuery(HqlParser.OrderedQueryContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.query() != null) { + builder.append(visit(ctx.query())); + } else if (ctx.queryExpression() != null) { + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.queryExpression())); + builder.append(TOKEN_CLOSE_PAREN); + } + + if (!isSubquery(ctx)) { + + if (ctx.queryOrder() != null) { + QueryRendererBuilder existingOrder = visit(ctx.queryOrder()); + if (sort.isSorted()) { + builder.appendInline(existingOrder); + } else { + builder.append(existingOrder); + } + } + + if (sort.isSorted()) { + + List sortBy = transformerSupport.orderBy(primaryFromAlias, sort); + + if (ctx.queryOrder() != null) { + + QueryRendererBuilder extension = QueryRenderer.builder().append(TOKEN_COMMA).append(sortBy); + + builder.appendInline(extension); + } else { + builder.append(TOKEN_ORDER_BY); + builder.append(sortBy); + } + } + } else { + + if (ctx.queryOrder() != null) { + builder.append(visit(ctx.queryOrder())); + } + } + + return builder; + } + + @Override + public QueryRendererBuilder visitJoinPath(HqlParser.JoinPathContext ctx) { + + QueryRendererBuilder builder = super.visitJoinPath(ctx); + + if (ctx.variable() != null) { + List tokens = builder.build().stream().toList(); + transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); + } + + return builder; + } + + @Override + public QueryRendererBuilder visitJoinSubquery(HqlParser.JoinSubqueryContext ctx) { + + QueryRendererBuilder builder = super.visitJoinSubquery(ctx); + + if (ctx.variable() != null) { + List tokens = builder.build().stream().toList(); + transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); + } + + return builder; + } + + @Override + public QueryRendererBuilder visitVariable(HqlParser.VariableContext ctx) { + + QueryRendererBuilder builder = super.visitVariable(ctx); + + if (ctx.identifier() != null) { + List tokens = builder.build().stream().toList(); + transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); + } + + return builder; + } + +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java index 30f3742e2..d5edc977c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java @@ -22,7 +22,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * Implementation of {@link QueryEnhancer} to enhance JPA queries using a {@link JpaQueryParserSupport}. + * Implementation of {@link QueryEnhancer} to enhance JPA queries using a {@link JpaQueryParser}. * * @author Greg Turnquist * @author Mark Paluch @@ -34,22 +34,22 @@ import org.springframework.util.Assert; class JpaQueryEnhancer implements QueryEnhancer { private final DeclaredQuery query; - private final JpaQueryParserSupport queryParser; + private final JpaQueryParser queryParser; /** - * Initialize with an {@link JpaQueryParserSupport}. + * Initialize with an {@link JpaQueryParser}. * * @param query * @param queryParser */ - private JpaQueryEnhancer(DeclaredQuery query, JpaQueryParserSupport queryParser) { + private JpaQueryEnhancer(DeclaredQuery query, JpaQueryParser queryParser) { this.query = query; this.queryParser = queryParser; } /** - * Factory method to create a {@link JpaQueryParserSupport} for {@link DeclaredQuery} using JPQL grammar. + * Factory method to create a {@link JpaQueryParser} for {@link DeclaredQuery} using JPQL grammar. * * @param query must not be {@literal null}. * @return a new {@link JpaQueryEnhancer} using JPQL. @@ -58,11 +58,11 @@ class JpaQueryEnhancer implements QueryEnhancer { Assert.notNull(query, "DeclaredQuery must not be null!"); - return new JpaQueryEnhancer(query, new JpqlQueryParser(query.getQueryString())); + return new JpaQueryEnhancer(query, JpqlQueryParser.parseQuery(query.getQueryString())); } /** - * Factory method to create a {@link JpaQueryParserSupport} for {@link DeclaredQuery} using HQL grammar. + * Factory method to create a {@link JpaQueryParser} for {@link DeclaredQuery} using HQL grammar. * * @param query must not be {@literal null}. * @return a new {@link JpaQueryEnhancer} using HQL. @@ -71,11 +71,11 @@ class JpaQueryEnhancer implements QueryEnhancer { Assert.notNull(query, "DeclaredQuery must not be null!"); - return new JpaQueryEnhancer(query, new HqlQueryParser(query.getQueryString())); + return new JpaQueryEnhancer(query, HqlQueryParser.parseQuery(query.getQueryString())); } /** - * Factory method to create a {@link JpaQueryParserSupport} for {@link DeclaredQuery} using EQL grammar. + * Factory method to create a {@link JpaQueryParser} for {@link DeclaredQuery} using EQL grammar. * * @param query must not be {@literal null}. * @return a new {@link JpaQueryEnhancer} using EQL. @@ -85,10 +85,10 @@ class JpaQueryEnhancer implements QueryEnhancer { Assert.notNull(query, "DeclaredQuery must not be null!"); - return new JpaQueryEnhancer(query, new EqlQueryParser(query.getQueryString())); + return new JpaQueryEnhancer(query, EqlQueryParser.parseQuery(query.getQueryString())); } - protected JpaQueryParserSupport getQueryParsingStrategy() { + protected JpaQueryParser getQueryParsingStrategy() { return queryParser; } @@ -116,8 +116,8 @@ class JpaQueryEnhancer implements QueryEnhancer { } /** - * Resolves the alias for the entity in the FROM clause from the JPA query. Since the {@link JpaQueryParserSupport} - * can already find the alias when generating sorted and count queries, this is mainly to serve test cases. + * Resolves the alias for the entity in the FROM clause from the JPA query. Since the {@link JpaQueryParser} can + * already find the alias when generating sorted and count queries, this is mainly to serve test cases. */ @Override public String detectAlias() { @@ -155,16 +155,16 @@ class JpaQueryEnhancer implements QueryEnhancer { } /** - * Looks up the projection of the JPA query. Since the {@link JpaQueryParserSupport} can already find the projection - * when generating sorted and count queries, this is mainly to serve test cases. + * Looks up the projection of the JPA query. Since the {@link JpaQueryParser} can already find the projection when + * generating sorted and count queries, this is mainly to serve test cases. */ @Override public String getProjection() { - return queryParser.projection(); + return queryParser.getProjection(); } /** - * Since the {@link JpaQueryParserSupport} can already fully transform sorted and count queries by itself, this is a + * Since the {@link JpaQueryParser} can already fully transform sorted and count queries by itself, this is a * placeholder method. * * @return empty set @@ -175,7 +175,7 @@ class JpaQueryEnhancer implements QueryEnhancer { } /** - * Look up the {@link DeclaredQuery} from the {@link JpaQueryParserSupport}. + * Look up the {@link DeclaredQuery} from the {@link JpaQueryParser}. */ @Override public DeclaredQuery getQuery() { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index 2bb15aa97..d56d43d1f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -24,7 +24,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; @@ -49,7 +48,6 @@ import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.StringUtils; /** @@ -100,7 +98,7 @@ public class JpaQueryMethod extends QueryMethod { private final Lazy isCollectionQuery; private final Lazy isProcedureQuery; private final Lazy> entityMetadata; - private final Map, Optional> annotationCache; + private final Lazy> metaAnnotation; /** * Creates a {@link JpaQueryMethod}. @@ -142,7 +140,8 @@ public class JpaQueryMethod extends QueryMethod { this.isCollectionQuery = Lazy.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(this.returnType)); this.isProcedureQuery = Lazy.of(() -> AnnotationUtils.findAnnotation(method, Procedure.class) != null); this.entityMetadata = Lazy.of(() -> new DefaultJpaEntityMetadata<>(getDomainClass())); - this.annotationCache = new ConcurrentReferenceHashMap<>(); + this.metaAnnotation = Lazy + .of(() -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, Meta.class))); Assert.isTrue(!(isModifyingQuery() && getParameters().hasSpecialParameter()), () -> String.format("Modifying method must not contain %s", Parameters.TYPES)); @@ -165,7 +164,7 @@ public class JpaQueryMethod extends QueryMethod { String annotatedQuery = getAnnotatedQuery(); - if (!DeclaredQuery.of(annotatedQuery, this.isNativeQuery.get()).hasNamedParameter()) { + if (!DeclaredQuery.hasNamedParameter(annotatedQuery)) { return; } @@ -201,13 +200,6 @@ public class JpaQueryMethod extends QueryMethod { return modifying.getNullable() != null; } - @SuppressWarnings("unchecked") - private Optional doFindAnnotation(Class annotationType) { - - return (Optional) this.annotationCache.computeIfAbsent(annotationType, - it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it))); - } - /** * Returns all {@link QueryHint}s annotated at this class. Note, that {@link QueryHints} * @@ -290,7 +282,7 @@ public class JpaQueryMethod extends QueryMethod { */ @Nullable Meta getMetaAnnotation() { - return doFindAnnotation(Meta.class).orElse(null); + return metaAnnotation.get().orElse(null); } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParser.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParser.java new file mode 100644 index 000000000..835b71a7b --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParser.java @@ -0,0 +1,142 @@ +/* + * Copyright 2022-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.atn.PredictionMode; +import org.antlr.v4.runtime.tree.ParseTreeVisitor; + +import org.springframework.data.domain.Sort; +import org.springframework.lang.Nullable; + +/** + * Operations needed to parse a JPA query. + * + * @author Greg Turnquist + * @author Mark Paluch + * @since 3.1 + */ +abstract class JpaQueryParser { + + private final ParserRuleContext context; + private final ParsedQueryIntrospector introspector; + private final String projection; + private final BiFunction> sortFunction; + private final BiFunction> countQueryFunction; + + JpaQueryParser(ParserRuleContext context, ParsedQueryIntrospector introspector, + @Nullable BiFunction> sortFunction, + @Nullable BiFunction> countQueryFunction) { + + this.context = context; + this.introspector = introspector; + this.sortFunction = sortFunction; + this.countQueryFunction = countQueryFunction; + this.introspector.visit(context); + + List tokens = introspector.getProjection(); + this.projection = tokens.isEmpty() ? "" : render(tokens); + } + + static

ParserRuleContext parse(String query, Function lexerFactoryFunction, + Function parserFactoryFunction, Function parseFunction) { + + Lexer lexer = lexerFactoryFunction.apply(CharStreams.fromString(query)); + P parser = parserFactoryFunction.apply(new CommonTokenStream(lexer)); + + configureParser(query, lexer, parser); + + return parseFunction.apply(parser); + } + + /** + * Generate a query using the original query with an {@literal order by} clause added (or amended) based upon the + * provider {@link Sort} parameter. + * + * @param sort can be {@literal null} + */ + String renderSortedQuery(Sort sort) { + return render(sortFunction.apply(sort, findAlias()).visit(context)); + } + + /** + * Generate a count-based query derived from the original query. + * + * @param countProjection + */ + String createCountQuery(@Nullable String countProjection) { + return render(countQueryFunction.apply(countProjection, findAlias()).visit(context)); + } + + /** + * Find the projection of the query. + */ + String getProjection() { + return this.projection; + } + + /** + * Find the alias of the query's primary FROM clause + * + * @return can be {@literal null} + */ + @Nullable + String findAlias() { + return this.introspector.getAlias(); + } + + /** + * Discern if the query has a {@code new com.example.Dto()} DTO constructor in the select clause. + * + * @return Guaranteed to be {@literal true} or {@literal false}. + */ + boolean hasConstructorExpression() { + return this.introspector.hasConstructorExpression(); + } + + /** + * Apply common configuration (SLL prediction for performance, our own error listeners). + * + * @param query + * @param lexer + * @param parser + */ + static void configureParser(String query, Lexer lexer, Parser parser) { + + BadJpqlGrammarErrorListener errorListener = new BadJpqlGrammarErrorListener(query); + + lexer.removeErrorListeners(); + lexer.addErrorListener(errorListener); + + parser.getInterpreter().setPredictionMode(PredictionMode.SLL); + + parser.removeErrorListeners(); + parser.addErrorListener(errorListener); + } + +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParserSupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParserSupport.java deleted file mode 100644 index ca17005e7..000000000 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParserSupport.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2022-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jpa.repository.query; - -import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; - -import java.util.List; - -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.Parser; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.atn.PredictionMode; -import org.springframework.data.domain.Sort; -import org.springframework.data.util.Lazy; -import org.springframework.lang.Nullable; - -/** - * Operations needed to parse a JPA query. - * - * @author Greg Turnquist - * @author Mark Paluch - * @since 3.1 - */ -abstract class JpaQueryParserSupport { - - private final ParseState state; - - JpaQueryParserSupport(String query) { - this.state = new ParseState(query); - } - - /** - * Generate a query using the original query with an @literal order by} clause added (or amended) based upon the - * provider {@link Sort} parameter. - * - * @param sort can be {@literal null} - */ - String renderSortedQuery(Sort sort) { - - try { - return render(applySort(state.getContext(), sort)); - } catch (BadJpqlGrammarException e) { - throw new IllegalArgumentException(e); - } - } - - /** - * Generate a count-based query using the original query. - * - * @param countProjection - */ - String createCountQuery(@Nullable String countProjection) { - - try { - return render(doCreateCountQuery(state.getContext(), countProjection)); - } catch (BadJpqlGrammarException e) { - throw new IllegalArgumentException(e); - } - } - - /** - * Find the projection of the query. - */ - String projection() { - - try { - List tokens = doFindProjection(state.getContext()); - return tokens.isEmpty() ? "" : render(tokens); - } catch (BadJpqlGrammarException e) { - return ""; - } - } - - /** - * Find the alias of the query's primary FROM clause - * - * @return can be {@literal null} - */ - @Nullable - String findAlias() { - - try { - return doFindAlias(state.getContext()); - } catch (BadJpqlGrammarException e) { - return null; - } - } - - /** - * Discern if the query has a {@code new com.example.Dto()} DTO constructor in the select clause. - * - * @return Guaranteed to be {@literal true} or {@literal false}. - */ - boolean hasConstructorExpression() { - - try { - return doCheckForConstructor(state.getContext()); - } catch (BadJpqlGrammarException e) { - return false; - } - } - - /** - * Parse the JPA query using its corresponding ANTLR parser. - */ - protected abstract ParserRuleContext parse(String query); - - /** - * Apply common configuration (SLL prediction for performance, our own error listeners). - * - * @param query - * @param lexer - * @param parser - */ - static void configureParser(String query, Lexer lexer, Parser parser) { - - BadJpqlGrammarErrorListener errorListener = new BadJpqlGrammarErrorListener(query); - - lexer.removeErrorListeners(); - lexer.addErrorListener(errorListener); - - parser.getInterpreter().setPredictionMode(PredictionMode.SLL); - - parser.removeErrorListeners(); - parser.addErrorListener(errorListener); - } - - /** - * Create a {@link JpaQueryParsingToken}-based query with an {@literal order by} applied/amended based upon the - * {@link Sort} parameter. - * - * @param parsedQuery - * @param sort can be {@literal null} - */ - protected abstract List applySort(ParserRuleContext parsedQuery, Sort sort); - - /** - * Create a {@link JpaQueryParsingToken}-based count query. - * - * @param parsedQuery - * @param countProjection - */ - protected abstract List doCreateCountQuery(ParserRuleContext parsedQuery, - @Nullable String countProjection); - - @Nullable - protected abstract String doFindAlias(ParserRuleContext parsedQuery); - - /** - * Find the projection of the query's primary SELECT clause. - * - * @param parsedQuery - */ - protected abstract List doFindProjection(ParserRuleContext parsedQuery); - - protected abstract boolean doCheckForConstructor(ParserRuleContext parsedQuery); - - /** - * Parser state capturing the lazily-parsed parser context. - */ - class ParseState { - - private final Lazy parsedQuery; - private volatile @Nullable BadJpqlGrammarException error; - private final String query; - - public ParseState(String query) { - this.query = query; - this.parsedQuery = Lazy.of(() -> parse(query)); - } - - public ParserRuleContext getContext() { - - BadJpqlGrammarException error = this.error; - - if (error != null) { - throw error; - } - - try { - return parsedQuery.get(); - } catch (BadJpqlGrammarException e) { - this.error = error = e; - throw error; - } - } - - public String getQuery() { - return query; - } - } - -} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParsingToken.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParsingToken.java index 0882174ef..c8d71e507 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParsingToken.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParsingToken.java @@ -15,7 +15,7 @@ */ package org.springframework.data.jpa.repository.query; -import java.util.List; +import java.util.Collection; import java.util.function.Supplier; import org.antlr.v4.runtime.Token; @@ -33,102 +33,74 @@ class JpaQueryParsingToken { /** * Commonly use tokens. */ - public static final JpaQueryParsingToken TOKEN_COMMA = new JpaQueryParsingToken(","); - public static final JpaQueryParsingToken TOKEN_DOT = new JpaQueryParsingToken(".", false); - public static final JpaQueryParsingToken TOKEN_EQUALS = new JpaQueryParsingToken("="); - public static final JpaQueryParsingToken TOKEN_OPEN_PAREN = new JpaQueryParsingToken("(", false); - public static final JpaQueryParsingToken TOKEN_CLOSE_PAREN = new JpaQueryParsingToken(")"); - public static final JpaQueryParsingToken TOKEN_ORDER_BY = new JpaQueryParsingToken("order by"); - public static final JpaQueryParsingToken TOKEN_LOWER_FUNC = new JpaQueryParsingToken("lower(", false); - public static final JpaQueryParsingToken TOKEN_SELECT_COUNT = new JpaQueryParsingToken("select count(", false); - public static final JpaQueryParsingToken TOKEN_PERCENT = new JpaQueryParsingToken("%"); - public static final JpaQueryParsingToken TOKEN_COUNT_FUNC = new JpaQueryParsingToken("count(", false); - public static final JpaQueryParsingToken TOKEN_DOUBLE_PIPE = new JpaQueryParsingToken("||"); - public static final JpaQueryParsingToken TOKEN_OPEN_SQUARE_BRACKET = new JpaQueryParsingToken("[", false); + public static final JpaQueryParsingToken TOKEN_NONE = JpaQueryParsingToken.token(""); + public static final JpaQueryParsingToken TOKEN_COMMA = JpaQueryParsingToken.token(", "); + public static final JpaQueryParsingToken TOKEN_SPACE = JpaQueryParsingToken.token(" "); + public static final JpaQueryParsingToken TOKEN_DOT = JpaQueryParsingToken.token("."); + public static final JpaQueryParsingToken TOKEN_EQUALS = JpaQueryParsingToken.token(" = "); + public static final JpaQueryParsingToken TOKEN_OPEN_PAREN = JpaQueryParsingToken.token("("); + public static final JpaQueryParsingToken TOKEN_CLOSE_PAREN = JpaQueryParsingToken.token(")"); + public static final JpaQueryParsingToken TOKEN_ORDER_BY = JpaQueryParsingToken.expression("order by"); + public static final JpaQueryParsingToken TOKEN_LOWER_FUNC = new JpaQueryParsingToken("lower("); + public static final JpaQueryParsingToken TOKEN_SELECT_COUNT = JpaQueryParsingToken.token("select count("); + public static final JpaQueryParsingToken TOKEN_COUNT_FUNC = JpaQueryParsingToken.token("count("); + public static final JpaQueryParsingToken TOKEN_DOUBLE_PIPE = JpaQueryParsingToken.token(" || "); + public static final JpaQueryParsingToken TOKEN_OPEN_SQUARE_BRACKET = JpaQueryParsingToken.token("["); public static final JpaQueryParsingToken TOKEN_CLOSE_SQUARE_BRACKET = new JpaQueryParsingToken("]"); - public static final JpaQueryParsingToken TOKEN_COLON = new JpaQueryParsingToken(":", false); - public static final JpaQueryParsingToken TOKEN_QUESTION_MARK = new JpaQueryParsingToken("?", false); - public static final JpaQueryParsingToken TOKEN_OPEN_BRACE = new JpaQueryParsingToken("{", false); + public static final JpaQueryParsingToken TOKEN_COLON = JpaQueryParsingToken.token(":"); + public static final JpaQueryParsingToken TOKEN_QUESTION_MARK = JpaQueryParsingToken.token("?"); + public static final JpaQueryParsingToken TOKEN_OPEN_BRACE = JpaQueryParsingToken.token("{"); public static final JpaQueryParsingToken TOKEN_CLOSE_BRACE = new JpaQueryParsingToken("}"); - public static final JpaQueryParsingToken TOKEN_CLOSE_SQUARE_BRACKET_BRACE = new JpaQueryParsingToken("]}"); - public static final JpaQueryParsingToken TOKEN_CLOSE_PAREN_BRACE = new JpaQueryParsingToken(")}"); - - public static final JpaQueryParsingToken TOKEN_DOUBLE_UNDERSCORE = new JpaQueryParsingToken("__"); - - public static final JpaQueryParsingToken TOKEN_AS = new JpaQueryParsingToken("AS"); - - public static final JpaQueryParsingToken TOKEN_DESC = new JpaQueryParsingToken("desc", false); - - public static final JpaQueryParsingToken TOKEN_ASC = new JpaQueryParsingToken("asc", false); - - public static final JpaQueryParsingToken TOKEN_WITH = new JpaQueryParsingToken("WITH"); - - public static final JpaQueryParsingToken TOKEN_NOT = new JpaQueryParsingToken("NOT"); - - public static final JpaQueryParsingToken TOKEN_MATERIALIZED = new JpaQueryParsingToken("materialized"); - - public static final JpaQueryParsingToken TOKEN_NULLS = new JpaQueryParsingToken("NULLS"); - - public static final JpaQueryParsingToken TOKEN_FIRST = new JpaQueryParsingToken("FIRST"); - - public static final JpaQueryParsingToken TOKEN_LAST = new JpaQueryParsingToken("LAST"); + public static final JpaQueryParsingToken TOKEN_DOUBLE_UNDERSCORE = JpaQueryParsingToken.token("__"); + public static final JpaQueryParsingToken TOKEN_AS = JpaQueryParsingToken.expression("AS"); + public static final JpaQueryParsingToken TOKEN_DESC = JpaQueryParsingToken.expression("desc"); + public static final JpaQueryParsingToken TOKEN_ASC = JpaQueryParsingToken.expression("asc"); + public static final JpaQueryParsingToken TOKEN_WITH = JpaQueryParsingToken.expression("WITH"); + public static final JpaQueryParsingToken TOKEN_NOT = JpaQueryParsingToken.expression("NOT"); + public static final JpaQueryParsingToken TOKEN_MATERIALIZED = JpaQueryParsingToken.expression("materialized"); + public static final JpaQueryParsingToken TOKEN_NULLS = JpaQueryParsingToken.expression("NULLS"); + public static final JpaQueryParsingToken TOKEN_FIRST = JpaQueryParsingToken.expression("FIRST"); + public static final JpaQueryParsingToken TOKEN_LAST = JpaQueryParsingToken.expression("LAST"); /** * The text value of the token. */ - private final Supplier token; - - /** - * Space|NoSpace after token is rendered? - */ - private final boolean space; - - JpaQueryParsingToken(Supplier token, boolean space) { - - this.token = token; - this.space = space; - } - - JpaQueryParsingToken(String token, boolean space) { - this(() -> token, space); - } - - JpaQueryParsingToken(Supplier token) { - this(token, true); - } + private final String token; JpaQueryParsingToken(String token) { - this(() -> token, true); + this.token = token; } - JpaQueryParsingToken(TerminalNode node, boolean space) { - this(node.getText(), space); + public static JpaQueryParsingToken token(TerminalNode node) { + return token(node.getText()); } - JpaQueryParsingToken(TerminalNode node) { - this(node.getText()); + public static JpaQueryParsingToken token(Token token) { + return token(token.getText()); } - JpaQueryParsingToken(Token token, boolean space) { - this(token.getText(), space); + static JpaQueryParsingToken token(String token) { + return new JpaQueryParsingToken(token); } - JpaQueryParsingToken(Token token) { - this(token.getText(), true); + static JpaQueryParsingToken expression(String expression) { + return new JpaQueryExpression(expression); + } + + public static JpaQueryParsingToken expression(Token token) { + return expression(token.getText()); + } + + public static JpaQueryParsingToken expression(TerminalNode node) { + return expression(node.getText()); + } + + public static JpaQueryParsingToken ventilated(Token op) { + return new JpaQueryParsingToken(" " + op.getText() + " "); } - /** - * Extract the token's value from it's {@link Supplier}. - */ String getToken() { - return this.token.get(); - } - - /** - * Should we render a space after the token? - */ - boolean getSpace() { - return this.space; + return token; } /** @@ -147,43 +119,18 @@ class JpaQueryParsingToken { } /** - * Switch the last {@link JpaQueryParsingToken}'s spacing to {@literal true}. + * Render a list of {@link JpaQueryParsingToken}s into a string. + * + * @param tokens + * @return rendered string containing either a query or some subset of that query */ - static void SPACE(List tokens) { + static String render(Object tokens) { - if (!tokens.isEmpty()) { - - int index = tokens.size() - 1; - - JpaQueryParsingToken lastTokenWithSpacing = new JpaQueryParsingToken(tokens.get(index).token); - tokens.remove(index); - tokens.add(lastTokenWithSpacing); + if (tokens instanceof Collection tpr) { + return render(tpr); } - } - /** - * Switch the last {@link JpaQueryParsingToken}'s spacing to {@literal false}. - */ - static void NOSPACE(List tokens) { - - if (!tokens.isEmpty()) { - - int index = tokens.size() - 1; - - JpaQueryParsingToken lastTokenWithNoSpacing = new JpaQueryParsingToken(tokens.get(index).token, false); - tokens.remove(index); - tokens.add(lastTokenWithNoSpacing); - } - } - - /** - * Drop the last entry from the list of {@link JpaQueryParsingToken}s. - */ - static void CLIP(List tokens) { - - if (!tokens.isEmpty()) { - tokens.remove(tokens.size() - 1); - } + return ((QueryRenderer.QueryRendererBuilder) tokens).build().render(); } /** @@ -192,19 +139,31 @@ class JpaQueryParsingToken { * @param tokens * @return rendered string containing either a query or some subset of that query */ - static String render(List tokens) { + static String render(Collection tokens) { StringBuilder results = new StringBuilder(); - tokens.forEach(token -> { + boolean previousExpression = false; - results.append(token.getToken()); + for (JpaQueryParsingToken jpaQueryParsingToken : tokens) { - if (token.getSpace()) { - results.append(" "); + if (previousExpression) { + if (!results.isEmpty() && results.charAt(results.length() - 1) != ' ') { + results.append(' '); + } } - }); - return results.toString().trim(); + previousExpression = jpaQueryParsingToken instanceof JpaQueryExpression; + results.append(jpaQueryParsingToken.getToken()); + } + + return results.toString(); + } + + static class JpaQueryExpression extends JpaQueryParsingToken { + + JpaQueryExpression(String token) { + super(token); + } } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryTransformerSupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryTransformerSupport.java index 6c730e792..215426616 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryTransformerSupport.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryTransformerSupport.java @@ -15,8 +15,9 @@ import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; /** - * Transformational operations needed to support either {@link HqlQueryTransformer} or {@link JpqlQueryTransformer}. - * + * Transformational operations needed to support either {@link HqlSortedQueryTransformer} or + * {@link JpqlSortedQueryTransformer}. + * * @author Greg Turnquist * @author Donghun Shin * @since 3.1 @@ -29,11 +30,7 @@ class JpaQueryTransformerSupport { + "aliases used in the select clause; If you really want to use something other than that for sorting, please use " + "JpaSort.unsafe(…)"; - private Set projectionAliases; - - JpaQueryTransformerSupport() { - this.projectionAliases = new HashSet<>(); - } + private final Set projectionAliases = new HashSet<>(); /** * Register an {@literal alias} so it can later be evaluated when applying {@link Sort}s. @@ -47,12 +44,12 @@ class JpaQueryTransformerSupport { /** * Using the primary {@literal FROM} clause's alias and a {@link Sort}, construct all the {@literal ORDER BY} * arguments. - * + * * @param primaryFromAlias * @param sort * @return */ - List generateOrderByArguments(String primaryFromAlias, Sort sort) { + List orderBy(String primaryFromAlias, Sort sort) { List tokens = new ArrayList<>(); @@ -60,20 +57,27 @@ class JpaQueryTransformerSupport { checkSortExpression(order); - if (order.isIgnoreCase()) { - tokens.add(TOKEN_LOWER_FUNC); - } - - tokens.add(new JpaQueryParsingToken(() -> generateOrderByArgument(primaryFromAlias, order))); + StringBuilder builder = new StringBuilder(); if (order.isIgnoreCase()) { - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_LOWER_FUNC.getToken()); } - tokens.add(order.isDescending() ? TOKEN_DESC : TOKEN_ASC); - tokens.add(TOKEN_COMMA); + + builder.append(generateOrderByArgument(primaryFromAlias, order)); + + if (order.isIgnoreCase()) { + builder.append(TOKEN_CLOSE_PAREN); + } + builder.append(" "); + + builder.append(order.isDescending() ? TOKEN_DESC : TOKEN_ASC); + + if (!tokens.isEmpty()) { + tokens.add(TOKEN_COMMA); + } + + tokens.add(JpaQueryParsingToken.token(builder.toString())); }); - CLIP(tokens); return tokens; } @@ -98,7 +102,7 @@ class JpaQueryTransformerSupport { /** * Using the {@code primaryFromAlias} and the {@link org.springframework.data.domain.Sort.Order}, construct a suitable * argument to be added to an {@literal ORDER BY} expression. - * + * * @param primaryFromAlias * @param order * @return @@ -120,7 +124,7 @@ class JpaQueryTransformerSupport { * @param primaryFromAlias * @return boolean whether or not to apply the primary FROM clause's alias as a prefix */ - private boolean shouldPrefixWithAlias(Sort.Order order, String primaryFromAlias) { + private boolean shouldPrefixWithAlias(Sort.Order order, @Nullable String primaryFromAlias) { // If there is no primary alias if (ObjectUtils.isEmpty(primaryFromAlias)) { 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 new file mode 100644 index 000000000..c2898686c --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java @@ -0,0 +1,111 @@ +/* + * Copyright 2022-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import java.util.List; + +import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.lang.Nullable; + +/** + * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed JPQL query into a + * {@code COUNT(…)} query. + * + * @author Greg Turnquist + * @author Mark Paluch + * @since 3.1 + */ +@SuppressWarnings("ConstantValue") +class JpqlCountQueryTransformer extends JpqlQueryRenderer { + + private final @Nullable String countProjection; + private final @Nullable String primaryFromAlias; + + JpqlCountQueryTransformer(@Nullable String countProjection, @Nullable String primaryFromAlias) { + this.countProjection = countProjection; + this.primaryFromAlias = primaryFromAlias; + } + + @Override + public QueryRenderer.QueryRendererBuilder visitSelect_statement(JpqlParser.Select_statementContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.select_clause())); + builder.appendExpression(visit(ctx.from_clause())); + + if (ctx.where_clause() != null) { + builder.appendExpression(visit(ctx.where_clause())); + } + if (ctx.groupby_clause() != null) { + builder.appendExpression(visit(ctx.groupby_clause())); + } + if (ctx.having_clause() != null) { + builder.appendExpression(visit(ctx.having_clause())); + } + + return builder; + } + + @Override + public QueryRendererBuilder visitSelect_clause(JpqlParser.Select_clauseContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(JpaQueryParsingToken.expression(ctx.SELECT())); + builder.append(TOKEN_COUNT_FUNC); + + if (countProjection != null) { + builder.append(JpaQueryParsingToken.token(countProjection)); + } + + QueryRendererBuilder nested = QueryRenderer.builder(); + + if (ctx.DISTINCT() != null) { + nested.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); + } + + if (countProjection == null) { + + if (ctx.DISTINCT() != null) { + + QueryRendererBuilder selectionListbuilder = QueryRendererBuilder.concat(ctx.select_item(), this::visit, + TOKEN_COMMA); + + List countSelection = QueryTransformers + .filterCountSelection(selectionListbuilder.build().stream().toList()); + + if (countSelection.stream().anyMatch(jpqlToken -> jpqlToken.getToken().contains("new"))) { + // constructor + nested.append(new JpaQueryParsingToken(primaryFromAlias)); + } else { + // keep all the select items to distinct against + nested.append(countSelection); + } + } else { + nested.append(new JpaQueryParsingToken(primaryFromAlias)); + } + } + + builder.appendInline(nested); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java new file mode 100644 index 000000000..d02030a0b --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java @@ -0,0 +1,96 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.lang.Nullable; + +/** + * {@link ParsedQueryIntrospector} for JPQL queries. + * + * @author Mark Paluch + */ +@SuppressWarnings({ "UnreachableCode", "ConstantValue" }) +class JpqlQueryIntrospector extends JpqlBaseVisitor implements ParsedQueryIntrospector { + + private final JpqlQueryRenderer renderer = new JpqlQueryRenderer(); + + private @Nullable String primaryFromAlias = null; + private @Nullable List projection; + private boolean projectionProcessed; + private boolean hasConstructorExpression = false; + + @Nullable + public String getAlias() { + return primaryFromAlias; + } + + public List getProjection() { + return projection == null ? Collections.emptyList() : projection; + } + + public boolean hasConstructorExpression() { + return hasConstructorExpression; + } + + @Override + public Void visitRange_variable_declaration(JpqlParser.Range_variable_declarationContext ctx) { + + if (primaryFromAlias == null) { + primaryFromAlias = ctx.identification_variable() == null ? ctx.entity_name().getText() + : ctx.identification_variable().getText(); + } + + return null; + } + + @Override + public Void visitSelect_clause(JpqlParser.Select_clauseContext ctx) { + + List selections = ctx.select_item(); + List selectItemTokens = new ArrayList<>(selections.size() * 2); + + for (JpqlParser.Select_itemContext selection : selections) { + + if (!selectItemTokens.isEmpty()) { + selectItemTokens.add(TOKEN_COMMA); + } + + selectItemTokens.add(JpaQueryParsingToken.token(renderer.visitSelect_item(selection).build().render())); + } + + if (!projectionProcessed) { + projection = selectItemTokens; + projectionProcessed = true; + } + + return super.visitSelect_clause(ctx); + } + + @Override + public Void visitConstructor_expression(JpqlParser.Constructor_expressionContext ctx) { + + hasConstructorExpression = true; + + return super.visitConstructor_expression(ctx); + } + +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryParser.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryParser.java index 98988cf03..39276bcf5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryParser.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryParser.java @@ -15,120 +15,29 @@ */ package org.springframework.data.jpa.repository.query; -import java.util.List; - -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.ParserRuleContext; -import org.springframework.data.domain.Sort; -import org.springframework.lang.Nullable; - /** - * Implements the {@code JPQL} parsing operations of a {@link JpaQueryParserSupport} using the ANTLR-generated - * {@link JpqlParser} and {@link JpqlQueryTransformer}. + * Implements the {@code JPQL} parsing operations of a {@link JpaQueryParser} using the ANTLR-generated + * {@link JpqlParser} and {@link JpqlSortedQueryTransformer}. * * @author Greg Turnquist * @author Mark Paluch * @since 3.1 */ -class JpqlQueryParser extends JpaQueryParserSupport { +class JpqlQueryParser extends JpaQueryParser { - JpqlQueryParser(String query) { - super(query); + private JpqlQueryParser(String query) { + super(parse(query, JpqlLexer::new, JpqlParser::new, JpqlParser::start), new JpqlQueryIntrospector(), + JpqlSortedQueryTransformer::new, JpqlCountQueryTransformer::new); } /** - * Convenience method to parse a JPQL query. Will throw a {@link BadJpqlGrammarException} if the query is invalid. + * Parse a JPQL query. * * @param query - * @return a parsed query, ready for postprocessing + * @return the query parser. + * @throws BadJpqlGrammarException */ - public static ParserRuleContext parseQuery(String query) { - - JpqlLexer lexer = new JpqlLexer(CharStreams.fromString(query)); - JpqlParser parser = new JpqlParser(new CommonTokenStream(lexer)); - - configureParser(query, lexer, parser); - - return parser.start(); - } - - - /** - * Parse the query using {@link #parseQuery(String)}. - * - * @return a parsed query - */ - @Override - protected ParserRuleContext parse(String query) { - return parseQuery(query); - } - - /** - * Use the {@link JpqlQueryTransformer} to transform the original query into a query with the {@link Sort} applied. - * - * @param parsedQuery - * @param sort can be {@literal null} - * @return list of {@link JpaQueryParsingToken}s - */ - @Override - protected List applySort(ParserRuleContext parsedQuery, Sort sort) { - return new JpqlQueryTransformer(sort).visit(parsedQuery); - } - - /** - * Use the {@link JpqlQueryTransformer} to transform the original query into a count query. - * - * @param parsedQuery - * @param countProjection - * @return list of {@link JpaQueryParsingToken}s - */ - @Override - protected List doCreateCountQuery(ParserRuleContext parsedQuery, - @Nullable String countProjection) { - return new JpqlQueryTransformer(true, countProjection).visit(parsedQuery); - } - - /** - * Run the parsed query through {@link JpqlQueryTransformer} to find the primary FROM clause's alias. - * - * @param parsedQuery - * @return can be {@literal null} - */ - @Override - protected String doFindAlias(ParserRuleContext parsedQuery) { - - JpqlQueryTransformer transformVisitor = new JpqlQueryTransformer(); - transformVisitor.visit(parsedQuery); - return transformVisitor.getAlias(); - } - - /** - * Use {@link JpqlQueryTransformer} to find the projection of the query. - * - * @param parsedQuery - * @return - */ - @Override - protected List doFindProjection(ParserRuleContext parsedQuery) { - - JpqlQueryTransformer transformVisitor = new JpqlQueryTransformer(); - transformVisitor.visit(parsedQuery); - return transformVisitor.getProjection(); - } - - /** - * Use {@link JpqlQueryTransformer} to detect if the query uses a {@code new com.example.Dto()} DTO constructor in the - * primary select clause. - * - * @param parsedQuery - * @return Guaranteed to be {@literal true} or {@literal false}. - */ - @Override - protected boolean doCheckForConstructor(ParserRuleContext parsedQuery) { - - JpqlQueryTransformer transformVisitor = new JpqlQueryTransformer(); - transformVisitor.visit(parsedQuery); - return transformVisitor.hasConstructorExpression(); + public static JpqlQueryParser parseQuery(String query) throws BadJpqlGrammarException { + return new JpqlQueryParser(query); } } 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 492215e48..3ff0a01f5 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 @@ -20,7 +20,10 @@ import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken import java.util.ArrayList; import java.util.List; +import org.antlr.v4.runtime.tree.ParseTree; + import org.springframework.data.jpa.repository.query.JpqlParser.Reserved_wordContext; +import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders a JPQL query without making any changes. @@ -30,15 +33,15 @@ import org.springframework.data.jpa.repository.query.JpqlParser.Reserved_wordCon * @since 3.1 */ @SuppressWarnings({ "ConstantConditions", "DuplicatedCode" }) -class JpqlQueryRenderer extends JpqlBaseVisitor> { +class JpqlQueryRenderer extends JpqlBaseVisitor { @Override - public List visitStart(JpqlParser.StartContext ctx) { + public QueryRendererBuilder visitStart(JpqlParser.StartContext ctx) { return visit(ctx.ql_statement()); } @Override - public List visitQl_statement(JpqlParser.Ql_statementContext ctx) { + public QueryRendererBuilder visitQl_statement(JpqlParser.Ql_statementContext ctx) { if (ctx.select_statement() != null) { return visit(ctx.select_statement()); @@ -47,86 +50,85 @@ class JpqlQueryRenderer extends JpqlBaseVisitor> { } else if (ctx.delete_statement() != null) { return visit(ctx.delete_statement()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitSelect_statement(JpqlParser.Select_statementContext ctx) { + public QueryRendererBuilder visitSelect_statement(JpqlParser.Select_statementContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.select_clause())); - tokens.addAll(visit(ctx.from_clause())); + builder.appendExpression(visit(ctx.select_clause())); + builder.appendExpression(visit(ctx.from_clause())); if (ctx.where_clause() != null) { - tokens.addAll(visit(ctx.where_clause())); + builder.appendExpression(visit(ctx.where_clause())); } if (ctx.groupby_clause() != null) { - tokens.addAll(visit(ctx.groupby_clause())); + builder.appendExpression(visit(ctx.groupby_clause())); } if (ctx.having_clause() != null) { - tokens.addAll(visit(ctx.having_clause())); + builder.appendExpression(visit(ctx.having_clause())); } if (ctx.orderby_clause() != null) { - tokens.addAll(visit(ctx.orderby_clause())); + builder.appendExpression(visit(ctx.orderby_clause())); } - return tokens; + return builder; } @Override - public List visitUpdate_statement(JpqlParser.Update_statementContext ctx) { + public QueryRendererBuilder visitUpdate_statement(JpqlParser.Update_statementContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.update_clause())); + builder.append(visit(ctx.update_clause())); if (ctx.where_clause() != null) { - tokens.addAll(visit(ctx.where_clause())); + builder.append(visit(ctx.where_clause())); } - return tokens; + return builder; } @Override - public List visitDelete_statement(JpqlParser.Delete_statementContext ctx) { + public QueryRendererBuilder visitDelete_statement(JpqlParser.Delete_statementContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.delete_clause())); + builder.appendExpression(visit(ctx.delete_clause())); if (ctx.where_clause() != null) { - tokens.addAll(visit(ctx.where_clause())); + builder.appendExpression(visit(ctx.where_clause())); } - return tokens; + return builder; } @Override - public List visitFrom_clause(JpqlParser.From_clauseContext ctx) { + public QueryRendererBuilder visitFrom_clause(JpqlParser.From_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.FROM(), true)); - tokens.addAll(visit(ctx.identification_variable_declaration())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.appendInline(visit(ctx.identification_variable_declaration())); - ctx.identificationVariableDeclarationOrCollectionMemberDeclaration() - .forEach(identificationVariableDeclarationOrCollectionMemberDeclarationContext -> { - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(identificationVariableDeclarationOrCollectionMemberDeclarationContext)); - }); - SPACE(tokens); + if (!ctx.identificationVariableDeclarationOrCollectionMemberDeclaration().isEmpty()) { - return tokens; + builder.append(TOKEN_COMMA); + builder.appendExpression(QueryRendererBuilder + .concat(ctx.identificationVariableDeclarationOrCollectionMemberDeclaration(), this::visit, TOKEN_COMMA)); + } + + return builder; } @Override - public List visitIdentificationVariableDeclarationOrCollectionMemberDeclaration( + public QueryRendererBuilder visitIdentificationVariableDeclarationOrCollectionMemberDeclaration( JpqlParser.IdentificationVariableDeclarationOrCollectionMemberDeclarationContext ctx) { if (ctx.identification_variable_declaration() != null) { @@ -134,533 +136,478 @@ class JpqlQueryRenderer extends JpqlBaseVisitor> { } else if (ctx.collection_member_declaration() != null) { return visit(ctx.collection_member_declaration()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitIdentification_variable_declaration( + public QueryRendererBuilder visitIdentification_variable_declaration( JpqlParser.Identification_variable_declarationContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.range_variable_declaration())); + builder.appendExpression(visit(ctx.range_variable_declaration())); ctx.join().forEach(joinContext -> { - tokens.addAll(visit(joinContext)); + builder.append(visit(joinContext)); }); + ctx.fetch_join().forEach(fetchJoinContext -> { - tokens.addAll(visit(fetchJoinContext)); + builder.append(visit(fetchJoinContext)); }); - return tokens; + return builder; } @Override - public List visitRange_variable_declaration(JpqlParser.Range_variable_declarationContext ctx) { + public QueryRendererBuilder visitRange_variable_declaration(JpqlParser.Range_variable_declarationContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.entity_name())); + builder.appendExpression(visit(ctx.entity_name())); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } - tokens.addAll(visit(ctx.identification_variable())); + builder.append(visit(ctx.identification_variable())); - return tokens; + return builder; } @Override - public List visitJoin(JpqlParser.JoinContext ctx) { + public QueryRendererBuilder visitJoin(JpqlParser.JoinContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.join_spec())); - tokens.addAll(visit(ctx.join_association_path_expression())); + builder.append(visit(ctx.join_spec())); + builder.append(visit(ctx.join_association_path_expression())); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } - tokens.addAll(visit(ctx.identification_variable())); + builder.append(visit(ctx.identification_variable())); if (ctx.join_condition() != null) { - tokens.addAll(visit(ctx.join_condition())); + builder.append(visit(ctx.join_condition())); } - return tokens; + return builder; } @Override - public List visitFetch_join(JpqlParser.Fetch_joinContext ctx) { + public QueryRendererBuilder visitFetch_join(JpqlParser.Fetch_joinContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.join_spec())); - tokens.add(new JpaQueryParsingToken(ctx.FETCH())); - tokens.addAll(visit(ctx.join_association_path_expression())); + builder.append(visit(ctx.join_spec())); + builder.append(JpaQueryParsingToken.expression(ctx.FETCH())); + builder.append(visit(ctx.join_association_path_expression())); - return tokens; + return builder; } @Override - public List visitJoin_spec(JpqlParser.Join_specContext ctx) { + public QueryRendererBuilder visitJoin_spec(JpqlParser.Join_specContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.LEFT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LEFT())); + builder.append(JpaQueryParsingToken.expression(ctx.LEFT())); } if (ctx.OUTER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.OUTER())); + builder.append(JpaQueryParsingToken.expression(ctx.OUTER())); } if (ctx.INNER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INNER())); + builder.append(JpaQueryParsingToken.expression(ctx.INNER())); } if (ctx.JOIN() != null) { - tokens.add(new JpaQueryParsingToken(ctx.JOIN())); + builder.append(JpaQueryParsingToken.expression(ctx.JOIN())); } - return tokens; + return builder; } @Override - public List visitJoin_condition(JpqlParser.Join_conditionContext ctx) { + public QueryRendererBuilder visitJoin_condition(JpqlParser.Join_conditionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.ON())); - tokens.addAll(visit(ctx.conditional_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.ON())); + builder.appendExpression(visit(ctx.conditional_expression())); - return tokens; + return builder; } @Override - public List visitJoin_association_path_expression( + public QueryRendererBuilder visitJoin_association_path_expression( JpqlParser.Join_association_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.TREAT() == null) { if (ctx.join_collection_valued_path_expression() != null) { - tokens.addAll(visit(ctx.join_collection_valued_path_expression())); + builder.appendExpression(visit(ctx.join_collection_valued_path_expression())); } else if (ctx.join_single_valued_path_expression() != null) { - tokens.addAll(visit(ctx.join_single_valued_path_expression())); + builder.appendExpression(visit(ctx.join_single_valued_path_expression())); } } else { if (ctx.join_collection_valued_path_expression() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TREAT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.join_collection_valued_path_expression())); - tokens.add(new JpaQueryParsingToken(ctx.AS())); - tokens.addAll(visit(ctx.subtype())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TREAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.join_collection_valued_path_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); + builder.appendInline(visit(ctx.subtype())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.join_single_valued_path_expression() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TREAT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.join_single_valued_path_expression())); - tokens.add(new JpaQueryParsingToken(ctx.AS())); - tokens.addAll(visit(ctx.subtype())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TREAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.join_single_valued_path_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); + builder.appendInline(visit(ctx.subtype())); + builder.append(TOKEN_CLOSE_PAREN); } } - return tokens; + return builder; } @Override - public List visitJoin_collection_valued_path_expression( + public QueryRendererBuilder visitJoin_collection_valued_path_expression( JpqlParser.Join_collection_valued_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + List items = new ArrayList<>(3 + ctx.single_valued_embeddable_object_field().size()); - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); + items.add(ctx.identification_variable()); + items.addAll(ctx.single_valued_embeddable_object_field()); + items.add(ctx.collection_valued_field()); - ctx.single_valued_embeddable_object_field().forEach(singleValuedEmbeddableObjectFieldContext -> { - tokens.addAll(visit(singleValuedEmbeddableObjectFieldContext)); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - }); - - tokens.addAll(visit(ctx.collection_valued_field())); - - return tokens; + return QueryRendererBuilder.concat(items, this::visit, TOKEN_DOT); } @Override - public List visitJoin_single_valued_path_expression( + public QueryRendererBuilder visitJoin_single_valued_path_expression( JpqlParser.Join_single_valued_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + List items = new ArrayList<>(3 + ctx.single_valued_embeddable_object_field().size()); - tokens.addAll(visit(ctx.identification_variable())); - tokens.add(TOKEN_DOT); + items.add(ctx.identification_variable()); + items.addAll(ctx.single_valued_embeddable_object_field()); + items.add(ctx.single_valued_object_field()); - ctx.single_valued_embeddable_object_field().forEach(singleValuedEmbeddableObjectFieldContext -> { - tokens.addAll(visit(singleValuedEmbeddableObjectFieldContext)); - tokens.add(TOKEN_DOT); - }); - - tokens.addAll(visit(ctx.single_valued_object_field())); - - return tokens; + return QueryRendererBuilder.concat(items, this::visit, TOKEN_DOT); } @Override - public List visitCollection_member_declaration( - JpqlParser.Collection_member_declarationContext ctx) { + public QueryRendererBuilder visitCollection_member_declaration(JpqlParser.Collection_member_declarationContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.IN(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.collection_valued_path_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.IN())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.collection_valued_path_expression())); + builder.append(TOKEN_CLOSE_PAREN); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } - tokens.addAll(visit(ctx.identification_variable())); + builder.appendExpression(visit(ctx.identification_variable())); - return tokens; + return builder; } @Override - public List visitQualified_identification_variable( + public QueryRendererBuilder visitQualified_identification_variable( JpqlParser.Qualified_identification_variableContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.map_field_identification_variable() != null) { - tokens.addAll(visit(ctx.map_field_identification_variable())); + builder.append(visit(ctx.map_field_identification_variable())); } else if (ctx.identification_variable() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ENTRY())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.identification_variable())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.expression(ctx.ENTRY())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.identification_variable())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitMap_field_identification_variable( + public QueryRendererBuilder visitMap_field_identification_variable( JpqlParser.Map_field_identification_variableContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.KEY() != null) { - tokens.add(new JpaQueryParsingToken(ctx.KEY(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.KEY())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.identification_variable())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.VALUE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.VALUE(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.VALUE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.identification_variable())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitSingle_valued_path_expression( - JpqlParser.Single_valued_path_expressionContext ctx) { + public QueryRendererBuilder visitSingle_valued_path_expression(JpqlParser.Single_valued_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.qualified_identification_variable() != null) { - tokens.addAll(visit(ctx.qualified_identification_variable())); + builder.append(visit(ctx.qualified_identification_variable())); } else if (ctx.qualified_identification_variable() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TREAT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.qualified_identification_variable())); - tokens.add(new JpaQueryParsingToken(ctx.AS())); - tokens.addAll(visit(ctx.subtype())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TREAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.qualified_identification_variable())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); + builder.appendInline(visit(ctx.subtype())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.state_field_path_expression() != null) { - tokens.addAll(visit(ctx.state_field_path_expression())); + builder.append(visit(ctx.state_field_path_expression())); } else if (ctx.single_valued_object_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_object_path_expression())); + builder.append(visit(ctx.single_valued_object_path_expression())); } - return tokens; + return builder; } @Override - public List visitGeneral_identification_variable( + public QueryRendererBuilder visitGeneral_identification_variable( JpqlParser.General_identification_variableContext ctx) { - List tokens = new ArrayList<>(); - if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); } else if (ctx.map_field_identification_variable() != null) { - tokens.addAll(visit(ctx.map_field_identification_variable())); + return visit(ctx.map_field_identification_variable()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitGeneral_subpath(JpqlParser.General_subpathContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitGeneral_subpath(JpqlParser.General_subpathContext ctx) { if (ctx.simple_subpath() != null) { - tokens.addAll(visit(ctx.simple_subpath())); + return visit(ctx.simple_subpath()); } else if (ctx.treated_subpath() != null) { - tokens.addAll(visit(ctx.treated_subpath())); + List items = new ArrayList<>(1 + ctx.single_valued_object_field().size()); - ctx.single_valued_object_field().forEach(singleValuedObjectFieldContext -> { - tokens.add(TOKEN_DOT); - tokens.addAll(visit(singleValuedObjectFieldContext)); - }); + items.add(ctx.treated_subpath()); + items.addAll(ctx.single_valued_object_field()); + return QueryRendererBuilder.concat(items, this::visit, TOKEN_DOT); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitSimple_subpath(JpqlParser.Simple_subpathContext ctx) { + public QueryRendererBuilder visitSimple_subpath(JpqlParser.Simple_subpathContext ctx) { - List tokens = new ArrayList<>(); + List items = new ArrayList<>(1 + ctx.single_valued_object_field().size()); - tokens.addAll(visit(ctx.general_identification_variable())); - NOSPACE(tokens); - - ctx.single_valued_object_field().forEach(singleValuedObjectFieldContext -> { - tokens.add(TOKEN_DOT); - tokens.addAll(visit(singleValuedObjectFieldContext)); - NOSPACE(tokens); - }); - SPACE(tokens); - - return tokens; + items.add(ctx.general_identification_variable()); + items.addAll(ctx.single_valued_object_field()); + return QueryRendererBuilder.concat(items, this::visit, TOKEN_DOT); } @Override - public List visitTreated_subpath(JpqlParser.Treated_subpathContext ctx) { + public QueryRendererBuilder visitTreated_subpath(JpqlParser.Treated_subpathContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.TREAT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.general_subpath())); - SPACE(tokens); - tokens.add(new JpaQueryParsingToken(ctx.AS())); - tokens.addAll(visit(ctx.subtype())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TREAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.general_subpath())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); + builder.appendInline(visit(ctx.subtype())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitState_field_path_expression( - JpqlParser.State_field_path_expressionContext ctx) { + public QueryRendererBuilder visitState_field_path_expression(JpqlParser.State_field_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.general_subpath())); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - tokens.addAll(visit(ctx.state_field())); + builder.appendInline(visit(ctx.general_subpath())); + builder.append(TOKEN_DOT); + builder.appendInline(visit(ctx.state_field())); - return tokens; + return builder; } @Override - public List visitState_valued_path_expression( - JpqlParser.State_valued_path_expressionContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitState_valued_path_expression(JpqlParser.State_valued_path_expressionContext ctx) { if (ctx.state_field_path_expression() != null) { - tokens.addAll(visit(ctx.state_field_path_expression())); + return visit(ctx.state_field_path_expression()); } else if (ctx.general_identification_variable() != null) { - tokens.addAll(visit(ctx.general_identification_variable())); + return visit(ctx.general_identification_variable()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitSingle_valued_object_path_expression( + public QueryRendererBuilder visitSingle_valued_object_path_expression( JpqlParser.Single_valued_object_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.general_subpath())); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - tokens.addAll(visit(ctx.single_valued_object_field())); + builder.appendInline(visit(ctx.general_subpath())); + builder.append(TOKEN_DOT); + builder.appendInline(visit(ctx.single_valued_object_field())); - return tokens; + return builder; } @Override - public List visitCollection_valued_path_expression( + public QueryRendererBuilder visitCollection_valued_path_expression( JpqlParser.Collection_valued_path_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.general_subpath())); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - tokens.addAll(visit(ctx.collection_value_field())); + builder.appendInline(visit(ctx.general_subpath())); + builder.append(TOKEN_DOT); + builder.appendInline(visit(ctx.collection_value_field())); - return tokens; + return builder; } @Override - public List visitUpdate_clause(JpqlParser.Update_clauseContext ctx) { + public QueryRendererBuilder visitUpdate_clause(JpqlParser.Update_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.UPDATE())); - tokens.addAll(visit(ctx.entity_name())); + builder.append(JpaQueryParsingToken.expression(ctx.UPDATE())); + builder.appendExpression(visit(ctx.entity_name())); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } + if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.appendExpression(visit(ctx.identification_variable())); } - tokens.add(new JpaQueryParsingToken(ctx.SET())); + builder.append(JpaQueryParsingToken.expression(ctx.SET())); + builder.append(QueryRendererBuilder.concat(ctx.update_item(), this::visit, TOKEN_COMMA)); - ctx.update_item().forEach(updateItemContext -> { - tokens.addAll(visit(updateItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); - - return tokens; + return builder; } @Override - public List visitUpdate_item(JpqlParser.Update_itemContext ctx) { + public QueryRendererBuilder visitUpdate_item(JpqlParser.Update_itemContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); + + List items = new ArrayList<>(2 + ctx.single_valued_embeddable_object_field().size()); if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); + items.add(ctx.identification_variable()); } - ctx.single_valued_embeddable_object_field().forEach(singleValuedEmbeddableObjectFieldContext -> { - tokens.addAll(visit(singleValuedEmbeddableObjectFieldContext)); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - }); + items.addAll(ctx.single_valued_embeddable_object_field()); if (ctx.state_field() != null) { - tokens.addAll(visit(ctx.state_field())); + items.add(ctx.state_field()); } else if (ctx.single_valued_object_field() != null) { - tokens.addAll(visit(ctx.single_valued_object_field())); + items.add(ctx.single_valued_object_field()); } - tokens.add(TOKEN_EQUALS); - tokens.addAll(visit(ctx.new_value())); + builder.appendInline(QueryRendererBuilder.concat(items, this::visit, TOKEN_DOT)); + builder.append(TOKEN_EQUALS); + builder.appendInline(visit(ctx.new_value())); - return tokens; + return builder; } @Override - public List visitNew_value(JpqlParser.New_valueContext ctx) { + public QueryRendererBuilder visitNew_value(JpqlParser.New_valueContext ctx) { if (ctx.scalar_expression() != null) { return visit(ctx.scalar_expression()); } else if (ctx.simple_entity_expression() != null) { return visit(ctx.simple_entity_expression()); } else if (ctx.NULL() != null) { - return List.of(new JpaQueryParsingToken(ctx.NULL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.NULL())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitDelete_clause(JpqlParser.Delete_clauseContext ctx) { + public QueryRendererBuilder visitDelete_clause(JpqlParser.Delete_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.DELETE())); - tokens.add(new JpaQueryParsingToken(ctx.FROM())); - tokens.addAll(visit(ctx.entity_name())); + builder.append(JpaQueryParsingToken.expression(ctx.DELETE())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.appendExpression(visit(ctx.entity_name())); if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + builder.append(JpaQueryParsingToken.expression(ctx.AS())); } if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.appendExpression(visit(ctx.identification_variable())); } - return tokens; + return builder; } @Override - public List visitSelect_clause(JpqlParser.Select_clauseContext ctx) { + public QueryRendererBuilder visitSelect_clause(JpqlParser.Select_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.SELECT())); + builder.append(JpaQueryParsingToken.expression(ctx.SELECT())); if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); } - ctx.select_item().forEach(selectItemContext -> { - tokens.addAll(visit(selectItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); + builder.append(QueryRendererBuilder.concat(ctx.select_item(), this::visit, TOKEN_COMMA)); - return tokens; + return builder; } @Override - public List visitSelect_item(JpqlParser.Select_itemContext ctx) { + public QueryRendererBuilder visitSelect_item(JpqlParser.Select_itemContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.select_expression())); - SPACE(tokens); + builder.appendExpression(visit(ctx.select_expression())); - if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); + if (ctx.AS() != null || ctx.result_variable() != null) { + + if (ctx.AS() != null) { + builder.append(JpaQueryParsingToken.expression(ctx.AS())); + } + + if (ctx.result_variable() != null) { + builder.appendExpression(visit(ctx.result_variable())); + } } - if (ctx.result_variable() != null) { - tokens.addAll(visit(ctx.result_variable())); - } - - return tokens; + return builder; } @Override - public List visitSelect_expression(JpqlParser.Select_expressionContext ctx) { + public QueryRendererBuilder visitSelect_expression(JpqlParser.Select_expressionContext ctx) { if (ctx.single_valued_path_expression() != null) { return visit(ctx.single_valued_path_expression()); @@ -674,1316 +621,1208 @@ class JpqlQueryRenderer extends JpqlBaseVisitor> { return visit(ctx.identification_variable()); } else { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.OBJECT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.OBJECT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.identification_variable())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } } else if (ctx.constructor_expression() != null) { return visit(ctx.constructor_expression()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitConstructor_expression(JpqlParser.Constructor_expressionContext ctx) { + public QueryRendererBuilder visitConstructor_expression(JpqlParser.Constructor_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.NEW())); - tokens.addAll(visit(ctx.constructor_name())); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(JpaQueryParsingToken.expression(ctx.NEW())); + builder.append(visit(ctx.constructor_name())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryRendererBuilder.concat(ctx.constructor_item(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); - ctx.constructor_item().forEach(constructorItemContext -> { - tokens.addAll(visit(constructorItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - tokens.add(TOKEN_CLOSE_PAREN); - - return tokens; + return builder; } @Override - public List visitConstructor_item(JpqlParser.Constructor_itemContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitConstructor_item(JpqlParser.Constructor_itemContext ctx) { if (ctx.single_valued_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_path_expression())); + return visit(ctx.single_valued_path_expression()); } else if (ctx.scalar_expression() != null) { - tokens.addAll(visit(ctx.scalar_expression())); + return visit(ctx.scalar_expression()); } else if (ctx.aggregate_expression() != null) { - tokens.addAll(visit(ctx.aggregate_expression())); + return visit(ctx.aggregate_expression()); } else if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); } else if (ctx.literal() != null) { - tokens.addAll(visit(ctx.literal())); + return visit(ctx.literal()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitAggregate_expression(JpqlParser.Aggregate_expressionContext ctx) { + public QueryRendererBuilder visitAggregate_expression(JpqlParser.Aggregate_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.AVG() != null || ctx.MAX() != null || ctx.MIN() != null || ctx.SUM() != null) { if (ctx.AVG() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AVG(), false)); + builder.append(JpaQueryParsingToken.token(ctx.AVG())); } if (ctx.MAX() != null) { - tokens.add(new JpaQueryParsingToken(ctx.MAX(), false)); + builder.append(JpaQueryParsingToken.token(ctx.MAX())); } if (ctx.MIN() != null) { - tokens.add(new JpaQueryParsingToken(ctx.MIN(), false)); + builder.append(JpaQueryParsingToken.token(ctx.MIN())); } if (ctx.SUM() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SUM(), false)); + builder.append(JpaQueryParsingToken.token(ctx.SUM())); } - tokens.add(TOKEN_OPEN_PAREN); + builder.append(TOKEN_OPEN_PAREN); if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); } - tokens.addAll(visit(ctx.state_valued_path_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.appendInline(visit(ctx.state_valued_path_expression())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.COUNT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.COUNT(), false)); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.COUNT())); + builder.append(TOKEN_OPEN_PAREN); if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); } if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + builder.appendInline(visit(ctx.identification_variable())); } else if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.appendInline(visit(ctx.state_valued_path_expression())); } else if (ctx.single_valued_object_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_object_path_expression())); + builder.appendInline(visit(ctx.single_valued_object_path_expression())); } - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.function_invocation() != null) { - tokens.addAll(visit(ctx.function_invocation())); + builder.append(visit(ctx.function_invocation())); } - return tokens; + return builder; } @Override - public List visitWhere_clause(JpqlParser.Where_clauseContext ctx) { + public QueryRendererBuilder visitWhere_clause(JpqlParser.Where_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.WHERE(), true)); - tokens.addAll(visit(ctx.conditional_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.WHERE())); + builder.appendExpression(visit(ctx.conditional_expression())); - return tokens; + return builder; } @Override - public List visitGroupby_clause(JpqlParser.Groupby_clauseContext ctx) { + public QueryRendererBuilder visitGroupby_clause(JpqlParser.Groupby_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.GROUP())); - tokens.add(new JpaQueryParsingToken(ctx.BY())); - ctx.groupby_item().forEach(groupbyItemContext -> { - tokens.addAll(visit(groupbyItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); + builder.append(JpaQueryParsingToken.expression(ctx.GROUP())); + builder.append(JpaQueryParsingToken.expression(ctx.BY())); + builder.appendExpression(QueryRendererBuilder.concat(ctx.groupby_item(), this::visit, TOKEN_COMMA)); - return tokens; + return builder; } @Override - public List visitGroupby_item(JpqlParser.Groupby_itemContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitGroupby_item(JpqlParser.Groupby_itemContext ctx) { if (ctx.single_valued_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_path_expression())); + return visit(ctx.single_valued_path_expression()); } else if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitHaving_clause(JpqlParser.Having_clauseContext ctx) { + public QueryRendererBuilder visitHaving_clause(JpqlParser.Having_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.HAVING())); - tokens.addAll(visit(ctx.conditional_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.HAVING())); + builder.appendExpression(visit(ctx.conditional_expression())); - return tokens; + return builder; } @Override - public List visitOrderby_clause(JpqlParser.Orderby_clauseContext ctx) { + public QueryRendererBuilder visitOrderby_clause(JpqlParser.Orderby_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.ORDER())); - tokens.add(new JpaQueryParsingToken(ctx.BY())); + builder.append(JpaQueryParsingToken.expression(ctx.ORDER())); + builder.append(JpaQueryParsingToken.expression(ctx.BY())); + builder.appendExpression(QueryRendererBuilder.concat(ctx.orderby_item(), this::visit, TOKEN_COMMA)); - ctx.orderby_item().forEach(orderbyItemContext -> { - tokens.addAll(visit(orderbyItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - return tokens; + return builder; } @Override - public List visitOrderby_item(JpqlParser.Orderby_itemContext ctx) { + public QueryRendererBuilder visitOrderby_item(JpqlParser.Orderby_itemContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_field_path_expression() != null) { - tokens.addAll(visit(ctx.state_field_path_expression())); + builder.appendExpression(visit(ctx.state_field_path_expression())); } else if (ctx.general_identification_variable() != null) { - tokens.addAll(visit(ctx.general_identification_variable())); + builder.appendExpression(visit(ctx.general_identification_variable())); } else if (ctx.result_variable() != null) { - tokens.addAll(visit(ctx.result_variable())); + builder.appendExpression(visit(ctx.result_variable())); } if (ctx.ASC() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ASC())); + builder.append(JpaQueryParsingToken.expression(ctx.ASC())); } if (ctx.DESC() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DESC())); + builder.append(JpaQueryParsingToken.expression(ctx.DESC())); } - return tokens; + return builder; } @Override - public List visitSubquery(JpqlParser.SubqueryContext ctx) { + public QueryRendererBuilder visitSubquery(JpqlParser.SubqueryContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.simple_select_clause())); + builder.appendExpression(visit(ctx.subquery_from_clause())); - tokens.addAll(visit(ctx.simple_select_clause())); - tokens.addAll(visit(ctx.subquery_from_clause())); if (ctx.where_clause() != null) { - tokens.addAll(visit(ctx.where_clause())); + builder.appendExpression(visit(ctx.where_clause())); } if (ctx.groupby_clause() != null) { - tokens.addAll(visit(ctx.groupby_clause())); + builder.appendExpression(visit(ctx.groupby_clause())); } if (ctx.having_clause() != null) { - tokens.addAll(visit(ctx.having_clause())); + builder.appendExpression(visit(ctx.having_clause())); } - return tokens; + return builder; } @Override - public List visitSubquery_from_clause(JpqlParser.Subquery_from_clauseContext ctx) { + public QueryRendererBuilder visitSubquery_from_clause(JpqlParser.Subquery_from_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.FROM())); - ctx.subselect_identification_variable_declaration().forEach(subselectIdentificationVariableDeclarationContext -> { - tokens.addAll(visit(subselectIdentificationVariableDeclarationContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - SPACE(tokens); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.appendExpression( + QueryRendererBuilder.concat(ctx.subselect_identification_variable_declaration(), this::visit, TOKEN_COMMA)); - return tokens; + return builder; } @Override - public List visitSubselect_identification_variable_declaration( + public QueryRendererBuilder visitSubselect_identification_variable_declaration( JpqlParser.Subselect_identification_variable_declarationContext ctx) { return super.visitSubselect_identification_variable_declaration(ctx); } @Override - public List visitDerived_path_expression(JpqlParser.Derived_path_expressionContext ctx) { + public QueryRendererBuilder visitDerived_path_expression(JpqlParser.Derived_path_expressionContext ctx) { return super.visitDerived_path_expression(ctx); } @Override - public List visitGeneral_derived_path(JpqlParser.General_derived_pathContext ctx) { + public QueryRendererBuilder visitGeneral_derived_path(JpqlParser.General_derived_pathContext ctx) { return super.visitGeneral_derived_path(ctx); } @Override - public List visitSimple_derived_path(JpqlParser.Simple_derived_pathContext ctx) { + public QueryRendererBuilder visitSimple_derived_path(JpqlParser.Simple_derived_pathContext ctx) { return super.visitSimple_derived_path(ctx); } @Override - public List visitTreated_derived_path(JpqlParser.Treated_derived_pathContext ctx) { + public QueryRendererBuilder visitTreated_derived_path(JpqlParser.Treated_derived_pathContext ctx) { return super.visitTreated_derived_path(ctx); } @Override - public List visitDerived_collection_member_declaration( + public QueryRendererBuilder visitDerived_collection_member_declaration( JpqlParser.Derived_collection_member_declarationContext ctx) { return super.visitDerived_collection_member_declaration(ctx); } @Override - public List visitSimple_select_clause(JpqlParser.Simple_select_clauseContext ctx) { + public QueryRendererBuilder visitSimple_select_clause(JpqlParser.Simple_select_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.SELECT())); + builder.append(JpaQueryParsingToken.expression(ctx.SELECT())); if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); + builder.append(JpaQueryParsingToken.expression(ctx.DISTINCT())); } - tokens.addAll(visit(ctx.simple_select_expression())); + builder.appendExpression(visit(ctx.simple_select_expression())); - return tokens; + return builder; } @Override - public List visitSimple_select_expression(JpqlParser.Simple_select_expressionContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitSimple_select_expression(JpqlParser.Simple_select_expressionContext ctx) { if (ctx.single_valued_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_path_expression())); + return visit(ctx.single_valued_path_expression()); } else if (ctx.scalar_expression() != null) { - tokens.addAll(visit(ctx.scalar_expression())); + return visit(ctx.scalar_expression()); } else if (ctx.aggregate_expression() != null) { - tokens.addAll(visit(ctx.aggregate_expression())); + return visit(ctx.aggregate_expression()); } else if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitScalar_expression(JpqlParser.Scalar_expressionContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitScalar_expression(JpqlParser.Scalar_expressionContext ctx) { if (ctx.arithmetic_expression() != null) { - tokens.addAll(visit(ctx.arithmetic_expression())); + return visit(ctx.arithmetic_expression()); } else if (ctx.string_expression() != null) { - tokens.addAll(visit(ctx.string_expression())); + return visit(ctx.string_expression()); } else if (ctx.enum_expression() != null) { - tokens.addAll(visit(ctx.enum_expression())); + return visit(ctx.enum_expression()); } else if (ctx.datetime_expression() != null) { - tokens.addAll(visit(ctx.datetime_expression())); + return visit(ctx.datetime_expression()); } else if (ctx.boolean_expression() != null) { - tokens.addAll(visit(ctx.boolean_expression())); + return visit(ctx.boolean_expression()); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + return visit(ctx.case_expression()); } else if (ctx.entity_type_expression() != null) { - tokens.addAll(visit(ctx.entity_type_expression())); + return visit(ctx.entity_type_expression()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitConditional_expression(JpqlParser.Conditional_expressionContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitConditional_expression(JpqlParser.Conditional_expressionContext ctx) { if (ctx.conditional_expression() != null) { - tokens.addAll(visit(ctx.conditional_expression())); - tokens.add(new JpaQueryParsingToken(ctx.OR())); - tokens.addAll(visit(ctx.conditional_term())); - } else { - tokens.addAll(visit(ctx.conditional_term())); - } + QueryRendererBuilder builder = QueryRenderer.builder(); - return tokens; + builder.appendExpression(visit(ctx.conditional_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.OR())); + builder.appendExpression(visit(ctx.conditional_term())); + + return builder; + } else { + return visit(ctx.conditional_term()); + } } @Override - public List visitConditional_term(JpqlParser.Conditional_termContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitConditional_term(JpqlParser.Conditional_termContext ctx) { if (ctx.conditional_term() != null) { - tokens.addAll(visit(ctx.conditional_term())); - tokens.add(new JpaQueryParsingToken(ctx.AND())); - tokens.addAll(visit(ctx.conditional_factor())); - } else { - tokens.addAll(visit(ctx.conditional_factor())); - } + QueryRendererBuilder builder = QueryRenderer.builder(); - return tokens; + builder.appendExpression(visit(ctx.conditional_term())); + builder.append(JpaQueryParsingToken.expression(ctx.AND())); + builder.appendExpression(visit(ctx.conditional_factor())); + + return builder; + } else { + return visit(ctx.conditional_factor()); + } } @Override - public List visitConditional_factor(JpqlParser.Conditional_factorContext ctx) { + public QueryRendererBuilder visitConditional_factor(JpqlParser.Conditional_factorContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - JpqlParser.Conditional_primaryContext conditionalPrimary = ctx.conditional_primary(); - List visitedConditionalPrimary = visit(conditionalPrimary); - tokens.addAll(visitedConditionalPrimary); + builder.append(visit(ctx.conditional_primary())); - return tokens; + return builder; } @Override - public List visitConditional_primary(JpqlParser.Conditional_primaryContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitConditional_primary(JpqlParser.Conditional_primaryContext ctx) { if (ctx.simple_cond_expression() != null) { - tokens.addAll(visit(ctx.simple_cond_expression())); - } else if (ctx.conditional_expression() != null) { - - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.conditional_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + return visit(ctx.simple_cond_expression()); } - return tokens; + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.conditional_expression() != null) { + + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.conditional_expression())); + builder.append(TOKEN_CLOSE_PAREN); + } + + return builder; } @Override - public List visitSimple_cond_expression(JpqlParser.Simple_cond_expressionContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitSimple_cond_expression(JpqlParser.Simple_cond_expressionContext ctx) { if (ctx.comparison_expression() != null) { - tokens.addAll(visit(ctx.comparison_expression())); + return visit(ctx.comparison_expression()); } else if (ctx.between_expression() != null) { - tokens.addAll(visit(ctx.between_expression())); + return visit(ctx.between_expression()); } else if (ctx.in_expression() != null) { - tokens.addAll(visit(ctx.in_expression())); + return visit(ctx.in_expression()); } else if (ctx.like_expression() != null) { - tokens.addAll(visit(ctx.like_expression())); + return visit(ctx.like_expression()); } else if (ctx.null_comparison_expression() != null) { - tokens.addAll(visit(ctx.null_comparison_expression())); + return visit(ctx.null_comparison_expression()); } else if (ctx.empty_collection_comparison_expression() != null) { - tokens.addAll(visit(ctx.empty_collection_comparison_expression())); + return visit(ctx.empty_collection_comparison_expression()); } else if (ctx.collection_member_expression() != null) { - tokens.addAll(visit(ctx.collection_member_expression())); + return visit(ctx.collection_member_expression()); } else if (ctx.exists_expression() != null) { - tokens.addAll(visit(ctx.exists_expression())); + return visit(ctx.exists_expression()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitBetween_expression(JpqlParser.Between_expressionContext ctx) { + public QueryRendererBuilder visitBetween_expression(JpqlParser.Between_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.arithmetic_expression(0) != null) { - tokens.addAll(visit(ctx.arithmetic_expression(0))); + builder.appendExpression(visit(ctx.arithmetic_expression(0))); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.BETWEEN())); - tokens.addAll(visit(ctx.arithmetic_expression(1))); - tokens.add(new JpaQueryParsingToken(ctx.AND())); - tokens.addAll(visit(ctx.arithmetic_expression(2))); + builder.append(JpaQueryParsingToken.expression(ctx.BETWEEN())); + builder.appendExpression(visit(ctx.arithmetic_expression(1))); + builder.append(JpaQueryParsingToken.expression(ctx.AND())); + builder.appendExpression(visit(ctx.arithmetic_expression(2))); } else if (ctx.string_expression(0) != null) { - tokens.addAll(visit(ctx.string_expression(0))); + builder.appendExpression(visit(ctx.string_expression(0))); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.BETWEEN())); - tokens.addAll(visit(ctx.string_expression(1))); - tokens.add(new JpaQueryParsingToken(ctx.AND())); - tokens.addAll(visit(ctx.string_expression(2))); + builder.append(JpaQueryParsingToken.expression(ctx.BETWEEN())); + builder.appendExpression(visit(ctx.string_expression(1))); + builder.append(JpaQueryParsingToken.expression(ctx.AND())); + builder.appendExpression(visit(ctx.string_expression(2))); } else if (ctx.datetime_expression(0) != null) { - tokens.addAll(visit(ctx.datetime_expression(0))); + builder.appendExpression(visit(ctx.datetime_expression(0))); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.BETWEEN())); - tokens.addAll(visit(ctx.datetime_expression(1))); - tokens.add(new JpaQueryParsingToken(ctx.AND())); - tokens.addAll(visit(ctx.datetime_expression(2))); + builder.append(JpaQueryParsingToken.expression(ctx.BETWEEN())); + builder.appendExpression(visit(ctx.datetime_expression(1))); + builder.append(JpaQueryParsingToken.expression(ctx.AND())); + builder.appendExpression(visit(ctx.datetime_expression(2))); } - return tokens; + return builder; } @Override - public List visitIn_expression(JpqlParser.In_expressionContext ctx) { + public QueryRendererBuilder visitIn_expression(JpqlParser.In_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.appendExpression(visit(ctx.state_valued_path_expression())); } if (ctx.type_discriminator() != null) { - tokens.addAll(visit(ctx.type_discriminator())); + builder.appendExpression(visit(ctx.type_discriminator())); } if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } if (ctx.IN() != null) { - tokens.add(new JpaQueryParsingToken(ctx.IN())); + builder.append(JpaQueryParsingToken.expression(ctx.IN())); } if (ctx.in_item() != null && !ctx.in_item().isEmpty()) { - tokens.add(TOKEN_OPEN_PAREN); - - ctx.in_item().forEach(inItemContext -> { - - tokens.addAll(visit(inItemContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryRendererBuilder.concat(ctx.in_item(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.collection_valued_input_parameter() != null) { - tokens.addAll(visit(ctx.collection_valued_input_parameter())); + builder.append(visit(ctx.collection_valued_input_parameter())); } - return tokens; + return builder; } @Override - public List visitIn_item(JpqlParser.In_itemContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitIn_item(JpqlParser.In_itemContext ctx) { if (ctx.literal() != null) { - tokens.addAll(visit(ctx.literal())); + return visit(ctx.literal()); } else if (ctx.single_valued_input_parameter() != null) { - tokens.addAll(visit(ctx.single_valued_input_parameter())); + return visit(ctx.single_valued_input_parameter()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitLike_expression(JpqlParser.Like_expressionContext ctx) { + public QueryRendererBuilder visitLike_expression(JpqlParser.Like_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.string_expression())); - tokens.addAll(visit(ctx.string_expression())); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.LIKE())); - tokens.addAll(visit(ctx.pattern_value())); + builder.append(JpaQueryParsingToken.expression(ctx.LIKE())); + builder.appendExpression(visit(ctx.pattern_value())); if (ctx.ESCAPE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ESCAPE())); - tokens.addAll(visit(ctx.escape_character())); + builder.append(JpaQueryParsingToken.expression(ctx.ESCAPE())); + builder.appendExpression(visit(ctx.escape_character())); } - return tokens; + return builder; } @Override - public List visitNull_comparison_expression(JpqlParser.Null_comparison_expressionContext ctx) { + public QueryRendererBuilder visitNull_comparison_expression(JpqlParser.Null_comparison_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.single_valued_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_path_expression())); + builder.appendExpression(visit(ctx.single_valued_path_expression())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.appendExpression(visit(ctx.input_parameter())); } - tokens.add(new JpaQueryParsingToken(ctx.IS())); + builder.append(JpaQueryParsingToken.expression(ctx.IS())); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.NULL())); + builder.append(JpaQueryParsingToken.expression(ctx.NULL())); - return tokens; + return builder; } @Override - public List visitEmpty_collection_comparison_expression( + public QueryRendererBuilder visitEmpty_collection_comparison_expression( JpqlParser.Empty_collection_comparison_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.collection_valued_path_expression())); - tokens.add(new JpaQueryParsingToken(ctx.IS())); + builder.appendExpression(visit(ctx.collection_valued_path_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.IS())); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.EMPTY())); + builder.append(JpaQueryParsingToken.expression(ctx.EMPTY())); - return tokens; + return builder; } @Override - public List visitCollection_member_expression( - JpqlParser.Collection_member_expressionContext ctx) { + public QueryRendererBuilder visitCollection_member_expression(JpqlParser.Collection_member_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.addAll(visit(ctx.entity_or_value_expression())); + builder.appendExpression(visit(ctx.entity_or_value_expression())); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.MEMBER())); + builder.append(JpaQueryParsingToken.expression(ctx.MEMBER())); if (ctx.OF() != null) { - tokens.add(new JpaQueryParsingToken(ctx.OF())); + builder.append(JpaQueryParsingToken.expression(ctx.OF())); } - tokens.addAll(visit(ctx.collection_valued_path_expression())); + builder.append(visit(ctx.collection_valued_path_expression())); - return tokens; + return builder; } @Override - public List visitEntity_or_value_expression(JpqlParser.Entity_or_value_expressionContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitEntity_or_value_expression(JpqlParser.Entity_or_value_expressionContext ctx) { if (ctx.single_valued_object_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_object_path_expression())); + return visit(ctx.single_valued_object_path_expression()); } else if (ctx.state_field_path_expression() != null) { - tokens.addAll(visit(ctx.state_field_path_expression())); + return visit(ctx.state_field_path_expression()); } else if (ctx.simple_entity_or_value_expression() != null) { - tokens.addAll(visit(ctx.simple_entity_or_value_expression())); + return visit(ctx.simple_entity_or_value_expression()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitSimple_entity_or_value_expression( + public QueryRendererBuilder visitSimple_entity_or_value_expression( JpqlParser.Simple_entity_or_value_expressionContext ctx) { - List tokens = new ArrayList<>(); - if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + return visit(ctx.input_parameter()); } else if (ctx.literal() != null) { - tokens.addAll(visit(ctx.literal())); + return visit(ctx.literal()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitExists_expression(JpqlParser.Exists_expressionContext ctx) { + public QueryRendererBuilder visitExists_expression(JpqlParser.Exists_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.NOT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.NOT())); + builder.append(JpaQueryParsingToken.expression(ctx.NOT())); } - tokens.add(new JpaQueryParsingToken(ctx.EXISTS())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); - return tokens; + builder.append(JpaQueryParsingToken.expression(ctx.EXISTS())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; } @Override - public List visitAll_or_any_expression(JpqlParser.All_or_any_expressionContext ctx) { + public QueryRendererBuilder visitAll_or_any_expression(JpqlParser.All_or_any_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.ALL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ALL())); + builder.append(JpaQueryParsingToken.expression(ctx.ALL())); } else if (ctx.ANY() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ANY())); + builder.append(JpaQueryParsingToken.expression(ctx.ANY())); } else if (ctx.SOME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SOME())); + builder.append(JpaQueryParsingToken.expression(ctx.SOME())); } - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitComparison_expression(JpqlParser.Comparison_expressionContext ctx) { + public QueryRendererBuilder visitComparison_expression(JpqlParser.Comparison_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (!ctx.string_expression().isEmpty()) { - tokens.addAll(visit(ctx.string_expression(0))); - tokens.addAll(visit(ctx.comparison_operator())); + builder.appendExpression(visit(ctx.string_expression(0))); + builder.appendExpression(visit(ctx.comparison_operator())); if (ctx.string_expression(1) != null) { - tokens.addAll(visit(ctx.string_expression(1))); + builder.appendExpression(visit(ctx.string_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } } else if (!ctx.boolean_expression().isEmpty()) { - tokens.addAll(visit(ctx.boolean_expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.op)); + builder.appendInline(visit(ctx.boolean_expression(0))); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); if (ctx.boolean_expression(1) != null) { - tokens.addAll(visit(ctx.boolean_expression(1))); + builder.appendExpression(visit(ctx.boolean_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } } else if (!ctx.enum_expression().isEmpty()) { - tokens.addAll(visit(ctx.enum_expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.op)); + builder.appendInline(visit(ctx.enum_expression(0))); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); if (ctx.enum_expression(1) != null) { - tokens.addAll(visit(ctx.enum_expression(1))); + builder.appendExpression(visit(ctx.enum_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } } else if (!ctx.datetime_expression().isEmpty()) { - tokens.addAll(visit(ctx.datetime_expression(0))); - tokens.addAll(visit(ctx.comparison_operator())); + builder.appendExpression(visit(ctx.datetime_expression(0))); + builder.appendExpression(visit(ctx.comparison_operator())); if (ctx.datetime_expression(1) != null) { - tokens.addAll(visit(ctx.datetime_expression(1))); + builder.appendExpression(visit(ctx.datetime_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } } else if (!ctx.entity_expression().isEmpty()) { - tokens.addAll(visit(ctx.entity_expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.op)); + builder.appendInline(visit(ctx.entity_expression(0))); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); if (ctx.entity_expression(1) != null) { - tokens.addAll(visit(ctx.entity_expression(1))); + builder.appendExpression(visit(ctx.entity_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } } else if (!ctx.arithmetic_expression().isEmpty()) { - tokens.addAll(visit(ctx.arithmetic_expression(0))); - tokens.addAll(visit(ctx.comparison_operator())); + builder.appendExpression(visit(ctx.arithmetic_expression(0))); + builder.appendExpression(visit(ctx.comparison_operator())); if (ctx.arithmetic_expression(1) != null) { - tokens.addAll(visit(ctx.arithmetic_expression(1))); + builder.appendExpression(visit(ctx.arithmetic_expression(1))); } else { - tokens.addAll(visit(ctx.all_or_any_expression())); + builder.appendExpression(visit(ctx.all_or_any_expression())); } } else if (!ctx.entity_type_expression().isEmpty()) { - tokens.addAll(visit(ctx.entity_type_expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.op)); - tokens.addAll(visit(ctx.entity_type_expression(1))); + builder.appendInline(visit(ctx.entity_type_expression(0))); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); + builder.appendExpression(visit(ctx.entity_type_expression(1))); } - return tokens; + return builder; } @Override - public List visitComparison_operator(JpqlParser.Comparison_operatorContext ctx) { - return List.of(new JpaQueryParsingToken(ctx.op)); + public QueryRendererBuilder visitComparison_operator(JpqlParser.Comparison_operatorContext ctx) { + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.op)); } @Override - public List visitArithmetic_expression(JpqlParser.Arithmetic_expressionContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitArithmetic_expression(JpqlParser.Arithmetic_expressionContext ctx) { if (ctx.arithmetic_expression() != null) { - tokens.addAll(visit(ctx.arithmetic_expression())); - tokens.add(new JpaQueryParsingToken(ctx.op)); - tokens.addAll(visit(ctx.arithmetic_term())); + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.append(visit(ctx.arithmetic_expression())); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); + builder.append(visit(ctx.arithmetic_term())); + return builder; } else { - tokens.addAll(visit(ctx.arithmetic_term())); + return visit(ctx.arithmetic_term()); } - - return tokens; } @Override - public List visitArithmetic_term(JpqlParser.Arithmetic_termContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitArithmetic_term(JpqlParser.Arithmetic_termContext ctx) { if (ctx.arithmetic_term() != null) { - tokens.addAll(visit(ctx.arithmetic_term())); - NOSPACE(tokens); - tokens.add(new JpaQueryParsingToken(ctx.op, false)); - tokens.addAll(visit(ctx.arithmetic_factor())); - } else { - tokens.addAll(visit(ctx.arithmetic_factor())); - } + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.appendInline(visit(ctx.arithmetic_term())); + builder.append(JpaQueryParsingToken.ventilated(ctx.op)); + builder.append(visit(ctx.arithmetic_factor())); - return tokens; + return builder; + } else { + return visit(ctx.arithmetic_factor()); + } } @Override - public List visitArithmetic_factor(JpqlParser.Arithmetic_factorContext ctx) { + public QueryRendererBuilder visitArithmetic_factor(JpqlParser.Arithmetic_factorContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.op != null) { - tokens.add(new JpaQueryParsingToken(ctx.op, false)); + builder.append(JpaQueryParsingToken.token(ctx.op)); } - tokens.addAll(visit(ctx.arithmetic_primary())); - return tokens; + builder.append(visit(ctx.arithmetic_primary())); + + return builder; } @Override - public List visitArithmetic_primary(JpqlParser.Arithmetic_primaryContext ctx) { + public QueryRendererBuilder visitArithmetic_primary(JpqlParser.Arithmetic_primaryContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.append(visit(ctx.state_valued_path_expression())); } else if (ctx.numeric_literal() != null) { - tokens.addAll(visit(ctx.numeric_literal())); + builder.append(visit(ctx.numeric_literal())); } else if (ctx.arithmetic_expression() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.functions_returning_numerics() != null) { - tokens.addAll(visit(ctx.functions_returning_numerics())); + builder.append(visit(ctx.functions_returning_numerics())); } else if (ctx.aggregate_expression() != null) { - tokens.addAll(visit(ctx.aggregate_expression())); + builder.append(visit(ctx.aggregate_expression())); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + builder.append(visit(ctx.case_expression())); } else if (ctx.function_invocation() != null) { - tokens.addAll(visit(ctx.function_invocation())); + builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitString_expression(JpqlParser.String_expressionContext ctx) { + public QueryRendererBuilder visitString_expression(JpqlParser.String_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.append(visit(ctx.state_valued_path_expression())); } else if (ctx.string_literal() != null) { - tokens.addAll(visit(ctx.string_literal())); + builder.append(visit(ctx.string_literal())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.functions_returning_strings() != null) { - tokens.addAll(visit(ctx.functions_returning_strings())); + builder.append(visit(ctx.functions_returning_strings())); } else if (ctx.aggregate_expression() != null) { - tokens.addAll(visit(ctx.aggregate_expression())); + builder.append(visit(ctx.aggregate_expression())); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + builder.append(visit(ctx.case_expression())); } else if (ctx.function_invocation() != null) { - tokens.addAll(visit(ctx.function_invocation())); + builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitDatetime_expression(JpqlParser.Datetime_expressionContext ctx) { + public QueryRendererBuilder visitDatetime_expression(JpqlParser.Datetime_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.append(visit(ctx.state_valued_path_expression())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.functions_returning_datetime() != null) { - tokens.addAll(visit(ctx.functions_returning_datetime())); + builder.append(visit(ctx.functions_returning_datetime())); } else if (ctx.aggregate_expression() != null) { - tokens.addAll(visit(ctx.aggregate_expression())); + builder.append(visit(ctx.aggregate_expression())); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + builder.append(visit(ctx.case_expression())); } else if (ctx.function_invocation() != null) { - tokens.addAll(visit(ctx.function_invocation())); + builder.append(visit(ctx.function_invocation())); } else if (ctx.date_time_timestamp_literal() != null) { - tokens.addAll(visit(ctx.date_time_timestamp_literal())); + builder.append(visit(ctx.date_time_timestamp_literal())); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitBoolean_expression(JpqlParser.Boolean_expressionContext ctx) { + public QueryRendererBuilder visitBoolean_expression(JpqlParser.Boolean_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.append(visit(ctx.state_valued_path_expression())); } else if (ctx.boolean_literal() != null) { - tokens.addAll(visit(ctx.boolean_literal())); + builder.append(visit(ctx.boolean_literal())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + builder.append(visit(ctx.case_expression())); } else if (ctx.function_invocation() != null) { - tokens.addAll(visit(ctx.function_invocation())); + builder.append(visit(ctx.function_invocation())); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitEnum_expression(JpqlParser.Enum_expressionContext ctx) { + public QueryRendererBuilder visitEnum_expression(JpqlParser.Enum_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.state_valued_path_expression() != null) { - tokens.addAll(visit(ctx.state_valued_path_expression())); + builder.append(visit(ctx.state_valued_path_expression())); } else if (ctx.enum_literal() != null) { - tokens.addAll(visit(ctx.enum_literal())); + builder.append(visit(ctx.enum_literal())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.append(visit(ctx.input_parameter())); } else if (ctx.case_expression() != null) { - tokens.addAll(visit(ctx.case_expression())); + builder.append(visit(ctx.case_expression())); } else if (ctx.subquery() != null) { - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.subquery())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.subquery())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitEntity_expression(JpqlParser.Entity_expressionContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitEntity_expression(JpqlParser.Entity_expressionContext ctx) { if (ctx.single_valued_object_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_object_path_expression())); + return visit(ctx.single_valued_object_path_expression()); } else if (ctx.simple_entity_expression() != null) { - tokens.addAll(visit(ctx.simple_entity_expression())); + return visit(ctx.simple_entity_expression()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitSimple_entity_expression(JpqlParser.Simple_entity_expressionContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitSimple_entity_expression(JpqlParser.Simple_entity_expressionContext ctx) { if (ctx.identification_variable() != null) { - tokens.addAll(visit(ctx.identification_variable())); + return visit(ctx.identification_variable()); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + return visit(ctx.input_parameter()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitEntity_type_expression(JpqlParser.Entity_type_expressionContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitEntity_type_expression(JpqlParser.Entity_type_expressionContext ctx) { if (ctx.type_discriminator() != null) { - tokens.addAll(visit(ctx.type_discriminator())); + return visit(ctx.type_discriminator()); } else if (ctx.entity_type_literal() != null) { - tokens.addAll(visit(ctx.entity_type_literal())); + return visit(ctx.entity_type_literal()); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + return visit(ctx.input_parameter()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitType_discriminator(JpqlParser.Type_discriminatorContext ctx) { + public QueryRendererBuilder visitType_discriminator(JpqlParser.Type_discriminatorContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.TYPE(), false)); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TYPE())); + builder.append(TOKEN_OPEN_PAREN); if (ctx.general_identification_variable() != null) { - tokens.addAll(visit(ctx.general_identification_variable())); + builder.appendInline(visit(ctx.general_identification_variable())); } else if (ctx.single_valued_object_path_expression() != null) { - tokens.addAll(visit(ctx.single_valued_object_path_expression())); + builder.appendInline(visit(ctx.single_valued_object_path_expression())); } else if (ctx.input_parameter() != null) { - tokens.addAll(visit(ctx.input_parameter())); + builder.appendInline(visit(ctx.input_parameter())); } - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitFunctions_returning_numerics( - JpqlParser.Functions_returning_numericsContext ctx) { + public QueryRendererBuilder visitFunctions_returning_numerics(JpqlParser.Functions_returning_numericsContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.LENGTH() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LENGTH(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.string_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.LENGTH())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LOCATE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LOCATE(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.string_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.string_expression(1))); - NOSPACE(tokens); + builder.append(JpaQueryParsingToken.token(ctx.LOCATE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.string_expression(1))); if (ctx.arithmetic_expression() != null) { - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(0))); } - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.ABS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ABS(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.ABS())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.CEILING() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CEILING(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.CEILING())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.EXP() != null) { - tokens.add(new JpaQueryParsingToken(ctx.EXP(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.EXP())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.FLOOR() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FLOOR(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.FLOOR())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LN() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LN(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.LN())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.SIGN() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SIGN(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.SIGN())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.SQRT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SQRT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.SQRT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.MOD() != null) { - tokens.add(new JpaQueryParsingToken(ctx.MOD(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.arithmetic_expression(1))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.MOD())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(1))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.POWER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.POWER(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.arithmetic_expression(1))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.POWER())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(1))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.ROUND() != null) { - tokens.add(new JpaQueryParsingToken(ctx.ROUND(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.arithmetic_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.arithmetic_expression(1))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.ROUND())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.arithmetic_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.arithmetic_expression(1))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.SIZE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SIZE(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.collection_valued_path_expression())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.SIZE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.collection_valued_path_expression())); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.INDEX() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INDEX(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.identification_variable())); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.INDEX())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.identification_variable())); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitFunctions_returning_datetime( - JpqlParser.Functions_returning_datetimeContext ctx) { + public QueryRendererBuilder visitFunctions_returning_datetime(JpqlParser.Functions_returning_datetimeContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.CURRENT_DATE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CURRENT_DATE())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT_DATE())); } else if (ctx.CURRENT_TIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CURRENT_TIME())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT_TIME())); } else if (ctx.CURRENT_TIMESTAMP() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CURRENT_TIMESTAMP())); + builder.append(JpaQueryParsingToken.expression(ctx.CURRENT_TIMESTAMP())); } else if (ctx.LOCAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LOCAL())); + builder.append(JpaQueryParsingToken.expression(ctx.LOCAL())); if (ctx.DATE() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DATE())); + builder.append(JpaQueryParsingToken.expression(ctx.DATE())); } else if (ctx.TIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TIME())); + builder.append(JpaQueryParsingToken.expression(ctx.TIME())); } else if (ctx.DATETIME() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DATETIME())); + builder.append(JpaQueryParsingToken.expression(ctx.DATETIME())); } } - return tokens; + return builder; } @Override - public List visitFunctions_returning_strings( - JpqlParser.Functions_returning_stringsContext ctx) { + public QueryRendererBuilder visitFunctions_returning_strings(JpqlParser.Functions_returning_stringsContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.CONCAT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.CONCAT(), false)); - tokens.add(TOKEN_OPEN_PAREN); - ctx.string_expression().forEach(stringExpressionContext -> { - tokens.addAll(visit(stringExpressionContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.CONCAT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryRendererBuilder.concat(ctx.string_expression(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.SUBSTRING() != null) { - tokens.add(new JpaQueryParsingToken(ctx.SUBSTRING(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.string_expression(0))); - NOSPACE(tokens); - ctx.arithmetic_expression().forEach(arithmeticExpressionContext -> { - tokens.addAll(visit(arithmeticExpressionContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.SUBSTRING())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.string_expression(0))); + builder.appendInline(QueryRendererBuilder.concat(ctx.arithmetic_expression(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.TRIM() != null) { - tokens.add(new JpaQueryParsingToken(ctx.TRIM(), false)); - tokens.add(TOKEN_OPEN_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.TRIM())); + builder.append(TOKEN_OPEN_PAREN); if (ctx.trim_specification() != null) { - tokens.addAll(visit(ctx.trim_specification())); + builder.appendExpression(visit(ctx.trim_specification())); } if (ctx.trim_character() != null) { - tokens.addAll(visit(ctx.trim_character())); + builder.appendExpression(visit(ctx.trim_character())); } if (ctx.FROM() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FROM())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); } - tokens.addAll(visit(ctx.string_expression(0))); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(visit(ctx.string_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.LOWER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LOWER(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.string_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.LOWER())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.string_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } else if (ctx.UPPER() != null) { - tokens.add(new JpaQueryParsingToken(ctx.UPPER(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.string_expression(0))); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.UPPER())); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.string_expression(0))); + builder.append(TOKEN_CLOSE_PAREN); } - return tokens; + return builder; } @Override - public List visitTrim_specification(JpqlParser.Trim_specificationContext ctx) { + public QueryRendererBuilder visitTrim_specification(JpqlParser.Trim_specificationContext ctx) { if (ctx.LEADING() != null) { - return List.of(new JpaQueryParsingToken(ctx.LEADING())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.LEADING())); } else if (ctx.TRAILING() != null) { - return List.of(new JpaQueryParsingToken(ctx.TRAILING())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.TRAILING())); } else { - return List.of(new JpaQueryParsingToken(ctx.BOTH())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.BOTH())); } } @Override - public List visitFunction_invocation(JpqlParser.Function_invocationContext ctx) { + public QueryRendererBuilder visitFunction_invocation(JpqlParser.Function_invocationContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.FUNCTION(), false)); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.function_name())); - NOSPACE(tokens); - ctx.function_arg().forEach(functionArgContext -> { - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(functionArgContext)); - NOSPACE(tokens); - }); - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.FUNCTION())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.function_name())); + if (!ctx.function_arg().isEmpty()) { + builder.append(TOKEN_COMMA); + builder.appendInline(QueryRendererBuilder.concat(ctx.function_arg(), this::visit, TOKEN_COMMA)); + } + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitExtract_datetime_field(JpqlParser.Extract_datetime_fieldContext ctx) { + public QueryRendererBuilder visitExtract_datetime_field(JpqlParser.Extract_datetime_fieldContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.EXTRACT())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.datetime_field())); - tokens.add(new JpaQueryParsingToken(ctx.FROM())); - tokens.addAll(visit(ctx.datetime_expression())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.expression(ctx.EXTRACT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendExpression(visit(ctx.datetime_field())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.appendInline(visit(ctx.datetime_expression())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitDatetime_field(JpqlParser.Datetime_fieldContext ctx) { + public QueryRendererBuilder visitDatetime_field(JpqlParser.Datetime_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitExtract_datetime_part(JpqlParser.Extract_datetime_partContext ctx) { + public QueryRendererBuilder visitExtract_datetime_part(JpqlParser.Extract_datetime_partContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.EXTRACT())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.datetime_part())); - tokens.add(new JpaQueryParsingToken(ctx.FROM())); - tokens.addAll(visit(ctx.datetime_expression())); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.expression(ctx.EXTRACT())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendExpression(visit(ctx.datetime_part())); + builder.append(JpaQueryParsingToken.expression(ctx.FROM())); + builder.append(visit(ctx.datetime_expression())); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitDatetime_part(JpqlParser.Datetime_partContext ctx) { + public QueryRendererBuilder visitDatetime_part(JpqlParser.Datetime_partContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitFunction_arg(JpqlParser.Function_argContext ctx) { + public QueryRendererBuilder visitFunction_arg(JpqlParser.Function_argContext ctx) { if (ctx.literal() != null) { return visit(ctx.literal()); @@ -1997,7 +1836,7 @@ class JpqlQueryRenderer extends JpqlBaseVisitor> { } @Override - public List visitCase_expression(JpqlParser.Case_expressionContext ctx) { + public QueryRendererBuilder visitCase_expression(JpqlParser.Case_expressionContext ctx) { if (ctx.general_case_expression() != null) { return visit(ctx.general_case_expression()); @@ -2011,57 +1850,51 @@ class JpqlQueryRenderer extends JpqlBaseVisitor> { } @Override - public List visitGeneral_case_expression(JpqlParser.General_case_expressionContext ctx) { + public QueryRendererBuilder visitGeneral_case_expression(JpqlParser.General_case_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CASE())); + builder.append(JpaQueryParsingToken.expression(ctx.CASE())); + builder.appendExpression(QueryRendererBuilder.concatExpressions(ctx.when_clause(), this::visit, TOKEN_NONE)); - ctx.when_clause().forEach(whenClauseContext -> { - tokens.addAll(visit(whenClauseContext)); - }); + builder.append(JpaQueryParsingToken.expression(ctx.ELSE())); + builder.appendExpression(visit(ctx.scalar_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.END())); - tokens.add(new JpaQueryParsingToken(ctx.ELSE())); - tokens.addAll(visit(ctx.scalar_expression())); - tokens.add(new JpaQueryParsingToken(ctx.END())); - - return tokens; + return builder; } @Override - public List visitWhen_clause(JpqlParser.When_clauseContext ctx) { + public QueryRendererBuilder visitWhen_clause(JpqlParser.When_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.WHEN())); - tokens.addAll(visit(ctx.conditional_expression())); - tokens.add(new JpaQueryParsingToken(ctx.THEN())); - tokens.addAll(visit(ctx.scalar_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.WHEN())); + builder.appendExpression(visit(ctx.conditional_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.THEN())); + builder.appendExpression(visit(ctx.scalar_expression())); - return tokens; + return builder; } @Override - public List visitSimple_case_expression(JpqlParser.Simple_case_expressionContext ctx) { + public QueryRendererBuilder visitSimple_case_expression(JpqlParser.Simple_case_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.CASE())); - tokens.addAll(visit(ctx.case_operand())); + builder.append(JpaQueryParsingToken.expression(ctx.CASE())); + builder.appendExpression(visit(ctx.case_operand())); + builder.appendExpression(QueryRendererBuilder.concatExpressions(ctx.simple_when_clause(), this::visit, TOKEN_NONE)); - ctx.simple_when_clause().forEach(simpleWhenClauseContext -> { - tokens.addAll(visit(simpleWhenClauseContext)); - }); + builder.append(JpaQueryParsingToken.expression(ctx.ELSE())); + builder.appendExpression(visit(ctx.scalar_expression())); + builder.append(JpaQueryParsingToken.expression(ctx.END())); - tokens.add(new JpaQueryParsingToken(ctx.ELSE())); - tokens.addAll(visit(ctx.scalar_expression())); - tokens.add(new JpaQueryParsingToken(ctx.END())); - - return tokens; + return builder; } @Override - public List visitCase_operand(JpqlParser.Case_operandContext ctx) { + public QueryRendererBuilder visitCase_operand(JpqlParser.Case_operandContext ctx) { if (ctx.state_valued_path_expression() != null) { return visit(ctx.state_valued_path_expression()); @@ -2071,293 +1904,262 @@ class JpqlQueryRenderer extends JpqlBaseVisitor> { } @Override - public List visitSimple_when_clause(JpqlParser.Simple_when_clauseContext ctx) { + public QueryRendererBuilder visitSimple_when_clause(JpqlParser.Simple_when_clauseContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.WHEN())); - tokens.addAll(visit(ctx.scalar_expression(0))); - tokens.add(new JpaQueryParsingToken(ctx.THEN())); - tokens.addAll(visit(ctx.scalar_expression(1))); + builder.append(JpaQueryParsingToken.expression(ctx.WHEN())); + builder.appendExpression(visit(ctx.scalar_expression(0))); + builder.append(JpaQueryParsingToken.expression(ctx.THEN())); + builder.appendExpression(visit(ctx.scalar_expression(1))); - return tokens; + return builder; } @Override - public List visitCoalesce_expression(JpqlParser.Coalesce_expressionContext ctx) { + public QueryRendererBuilder visitCoalesce_expression(JpqlParser.Coalesce_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.COALESCE(), false)); - tokens.add(TOKEN_OPEN_PAREN); - ctx.scalar_expression().forEach(scalarExpressionContext -> { - tokens.addAll(visit(scalarExpressionContext)); - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - }); - CLIP(tokens); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.token(ctx.COALESCE())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(QueryRendererBuilder.concat(ctx.scalar_expression(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitNullif_expression(JpqlParser.Nullif_expressionContext ctx) { + public QueryRendererBuilder visitNullif_expression(JpqlParser.Nullif_expressionContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); - tokens.add(new JpaQueryParsingToken(ctx.NULLIF())); - tokens.add(TOKEN_OPEN_PAREN); - tokens.addAll(visit(ctx.scalar_expression(0))); - tokens.add(TOKEN_COMMA); - tokens.addAll(visit(ctx.scalar_expression(1))); - tokens.add(TOKEN_CLOSE_PAREN); + builder.append(JpaQueryParsingToken.expression(ctx.NULLIF())); + builder.append(TOKEN_OPEN_PAREN); + builder.appendInline(visit(ctx.scalar_expression(0))); + builder.append(TOKEN_COMMA); + builder.appendInline(visit(ctx.scalar_expression(1))); + builder.append(TOKEN_CLOSE_PAREN); - return tokens; + return builder; } @Override - public List visitTrim_character(JpqlParser.Trim_characterContext ctx) { + public QueryRendererBuilder visitTrim_character(JpqlParser.Trim_characterContext ctx) { if (ctx.CHARACTER() != null) { - return List.of(new JpaQueryParsingToken(ctx.CHARACTER())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.CHARACTER())); } else if (ctx.character_valued_input_parameter() != null) { return visit(ctx.character_valued_input_parameter()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitIdentification_variable(JpqlParser.Identification_variableContext ctx) { + public QueryRendererBuilder visitIdentification_variable(JpqlParser.Identification_variableContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return List.of(new JpaQueryParsingToken(ctx.IDENTIFICATION_VARIABLE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.IDENTIFICATION_VARIABLE())); } else if (ctx.f != null) { - return List.of(new JpaQueryParsingToken(ctx.f)); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.f)); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitConstructor_name(JpqlParser.Constructor_nameContext ctx) { - - List tokens = new ArrayList<>(); - - tokens.addAll(visit(ctx.entity_name())); - NOSPACE(tokens); - - return tokens; + public QueryRendererBuilder visitConstructor_name(JpqlParser.Constructor_nameContext ctx) { + return visit(ctx.entity_name()); } @Override - public List visitLiteral(JpqlParser.LiteralContext ctx) { - - List tokens = new ArrayList<>(); + public QueryRendererBuilder visitLiteral(JpqlParser.LiteralContext ctx) { if (ctx.STRINGLITERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.STRINGLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.STRINGLITERAL())); } else if (ctx.JAVASTRINGLITERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.JAVASTRINGLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.JAVASTRINGLITERAL())); } else if (ctx.INTLITERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.INTLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.INTLITERAL())); } else if (ctx.FLOATLITERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.FLOATLITERAL())); - } else if(ctx.LONGLITERAL() != null) { - tokens.add(new JpaQueryParsingToken(ctx.LONGLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.FLOATLITERAL())); + } else if (ctx.LONGLITERAL() != null) { + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.LONGLITERAL())); } else if (ctx.boolean_literal() != null) { - tokens.addAll(visit(ctx.boolean_literal())); + return visit(ctx.boolean_literal()); } else if (ctx.entity_type_literal() != null) { - tokens.addAll(visit(ctx.entity_type_literal())); + return visit(ctx.entity_type_literal()); } - return tokens; + return QueryRenderer.builder(); } @Override - public List visitInput_parameter(JpqlParser.Input_parameterContext ctx) { + public QueryRendererBuilder visitInput_parameter(JpqlParser.Input_parameterContext ctx) { - List tokens = new ArrayList<>(); + QueryRendererBuilder builder = QueryRenderer.builder(); if (ctx.INTLITERAL() != null) { - tokens.add(TOKEN_QUESTION_MARK); - tokens.add(new JpaQueryParsingToken(ctx.INTLITERAL())); + builder.append(TOKEN_QUESTION_MARK); + builder.append(JpaQueryParsingToken.token(ctx.INTLITERAL())); } else if (ctx.identification_variable() != null) { - tokens.add(TOKEN_COLON); - tokens.addAll(visit(ctx.identification_variable())); + builder.append(TOKEN_COLON); + builder.appendInline(visit(ctx.identification_variable())); } - return tokens; + return builder; } @Override - public List visitPattern_value(JpqlParser.Pattern_valueContext ctx) { - - List tokens = new ArrayList<>(); - - tokens.addAll(visit(ctx.string_expression())); - - return tokens; + public QueryRendererBuilder visitPattern_value(JpqlParser.Pattern_valueContext ctx) { + return visit(ctx.string_expression()); } @Override - public List visitDate_time_timestamp_literal( - JpqlParser.Date_time_timestamp_literalContext ctx) { - return List.of(new JpaQueryParsingToken(ctx.STRINGLITERAL())); + public QueryRendererBuilder visitDate_time_timestamp_literal(JpqlParser.Date_time_timestamp_literalContext ctx) { + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.STRINGLITERAL())); } @Override - public List visitEntity_type_literal(JpqlParser.Entity_type_literalContext ctx) { + public QueryRendererBuilder visitEntity_type_literal(JpqlParser.Entity_type_literalContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitEscape_character(JpqlParser.Escape_characterContext ctx) { - return List.of(new JpaQueryParsingToken(ctx.CHARACTER())); + public QueryRendererBuilder visitEscape_character(JpqlParser.Escape_characterContext ctx) { + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.CHARACTER())); } @Override - public List visitNumeric_literal(JpqlParser.Numeric_literalContext ctx) { + public QueryRendererBuilder visitNumeric_literal(JpqlParser.Numeric_literalContext ctx) { if (ctx.INTLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.INTLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.INTLITERAL())); } else if (ctx.FLOATLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.FLOATLITERAL())); - } else if(ctx.LONGLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.LONGLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.FLOATLITERAL())); + } else if (ctx.LONGLITERAL() != null) { + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.LONGLITERAL())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitBoolean_literal(JpqlParser.Boolean_literalContext ctx) { + public QueryRendererBuilder visitBoolean_literal(JpqlParser.Boolean_literalContext ctx) { if (ctx.TRUE() != null) { - return List.of(new JpaQueryParsingToken(ctx.TRUE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.TRUE())); } else if (ctx.FALSE() != null) { - return List.of(new JpaQueryParsingToken(ctx.FALSE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.FALSE())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitEnum_literal(JpqlParser.Enum_literalContext ctx) { + public QueryRendererBuilder visitEnum_literal(JpqlParser.Enum_literalContext ctx) { return visit(ctx.state_field_path_expression()); } @Override - public List visitString_literal(JpqlParser.String_literalContext ctx) { + public QueryRendererBuilder visitString_literal(JpqlParser.String_literalContext ctx) { if (ctx.CHARACTER() != null) { - return List.of(new JpaQueryParsingToken(ctx.CHARACTER())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.CHARACTER())); } else if (ctx.STRINGLITERAL() != null) { - return List.of(new JpaQueryParsingToken(ctx.STRINGLITERAL())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.STRINGLITERAL())); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitSingle_valued_embeddable_object_field( + public QueryRendererBuilder visitSingle_valued_embeddable_object_field( JpqlParser.Single_valued_embeddable_object_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitSubtype(JpqlParser.SubtypeContext ctx) { + public QueryRendererBuilder visitSubtype(JpqlParser.SubtypeContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitCollection_valued_field(JpqlParser.Collection_valued_fieldContext ctx) { + public QueryRendererBuilder visitCollection_valued_field(JpqlParser.Collection_valued_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitSingle_valued_object_field(JpqlParser.Single_valued_object_fieldContext ctx) { + public QueryRendererBuilder visitSingle_valued_object_field(JpqlParser.Single_valued_object_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitState_field(JpqlParser.State_fieldContext ctx) { + public QueryRendererBuilder visitState_field(JpqlParser.State_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitCollection_value_field(JpqlParser.Collection_value_fieldContext ctx) { + public QueryRendererBuilder visitCollection_value_field(JpqlParser.Collection_value_fieldContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitEntity_name(JpqlParser.Entity_nameContext ctx) { - - List tokens = new ArrayList<>(); - - ctx.reserved_word().forEach(ctx2 -> { - tokens.addAll(visitReserved_word(ctx2)); - NOSPACE(tokens); - tokens.add(TOKEN_DOT); - }); - CLIP(tokens); - SPACE(tokens); - - return tokens; + public QueryRendererBuilder visitEntity_name(JpqlParser.Entity_nameContext ctx) { + return QueryRendererBuilder.concat(ctx.reserved_word(), this::visitReserved_word, TOKEN_DOT); } @Override - public List visitResult_variable(JpqlParser.Result_variableContext ctx) { + public QueryRendererBuilder visitResult_variable(JpqlParser.Result_variableContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitSuperquery_identification_variable( + public QueryRendererBuilder visitSuperquery_identification_variable( JpqlParser.Superquery_identification_variableContext ctx) { return visit(ctx.identification_variable()); } @Override - public List visitCollection_valued_input_parameter( + public QueryRendererBuilder visitCollection_valued_input_parameter( JpqlParser.Collection_valued_input_parameterContext ctx) { return visit(ctx.input_parameter()); } @Override - public List visitSingle_valued_input_parameter( - JpqlParser.Single_valued_input_parameterContext ctx) { + public QueryRendererBuilder visitSingle_valued_input_parameter(JpqlParser.Single_valued_input_parameterContext ctx) { return visit(ctx.input_parameter()); } @Override - public List visitFunction_name(JpqlParser.Function_nameContext ctx) { + public QueryRendererBuilder visitFunction_name(JpqlParser.Function_nameContext ctx) { return visit(ctx.string_literal()); } @Override - public List visitCharacter_valued_input_parameter( + public QueryRendererBuilder visitCharacter_valued_input_parameter( JpqlParser.Character_valued_input_parameterContext ctx) { if (ctx.CHARACTER() != null) { - return List.of(new JpaQueryParsingToken(ctx.CHARACTER())); + return QueryRendererBuilder.from(JpaQueryParsingToken.expression(ctx.CHARACTER())); } else if (ctx.input_parameter() != null) { return visit(ctx.input_parameter()); } else { - return List.of(); + return QueryRenderer.builder(); } } @Override - public List visitReserved_word(Reserved_wordContext ctx) { + public QueryRendererBuilder visitReserved_word(Reserved_wordContext ctx) { if (ctx.IDENTIFICATION_VARIABLE() != null) { - return List.of(new JpaQueryParsingToken(ctx.IDENTIFICATION_VARIABLE())); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.IDENTIFICATION_VARIABLE())); } else if (ctx.f != null) { - return List.of(new JpaQueryParsingToken(ctx.f)); + return QueryRendererBuilder.from(JpaQueryParsingToken.token(ctx.f)); } else { - return List.of(); + return QueryRenderer.builder(); } } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformer.java deleted file mode 100644 index 4c2f5f6c4..000000000 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformer.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2022-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jpa.repository.query; - -import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.springframework.data.domain.Sort; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed JPQL query. - * - * @author Greg Turnquist - * @since 3.1 - */ -class JpqlQueryTransformer extends JpqlQueryRenderer { - - // TODO: Separate input from result parameters, encapsulation... - private final Sort sort; - private final boolean countQuery; - - private final @Nullable String countProjection; - - private @Nullable String primaryFromAlias = null; - - private List projection = Collections.emptyList(); - private boolean projectionProcessed; - - private boolean hasConstructorExpression = false; - - private JpaQueryTransformerSupport transformerSupport; - - JpqlQueryTransformer() { - this(Sort.unsorted(), false, null); - } - - JpqlQueryTransformer(Sort sort) { - this(sort, false, null); - } - - JpqlQueryTransformer(boolean countQuery, @Nullable String countProjection) { - this(Sort.unsorted(), countQuery, countProjection); - } - - private JpqlQueryTransformer(Sort sort, boolean countQuery, @Nullable String countProjection) { - - Assert.notNull(sort, "Sort must not be null"); - - this.sort = sort; - this.countQuery = countQuery; - this.countProjection = countProjection; - this.transformerSupport = new JpaQueryTransformerSupport(); - } - - @Nullable - public String getAlias() { - return this.primaryFromAlias; - } - - public List getProjection() { - return this.projection; - } - - public boolean hasConstructorExpression() { - return this.hasConstructorExpression; - } - - @Override - public List visitSelect_statement(JpqlParser.Select_statementContext ctx) { - - List tokens = newArrayList(); - - tokens.addAll(visit(ctx.select_clause())); - tokens.addAll(visit(ctx.from_clause())); - - if (ctx.where_clause() != null) { - tokens.addAll(visit(ctx.where_clause())); - } - - if (ctx.groupby_clause() != null) { - tokens.addAll(visit(ctx.groupby_clause())); - } - - if (ctx.having_clause() != null) { - tokens.addAll(visit(ctx.having_clause())); - } - - if (!countQuery) { - - if (ctx.orderby_clause() != null) { - tokens.addAll(visit(ctx.orderby_clause())); - } - - if (sort.isSorted()) { - - if (ctx.orderby_clause() != null) { - - NOSPACE(tokens); - tokens.add(TOKEN_COMMA); - } else { - - SPACE(tokens); - tokens.add(TOKEN_ORDER_BY); - } - - tokens.addAll(transformerSupport.generateOrderByArguments(primaryFromAlias, sort)); - } - } - - return tokens; - } - - @Override - public List visitSelect_clause(JpqlParser.Select_clauseContext ctx) { - - List tokens = newArrayList(); - - tokens.add(new JpaQueryParsingToken(ctx.SELECT())); - - if (countQuery) { - tokens.add(TOKEN_COUNT_FUNC); - } - - if (ctx.DISTINCT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.DISTINCT())); - } - - List selectItemTokens = newArrayList(); - - ctx.select_item().forEach(selectItemContext -> { - selectItemTokens.addAll(visit(selectItemContext)); - NOSPACE(selectItemTokens); - selectItemTokens.add(TOKEN_COMMA); - }); - CLIP(selectItemTokens); - SPACE(selectItemTokens); - - if (countQuery) { - - if (countProjection != null) { - tokens.add(new JpaQueryParsingToken(countProjection)); - } else { - - if (ctx.DISTINCT() != null) { - - List countSelection = QueryTransformers.filterCountSelection(selectItemTokens); - - if (countSelection.stream().anyMatch(jpqlToken -> jpqlToken.getToken().contains("new"))) { - // constructor - tokens.add(new JpaQueryParsingToken(() -> primaryFromAlias)); - } else { - // keep all the select items to distinct against - tokens.addAll(countSelection); - } - } else { - tokens.add(new JpaQueryParsingToken(() -> primaryFromAlias)); - } - } - - NOSPACE(tokens); - tokens.add(TOKEN_CLOSE_PAREN); - } else { - tokens.addAll(selectItemTokens); - } - - if (!projectionProcessed) { - projection = selectItemTokens; - projectionProcessed = true; - } - - return tokens; - } - - @Override - public List visitSelect_item(JpqlParser.Select_itemContext ctx) { - - List tokens = super.visitSelect_item(ctx); - - if (ctx.result_variable() != null) { - transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); - } - - return tokens; - } - - @Override - public List visitRange_variable_declaration(JpqlParser.Range_variable_declarationContext ctx) { - - List tokens = newArrayList(); - - tokens.addAll(visit(ctx.entity_name())); - - if (ctx.AS() != null) { - tokens.add(new JpaQueryParsingToken(ctx.AS())); - } - - tokens.addAll(visit(ctx.identification_variable())); - - if (primaryFromAlias == null) { - primaryFromAlias = tokens.get(tokens.size() - 1).getToken(); - } - - return tokens; - } - - @Override - public List visitJoin(JpqlParser.JoinContext ctx) { - - List tokens = super.visitJoin(ctx); - - transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); - - return tokens; - } - - @Override - public List visitConstructor_expression(JpqlParser.Constructor_expressionContext ctx) { - - hasConstructorExpression = true; - - return super.visitConstructor_expression(ctx); - } - - private static ArrayList newArrayList() { - return new ArrayList<>(); - } -} 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 new file mode 100644 index 000000000..5b8a0aa29 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java @@ -0,0 +1,125 @@ +/* + * Copyright 2022-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import java.util.List; + +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed JPQL query by applying + * {@link Sort}. + * + * @author Greg Turnquist + * @author Mark Paluch + * @since 3.1 + */ +@SuppressWarnings("ConstantValue") +class JpqlSortedQueryTransformer extends JpqlQueryRenderer { + + private final JpaQueryTransformerSupport transformerSupport = new JpaQueryTransformerSupport(); + private final Sort sort; + private final @Nullable String primaryFromAlias; + + JpqlSortedQueryTransformer(Sort sort, @Nullable String primaryFromAlias) { + + Assert.notNull(sort, "Sort must not be null"); + + this.sort = sort; + this.primaryFromAlias = primaryFromAlias; + } + + @Override + public QueryRendererBuilder visitSelect_statement(JpqlParser.Select_statementContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.select_clause())); + builder.appendExpression(visit(ctx.from_clause())); + + if (ctx.where_clause() != null) { + builder.appendExpression(visit(ctx.where_clause())); + } + + if (ctx.groupby_clause() != null) { + builder.appendExpression(visit(ctx.groupby_clause())); + } + + if (ctx.having_clause() != null) { + builder.appendExpression(visit(ctx.having_clause())); + } + + doVisitOrderBy(builder, ctx); + + return builder; + } + + private void doVisitOrderBy(QueryRendererBuilder builder, JpqlParser.Select_statementContext ctx) { + + if (ctx.orderby_clause() != null) { + QueryRendererBuilder existingOrder = visit(ctx.orderby_clause()); + if (sort.isSorted()) { + builder.appendInline(existingOrder); + } else { + builder.append(existingOrder); + } + } + + if (sort.isSorted()) { + + List sortBy = transformerSupport.orderBy(primaryFromAlias, sort); + + if (ctx.orderby_clause() != null) { + + QueryRendererBuilder extension = QueryRenderer.builder().append(TOKEN_COMMA).append(sortBy); + + builder.appendInline(extension); + } else { + builder.append(TOKEN_ORDER_BY); + builder.append(sortBy); + } + } + } + + @Override + public QueryRendererBuilder visitSelect_item(JpqlParser.Select_itemContext ctx) { + + QueryRendererBuilder builder = super.visitSelect_item(ctx); + + if (ctx.result_variable() != null) { + List tokens = builder.build().stream().toList(); + transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); + } + + return builder; + } + + @Override + public QueryRendererBuilder visitJoin(JpqlParser.JoinContext ctx) { + + QueryRendererBuilder builder = super.visitJoin(ctx); + + List tokens = builder.build().stream().toList(); + transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken()); + + return builder; + } +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java index 5b665c287..ebe0278b0 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java @@ -77,7 +77,8 @@ final class NamedQuery extends AbstractJpaQuery { Query query = em.createNamedQuery(queryName); String queryString = extractor.extractQueryString(query); - this.declaredQuery = DeclaredQuery.of(queryString, false); + // TODO: Detect whether a named query is a named one. + this.declaredQuery = DeclaredQuery.of(queryString, query != null && query.toString().contains("NativeQuery")); boolean weNeedToCreateCountQuery = !namedCountQueryIsPresent && method.getParameters().hasLimitingParameters(); boolean cantExtractQuery = !extractor.canExtractQuery(); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParsedQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParsedQueryIntrospector.java new file mode 100644 index 000000000..067371eb8 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParsedQueryIntrospector.java @@ -0,0 +1,45 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import java.util.List; + +import org.antlr.v4.runtime.tree.ParseTree; + +import org.springframework.lang.Nullable; + +/** + * Interface defining an introspector for String-queries providing details about the primary table alias, the primary + * selection projection and whether the query makes use of constructor expressions. + * + * @author Mark Paluch + */ +interface ParsedQueryIntrospector { + + Void visit(ParseTree tree); + + /** + * Primary table alias. Contains the first table name/table alias in case multiple tables are specified in the query. + * + * @return + */ + @Nullable + String getAlias(); + + List getProjection(); + + boolean hasConstructorExpression(); +} 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 new file mode 100644 index 000000000..e7b83c69b --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java @@ -0,0 +1,464 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Abstraction to encapsulate query expressions and render a query. + *

+ * Query rendering consists of multiple building blocks: + *

    + *
  • {@link JpaQueryParsingToken tokens} and + * {@link org.springframework.data.jpa.repository.query.JpaQueryParsingToken.JpaQueryExpression expression tokens}
  • + *
  • {@link QueryRenderer compositions} such as a composition of multiple tokens.
  • + *
  • {@link QueryRenderer expressions} that are individual parts such as {@code SELECT} or {@code ORDER BY …}
  • + *
  • {@link QueryRenderer inline expressions} such as composition of tokens and expressions such as function calls + * with parenthesis {@code SOME_FUNCTION(ARGS)}
  • + *
+ * + * @author Mark Paluch + */ +abstract class QueryRenderer { + + /** + * Creates a QueryRenderer from a collection of {@link JpaQueryParsingToken}. + * + * @param tokens + * @return + */ + static QueryRenderer from(Collection tokens) { + List tokensToUse = new ArrayList<>(32); + tokensToUse.addAll(tokens); + return new TokenRenderer(tokensToUse); + } + + /** + * Creates a new empty {@link QueryRenderer}. + * + * @return + */ + public static QueryRenderer empty() { + return EmptyQueryRenderer.INSTANCE; + } + + /** + * Creates a new {@link QueryRendererBuilder}. + * + * @return + */ + static QueryRendererBuilder builder() { + return new QueryRendererBuilder(); + } + + /** + * @return the rendered query. + */ + abstract String render(); + + /** + * Append a {@link QueryRenderer} to create a composed renderer. + * + * @param renderer + * @return + */ + QueryRenderer append(QueryRenderer renderer) { + List objects = new ArrayList<>(32); + objects.add(this); + objects.add(renderer); + return new CompositeRenderer(objects); + } + + /** + * @return {@code true} if the query renderer represents an expression. + */ + public boolean isExpression() { + return false; + } + + /** + * @return stream of tokens. + */ + public Stream stream() { + return Stream.empty(); + } + + @Override + public String toString() { + return render(); + } + + /** + * Composed renderer consisting of one or more QueryRenderers. + */ + static class CompositeRenderer extends QueryRenderer { + + private final List nested; + + CompositeRenderer(List nested) { + this.nested = new ArrayList<>(nested); + } + + @Override + String render() { + + StringBuilder builder = new StringBuilder(2048); + + boolean lastExpression = false; + for (QueryRenderer queryRenderer : nested) { + + if (!builder.isEmpty() && builder.charAt(builder.length() - 1) != ' ' + && (lastExpression || queryRenderer.isExpression())) { + builder.append(' '); + } + + builder.append(queryRenderer.render()); + lastExpression = queryRenderer.isExpression(); + } + + return builder.toString(); + } + + @Override + QueryRenderer append(QueryRenderer renderer) { + + nested.add(renderer); + return this; + } + + @Override + public boolean isExpression() { + return !nested.isEmpty() && nested.get(nested.size() - 1).isExpression(); + } + + @Override + public Stream stream() { + + Stream stream = Stream.empty(); + + for (QueryRenderer renderer : nested) { + stream = Stream.concat(stream, renderer.stream()); + } + + return stream; + } + } + + /** + * Renderer using {@link JpaQueryParsingToken}. + */ + static class TokenRenderer extends QueryRenderer { + + private final List tokens; + + TokenRenderer(List tokens) { + this.tokens = tokens; + } + + @Override + String render() { + return JpaQueryParsingToken.render(tokens); + } + + @Override + QueryRenderer append(QueryRenderer renderer) { + + if (renderer instanceof TokenRenderer tr) { + this.tokens.addAll(tr.tokens); + return this; + } + + return super.append(renderer); + } + + @Override + public boolean isExpression() { + return !tokens.isEmpty() && tokens.get(tokens.size() - 1) instanceof JpaQueryParsingToken.JpaQueryExpression; + } + + @Override + public Stream stream() { + return tokens.stream(); + } + } + + /** + * Builder for {@link QueryRenderer}. + */ + static class QueryRendererBuilder { + + 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, + JpaQueryParsingToken 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, JpaQueryParsingToken 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, JpaQueryParsingToken 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 JpaQueryParsingToken}. + * + * @param token + * @return + */ + public static QueryRendererBuilder from(JpaQueryParsingToken token) { + return new QueryRendererBuilder().append(token); + } + + /** + * Append a {@link JpaQueryParsingToken}. + * + * @param token + * @return {@code this} builder. + */ + QueryRendererBuilder append(JpaQueryParsingToken token) { + return append(List.of(token)); + } + + /** + * Append a collection of {@link JpaQueryParsingToken}. + * + * @param tokens + * @return {@code this} builder. + */ + QueryRendererBuilder append(Collection tokens) { + return append(QueryRenderer.from(tokens)); + } + + /** + * Append a QueryRendererBuilder. + * + * @param builder + * @return {@code this} builder. + */ + QueryRendererBuilder append(QueryRendererBuilder builder) { + return append(builder.current); + } + + /** + * Append a QueryRendererBuilder as expression. + * + * @param builder + * @return {@code this} builder. + */ + QueryRendererBuilder appendExpression(QueryRendererBuilder builder) { + return appendExpression(builder.current); + } + + /** + * Append a QueryRendererBuilder as inline. + * + * @param builder + * @return {@code this} builder. + */ + QueryRendererBuilder appendInline(QueryRendererBuilder builder) { + return appendInline(builder.current); + } + + /** + * Append a QueryRenderer. + * + * @param renderer + * @return {@code this} builder. + */ + QueryRendererBuilder append(QueryRenderer renderer) { + + if (renderer instanceof EmptyQueryRenderer) { + return this; + } + + current = current.append(renderer); + + return this; + } + + /** + * Append a QueryRenderer inline. + * + * @param renderer + * @return {@code this} builder. + */ + QueryRendererBuilder appendInline(QueryRenderer renderer) { + + if (renderer instanceof EmptyQueryRenderer) { + return this; + } + + current = current.append(!renderer.isExpression() ? renderer : new InlineRenderer(renderer)); + + return this; + } + + /** + * Append a QueryRenderer as expression. + * + * @param renderer + * @return {@code this} builder. + */ + QueryRendererBuilder appendExpression(QueryRenderer renderer) { + + if (renderer instanceof EmptyQueryRenderer) { + return this; + } + + current = current.append(renderer.isExpression() ? renderer : new ExpressionRenderer(renderer)); + + return this; + } + + /** + * Return whet the builder is empty. + * + * @return + */ + public boolean isEmpty() { + return current instanceof EmptyQueryRenderer; + } + + public QueryRenderer build() { + return current; + } + + @Override + public String toString() { + return current.render(); + } + + private QueryRenderer toExpression() { + + if (current instanceof ExpressionRenderer) { + return current; + } + + return new ExpressionRenderer(current); + } + + public QueryRenderer toInline() { + return new InlineRenderer(current); + } + } + + private static class InlineRenderer extends QueryRenderer { + + private final QueryRenderer delegate; + + private InlineRenderer(QueryRenderer delegate) { + this.delegate = delegate; + } + + @Override + String render() { + return delegate.render(); + } + + @Override + public Stream stream() { + return delegate.stream(); + } + } + + private static class ExpressionRenderer extends QueryRenderer { + + private final QueryRenderer delegate; + + private ExpressionRenderer(QueryRenderer delegate) { + this.delegate = delegate; + } + + @Override + String render() { + return delegate.render(); + } + + @Override + public boolean isExpression() { + return true; + } + + @Override + public Stream stream() { + return delegate.stream(); + } + } + + private static class EmptyQueryRenderer extends QueryRenderer { + + public static final QueryRenderer INSTANCE = new EmptyQueryRenderer(); + + @Override + String render() { + return ""; + } + + @Override + QueryRenderer append(QueryRenderer renderer) { + return renderer; + } + } +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTransformers.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTransformers.java index 368e9a95b..a6f167cea 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTransformers.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTransformers.java @@ -51,6 +51,11 @@ class QueryTransformers { skipNext = true; continue; } + + if (!token.isA(TOKEN_COMMA) && token instanceof JpaQueryExpression) { + token = JpaQueryParsingToken.token(token.getToken()); + } + target.add(token); } 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 5bea73422..ed57263e8 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 @@ -60,8 +60,6 @@ class StringQuery implements DeclaredQuery { private final String query; private final List bindings; - private final @Nullable String alias; - private final boolean hasConstructorExpression; private final boolean containsPageableInSpel; private final boolean usesJdbcStyleParameters; private final boolean isNative; @@ -87,8 +85,22 @@ class StringQuery implements DeclaredQuery { this.usesJdbcStyleParameters = queryMeta.usesJdbcStyleParameters; this.queryEnhancer = QueryEnhancerFactory.forQuery(this); - this.alias = this.queryEnhancer.detectAlias(); - this.hasConstructorExpression = this.queryEnhancer.hasConstructorExpression(); + } + + // TODO: Conflict with eager JpaQueryMethod.assertParameterNamesInAnnotatedQuery validation that attempts parsing + // without pre-processing the query leaving #{#entityName} substitution to a later time. + public static boolean hasNamedParameter(String query) { + + if (ObjectUtils.isEmpty(query)) { + return false; + } + + List parameterBindings = new ArrayList<>(); + Metadata queryMeta = new Metadata(); + ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query, + parameterBindings, queryMeta); + + return parameterBindings.stream().anyMatch(b -> b.getIdentifier().hasName()); } /** @@ -134,17 +146,17 @@ class StringQuery implements DeclaredQuery { @Override @Nullable public String getAlias() { - return alias; + return queryEnhancer.detectAlias(); } @Override public boolean hasConstructorExpression() { - return hasConstructorExpression; + return queryEnhancer.hasConstructorExpression(); } @Override public boolean isDefaultProjection() { - return getProjection().equalsIgnoreCase(alias); + return getProjection().equalsIgnoreCase(getAlias()); } @Override @@ -221,7 +233,7 @@ class StringQuery implements DeclaredQuery { * Parses {@link ParameterBinding} instances from the given query and adds them to the registered bindings. Returns * the cleaned up query. */ - private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String query, + String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String query, List bindings, Metadata queryMeta) { int greatestParameterIndex = tryFindGreatestParameterIndexIn(query); @@ -447,7 +459,7 @@ class StringQuery implements DeclaredQuery { } } - private static class Metadata { + static class Metadata { private boolean usesJdbcStyleParameters = false; } 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 124de35a3..606434093 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 @@ -197,10 +197,10 @@ class EqlComplianceTests { 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 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'"); @@ -233,10 +233,10 @@ class EqlComplianceTests { 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 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'"); @@ -261,10 +261,10 @@ class EqlComplianceTests { assertQuery("SELECT e FROM Employee e ORDER BY e.salary - 1000"); assertQuery("SELECT e FROM Employee e ORDER BY e.salary + 1000"); - assertQuery("SELECT e FROM Employee e ORDER BY e.salary*2"); - assertQuery("SELECT e FROM Employee e ORDER BY e.salary*2.0"); - assertQuery("SELECT e FROM Employee e ORDER BY e.salary/2"); - assertQuery("SELECT e FROM Employee e ORDER BY e.salary/2.0"); + assertQuery("SELECT e FROM Employee e ORDER BY e.salary * 2"); + assertQuery("SELECT e FROM Employee e ORDER BY e.salary * 2.0"); + assertQuery("SELECT e FROM Employee e ORDER BY e.salary / 2"); + assertQuery("SELECT e FROM Employee e ORDER BY e.salary / 2.0"); assertQuery("SELECT e FROM Employee e ORDER BY ABS(e.salary - e.manager.salary)"); assertQuery("SELECT e FROM Employee e ORDER BY COALESCE(e.salary, 0)"); assertQuery("SELECT e FROM Employee e ORDER BY CONCAT(e.firstName, ' ', e.lastName)"); @@ -290,10 +290,10 @@ class EqlComplianceTests { assertQuery("SELECT e FROM Employee e GROUP BY e.salary - 1000"); assertQuery("SELECT e FROM Employee e GROUP BY e.salary + 1000"); - assertQuery("SELECT e FROM Employee e GROUP BY e.salary*2"); - assertQuery("SELECT e FROM Employee e GROUP BY e.salary*2.0"); - assertQuery("SELECT e FROM Employee e GROUP BY e.salary/2"); - assertQuery("SELECT e FROM Employee e GROUP BY e.salary/2.0"); + assertQuery("SELECT e FROM Employee e GROUP BY e.salary * 2"); + assertQuery("SELECT e FROM Employee e GROUP BY e.salary * 2.0"); + assertQuery("SELECT e FROM Employee e GROUP BY e.salary / 2"); + assertQuery("SELECT e FROM Employee e GROUP BY e.salary / 2.0"); assertQuery("SELECT e FROM Employee e GROUP BY ABS(e.salary - e.manager.salary)"); assertQuery("SELECT e FROM Employee e GROUP BY COALESCE(e.salary, 0)"); assertQuery("SELECT e FROM Employee e GROUP BY CONCAT(e.firstName, ' ', e.lastName)"); @@ -319,10 +319,10 @@ class EqlComplianceTests { assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary - 1000 > 0"); assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary + 1000 > 0"); - assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary*2 > 0"); - assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary*2.0 > 0.0"); - assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary/2 > 0"); - assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary/2.0 > 0.0"); + assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary * 2 > 0"); + assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary * 2.0 > 0.0"); + assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary / 2 > 0"); + assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING e.salary / 2.0 > 0.0"); assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING ABS(e.salary - e.manager.salary) > 0"); assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING COALESCE(e.salary, 0) > 0"); assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING CONCAT(e.firstName, ' ', e.lastName) = 'Bilbo'"); 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 7fccc9cb8..76dd2f01a 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 @@ -295,7 +295,7 @@ class EqlQueryRendererTests { assertQuery(""" SELECT b.name, b.ISBN FROM Order o JOIN TREAT(o.product AS Book) b - """); + """); } @Test @@ -304,7 +304,7 @@ class EqlQueryRendererTests { assertQuery(""" SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) lp WHERE lp.budget > 1000 - """); + """); } /** @@ -319,7 +319,7 @@ class EqlQueryRendererTests { WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE "cost overrun" - """); + """); } @Test @@ -330,7 +330,7 @@ class EqlQueryRendererTests { WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE 'cost overrun' - """); + """); } @Test @@ -340,7 +340,7 @@ class EqlQueryRendererTests { SELECT e FROM Employee e WHERE TREAT(e AS Exempt).vacationDays > 10 OR TREAT(e AS Contractor).hours > 100 - """); + """); } @Test @@ -404,7 +404,7 @@ class EqlQueryRendererTests { WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department) - """); + """); } @Test @@ -416,7 +416,7 @@ class EqlQueryRendererTests { WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) - """); + """); } @Test @@ -435,7 +435,7 @@ class EqlQueryRendererTests { assertQuery(""" SELECT goodCustomer FROM Customer goodCustomer - WHERE goodCustomer.balanceOwed < (SELECT AVG(c.balanceOwed)/2.0 FROM Customer c) + WHERE goodCustomer.balanceOwed < (SELECT AVG(c.balanceOwed) / 2.0 FROM Customer c) """); } @@ -480,11 +480,11 @@ class EqlQueryRendererTests { assertQuery(""" UPDATE Employee e SET e.salary = - CASE WHEN e.rating = 1 THEN e.salary*1.1 - WHEN e.rating = 2 THEN e.salary*1.05 - ELSE e.salary*1.01 + CASE WHEN e.rating = 1 THEN e.salary * 1.1 + WHEN e.rating = 2 THEN e.salary * 1.05 + ELSE e.salary * 1.01 END - """); + """); } @Test @@ -493,11 +493,11 @@ class EqlQueryRendererTests { assertQuery(""" UPDATE Employee e SET e.salary = - CASE e.rating WHEN 1 THEN e.salary*1.1 - WHEN 2 THEN e.salary*1.05 - ELSE e.salary*1.01 + CASE e.rating WHEN 1 THEN e.salary * 1.1 + WHEN 2 THEN e.salary * 1.05 + ELSE e.salary * 1.01 END - """); + """); } @Test @@ -537,7 +537,7 @@ class EqlQueryRendererTests { SELECT e FROM Employee e WHERE TYPE(e) IN (Exempt, Contractor) - """); + """); } @Test @@ -734,7 +734,7 @@ class EqlQueryRendererTests { void orderByThatMatchesAllSelectAliasesShouldWork() { assertQuery(""" - SELECT o.quantity, o.cost*1.08 AS taxedCost, a.zipcode + SELECT o.quantity, o.cost * 1.08 AS taxedCost, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' AND a.county = 'Santa Clara' ORDER BY o.quantity, taxedCost, a.zipcode @@ -967,11 +967,26 @@ class EqlQueryRendererTests { assertQuery("select e from Employee e where e.firstName != :name"); } + @Test + void regexShouldWork() { + assertQuery("select e from Employee e where e.lastName REGEXP '^Dr\\.*'"); + } + @Test // GH-3092 void dateAndFromShouldBeValidNames() { assertQuery("SELECT e FROM Entity e WHERE e.embeddedId.date BETWEEN :from AND :to"); } + @Test + void betweenStrings() { + assertQuery("SELECT e FROM Entity e WHERE e.embeddedId.date NOT BETWEEN 'a' AND 'b'"); + } + + @Test + void betweenDates() { + assertQuery("SELECT e FROM Entity e WHERE e.embeddedId.date BETWEEN CURRENT_DATE AND CURRENT_TIME"); + } + @Test // GH-3092 void timeShouldBeAValidParameterName() { assertQuery(""" @@ -1004,7 +1019,7 @@ class EqlQueryRendererTests { @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", + "select +1 * -100 from User u", "select count(u) * -0.7f from User u", "select count(oi) + (-100) as perc from StockOrderItem oi", "select p from Payment p where length(p.cardNumber) between +16 and -20" }) void signedLiteralShouldWork(String query) { @@ -1012,7 +1027,7 @@ class EqlQueryRendererTests { } @ParameterizedTest // GH-3342 - @ValueSource(strings = { "select -count(u) from User u", "select +1*(-count(u)) from User u" }) + @ValueSource(strings = { "select -count(u) from User u", "select +1 * (-count(u)) from User u" }) void signedExpressionsShouldWork(String query) { assertQuery(query); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java index 2b5e05252..18c42c875 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java @@ -188,7 +188,7 @@ class EqlQueryTransformerTests { assertThat(alias("select u from User u where not exists (select u2 from User u2)")).isEqualTo("u"); assertThat(alias( "select u from User u where not exists (select u2 from User u2 where not exists (select u3 from User u3))")) - .isEqualTo("u"); + .isEqualTo("u"); } @Test // GH-2557 @@ -349,7 +349,7 @@ class EqlQueryTransformerTests { from Bar lp join lp.investmentProduct ip where (lp.toDate is null and lp.fromDate <= :now and lp.fromDate is not null) and lp.accountId = :accountId group by ip.id, ip.name, lp.accountId order by ip.name ASC""")) - .isTrue(); + .isTrue(); } @Test // DATAJPA-938 @@ -441,7 +441,7 @@ class EqlQueryTransformerTests { String query = "SELECT AVG(m.price) AS m.avg FROM Magazine m"; Sort sort = Sort.by("m.avg"); - assertThatIllegalArgumentException().isThrownBy(() -> createQueryFor(query, sort)); + assertThatExceptionOfType(BadJpqlGrammarException.class).isThrownBy(() -> createQueryFor(query, sort)); } @Test // DATAJPA-965, DATAJPA-970, GH-2863 @@ -456,8 +456,10 @@ class EqlQueryTransformerTests { @Test // DATAJPA-1506 void detectsAliasWithGroupAndOrderBy() { - assertThat(alias("select * from User group by name")).isNull(); - assertThat(alias("select * from User order by name")).isNull(); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> alias("select * from User group by name")); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> alias("select * from User order by name")); assertThat(alias("select u from User u group by name")).isEqualTo("u"); assertThat(alias("select u from User u order by name")).isEqualTo("u"); } @@ -559,8 +561,10 @@ class EqlQueryTransformerTests { @Test void detectsAliasWithGroupAndOrderByWithLineBreaks() { - assertThat(alias("select * from User group\nby name")).isNull(); - assertThat(alias("select * from User order\nby name")).isNull(); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> alias("select * from User group\nby name")); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> alias("select * from User order\nby name")); assertThat(alias("select u from User u group\nby name")).isEqualTo("u"); assertThat(alias("select u from User u order\nby name")).isEqualTo("u"); assertThat(alias("select u from User\nu\norder \n by name")).isEqualTo("u"); @@ -583,7 +587,8 @@ class EqlQueryTransformerTests { // This is not a required behavior, in fact the opposite is, // but it documents a current limitation. // to fix this without breaking findProjectionClauseWithIncludedFrom we need a more sophisticated parser. - assertThat(projection("select * from (select x from y)")).isNotEqualTo("*"); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> projection("select * from (select x from y)")); } @Test // DATAJPA-1696 @@ -620,11 +625,11 @@ class EqlQueryTransformerTests { assertCountQuery("select distinct 1 as x from Employee e", "select count(distinct 1) from Employee e"); assertCountQuery("SELECT DISTINCT abc AS x FROM T t", "SELECT count(DISTINCT abc) FROM T t"); - assertCountQuery("select distinct a as x, b as y from Employee e", "select count(distinct a , b) from Employee e"); + assertCountQuery("select distinct a as x, b as y from Employee e", "select count(distinct a, b) from Employee e"); assertCountQuery("select distinct sum(amount) as x from Employee e GROUP BY n", "select count(distinct sum(amount)) from Employee e GROUP BY n"); assertCountQuery("select distinct a, b, sum(amount) as c, d from Employee e GROUP BY n", - "select count(distinct a, b, sum(amount) , d) from Employee e GROUP BY n"); + "select count(distinct a, b, sum(amount), d) from Employee e GROUP BY n"); assertCountQuery("select distinct a, count(b) as c from Employee e GROUP BY n", "select count(distinct a, count(b)) from Employee e GROUP BY n"); } @@ -660,14 +665,14 @@ class EqlQueryTransformerTests { assertThat( createCountQueryFor("SELECT t FROM mytable t WHERE nr = :number AND kon = :kon AND datum >= '2019-01-01'")) - .isEqualTo("SELECT count(t) FROM mytable t WHERE nr = :number AND kon = :kon AND datum >= '2019-01-01'"); + .isEqualTo("SELECT count(t) FROM mytable t WHERE nr = :number AND kon = :kon AND datum >= '2019-01-01'"); assertThat(createCountQueryFor("select s FROM users_statuses s WHERE (user_created_at BETWEEN $1 AND $2)")) .isEqualTo("select count(s) FROM users_statuses s WHERE (user_created_at BETWEEN $1 AND $2)"); assertThat( createCountQueryFor("SELECT us FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)")) - .isEqualTo("SELECT count(us) FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)"); + .isEqualTo("SELECT count(us) FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)"); } @Test // GH-2496, GH-2522, GH-2537, GH-2045 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 b2187ef6f..1dda209b7 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 @@ -16,7 +16,10 @@ package org.springframework.data.jpa.repository.query; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -32,13 +35,38 @@ class EqlSpecificationTests { private static final String SPEC_FAULT = "Disabled due to spec fault> "; + private static String parseWithoutChanges(String query) { + + EqlLexer lexer = new EqlLexer(CharStreams.fromString(query)); + EqlParser parser = new EqlParser(new CommonTokenStream(lexer)); + + parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); + + EqlParser.StartContext parsedQuery = parser.start(); + + return render(new EqlQueryRenderer().visit(parsedQuery)); + } + + private void assertQuery(String query) { + + String slimmedDownQuery = reduceWhitespace(query); + assertThat(parseWithoutChanges(slimmedDownQuery)).isEqualTo(slimmedDownQuery); + } + + private String reduceWhitespace(String original) { + + return original // + .replaceAll("[ \\t\\n]{1,}", " ") // + .trim(); + } + /** * @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#example */ @Test void joinExample1() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order AS o JOIN o.lineItems AS l WHERE l.shipped = FALSE @@ -52,7 +80,7 @@ class EqlSpecificationTests { @Test void joinExample2() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l JOIN l.product p WHERE p.productType = 'office_supplies' @@ -65,12 +93,12 @@ class EqlSpecificationTests { @Test void rangeVariableDeclarations() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o1 FROM Order o1, Order o2 WHERE o1.quantity > o2.quantity AND o2.customer.lastname = 'Smith' AND - o2.customer.firstname= 'John' + o2.customer.firstname = 'John' """); } @@ -80,7 +108,7 @@ class EqlSpecificationTests { @Test void pathExpressionsExample1() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT i.name, VALUE(p) FROM Item i JOIN i.photos p WHERE KEY(p) LIKE '%egret' @@ -93,7 +121,7 @@ class EqlSpecificationTests { @Test void pathExpressionsExample2() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT i.name, p FROM Item i JOIN i.photos p WHERE KEY(p) LIKE '%egret' @@ -106,7 +134,7 @@ class EqlSpecificationTests { @Test void pathExpressionsExample3() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.vendor FROM Employee e JOIN e.contactInfo.phones p """); @@ -118,7 +146,7 @@ class EqlSpecificationTests { @Test void pathExpressionsExample4() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.vendor FROM Employee e JOIN e.contactInfo c JOIN c.phones p WHERE e.contactInfo.address.zipcode = '95054' @@ -128,7 +156,7 @@ class EqlSpecificationTests { @Test void pathExpressionSyntaxExample1() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT l.product FROM Order AS o JOIN o.lineItems l """); @@ -137,7 +165,7 @@ class EqlSpecificationTests { @Test void joinsExample1() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c, Employee e WHERE c.hatsize = e.shoesize """); } @@ -145,7 +173,7 @@ class EqlSpecificationTests { @Test void joinsExample2() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c JOIN c.orders o WHERE c.status = 1 """); } @@ -153,7 +181,7 @@ class EqlSpecificationTests { @Test void joinsInnerExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c INNER JOIN c.orders o WHERE c.status = 1 """); } @@ -161,7 +189,7 @@ class EqlSpecificationTests { @Test void joinsInExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT OBJECT(c) FROM Customer c, IN(c.orders) o WHERE c.status = 1 """); } @@ -169,7 +197,7 @@ class EqlSpecificationTests { @Test void doubleJoinExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.vendor FROM Employee e JOIN e.contactInfo c JOIN c.phones p WHERE c.address.zipcode = '95054' @@ -179,7 +207,7 @@ class EqlSpecificationTests { @Test void leftJoinExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT s.name, COUNT(p) FROM Suppliers s LEFT JOIN s.products p GROUP BY s.name @@ -189,7 +217,7 @@ class EqlSpecificationTests { @Test void leftJoinOnExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT s.name, COUNT(p) FROM Suppliers s LEFT JOIN s.products p ON p.status = 'inStock' @@ -200,7 +228,7 @@ class EqlSpecificationTests { @Test void leftJoinWhereExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT s.name, COUNT(p) FROM Suppliers s LEFT JOIN s.products p WHERE p.status = 'inStock' @@ -211,7 +239,7 @@ class EqlSpecificationTests { @Test void leftJoinFetchExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT d FROM Department d LEFT JOIN FETCH d.employees WHERE d.deptno = 1 @@ -221,7 +249,7 @@ class EqlSpecificationTests { @Test void collectionMemberExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l WHERE l.product.productType = 'office_supplies' @@ -231,7 +259,7 @@ class EqlSpecificationTests { @Test void collectionMemberInExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o, IN(o.lineItems) l WHERE l.product.productType = 'office_supplies' @@ -241,7 +269,7 @@ class EqlSpecificationTests { @Test void fromClauseExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order AS o JOIN o.lineItems l JOIN l.product p """); @@ -250,7 +278,7 @@ class EqlSpecificationTests { @Test void fromClauseDowncastingExample1() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT b.name, b.ISBN FROM Order o JOIN TREAT(o.product AS Book) b """); @@ -259,7 +287,7 @@ class EqlSpecificationTests { @Test void fromClauseDowncastingExample2() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) lp WHERE lp.budget > 1000 """); @@ -272,7 +300,7 @@ class EqlSpecificationTests { @Disabled(SPEC_FAULT + "Use double-quotes when it should be using single-quotes for a string literal") void fromClauseDowncastingExample3_SPEC_BUG() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e JOIN e.projects p WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' @@ -283,7 +311,7 @@ class EqlSpecificationTests { @Test void fromClauseDowncastingExample3fixed() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e JOIN e.projects p WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' @@ -294,7 +322,7 @@ class EqlSpecificationTests { @Test void fromClauseDowncastingExample4() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TREAT(e AS Exempt).vacationDays > 10 OR TREAT(e AS Contractor).hours > 100 @@ -304,7 +332,7 @@ class EqlSpecificationTests { @Test void pathExpressionsNamedParametersExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE c.status = :stat @@ -314,7 +342,7 @@ class EqlSpecificationTests { @Test void betweenExpressionsExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT t FROM CreditCard c JOIN c.transactionHistory t WHERE c.holder.name = 'John Doe' AND INDEX(t) BETWEEN 0 AND 9 @@ -324,7 +352,7 @@ class EqlSpecificationTests { @Test void isEmptyExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.lineItems IS EMPTY @@ -334,7 +362,7 @@ class EqlSpecificationTests { @Test void memberOfExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p FROM Person p WHERE 'Joe' MEMBER OF p.nicknames @@ -344,11 +372,10 @@ class EqlSpecificationTests { @Test void existsSubSelectExample1() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT emp FROM Employee emp - WHERE EXISTS ( - SELECT spouseEmp + WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) """); @@ -357,11 +384,10 @@ class EqlSpecificationTests { @Test void allExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT emp FROM Employee emp - WHERE emp.salary > ALL ( - SELECT m.salary + WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department) """); @@ -370,11 +396,10 @@ class EqlSpecificationTests { @Test void existsSubSelectExample2() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT emp FROM Employee emp - WHERE EXISTS ( - SELECT spouseEmp + WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) """); @@ -383,7 +408,7 @@ class EqlSpecificationTests { @Test void subselectNumericComparisonExample1() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE (SELECT AVG(o.price) FROM c.orders o) > 100 @@ -393,18 +418,17 @@ class EqlSpecificationTests { @Test void subselectNumericComparisonExample2() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT goodCustomer FROM Customer goodCustomer - WHERE goodCustomer.balanceOwed < ( - SELECT AVG(c.balanceOwed)/2.0 FROM Customer c) + WHERE goodCustomer.balanceOwed < (SELECT AVG(c.balanceOwed) / 2.0 FROM Customer c) """); } @Test void indexExample() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT w.name FROM Course c JOIN c.studentWaitlist w WHERE c.name = 'Calculus' @@ -419,7 +443,7 @@ class EqlSpecificationTests { @Disabled(SPEC_FAULT + "FUNCTION calls needs a comparator") void functionInvocationExample_SPEC_BUG() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit) @@ -429,7 +453,7 @@ class EqlSpecificationTests { @Test void functionInvocationExampleWithCorrection() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit) = TRUE @@ -439,7 +463,7 @@ class EqlSpecificationTests { @Test void updateCaseExample1() { - EqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Employee e SET e.salary = CASE WHEN e.rating = 1 THEN e.salary * 1.1 @@ -452,7 +476,7 @@ class EqlSpecificationTests { @Test void updateCaseExample2() { - EqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Employee e SET e.salary = CASE e.rating WHEN 1 THEN e.salary * 1.1 @@ -465,7 +489,7 @@ class EqlSpecificationTests { @Test void selectCaseExample1() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e.name, CASE TYPE(e) WHEN Exempt THEN 'Exempt' WHEN Contractor THEN 'Contractor' @@ -480,7 +504,7 @@ class EqlSpecificationTests { @Test void selectCaseExample2() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e.name, f.name, CONCAT(CASE WHEN f.annualMiles > 50000 THEN 'Platinum ' @@ -495,7 +519,7 @@ class EqlSpecificationTests { @Test void theRest() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TYPE(e) IN (Exempt, Contractor) @@ -505,7 +529,7 @@ class EqlSpecificationTests { @Test void theRest2() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TYPE(e) IN (:empType1, :empType2) @@ -515,7 +539,7 @@ class EqlSpecificationTests { @Test void theRest3() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TYPE(e) IN :empTypes @@ -525,7 +549,7 @@ class EqlSpecificationTests { @Test void theRest4() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT TYPE(e) FROM Employee e WHERE TYPE(e) <> Exempt @@ -535,7 +559,7 @@ class EqlSpecificationTests { @Test void theRest5() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c.status, AVG(c.filledOrderCount), COUNT(c) FROM Customer c GROUP BY c.status @@ -546,7 +570,7 @@ class EqlSpecificationTests { @Test void theRest6() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c.country, COUNT(c) FROM Customer c GROUP BY c.country @@ -557,7 +581,7 @@ class EqlSpecificationTests { @Test void theRest7() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c, COUNT(o) FROM Customer c JOIN c.orders o GROUP BY c @@ -568,7 +592,7 @@ class EqlSpecificationTests { @Test void theRest8() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c.id, c.status FROM Customer c JOIN c.orders o WHERE o.count > 100 @@ -578,7 +602,7 @@ class EqlSpecificationTests { @Test void theRest9() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT v.location.street, KEY(i).title, VALUE(i) FROM VideoStore v JOIN v.videoInventory i WHERE v.location.zipcode = '94301' AND VALUE(i) > 0 @@ -588,7 +612,7 @@ class EqlSpecificationTests { @Test void theRest10() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o.lineItems FROM Order AS o """); } @@ -596,7 +620,7 @@ class EqlSpecificationTests { @Test void theRest11() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c, COUNT(l) AS itemCount FROM Customer c JOIN c.Orders o JOIN o.lineItems l WHERE c.address.state = 'CA' @@ -608,7 +632,7 @@ class EqlSpecificationTests { @Test void theRest12() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT NEW com.acme.example.CustomerDetails(c.id, c.status, o.count) FROM Customer c JOIN c.orders o WHERE o.count > 100 @@ -618,7 +642,7 @@ class EqlSpecificationTests { @Test void theRest13() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e.address AS addr FROM Employee e """); @@ -627,7 +651,7 @@ class EqlSpecificationTests { @Test void theRest14() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT AVG(o.quantity) FROM Order o """); } @@ -635,7 +659,7 @@ class EqlSpecificationTests { @Test void theRest15() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT SUM(l.price) FROM Order o JOIN o.lineItems l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' @@ -645,7 +669,7 @@ class EqlSpecificationTests { @Test void theRest16() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT COUNT(o) FROM Order o """); } @@ -653,7 +677,7 @@ class EqlSpecificationTests { @Test void theRest17() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT COUNT(l.price) FROM Order o JOIN o.lineItems l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' @@ -663,7 +687,7 @@ class EqlSpecificationTests { @Test void theRest18() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT COUNT(l) FROM Order o JOIN o.lineItems l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' AND l.price IS NOT NULL @@ -673,7 +697,7 @@ class EqlSpecificationTests { @Test void theRest19() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' @@ -684,7 +708,7 @@ class EqlSpecificationTests { @Test void theRest20() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o.quantity, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' @@ -695,8 +719,8 @@ class EqlSpecificationTests { @Test void theRest21() { - EqlQueryParser.parseQuery(""" - SELECT o.quantity, o.cost*1.08 AS taxedCost, a.zipcode + assertQuery(""" + SELECT o.quantity, o.cost * 1.08 AS taxedCost, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' AND a.county = 'Santa Clara' ORDER BY o.quantity, taxedCost, a.zipcode @@ -706,7 +730,7 @@ class EqlSpecificationTests { @Test void theRest22() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT AVG(o.quantity) as q, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' @@ -718,7 +742,7 @@ class EqlSpecificationTests { @Test void theRest23() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.product_name FROM Order o JOIN o.lineItems l JOIN l.product p JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' @@ -733,7 +757,7 @@ class EqlSpecificationTests { void theRest24() { assertThatExceptionOfType(BadJpqlGrammarException.class).isThrownBy(() -> { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.product_name FROM Order o, IN(o.lineItems) l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' @@ -745,7 +769,7 @@ class EqlSpecificationTests { @Test void theRest25() { - EqlQueryParser.parseQuery(""" + assertQuery(""" DELETE FROM Customer c WHERE c.status = 'inactive' @@ -755,7 +779,7 @@ class EqlSpecificationTests { @Test void theRest26() { - EqlQueryParser.parseQuery(""" + assertQuery(""" DELETE FROM Customer c WHERE c.status = 'inactive' @@ -766,7 +790,7 @@ class EqlSpecificationTests { @Test void theRest27() { - EqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Customer c SET c.status = 'outstanding' WHERE c.balance < 10000 @@ -776,7 +800,7 @@ class EqlSpecificationTests { @Test void theRest28() { - EqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Employee e SET e.address.building = 22 WHERE e.address.building = 14 @@ -788,7 +812,7 @@ class EqlSpecificationTests { @Test void theRest29() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o """); @@ -797,7 +821,7 @@ class EqlSpecificationTests { @Test void theRest30() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.shippingAddress.state = 'CA' @@ -807,7 +831,7 @@ class EqlSpecificationTests { @Test void theRest31() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o.shippingAddress.state FROM Order o """); @@ -816,7 +840,7 @@ class EqlSpecificationTests { @Test void theRest32() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l """); @@ -825,7 +849,7 @@ class EqlSpecificationTests { @Test void theRest33() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.lineItems IS NOT EMPTY @@ -835,7 +859,7 @@ class EqlSpecificationTests { @Test void theRest34() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.lineItems IS EMPTY @@ -845,7 +869,7 @@ class EqlSpecificationTests { @Test void theRest35() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l WHERE l.shipped = FALSE @@ -855,7 +879,7 @@ class EqlSpecificationTests { @Test void theRest36() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE @@ -868,7 +892,7 @@ class EqlSpecificationTests { @Test void theRest37() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.shippingAddress <> o.billingAddress @@ -878,7 +902,7 @@ class EqlSpecificationTests { @Test void theRest38() { - EqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l WHERE l.product.name = ?1 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 e2cb7138c..22a5b18eb 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 @@ -16,7 +16,6 @@ package org.springframework.data.jpa.repository.query; import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; import java.util.stream.Stream; @@ -56,7 +55,7 @@ class HqlQueryRendererTests { HqlParser.StartContext parsedQuery = parser.start(); - return render(new HqlQueryRenderer().visit(parsedQuery)); + return new HqlQueryRenderer().visit(parsedQuery).build().render(); } static Stream reservedWords() { @@ -404,7 +403,7 @@ class HqlQueryRendererTests { assertQuery(""" SELECT emp FROM Employee emp - WHERE emp.salary > ALL(SELECT m.salary + WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department) """); @@ -438,7 +437,7 @@ class HqlQueryRendererTests { assertQuery(""" SELECT goodCustomer FROM Customer goodCustomer - WHERE goodCustomer.balanceOwed < (SELECT AVG(c.balanceOwed)/2.0 FROM Customer c) + WHERE goodCustomer.balanceOwed < (SELECT AVG(c.balanceOwed) / 2.0 FROM Customer c) """); } @@ -483,9 +482,9 @@ class HqlQueryRendererTests { assertQuery(""" UPDATE Employee e SET e.salary = - CASE WHEN e.rating = 1 THEN e.salary*1.1 - WHEN e.rating = 2 THEN e.salary*1.05 - ELSE e.salary*1.01 + CASE WHEN e.rating = 1 THEN e.salary * 1.1 + WHEN e.rating = 2 THEN e.salary * 1.05 + ELSE e.salary * 1.01 END """); } @@ -496,9 +495,9 @@ class HqlQueryRendererTests { assertQuery(""" UPDATE Employee e SET e.salary = - CASE e.rating WHEN 1 THEN e.salary*1.1 - WHEN 2 THEN e.salary*1.05 - ELSE e.salary*1.01 + CASE e.rating WHEN 1 THEN e.salary * 1.1 + WHEN 2 THEN e.salary * 1.05 + ELSE e.salary * 1.01 END """); } @@ -753,7 +752,7 @@ class HqlQueryRendererTests { void theRest21() { assertQuery(""" - SELECT o.quantity, o.cost*1.08 AS taxedCost, a.zipcode + SELECT o.quantity, o.cost * 1.08 AS taxedCost, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' AND a.county = 'Santa Clara' ORDER BY o.quantity, taxedCost, a.zipcode @@ -969,6 +968,10 @@ class HqlQueryRendererTests { parseWithoutChanges("select p " + // "from Person p " + // "where p.name like 'Joe'"); + + parseWithoutChanges("select p " + // + "from Person p " + // + "where p.name ilike 'Joe'"); parseWithoutChanges("select p " + // "from Person p " + // "where p.name like 'Joe''s'"); @@ -1359,7 +1362,8 @@ class HqlQueryRendererTests { " sum(c.duration), " + // " min(c.duration), " + // " max(c.duration), " + // - " avg(c.duration)" + // + " avg(c.duration)," + // + " 1" + // ") " + // "from Call c "); parseWithoutChanges("select new map(" + // @@ -1449,6 +1453,12 @@ class HqlQueryRendererTests { "join c.phone p " + // "order by p.number " + // "fetch first 50 rows only"); + parseWithoutChanges("select c " + // + "from Call c " + // + "join c.phone p " + // + "order by p.number " + // + "offset 10 rows " + // + "fetch first 50 rows with ties"); parseWithoutChanges("select p " + // "from Phone p " + // "join fetch p.calls " + // @@ -1501,10 +1511,9 @@ class HqlQueryRendererTests { void cteWithClauseShouldWork() { assertQuery(""" - WITH maxId AS(select max(sr.snapshot.id) snapshotId from SnapshotReference sr + WITH maxId AS (select max(sr.snapshot.id) snapshotId from SnapshotReference sr where sr.id.selectionId = ?1 and sr.enabled - group by sr.userId - ) + group by sr.userId) select sr from maxId m join SnapshotReference sr on sr.snapshot.id = m.snapshotId """); } @@ -1654,15 +1663,15 @@ class HqlQueryRendererTests { @ParameterizedTest // GH-3342 @ValueSource( - strings = { "select 1 from User", "select -1 from User", "select +1 from User", "select +1*-100 from User", - "select count(u)*-0.7f from User u", "select count(oi) + (-100) as perc from StockOrderItem oi", + strings = { "select 1 from User", "select -1 from User", "select +1 from User", "select +1 * -100 from User", + "select count(u) * -0.7f from User u", "select count(oi) + (-100) as perc from StockOrderItem oi", "select p from Payment p where length(p.cardNumber) between +16 and -20" }) void signedLiteralShouldWork(String query) { assertQuery(query); } @ParameterizedTest // GH-3342 - @ValueSource(strings = { "select -count(u) from User u", "select +1*(-count(u)) from User u" }) + @ValueSource(strings = { "select -count(u) from User u", "select +1 * (-count(u)) from User u" }) void signedExpressionsShouldWork(String query) { assertQuery(query); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java index f50f2486d..94f3d2691 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java @@ -197,10 +197,10 @@ class HqlQueryTransformerTests { assertThat(alias("select u from User u where not exists (select u2 from User u2)")).isEqualTo("u"); assertThat(alias( "select u from User u where not exists (select u2 from User u2 where not exists (select u3 from User u3))")) - .isEqualTo("u"); + .isEqualTo("u"); assertThat(alias( "SELECT e FROM DbEvent e WHERE TREAT(modifiedFrom AS date) IS NULL OR e.modificationDate >= :modifiedFrom")) - .isEqualTo("e"); + .isEqualTo("e"); } @Test // GH-2557 @@ -360,7 +360,7 @@ class HqlQueryTransformerTests { from Bar lp join lp.investmentProduct ip where (lp.toDate is null and lp.fromDate <= :now and lp.fromDate is not null) and lp.accountId = :accountId group by ip.id, ip.name, lp.accountId order by ip.name ASC""")) - .isTrue(); + .isTrue(); } @Test // DATAJPA-938 @@ -452,7 +452,7 @@ class HqlQueryTransformerTests { String query = "SELECT AVG(m.price) AS m.avg FROM Magazine m"; Sort sort = Sort.by("m.avg"); - assertThatIllegalArgumentException().isThrownBy(() -> createQueryFor(query, sort)); + assertThatExceptionOfType(BadJpqlGrammarException.class).isThrownBy(() -> createQueryFor(query, sort)); } @Test // DATAJPA-965, DATAJPA-970, GH-2863 @@ -467,8 +467,10 @@ class HqlQueryTransformerTests { @Test // DATAJPA-1506 void detectsAliasWithGroupAndOrderBy() { - assertThat(alias("select * from User group by name")).isNull(); - assertThat(alias("select * from User order by name")).isNull(); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> alias("select * from User group by name")); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> alias("select * from User order by name")); assertThat(alias("select u from User u group by name")).isEqualTo("u"); assertThat(alias("select u from User u order by name")).isEqualTo("u"); } @@ -600,8 +602,10 @@ class HqlQueryTransformerTests { @Test void detectsAliasWithGroupAndOrderByWithLineBreaks() { - assertThat(alias("select * from User group\nby name")).isNull(); - assertThat(alias("select * from User order\nby name")).isNull(); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> alias("select * from User group\nby name")); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> alias("select * from User order\nby name")); assertThat(alias("select u from User u group\nby name")).isEqualTo("u"); assertThat(alias("select u from User u order\nby name")).isEqualTo("u"); assertThat(alias("select u from User\nu\norder \n by name")).isEqualTo("u"); @@ -624,7 +628,8 @@ class HqlQueryTransformerTests { // This is not a required behavior, in fact the opposite is, // but it documents a current limitation. // to fix this without breaking findProjectionClauseWithIncludedFrom we need a more sophisticated parser. - assertThat(projection("select * from (select x from y)")).isNotEqualTo("*"); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> projection("select * from (select x from y)")); } @Test // DATAJPA-1696 @@ -695,15 +700,18 @@ class HqlQueryTransformerTests { .isEqualTo("select dense_rank() over (order by lastname) from user u order by u.lastname, u.age desc"); // partition by + order by in over clause - assertThat(createQueryFor("select dense_rank() over (partition by active, age order by lastname) from user u", - sort)).isEqualTo( - "select dense_rank() over (partition by active, age order by lastname) from user u order by u.age desc"); + assertThat( + createQueryFor( + "select dense_rank() over (partition by active, age order by lastname range between 1.0 preceding and 1.0 following) from user u", + sort)) + .isEqualTo( + "select dense_rank() over (partition by active, age order by lastname range between 1.0 preceding and 1.0 following) from user u order by u.age desc"); // partition by + order by in over clause + order by at the end assertThat(createQueryFor( "select dense_rank() over (partition by active, age order by lastname) from user u order by active", sort)) - .isEqualTo( - "select dense_rank() over (partition by active, age order by lastname) from user u order by active, u.age desc"); + .isEqualTo( + "select dense_rank() over (partition by active, age order by lastname) from user u order by active, u.age desc"); // partition by + order by in over clause + frame clause assertThat(createQueryFor( @@ -728,13 +736,13 @@ class HqlQueryTransformerTests { // order by in subselect (from expression) assertThat(createQueryFor("select u from (select u2 from user u2 order by age desc limit 10) u", sort)) - .isEqualTo("select u from (select u2 from user u2 order by age desc limit 10 ) u order by u.age desc"); + .isEqualTo("select u from (select u2 from user u2 order by age desc limit 10) u order by u.age desc"); // order by in subselect (from expression) + at the end assertThat(createQueryFor( "select u from (select u2 from user u2 order by 1, 2, 3 desc limit 10) u order by u.active asc", sort)) - .isEqualTo( - "select u from (select u2 from user u2 order by 1, 2, 3 desc limit 10 ) u order by u.active asc, u.age desc"); + .isEqualTo( + "select u from (select u2 from user u2 order by 1, 2, 3 desc limit 10) u order by u.active asc, u.age desc"); } @Test // GH-2511 @@ -745,7 +753,7 @@ class HqlQueryTransformerTests { assertThat( createCountQueryFor("SELECT e FROM mytable e WHERE nr = :number AND kon = :kon AND datum >= '2019-01-01'")) - .isEqualTo("SELECT count(e) FROM mytable e WHERE nr = :number AND kon = :kon AND datum >= '2019-01-01'"); + .isEqualTo("SELECT count(e) FROM mytable e WHERE nr = :number AND kon = :kon AND datum >= '2019-01-01'"); assertThat(createCountQueryFor("SELECT e FROM context e ORDER BY time")) .isEqualTo("SELECT count(e) FROM context e"); @@ -755,7 +763,7 @@ class HqlQueryTransformerTests { assertThat( createCountQueryFor("SELECT us FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)")) - .isEqualTo("SELECT count(us) FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)"); + .isEqualTo("SELECT count(us) FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)"); } @Test // GH-2496, GH-2522, GH-2537, GH-2045 @@ -953,7 +961,7 @@ class HqlQueryTransformerTests { """, Sort.by(Sort.Direction.ASC, "cheapestBundlePrice") // .and(Sort.by(Sort.Direction.ASC, "earliestBundleStart")) // .and(Sort.by(Sort.Direction.ASC, "name")))) - .endsWith(" order by cheapestBundlePrice asc, earliestBundleStart asc, name asc"); + .endsWith(" order by cheapestBundlePrice asc, earliestBundleStart asc, name asc"); } @Test // GH-2863, GH-1655 @@ -1019,8 +1027,7 @@ class HqlQueryTransformerTests { assertThat( createQueryFor("select e from Employee e where e.name = :name", Sort.by(Sort.Order.desc("evaluationDate")))) - .isEqualToIgnoringWhitespace( - "select e from Employee e where e.name = :name order by e.evaluationDate desc"); + .isEqualToIgnoringWhitespace("select e from Employee e where e.name = :name order by e.evaluationDate desc"); assertThat(createQueryFor("select e from Employee e join training t where e.name = :name", Sort.by(Sort.Order.desc("trainingDueDate")))).isEqualToIgnoringWhitespace( @@ -1042,12 +1049,15 @@ class HqlQueryTransformerTests { @Test // GH-3269 void createsCountQueryUsingAliasCorrectly() { - assertCountQuery("select distinct 1 as x from Employee","select count(distinct 1) from Employee AS __"); - assertCountQuery("SELECT DISTINCT abc AS x FROM T","SELECT count(DISTINCT abc) FROM T AS __"); - assertCountQuery("select distinct a as x, b as y from Employee","select count(distinct a , b) from Employee AS __"); - assertCountQuery("select distinct sum(amount) as x from Employee GROUP BY n","select count(distinct sum(amount)) from Employee AS __ GROUP BY n"); - assertCountQuery("select distinct a, b, sum(amount) as c, d from Employee GROUP BY n","select count(distinct a, b, sum(amount) , d) from Employee AS __ GROUP BY n"); - assertCountQuery("select distinct a, count(b) as c from Employee GROUP BY n","select count(distinct a, count(b)) from Employee AS __ GROUP BY n"); + assertCountQuery("select distinct 1 as x from Employee", "select count(distinct 1) from Employee AS __"); + assertCountQuery("SELECT DISTINCT abc AS x FROM T", "SELECT count(DISTINCT abc) FROM T AS __"); + assertCountQuery("select distinct a as x, b as y from Employee", "select count(distinct a, b) from Employee AS __"); + assertCountQuery("select distinct sum(amount) as x from Employee GROUP BY n", + "select count(distinct sum(amount)) from Employee AS __ GROUP BY n"); + assertCountQuery("select distinct a, b, sum(amount) as c, d from Employee GROUP BY n", + "select count(distinct a, b, sum(amount), d) from Employee AS __ GROUP BY n"); + assertCountQuery("select distinct a, count(b) as c from Employee GROUP BY n", + "select count(distinct a, count(b)) from Employee AS __ GROUP BY n"); } private void assertCountQuery(String originalQuery, String countQuery) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java index 3440616f4..65f87fd85 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java @@ -15,6 +15,11 @@ */ package org.springframework.data.jpa.repository.query; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -32,13 +37,38 @@ class HqlSpecificationTests { private static final String SPEC_FAULT = "Disabled due to spec fault> "; + private static String parseWithoutChanges(String query) { + + HqlLexer lexer = new HqlLexer(CharStreams.fromString(query)); + HqlParser parser = new HqlParser(new CommonTokenStream(lexer)); + + parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); + + HqlParser.StartContext parsedQuery = parser.start(); + + return render(new HqlQueryRenderer().visit(parsedQuery)); + } + + private void assertQuery(String query) { + + String slimmedDownQuery = reduceWhitespace(query); + assertThat(parseWithoutChanges(slimmedDownQuery)).isEqualTo(slimmedDownQuery); + } + + private String reduceWhitespace(String original) { + + return original // + .replaceAll("[ \\t\\n]{1,}", " ") // + .trim(); + } + /** * @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#example */ @Test void joinExample1() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order AS o JOIN o.lineItems AS l WHERE l.shipped = FALSE @@ -52,7 +82,7 @@ class HqlSpecificationTests { @Test void joinExample2() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l JOIN l.product p WHERE p.productType = 'office_supplies' @@ -65,12 +95,12 @@ class HqlSpecificationTests { @Test void rangeVariableDeclarations() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o1 FROM Order o1, Order o2 WHERE o1.quantity > o2.quantity AND o2.customer.lastname = 'Smith' AND - o2.customer.firstname= 'John' + o2.customer.firstname = 'John' """); } @@ -80,7 +110,7 @@ class HqlSpecificationTests { @Test void pathExpressionsExample1() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT i.name, VALUE(p) FROM Item i JOIN i.photos p WHERE KEY(p) LIKE '%egret' @@ -93,7 +123,7 @@ class HqlSpecificationTests { @Test void pathExpressionsExample2() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT i.name, p FROM Item i JOIN i.photos p WHERE KEY(p) LIKE '%egret' @@ -106,7 +136,7 @@ class HqlSpecificationTests { @Test void pathExpressionsExample3() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.vendor FROM Employee e JOIN e.contactInfo.phones p """); @@ -118,7 +148,7 @@ class HqlSpecificationTests { @Test void pathExpressionsExample4() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.vendor FROM Employee e JOIN e.contactInfo c JOIN c.phones p WHERE e.contactInfo.address.zipcode = '95054' @@ -128,7 +158,7 @@ class HqlSpecificationTests { @Test void pathExpressionSyntaxExample1() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT l.product FROM Order AS o JOIN o.lineItems l """); @@ -137,7 +167,7 @@ class HqlSpecificationTests { @Test void joinsExample1() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c, Employee e WHERE c.hatsize = e.shoesize """); } @@ -145,7 +175,7 @@ class HqlSpecificationTests { @Test void joinsExample2() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c JOIN c.orders o WHERE c.status = 1 """); } @@ -153,7 +183,7 @@ class HqlSpecificationTests { @Test void joinsInnerExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c INNER JOIN c.orders o WHERE c.status = 1 """); } @@ -161,15 +191,15 @@ class HqlSpecificationTests { @Test void joinsInExample() { - HqlQueryParser.parseQuery(""" - SELECT OBJECT(c) FROM Customer c, IN(c.orders) o WHERE c.status = 1 + assertQuery(""" + SELECT OBJECT(c) FROM Customer c , IN(c.orders) o WHERE c.status = 1 """); } @Test void doubleJoinExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.vendor FROM Employee e JOIN e.contactInfo c JOIN c.phones p WHERE c.address.zipcode = '95054' @@ -179,7 +209,7 @@ class HqlSpecificationTests { @Test void leftJoinExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT s.name, COUNT(p) FROM Suppliers s LEFT JOIN s.products p GROUP BY s.name @@ -189,7 +219,7 @@ class HqlSpecificationTests { @Test void leftJoinOnExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT s.name, COUNT(p) FROM Suppliers s LEFT JOIN s.products p ON p.status = 'inStock' @@ -200,7 +230,7 @@ class HqlSpecificationTests { @Test void leftJoinWhereExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT s.name, COUNT(p) FROM Suppliers s LEFT JOIN s.products p WHERE p.status = 'inStock' @@ -211,7 +241,7 @@ class HqlSpecificationTests { @Test void leftJoinFetchExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT d FROM Department d LEFT JOIN FETCH d.employees WHERE d.deptno = 1 @@ -221,7 +251,7 @@ class HqlSpecificationTests { @Test void collectionMemberExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l WHERE l.product.productType = 'office_supplies' @@ -231,9 +261,9 @@ class HqlSpecificationTests { @Test void collectionMemberInExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o - FROM Order o, IN(o.lineItems) l + FROM Order o , IN(o.lineItems) l WHERE l.product.productType = 'office_supplies' """); } @@ -241,7 +271,7 @@ class HqlSpecificationTests { @Test void fromClauseExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order AS o JOIN o.lineItems l JOIN l.product p """); @@ -250,19 +280,19 @@ class HqlSpecificationTests { @Test void fromClauseDowncastingExample1() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT b.name, b.ISBN FROM Order o JOIN TREAT(o.product AS Book) b - """); + """); } @Test void fromClauseDowncastingExample2() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) lp WHERE lp.budget > 1000 - """); + """); } /** @@ -272,39 +302,39 @@ class HqlSpecificationTests { @Disabled(SPEC_FAULT + "Use double-quotes when it should be using single-quotes for a string literal") void fromClauseDowncastingExample3_SPEC_BUG() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e JOIN e.projects p WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE "cost overrun" - """); + """); } @Test void fromClauseDowncastingExample3fixed() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e JOIN e.projects p WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE 'cost overrun' - """); + """); } @Test void fromClauseDowncastingExample4() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TREAT(e AS Exempt).vacationDays > 10 OR TREAT(e AS Contractor).hours > 100 - """); + """); } @Test void pathExpressionsNamedParametersExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE c.status = :stat @@ -314,7 +344,7 @@ class HqlSpecificationTests { @Test void betweenExpressionsExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT t FROM CreditCard c JOIN c.transactionHistory t WHERE c.holder.name = 'John Doe' AND INDEX(t) BETWEEN 0 AND 9 @@ -324,7 +354,7 @@ class HqlSpecificationTests { @Test void isEmptyExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.lineItems IS EMPTY @@ -334,7 +364,7 @@ class HqlSpecificationTests { @Test void memberOfExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p FROM Person p WHERE 'Joe' MEMBER OF p.nicknames @@ -344,11 +374,10 @@ class HqlSpecificationTests { @Test void existsSubSelectExample1() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT emp FROM Employee emp - WHERE EXISTS ( - SELECT spouseEmp + WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) """); @@ -357,33 +386,31 @@ class HqlSpecificationTests { @Test void allExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT emp FROM Employee emp - WHERE emp.salary > ALL ( - SELECT m.salary + WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department) - """); + """); } @Test void existsSubSelectExample2() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT emp FROM Employee emp - WHERE EXISTS ( - SELECT spouseEmp + WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) - """); + """); } @Test void subselectNumericComparisonExample1() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE (SELECT AVG(o.price) FROM c.orders o) > 100 @@ -393,18 +420,17 @@ class HqlSpecificationTests { @Test void subselectNumericComparisonExample2() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT goodCustomer FROM Customer goodCustomer - WHERE goodCustomer.balanceOwed < ( - SELECT AVG(c.balanceOwed)/2.0 FROM Customer c) + WHERE goodCustomer.balanceOwed < (SELECT AVG(c.balanceOwed) / 2.0 FROM Customer c) """); } @Test void indexExample() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT w.name FROM Course c JOIN c.studentWaitlist w WHERE c.name = 'Calculus' @@ -419,7 +445,7 @@ class HqlSpecificationTests { @Disabled(SPEC_FAULT + "FUNCTION calls needs a comparator") void functionInvocationExample_SPEC_BUG() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit) @@ -429,7 +455,7 @@ class HqlSpecificationTests { @Test void functionInvocationExampleWithCorrection() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit) = TRUE @@ -439,33 +465,33 @@ class HqlSpecificationTests { @Test void updateCaseExample1() { - HqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Employee e SET e.salary = CASE WHEN e.rating = 1 THEN e.salary * 1.1 WHEN e.rating = 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test void updateCaseExample2() { - HqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Employee e SET e.salary = CASE e.rating WHEN 1 THEN e.salary * 1.1 WHEN 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test void selectCaseExample1() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e.name, CASE TYPE(e) WHEN Exempt THEN 'Exempt' WHEN Contractor THEN 'Contractor' @@ -480,7 +506,7 @@ class HqlSpecificationTests { @Test void selectCaseExample2() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e.name, f.name, CONCAT(CASE WHEN f.annualMiles > 50000 THEN 'Platinum ' @@ -495,17 +521,17 @@ class HqlSpecificationTests { @Test void theRest() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TYPE(e) IN (Exempt, Contractor) - """); + """); } @Test void theRest2() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TYPE(e) IN (:empType1, :empType2) @@ -515,7 +541,7 @@ class HqlSpecificationTests { @Test void theRest3() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TYPE(e) IN :empTypes @@ -525,7 +551,7 @@ class HqlSpecificationTests { @Test void theRest4() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT TYPE(e) FROM Employee e WHERE TYPE(e) <> Exempt @@ -535,7 +561,7 @@ class HqlSpecificationTests { @Test void theRest5() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c.status, AVG(c.filledOrderCount), COUNT(c) FROM Customer c GROUP BY c.status @@ -546,7 +572,7 @@ class HqlSpecificationTests { @Test void theRest6() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c.country, COUNT(c) FROM Customer c GROUP BY c.country @@ -557,7 +583,7 @@ class HqlSpecificationTests { @Test void theRest7() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c, COUNT(o) FROM Customer c JOIN c.orders o GROUP BY c @@ -568,7 +594,7 @@ class HqlSpecificationTests { @Test void theRest8() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c.id, c.status FROM Customer c JOIN c.orders o WHERE o.count > 100 @@ -578,7 +604,7 @@ class HqlSpecificationTests { @Test void theRest9() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT v.location.street, KEY(i).title, VALUE(i) FROM VideoStore v JOIN v.videoInventory i WHERE v.location.zipcode = '94301' AND VALUE(i) > 0 @@ -588,7 +614,7 @@ class HqlSpecificationTests { @Test void theRest10() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o.lineItems FROM Order AS o """); } @@ -596,7 +622,7 @@ class HqlSpecificationTests { @Test void theRest11() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c, COUNT(l) AS itemCount FROM Customer c JOIN c.Orders o JOIN o.lineItems l WHERE c.address.state = 'CA' @@ -608,7 +634,7 @@ class HqlSpecificationTests { @Test void theRest12() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT NEW com.acme.example.CustomerDetails(c.id, c.status, o.count) FROM Customer c JOIN c.orders o WHERE o.count > 100 @@ -618,7 +644,7 @@ class HqlSpecificationTests { @Test void theRest13() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e.address AS addr FROM Employee e """); @@ -627,7 +653,7 @@ class HqlSpecificationTests { @Test void theRest14() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT AVG(o.quantity) FROM Order o """); } @@ -635,7 +661,7 @@ class HqlSpecificationTests { @Test void theRest15() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT SUM(l.price) FROM Order o JOIN o.lineItems l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' @@ -645,7 +671,7 @@ class HqlSpecificationTests { @Test void theRest16() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT COUNT(o) FROM Order o """); } @@ -653,7 +679,7 @@ class HqlSpecificationTests { @Test void theRest17() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT COUNT(l.price) FROM Order o JOIN o.lineItems l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' @@ -663,7 +689,7 @@ class HqlSpecificationTests { @Test void theRest18() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT COUNT(l) FROM Order o JOIN o.lineItems l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' AND l.price IS NOT NULL @@ -673,7 +699,7 @@ class HqlSpecificationTests { @Test void theRest19() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' @@ -684,7 +710,7 @@ class HqlSpecificationTests { @Test void theRest20() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o.quantity, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' @@ -695,8 +721,8 @@ class HqlSpecificationTests { @Test void theRest21() { - HqlQueryParser.parseQuery(""" - SELECT o.quantity, o.cost*1.08 AS taxedCost, a.zipcode + assertQuery(""" + SELECT o.quantity, o.cost * 1.08 AS taxedCost, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' AND a.county = 'Santa Clara' ORDER BY o.quantity, taxedCost, a.zipcode @@ -706,7 +732,7 @@ class HqlSpecificationTests { @Test void theRest22() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT AVG(o.quantity) as q, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' @@ -718,7 +744,7 @@ class HqlSpecificationTests { @Test void theRest23() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.product_name FROM Order o JOIN o.lineItems l JOIN l.product p JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' @@ -732,9 +758,9 @@ class HqlSpecificationTests { @Test void theRest24() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.product_name - FROM Order o, IN(o.lineItems) l JOIN o.customer c + FROM Order o , IN(o.lineItems) l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' ORDER BY o.quantity """); @@ -743,7 +769,7 @@ class HqlSpecificationTests { @Test void theRest25() { - HqlQueryParser.parseQuery(""" + assertQuery(""" DELETE FROM Customer c WHERE c.status = 'inactive' @@ -753,7 +779,7 @@ class HqlSpecificationTests { @Test void theRest26() { - HqlQueryParser.parseQuery(""" + assertQuery(""" DELETE FROM Customer c WHERE c.status = 'inactive' @@ -764,7 +790,7 @@ class HqlSpecificationTests { @Test void theRest27() { - HqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Customer c SET c.status = 'outstanding' WHERE c.balance < 10000 @@ -774,7 +800,7 @@ class HqlSpecificationTests { @Test void theRest28() { - HqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Employee e SET e.address.building = 22 WHERE e.address.building = 14 @@ -786,7 +812,7 @@ class HqlSpecificationTests { @Test void theRest29() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o """); @@ -795,7 +821,7 @@ class HqlSpecificationTests { @Test void theRest30() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.shippingAddress.state = 'CA' @@ -805,7 +831,7 @@ class HqlSpecificationTests { @Test void theRest31() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o.shippingAddress.state FROM Order o """); @@ -814,7 +840,7 @@ class HqlSpecificationTests { @Test void theRest32() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l """); @@ -823,7 +849,7 @@ class HqlSpecificationTests { @Test void theRest33() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.lineItems IS NOT EMPTY @@ -833,7 +859,7 @@ class HqlSpecificationTests { @Test void theRest34() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.lineItems IS EMPTY @@ -843,7 +869,7 @@ class HqlSpecificationTests { @Test void theRest35() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l WHERE l.shipped = FALSE @@ -853,7 +879,7 @@ class HqlSpecificationTests { @Test void theRest36() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE @@ -866,7 +892,7 @@ class HqlSpecificationTests { @Test void theRest37() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.shippingAddress <> o.billingAddress @@ -876,7 +902,7 @@ class HqlSpecificationTests { @Test void theRest38() { - HqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l WHERE l.product.name = ?1 @@ -886,78 +912,78 @@ class HqlSpecificationTests { @Test void hqlQueries() { - HqlQueryParser.parseQuery("from Person"); - HqlQueryParser.parseQuery("select local datetime"); - HqlQueryParser.parseQuery("from Person p select p.name"); - HqlQueryParser.parseQuery("update Person set nickName = 'Nacho' " + // + assertQuery("from Person"); + assertQuery("select local datetime"); + assertQuery("from Person p select p.name"); + assertQuery("update Person set nickName = 'Nacho' " + // "where name = 'Ignacio'"); - HqlQueryParser.parseQuery("update Person p " + // + assertQuery("update Person p " + // "set p.name = :newName " + // "where p.name = :oldName"); - HqlQueryParser.parseQuery("update Person " + // + assertQuery("update Person " + // "set name = :newName " + // "where name = :oldName"); - HqlQueryParser.parseQuery("update versioned Person " + // + assertQuery("update versioned Person " + // "set name = :newName " + // "where name = :oldName"); - HqlQueryParser.parseQuery("insert Person (id, name) " + // + assertQuery("insert Person (id, name) " + // "values (100L, 'Jane Doe')"); - HqlQueryParser.parseQuery("insert Person (id, name) " + // + assertQuery("insert Person (id, name) " + // "values (101L, 'J A Doe III'), " + // "(102L, 'J X Doe'), " + // "(103L, 'John Doe, Jr')"); - HqlQueryParser.parseQuery("insert into Partner (id, name) " + // + assertQuery("insert into Partner (id, name) " + // "select p.id, p.name " + // "from Person p "); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.name like 'Joe'"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.name like 'Joe''s'"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.id = 1"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.id = 1L"); - HqlQueryParser.parseQuery("select c " + // + assertQuery("select c " + // "from Call c " + // "where c.duration > 100.5"); - HqlQueryParser.parseQuery("select c " + // + assertQuery("select c " + // "from Call c " + // "where c.duration > 100.5F"); - HqlQueryParser.parseQuery("select c " + // + assertQuery("select c " + // "from Call c " + // "where c.duration > 1e+2"); - HqlQueryParser.parseQuery("select c " + // + assertQuery("select c " + // "from Call c " + // "where c.duration > 1e+2F"); - HqlQueryParser.parseQuery("from Phone ph " + // + assertQuery("from Phone ph " + // "where ph.type = LAND_LINE"); - HqlQueryParser.parseQuery("select java.lang.Math.PI"); - HqlQueryParser.parseQuery("select 'Customer ' || p.name " + // + assertQuery("select java.lang.Math.PI"); + assertQuery("select 'Customer ' || p.name " + // "from Person p " + // "where p.id = 1"); - HqlQueryParser.parseQuery("select sum(ch.duration) * :multiplier " + // + assertQuery("select sum(ch.duration) * :multiplier " + // "from Person pr " + // "join pr.phones ph " + // "join ph.callHistory ch " + // "where ph.id = 1L "); - HqlQueryParser.parseQuery("select year(local date) - year(p.createdOn) " + // + assertQuery("select year(local date) - year(p.createdOn) " + // "from Person p " + // "where p.id = 1L"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where year(local date) - year(p.createdOn) > 1"); - HqlQueryParser.parseQuery("select " + // + assertQuery("select " + // " case p.nickName " + // " when 'NA' " + // " then '' " + // " else p.nickName " + // " end " + // "from Person p"); - HqlQueryParser.parseQuery("select " + // + assertQuery("select " + // " case " + // " when p.nickName is null " + // " then " + // @@ -969,259 +995,250 @@ class HqlSpecificationTests { " else p.nickName " + // " end " + // "from Person p"); - HqlQueryParser.parseQuery("select " + // + assertQuery("select " + // " case when p.nickName is null " + // " then p.id * 1000 " + // " else p.id " + // " end " + // "from Person p " + // "order by p.id"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Payment p " + // "where type(p) = CreditCardPayment"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Payment p " + // "where type(p) = :type"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Payment p " + // "where length(treat(p as CreditCardPayment).cardNumber) between 16 and 20"); - HqlQueryParser.parseQuery("select nullif(p.nickName, p.name) " + // + assertQuery("select nullif(p.nickName, p.name) " + // "from Person p"); - HqlQueryParser.parseQuery("select " + // + assertQuery("select " + // " case" + // " when p.nickName = p.name" + // " then null" + // " else p.nickName" + // " end " + // "from Person p"); - HqlQueryParser.parseQuery("select coalesce(p.nickName, '') " + // + assertQuery("select coalesce(p.nickName, '') " + // "from Person p"); - HqlQueryParser.parseQuery("select coalesce(p.nickName, p.name, '') " + // + assertQuery("select coalesce(p.nickName, p.name, '') " + // "from Person p"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where size(p.phones) >= 2"); - HqlQueryParser.parseQuery("select concat(p.number, ' : ' , cast(c.duration as string)) " + // + assertQuery("select concat(p.number, ' : ', cast(c.duration as string)) " + // "from Call c " + // "join c.phone p"); - HqlQueryParser.parseQuery("select substring(p.number, 1, 2) " + // + assertQuery("select substring(p.number, 1, 2) " + // "from Call c " + // "join c.phone p"); - HqlQueryParser.parseQuery("select upper(p.name) " + // + assertQuery("select upper(p.name) " + // "from Person p "); - HqlQueryParser.parseQuery("select lower(p.name) " + // + assertQuery("select lower(p.name) " + // "from Person p "); - HqlQueryParser.parseQuery("select trim(p.name) " + // + assertQuery("select trim(p.name) " + // "from Person p "); - HqlQueryParser.parseQuery("select trim(leading ' ' from p.name) " + // + assertQuery("select trim(leading ' ' from p.name) " + // "from Person p "); - HqlQueryParser.parseQuery("select length(p.name) " + // + assertQuery("select length(p.name) " + // "from Person p "); - HqlQueryParser.parseQuery("select locate('John', p.name) " + // + assertQuery("select locate('John', p.name) " + // "from Person p "); - HqlQueryParser.parseQuery("select abs(c.duration) " + // + assertQuery("select abs(c.duration) " + // "from Call c "); - HqlQueryParser.parseQuery("select mod(c.duration, 10) " + // + assertQuery("select mod(c.duration, 10) " + // "from Call c "); - HqlQueryParser.parseQuery("select sqrt(c.duration) " + // + assertQuery("select sqrt(c.duration) " + // "from Call c "); - HqlQueryParser.parseQuery("select cast(c.duration as String) " + // + assertQuery("select cast(c.duration as String) " + // "from Call c "); - HqlQueryParser.parseQuery("select str(c.timestamp) " + // + assertQuery("select str(c.timestamp) " + // "from Call c "); - HqlQueryParser.parseQuery("select str(cast(duration as float) / 60, 4, 2) " + // + assertQuery("select str(cast(duration as float) / 60, 4, 2) " + // "from Call c "); - HqlQueryParser.parseQuery("select c " + // + assertQuery("select c " + // "from Call c " + // "where extract(date from c.timestamp) = local date"); - HqlQueryParser.parseQuery("select extract(year from c.timestamp) " + // + assertQuery("select extract(year from c.timestamp) " + // "from Call c "); - HqlQueryParser.parseQuery("select year(c.timestamp) " + // + assertQuery("select year(c.timestamp) " + // "from Call c "); - HqlQueryParser.parseQuery("select var_samp(c.duration) as sampvar, var_pop(c.duration) as popvar " + // + assertQuery("select var_samp(c.duration) as sampvar, var_pop(c.duration) as popvar " + // "from Call c "); - HqlQueryParser.parseQuery("select bit_length(c.phone.number) " + // + assertQuery("select bit_length(c.phone.number) " + // "from Call c "); - HqlQueryParser.parseQuery("select c " + // + assertQuery("select c " + // "from Call c " + // "where c.duration < 30 "); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.name like 'John%' "); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.createdOn > '1950-01-01' "); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Phone p " + // "where p.type = 'MOBILE' "); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Payment p " + // "where p.completed = true "); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Payment p " + // "where type(p) = WireTransferPayment "); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Payment p, Phone ph " + // "where p.person = ph.person "); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "join p.phones ph " + // "where p.id = 1L and index(ph) between 0 and 3"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.createdOn between '1999-01-01' and '2001-01-02'"); - HqlQueryParser.parseQuery("select c " + // + assertQuery("select c " + // "from Call c " + // "where c.duration between 5 and 20"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.name between 'H' and 'M'"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.nickName is not null"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.nickName is null"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.name like 'Jo%'"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.name not like 'Jo%'"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.name like 'Dr|_%' escape '|'"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Payment p " + // "where type(p) in (CreditCardPayment, WireTransferPayment)"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Phone p " + // "where type in ('MOBILE', 'LAND_LINE')"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Phone p " + // "where type in :types"); - HqlQueryParser.parseQuery("select distinct p " + // + assertQuery("select distinct p " + // "from Phone p " + // - "where p.person.id in (" + // - " select py.person.id " + // + "where p.person.id in (select py.person.id " + // " from Payment py" + // - " where py.completed = true and py.amount > 50 " + // - ")"); - HqlQueryParser.parseQuery("select distinct p " + // + " where py.completed = true and py.amount > 50)"); + assertQuery("select distinct p " + // "from Phone p " + // - "where p.person in (" + // - " select py.person " + // + "where p.person in (select py.person " + // " from Payment py" + // - " where py.completed = true and py.amount > 50 " + // - ")"); - HqlQueryParser.parseQuery("select distinct p " + // + " where py.completed = true and py.amount > 50)"); + assertQuery("select distinct p " + // "from Payment p " + // - "where (p.amount, p.completed) in (" + // - " (50, true)," + // + "where (p.amount, p.completed) in ((50, true)," + // " (100, true)," + // - " (5, false)" + // - ")"); - HqlQueryParser.parseQuery("select p " + // + " (5, false))"); + assertQuery("select p " + // "from Person p " + // - "where 1 in indices(p.phones)"); - HqlQueryParser.parseQuery("select distinct p.person " + // + "where 1 in indices (p.phones)"); + assertQuery("select distinct p.person " + // "from Phone p " + // "join p.calls c " + // - "where 50 > all (" + // - " select duration" + // + "where 50 > all (select duration" + // " from Call" + // - " where phone = p " + // - ") "); - HqlQueryParser.parseQuery("select p " + // + " where phone = p) "); + assertQuery("select p " + // "from Phone p " + // - "where local date > all elements(p.repairTimestamps)"); - HqlQueryParser.parseQuery("select p " + // + "where local date > all elements (p.repairTimestamps)"); + assertQuery("select p " + // "from Person p " + // - "where :phone = some elements(p.phones)"); - HqlQueryParser.parseQuery("select p " + // + "where :phone = some elements (p.phones)"); + assertQuery("select p " + // "from Person p " + // "where :phone member of p.phones"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // - "where exists elements(p.phones)"); - HqlQueryParser.parseQuery("select p " + // + "where exists elements (p.phones)"); + assertQuery("select p " + // "from Person p " + // "where p.phones is empty"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.phones is not empty"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.phones is not empty"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where 'Home address' member of p.addresses"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where 'Home address' not member of p.addresses"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from org.hibernate.userguide.model.Person p"); - HqlQueryParser.parseQuery("select distinct pr, ph " + // + assertQuery("select distinct pr, ph " + // "from Person pr, Phone ph " + // "where ph.person = pr and ph is not null"); - HqlQueryParser.parseQuery("select distinct pr1 " + // + assertQuery("select distinct pr1 " + // "from Person pr1, Person pr2 " + // "where pr1.id <> pr2.id " + // " and pr1.address = pr2.address " + // " and pr1.createdOn < pr2.createdOn"); - HqlQueryParser.parseQuery("select distinct pr, ph " + // + assertQuery("select distinct pr, ph " + // "from Person pr cross join Phone ph " + // "where ph.person = pr and ph is not null"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Payment p "); - HqlQueryParser.parseQuery("select d.owner, d.payed " + // - "from (" + // - " select p.person as owner, c.payment is not null as payed " + // + assertQuery("select d.owner, d.payed " + // + "from (select p.person as owner, c.payment is not null as payed " + // " from Call c " + // " join c.phone p " + // " where p.number = :phoneNumber) d"); - HqlQueryParser.parseQuery("select distinct pr " + // + assertQuery("select distinct pr " + // "from Person pr " + // "join Phone ph on ph.person = pr " + // "where ph.type = :phoneType"); - HqlQueryParser.parseQuery("select distinct pr " + // + assertQuery("select distinct pr " + // "from Person pr " + // "join pr.phones ph " + // "where ph.type = :phoneType"); - HqlQueryParser.parseQuery("select distinct pr " + // + assertQuery("select distinct pr " + // "from Person pr " + // "inner join pr.phones ph " + // "where ph.type = :phoneType"); - HqlQueryParser.parseQuery("select distinct pr " + // + assertQuery("select distinct pr " + // "from Person pr " + // "left join pr.phones ph " + // "where ph is null " + // " or ph.type = :phoneType"); - HqlQueryParser.parseQuery("select distinct pr " + // + assertQuery("select distinct pr " + // "from Person pr " + // "left outer join pr.phones ph " + // "where ph is null " + // " or ph.type = :phoneType"); - HqlQueryParser.parseQuery("select pr.name, ph.number " + // + assertQuery("select pr.name, ph.number " + // "from Person pr " + // "left join pr.phones ph with ph.type = :phoneType "); - HqlQueryParser.parseQuery("select pr.name, ph.number " + // + assertQuery("select pr.name, ph.number " + // "from Person pr " + // "left join pr.phones ph on ph.type = :phoneType "); - HqlQueryParser.parseQuery("select distinct pr " + // + assertQuery("select distinct pr " + // "from Person pr " + // "left join fetch pr.phones "); - HqlQueryParser.parseQuery("select a, ccp " + // + assertQuery("select a, ccp " + // "from Account a " + // "join treat(a.payments as CreditCardPayment) ccp " + // "where length(ccp.cardNumber) between 16 and 20"); - HqlQueryParser.parseQuery("select c, ccp " + // + assertQuery("select c, ccp " + // "from Call c " + // "join treat(c.payment as CreditCardPayment) ccp " + // "where length(ccp.cardNumber) between 16 and 20"); - HqlQueryParser.parseQuery("select longest.duration " + // + assertQuery("select longest.duration " + // "from Phone p " + // "left join lateral (" + // " select c.duration as duration " + // @@ -1230,169 +1247,164 @@ class HqlSpecificationTests { " limit 1 " + // " ) longest " + // "where p.number = :phoneNumber"); - HqlQueryParser.parseQuery("select ph " + // + assertQuery("select ph " + // "from Phone ph " + // "where ph.person.address = :address "); - HqlQueryParser.parseQuery("select ph " + // + assertQuery("select ph " + // "from Phone ph " + // "join ph.person pr " + // "where pr.address = :address "); - HqlQueryParser.parseQuery("select ph " + // + assertQuery("select ph " + // "from Phone ph " + // "where ph.person.address = :address " + // " and ph.person.createdOn > :timestamp"); - HqlQueryParser.parseQuery("select ph " + // + assertQuery("select ph " + // "from Phone ph " + // "inner join ph.person pr " + // "where pr.address = :address " + // " and pr.createdOn > :timestamp"); - HqlQueryParser.parseQuery("select ph " + // + assertQuery("select ph " + // "from Person pr " + // "join pr.phones ph " + // "join ph.calls c " + // "where pr.address = :address " + // " and c.duration > :duration"); - HqlQueryParser.parseQuery("select ch " + // + assertQuery("select ch " + // "from Phone ph " + // "join ph.callHistory ch " + // "where ph.id = :id "); - HqlQueryParser.parseQuery("select value(ch) " + // + assertQuery("select value(ch) " + // "from Phone ph " + // "join ph.callHistory ch " + // "where ph.id = :id "); - HqlQueryParser.parseQuery("select key(ch) " + // + assertQuery("select key(ch) " + // "from Phone ph " + // "join ph.callHistory ch " + // "where ph.id = :id "); - HqlQueryParser.parseQuery("select key(ch) " + // + assertQuery("select key(ch) " + // "from Phone ph " + // "join ph.callHistory ch " + // "where ph.id = :id "); - HqlQueryParser.parseQuery("select entry(ch) " + // + assertQuery("select entry (ch) " + // "from Phone ph " + // "join ph.callHistory ch " + // "where ph.id = :id "); - HqlQueryParser.parseQuery("select sum(ch.duration) " + // + assertQuery("select sum(ch.duration) " + // "from Person pr " + // "join pr.phones ph " + // "join ph.callHistory ch " + // "where ph.id = :id " + // " and index(ph) = :phoneIndex"); - HqlQueryParser.parseQuery("select value(ph.callHistory) " + // + assertQuery("select value(ph.callHistory) " + // "from Phone ph " + // "where ph.id = :id "); - HqlQueryParser.parseQuery("select key(ph.callHistory) " + // + assertQuery("select key(ph.callHistory) " + // "from Phone ph " + // "where ph.id = :id "); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.phones[0].type = LAND_LINE"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where p.addresses['HOME'] = :address"); - HqlQueryParser.parseQuery("select pr " + // + assertQuery("select pr " + // "from Person pr " + // "where pr.phones[max(indices(pr.phones))].type = 'LAND_LINE'"); - HqlQueryParser.parseQuery("select p.name, p.nickName " + // + assertQuery("select p.name, p.nickName " + // "from Person p "); - HqlQueryParser.parseQuery("select p.name as name, p.nickName as nickName " + // + assertQuery("select p.name as name, p.nickName as nickName " + // "from Person p "); - HqlQueryParser.parseQuery("select new org.hibernate.userguide.hql.CallStatistics(" + // - " count(c), " + // + assertQuery("select new org.hibernate.userguide.hql.CallStatistics(count(c), " + // " sum(c.duration), " + // " min(c.duration), " + // " max(c.duration), " + // " avg(c.duration)" + // ") " + // "from Call c "); - HqlQueryParser.parseQuery("select new map(" + // - " p.number as phoneNumber , " + // + assertQuery("select new map(p.number as phoneNumber, " + // " sum(c.duration) as totalDuration, " + // - " avg(c.duration) as averageDuration " + // - ") " + // + " avg(c.duration) as averageDuration) " + // "from Call c " + // "join c.phone p " + // "group by p.number "); - HqlQueryParser.parseQuery("select new list(" + // - " p.number, " + // - " c.duration " + // - ") " + // + assertQuery("select new list(p.number," + // + " c.duration) " + // "from Call c " + // "join c.phone p "); - HqlQueryParser.parseQuery("select distinct p.lastName " + // + assertQuery("select distinct p.lastName " + // "from Person p"); - HqlQueryParser.parseQuery("select " + // + assertQuery("select " + // " count(c), " + // " sum(c.duration), " + // " min(c.duration), " + // " max(c.duration), " + // " avg(c.duration) " + // "from Call c "); - HqlQueryParser.parseQuery("select count(distinct c.phone) " + // + assertQuery("select count(distinct c.phone) " + // "from Call c "); - HqlQueryParser.parseQuery("select p.number, count(c) " + // + assertQuery("select p.number, count(c) " + // "from Call c " + // "join c.phone p " + // "group by p.number"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Phone p " + // "where max(elements(p.calls)) = :call"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Phone p " + // "where min(elements(p.calls)) = :call"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "where max(indices(p.phones)) = 0"); - HqlQueryParser.parseQuery("select count(c) filter (where c.duration < 30) " + // + assertQuery("select count(c) filter (where c.duration < 30) " + // "from Call c "); - HqlQueryParser.parseQuery("select p.number, count(c) filter (where c.duration < 30) " + // + assertQuery("select p.number, count(c) filter (where c.duration < 30) " + // "from Call c " + // "join c.phone p " + // "group by p.number"); - HqlQueryParser.parseQuery("select listagg(p.number, ', ') within group (order by p.type,p.number) " + // + assertQuery("select listagg(p.number, ', ') within group (order by p.type, p.number) " + // "from Phone p " + // "group by p.person"); - HqlQueryParser.parseQuery("select sum(c.duration) " + // + assertQuery("select sum(c.duration) " + // "from Call c "); - HqlQueryParser.parseQuery("select p.name, sum(c.duration) " + // + assertQuery("select p.name, sum(c.duration) " + // "from Call c " + // "join c.phone ph " + // "join ph.person p " + // "group by p.name"); - HqlQueryParser.parseQuery("select p, sum(c.duration) " + // + assertQuery("select p, sum(c.duration) " + // "from Call c " + // "join c.phone ph " + // "join ph.person p " + // "group by p"); - HqlQueryParser.parseQuery("select p.name, sum(c.duration) " + // + assertQuery("select p.name, sum(c.duration) " + // "from Call c " + // "join c.phone ph " + // "join ph.person p " + // "group by p.name " + // "having sum(c.duration) > 1000"); - HqlQueryParser.parseQuery("select p.name from Person p " + // + assertQuery("select p.name from Person p " + // "union " + // "select p.nickName from Person p where p.nickName is not null"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Person p " + // "order by p.name"); - HqlQueryParser.parseQuery("select p.name, sum(c.duration) as total " + // + assertQuery("select p.name, sum(c.duration) as total " + // "from Call c " + // "join c.phone ph " + // "join ph.person p " + // "group by p.name " + // "order by total"); - HqlQueryParser.parseQuery("select c " + // + assertQuery("select c " + // "from Call c " + // "join c.phone p " + // "order by p.number " + // "limit 50"); - HqlQueryParser.parseQuery("select c " + // + assertQuery("select c " + // "from Call c " + // "join c.phone p " + // "order by p.number " + // "fetch first 50 rows only"); - HqlQueryParser.parseQuery("select p " + // + assertQuery("select p " + // "from Phone p " + // "join fetch p.calls " + // "order by p " + // diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java index 5267425a5..fe85ba8ad 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java @@ -93,12 +93,8 @@ class JpaQueryLookupStrategyUnitTests { Method method = UserRepository.class.getMethod("findByFoo", String.class); RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class); - Throwable reference = new RuntimeException(); - when(em.createQuery(anyString())).thenThrow(reference); - - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> strategy.resolveQuery(method, metadata, projectionFactory, namedQueries)) - .withCause(reference); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> strategy.resolveQuery(method, metadata, projectionFactory, namedQueries)); } @Test // DATAJPA-554 @@ -244,7 +240,7 @@ class JpaQueryLookupStrategyUnitTests { @Query(value = "select foo from Foo foo", countName = "foo.count") Page findByStringQueryWithNamedCountQuery(String foo, Pageable pageable); - @Query(value = "something absurd", name = "my-query-name") + @Query(value = "select foo from Foo foo", name = "my-query-name") User annotatedQueryWithQueryAndQueryName(); @Query("SELECT * FROM table WHERE (json_col->'jsonKey')::jsonb \\?\\? :param ") 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 6ff73aa3b..bf9d1074b 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 @@ -436,7 +436,7 @@ class JpqlQueryRendererTests { assertQuery(""" SELECT goodCustomer FROM Customer goodCustomer - WHERE goodCustomer.balanceOwed < (SELECT AVG(c.balanceOwed)/2.0 FROM Customer c) + WHERE goodCustomer.balanceOwed < (SELECT AVG(c.balanceOwed) / 2.0 FROM Customer c) """); } @@ -481,9 +481,9 @@ class JpqlQueryRendererTests { assertQuery(""" UPDATE Employee e SET e.salary = - CASE WHEN e.rating = 1 THEN e.salary*1.1 - WHEN e.rating = 2 THEN e.salary*1.05 - ELSE e.salary*1.01 + CASE WHEN e.rating = 1 THEN e.salary * 1.1 + WHEN e.rating = 2 THEN e.salary * 1.05 + ELSE e.salary * 1.01 END """); } @@ -494,9 +494,9 @@ class JpqlQueryRendererTests { assertQuery(""" UPDATE Employee e SET e.salary = - CASE e.rating WHEN 1 THEN e.salary*1.1 - WHEN 2 THEN e.salary*1.05 - ELSE e.salary*1.01 + CASE e.rating WHEN 1 THEN e.salary * 1.1 + WHEN 2 THEN e.salary * 1.05 + ELSE e.salary * 1.01 END """); } @@ -735,7 +735,7 @@ class JpqlQueryRendererTests { void orderByThatMatchesAllSelectAliasesShouldWork() { assertQuery(""" - SELECT o.quantity, o.cost*1.08 AS taxedCost, a.zipcode + SELECT o.quantity, o.cost * 1.08 AS taxedCost, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' AND a.county = 'Santa Clara' ORDER BY o.quantity, taxedCost, a.zipcode @@ -1012,15 +1012,15 @@ class JpqlQueryRendererTests { @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", - "select count(u)*-0.7f as value from User u", "select count(oi) + (-100) as perc from StockOrderItem oi", + "select +1 as value from User u", "select +1 * -100 as value from User u", + "select count(u) * -0.7f as value from User u", "select count(oi) + (-100) as perc from StockOrderItem oi", "select p from Payment p where length(p.cardNumber) between +16 and -20" }) void signedLiteralShouldWork(String query) { assertQuery(query); } @ParameterizedTest // GH-3342 - @ValueSource(strings = { "select -count(u) from User u", "select +1*(-count(u)) from User u" }) + @ValueSource(strings = { "select -count(u) from User u", "select +1 * (-count(u)) from User u" }) void signedExpressionsShouldWork(String query) { assertQuery(query); } 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 72f0ff8b4..6bce914c7 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 @@ -34,6 +34,7 @@ import org.springframework.lang.Nullable; * {@link JpqlQueryParser}. * * @author Greg Turnquist + * @author Mark Paluch */ class JpqlQueryTransformerTests { @@ -189,7 +190,7 @@ class JpqlQueryTransformerTests { assertThat(alias("select u from User u where not exists (select u2 from User u2)")).isEqualTo("u"); assertThat(alias( "select u from User u where not exists (select u2 from User u2 where not exists (select u3 from User u3))")) - .isEqualTo("u"); + .isEqualTo("u"); } @Test // GH-2557 @@ -360,7 +361,7 @@ class JpqlQueryTransformerTests { from Bar lp join lp.investmentProduct ip where (lp.toDate is null and lp.fromDate <= :now and lp.fromDate is not null) and lp.accountId = :accountId group by ip.id, ip.name, lp.accountId order by ip.name ASC""")) - .isTrue(); + .isTrue(); } @Test // DATAJPA-938 @@ -452,7 +453,7 @@ class JpqlQueryTransformerTests { String query = "SELECT AVG(m.price) AS m.avg FROM Magazine m"; Sort sort = Sort.by("m.avg"); - assertThatIllegalArgumentException().isThrownBy(() -> createQueryFor(query, sort)); + assertThatExceptionOfType(BadJpqlGrammarException.class).isThrownBy(() -> createQueryFor(query, sort)); } @Test // DATAJPA-965, DATAJPA-970, GH-2863 @@ -467,8 +468,8 @@ class JpqlQueryTransformerTests { @Test // DATAJPA-1506 void detectsAliasWithGroupAndOrderBy() { - assertThat(alias("select * from User group by name")).isNull(); - assertThat(alias("select * from User order by name")).isNull(); + // assertThat(alias("select * from User group by name")).isNull(); + // assertThat(alias("select * from User order by name")).isNull(); assertThat(alias("select u from User u group by name")).isEqualTo("u"); assertThat(alias("select u from User u order by name")).isEqualTo("u"); } @@ -492,11 +493,6 @@ class JpqlQueryTransformerTests { @Test void createCountQuerySupportsLineBreaksInSelectClause() { - // - // - // - // - // assertThat(createCountQueryFor(""" select user.age, user.name @@ -582,8 +578,11 @@ class JpqlQueryTransformerTests { @Test void detectsAliasWithGroupAndOrderByWithLineBreaks() { - assertThat(alias("select * from User group\nby name")).isNull(); - assertThat(alias("select * from User order\nby name")).isNull(); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> alias("select * from User group\nby name")); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> alias("select * from User order\nby name")); + assertThat(alias("select u from User u group\nby name")).isEqualTo("u"); assertThat(alias("select u from User u order\nby name")).isEqualTo("u"); assertThat(alias("select u from User\nu\norder \n by name")).isEqualTo("u"); @@ -606,7 +605,8 @@ class JpqlQueryTransformerTests { // This is not a required behavior, in fact the opposite is, // but it documents a current limitation. // to fix this without breaking findProjectionClauseWithIncludedFrom we need a more sophisticated parser. - assertThat(projection("select * from (select x from y)")).isNotEqualTo("*"); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> projection("select * from (select x from y)")); } @Test // DATAJPA-1696 @@ -669,14 +669,14 @@ class JpqlQueryTransformerTests { assertThat( createCountQueryFor("SELECT t FROM mytable t WHERE nr = :number AND kon = :kon AND datum >= '2019-01-01'")) - .isEqualTo("SELECT count(t) FROM mytable t WHERE nr = :number AND kon = :kon AND datum >= '2019-01-01'"); + .isEqualTo("SELECT count(t) FROM mytable t WHERE nr = :number AND kon = :kon AND datum >= '2019-01-01'"); assertThat(createCountQueryFor("select s FROM users_statuses s WHERE (user_created_at BETWEEN $1 AND $2)")) .isEqualTo("select count(s) FROM users_statuses s WHERE (user_created_at BETWEEN $1 AND $2)"); assertThat( createCountQueryFor("SELECT us FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)")) - .isEqualTo("SELECT count(us) FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)"); + .isEqualTo("SELECT count(us) FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)"); } @Test // GH-3269 @@ -684,11 +684,11 @@ class JpqlQueryTransformerTests { assertCountQuery("select distinct 1 as x from Employee e", "select count(distinct 1) from Employee e"); assertCountQuery("SELECT DISTINCT abc AS x FROM T t", "SELECT count(DISTINCT abc) FROM T t"); - assertCountQuery("select distinct a as x, b as y from Employee e", "select count(distinct a , b) from Employee e"); + assertCountQuery("select distinct a as x, b as y from Employee e", "select count(distinct a, b) from Employee e"); assertCountQuery("select distinct sum(amount) as x from Employee e GROUP BY n", "select count(distinct sum(amount)) from Employee e GROUP BY n"); assertCountQuery("select distinct a, b, sum(amount) as c, d from Employee e GROUP BY n", - "select count(distinct a, b, sum(amount) , d) from Employee e GROUP BY n"); + "select count(distinct a, b, sum(amount), d) from Employee e GROUP BY n"); assertCountQuery("select distinct a, count(b) as c from Employee e GROUP BY n", "select count(distinct a, count(b)) from Employee e GROUP BY n"); } 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 44f57f264..02493d7f9 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 @@ -16,7 +16,10 @@ package org.springframework.data.jpa.repository.query; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -33,13 +36,41 @@ class JpqlSpecificationTests { private static final String SPEC_FAULT = "Disabled due to spec fault> "; + /** + * Parse the query using {@link HqlParser} then run it through the query-preserving {@link HqlQueryRenderer}. + */ + private static String parseWithoutChanges(String query) { + + JpqlLexer lexer = new JpqlLexer(CharStreams.fromString(query)); + JpqlParser parser = new JpqlParser(new CommonTokenStream(lexer)); + + parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); + + JpqlParser.StartContext parsedQuery = parser.start(); + + return render(new JpqlQueryRenderer().visit(parsedQuery)); + } + + private void assertQuery(String query) { + + String slimmedDownQuery = reduceWhitespace(query); + assertThat(parseWithoutChanges(slimmedDownQuery)).isEqualTo(slimmedDownQuery); + } + + private String reduceWhitespace(String original) { + + return original // + .replaceAll("[ \\t\\n]{1,}", " ") // + .trim(); + } + /** * @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#example */ @Test void joinExample1() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order AS o JOIN o.lineItems AS l WHERE l.shipped = FALSE @@ -53,7 +84,7 @@ class JpqlSpecificationTests { @Test void joinExample2() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l JOIN l.product p WHERE p.productType = 'office_supplies' @@ -66,12 +97,12 @@ class JpqlSpecificationTests { @Test void rangeVariableDeclarations() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o1 FROM Order o1, Order o2 WHERE o1.quantity > o2.quantity AND o2.customer.lastname = 'Smith' AND - o2.customer.firstname= 'John' + o2.customer.firstname = 'John' """); } @@ -81,7 +112,7 @@ class JpqlSpecificationTests { @Test void pathExpressionsExample1() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT i.name, VALUE(p) FROM Item i JOIN i.photos p WHERE KEY(p) LIKE '%egret' @@ -94,7 +125,7 @@ class JpqlSpecificationTests { @Test void pathExpressionsExample2() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT i.name, p FROM Item i JOIN i.photos p WHERE KEY(p) LIKE '%egret' @@ -107,7 +138,7 @@ class JpqlSpecificationTests { @Test void pathExpressionsExample3() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.vendor FROM Employee e JOIN e.contactInfo.phones p """); @@ -119,7 +150,7 @@ class JpqlSpecificationTests { @Test void pathExpressionsExample4() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.vendor FROM Employee e JOIN e.contactInfo c JOIN c.phones p WHERE e.contactInfo.address.zipcode = '95054' @@ -129,7 +160,7 @@ class JpqlSpecificationTests { @Test void pathExpressionSyntaxExample1() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT l.product FROM Order AS o JOIN o.lineItems l """); @@ -138,7 +169,7 @@ class JpqlSpecificationTests { @Test void joinsExample1() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c, Employee e WHERE c.hatsize = e.shoesize """); } @@ -146,7 +177,7 @@ class JpqlSpecificationTests { @Test void joinsExample2() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c JOIN c.orders o WHERE c.status = 1 """); } @@ -154,7 +185,7 @@ class JpqlSpecificationTests { @Test void joinsInnerExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c INNER JOIN c.orders o WHERE c.status = 1 """); } @@ -162,7 +193,7 @@ class JpqlSpecificationTests { @Test void joinsInExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT OBJECT(c) FROM Customer c, IN(c.orders) o WHERE c.status = 1 """); } @@ -170,7 +201,7 @@ class JpqlSpecificationTests { @Test void doubleJoinExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.vendor FROM Employee e JOIN e.contactInfo c JOIN c.phones p WHERE c.address.zipcode = '95054' @@ -180,7 +211,7 @@ class JpqlSpecificationTests { @Test void leftJoinExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT s.name, COUNT(p) FROM Suppliers s LEFT JOIN s.products p GROUP BY s.name @@ -190,7 +221,7 @@ class JpqlSpecificationTests { @Test void leftJoinOnExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT s.name, COUNT(p) FROM Suppliers s LEFT JOIN s.products p ON p.status = 'inStock' @@ -201,7 +232,7 @@ class JpqlSpecificationTests { @Test void leftJoinWhereExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT s.name, COUNT(p) FROM Suppliers s LEFT JOIN s.products p WHERE p.status = 'inStock' @@ -212,7 +243,7 @@ class JpqlSpecificationTests { @Test void leftJoinFetchExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT d FROM Department d LEFT JOIN FETCH d.employees WHERE d.deptno = 1 @@ -222,7 +253,7 @@ class JpqlSpecificationTests { @Test void collectionMemberExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l WHERE l.product.productType = 'office_supplies' @@ -232,7 +263,7 @@ class JpqlSpecificationTests { @Test void collectionMemberInExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o, IN(o.lineItems) l WHERE l.product.productType = 'office_supplies' @@ -242,7 +273,7 @@ class JpqlSpecificationTests { @Test void fromClauseExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order AS o JOIN o.lineItems l JOIN l.product p """); @@ -251,19 +282,19 @@ class JpqlSpecificationTests { @Test void fromClauseDowncastingExample1() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT b.name, b.ISBN FROM Order o JOIN TREAT(o.product AS Book) b - """); + """); } @Test void fromClauseDowncastingExample2() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) lp WHERE lp.budget > 1000 - """); + """); } /** @@ -273,39 +304,39 @@ class JpqlSpecificationTests { @Disabled(SPEC_FAULT + "Use double-quotes when it should be using single-quotes for a string literal") void fromClauseDowncastingExample3_SPEC_BUG() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e JOIN e.projects p WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE "cost overrun" - """); + """); } @Test void fromClauseDowncastingExample3fixed() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e JOIN e.projects p WHERE TREAT(p AS LargeProject).budget > 1000 OR TREAT(p AS SmallProject).name LIKE 'Persist%' OR p.description LIKE 'cost overrun' - """); + """); } @Test void fromClauseDowncastingExample4() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TREAT(e AS Exempt).vacationDays > 10 OR TREAT(e AS Contractor).hours > 100 - """); + """); } @Test void pathExpressionsNamedParametersExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE c.status = :stat @@ -315,7 +346,7 @@ class JpqlSpecificationTests { @Test void betweenExpressionsExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT t FROM CreditCard c JOIN c.transactionHistory t WHERE c.holder.name = 'John Doe' AND INDEX(t) BETWEEN 0 AND 9 @@ -325,7 +356,7 @@ class JpqlSpecificationTests { @Test void isEmptyExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.lineItems IS EMPTY @@ -335,7 +366,7 @@ class JpqlSpecificationTests { @Test void memberOfExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p FROM Person p WHERE 'Joe' MEMBER OF p.nicknames @@ -345,11 +376,10 @@ class JpqlSpecificationTests { @Test void existsSubSelectExample1() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT emp FROM Employee emp - WHERE EXISTS ( - SELECT spouseEmp + WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) """); @@ -358,33 +388,31 @@ class JpqlSpecificationTests { @Test void allExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT emp FROM Employee emp - WHERE emp.salary > ALL ( - SELECT m.salary + WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department) - """); + """); } @Test void existsSubSelectExample2() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT emp FROM Employee emp - WHERE EXISTS ( - SELECT spouseEmp + WHERE EXISTS (SELECT spouseEmp FROM Employee spouseEmp WHERE spouseEmp = emp.spouse) - """); + """); } @Test void subselectNumericComparisonExample1() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE (SELECT AVG(o.price) FROM c.orders o) > 100 @@ -394,18 +422,17 @@ class JpqlSpecificationTests { @Test void subselectNumericComparisonExample2() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT goodCustomer FROM Customer goodCustomer - WHERE goodCustomer.balanceOwed < ( - SELECT AVG(c.balanceOwed)/2.0 FROM Customer c) + WHERE goodCustomer.balanceOwed < (SELECT AVG(c.balanceOwed) / 2.0 FROM Customer c) """); } @Test void indexExample() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT w.name FROM Course c JOIN c.studentWaitlist w WHERE c.name = 'Calculus' @@ -420,7 +447,7 @@ class JpqlSpecificationTests { @Disabled(SPEC_FAULT + "FUNCTION calls needs a comparator") void functionInvocationExample_SPEC_BUG() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit) @@ -430,7 +457,7 @@ class JpqlSpecificationTests { @Test void functionInvocationExampleWithCorrection() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c FROM Customer c WHERE FUNCTION('hasGoodCredit', c.balance, c.creditLimit) = TRUE @@ -440,33 +467,33 @@ class JpqlSpecificationTests { @Test void updateCaseExample1() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Employee e SET e.salary = CASE WHEN e.rating = 1 THEN e.salary * 1.1 WHEN e.rating = 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test void updateCaseExample2() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Employee e SET e.salary = CASE e.rating WHEN 1 THEN e.salary * 1.1 WHEN 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END - """); + """); } @Test void selectCaseExample1() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e.name, CASE TYPE(e) WHEN Exempt THEN 'Exempt' WHEN Contractor THEN 'Contractor' @@ -481,7 +508,7 @@ class JpqlSpecificationTests { @Test void selectCaseExample2() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e.name, f.name, CONCAT(CASE WHEN f.annualMiles > 50000 THEN 'Platinum ' @@ -496,17 +523,17 @@ class JpqlSpecificationTests { @Test void theRest() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TYPE(e) IN (Exempt, Contractor) - """); + """); } @Test void theRest2() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TYPE(e) IN (:empType1, :empType2) @@ -516,7 +543,7 @@ class JpqlSpecificationTests { @Test void theRest3() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e FROM Employee e WHERE TYPE(e) IN :empTypes @@ -526,7 +553,7 @@ class JpqlSpecificationTests { @Test void theRest4() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT TYPE(e) FROM Employee e WHERE TYPE(e) <> Exempt @@ -536,7 +563,7 @@ class JpqlSpecificationTests { @Test void theRest5() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c.status, AVG(c.filledOrderCount), COUNT(c) FROM Customer c GROUP BY c.status @@ -547,7 +574,7 @@ class JpqlSpecificationTests { @Test void theRest6() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c.country, COUNT(c) FROM Customer c GROUP BY c.country @@ -558,7 +585,7 @@ class JpqlSpecificationTests { @Test void theRest7() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c, COUNT(o) FROM Customer c JOIN c.orders o GROUP BY c @@ -569,7 +596,7 @@ class JpqlSpecificationTests { @Test void theRest8() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c.id, c.status FROM Customer c JOIN c.orders o WHERE o.count > 100 @@ -579,7 +606,7 @@ class JpqlSpecificationTests { @Test void theRest9() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT v.location.street, KEY(i).title, VALUE(i) FROM VideoStore v JOIN v.videoInventory i WHERE v.location.zipcode = '94301' AND VALUE(i) > 0 @@ -589,7 +616,7 @@ class JpqlSpecificationTests { @Test void theRest10() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o.lineItems FROM Order AS o """); } @@ -597,7 +624,7 @@ class JpqlSpecificationTests { @Test void theRest11() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT c, COUNT(l) AS itemCount FROM Customer c JOIN c.Orders o JOIN o.lineItems l WHERE c.address.state = 'CA' @@ -609,7 +636,7 @@ class JpqlSpecificationTests { @Test void theRest12() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT NEW com.acme.example.CustomerDetails(c.id, c.status, o.count) FROM Customer c JOIN c.orders o WHERE o.count > 100 @@ -619,7 +646,7 @@ class JpqlSpecificationTests { @Test void theRest13() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT e.address AS addr FROM Employee e """); @@ -628,7 +655,7 @@ class JpqlSpecificationTests { @Test void theRest14() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT AVG(o.quantity) FROM Order o """); } @@ -636,7 +663,7 @@ class JpqlSpecificationTests { @Test void theRest15() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT SUM(l.price) FROM Order o JOIN o.lineItems l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' @@ -646,7 +673,7 @@ class JpqlSpecificationTests { @Test void theRest16() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT COUNT(o) FROM Order o """); } @@ -654,7 +681,7 @@ class JpqlSpecificationTests { @Test void theRest17() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT COUNT(l.price) FROM Order o JOIN o.lineItems l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' @@ -664,7 +691,7 @@ class JpqlSpecificationTests { @Test void theRest18() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT COUNT(l) FROM Order o JOIN o.lineItems l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' AND l.price IS NOT NULL @@ -674,7 +701,7 @@ class JpqlSpecificationTests { @Test void theRest19() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' @@ -685,7 +712,7 @@ class JpqlSpecificationTests { @Test void theRest20() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o.quantity, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' @@ -696,8 +723,8 @@ class JpqlSpecificationTests { @Test void theRest21() { - JpqlQueryParser.parseQuery(""" - SELECT o.quantity, o.cost*1.08 AS taxedCost, a.zipcode + assertQuery(""" + SELECT o.quantity, o.cost * 1.08 AS taxedCost, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' AND a.county = 'Santa Clara' ORDER BY o.quantity, taxedCost, a.zipcode @@ -707,7 +734,7 @@ class JpqlSpecificationTests { @Test void theRest22() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT AVG(o.quantity) as q, a.zipcode FROM Customer c JOIN c.orders o JOIN c.address a WHERE a.state = 'CA' @@ -719,7 +746,7 @@ class JpqlSpecificationTests { @Test void theRest23() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.product_name FROM Order o JOIN o.lineItems l JOIN l.product p JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' @@ -734,7 +761,7 @@ class JpqlSpecificationTests { void theRest24() { assertThatExceptionOfType(BadJpqlGrammarException.class).isThrownBy(() -> { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT p.product_name FROM Order o, IN(o.lineItems) l JOIN o.customer c WHERE c.lastname = 'Smith' AND c.firstname = 'John' @@ -746,7 +773,7 @@ class JpqlSpecificationTests { @Test void theRest25() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" DELETE FROM Customer c WHERE c.status = 'inactive' @@ -756,7 +783,7 @@ class JpqlSpecificationTests { @Test void theRest26() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" DELETE FROM Customer c WHERE c.status = 'inactive' @@ -767,7 +794,7 @@ class JpqlSpecificationTests { @Test void theRest27() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Customer c SET c.status = 'outstanding' WHERE c.balance < 10000 @@ -777,7 +804,7 @@ class JpqlSpecificationTests { @Test void theRest28() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" UPDATE Employee e SET e.address.building = 22 WHERE e.address.building = 14 @@ -789,7 +816,7 @@ class JpqlSpecificationTests { @Test void theRest29() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o """); @@ -798,7 +825,7 @@ class JpqlSpecificationTests { @Test void theRest30() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.shippingAddress.state = 'CA' @@ -808,7 +835,7 @@ class JpqlSpecificationTests { @Test void theRest31() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o.shippingAddress.state FROM Order o """); @@ -817,7 +844,7 @@ class JpqlSpecificationTests { @Test void theRest32() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l """); @@ -826,7 +853,7 @@ class JpqlSpecificationTests { @Test void theRest33() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.lineItems IS NOT EMPTY @@ -836,7 +863,7 @@ class JpqlSpecificationTests { @Test void theRest34() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.lineItems IS EMPTY @@ -846,7 +873,7 @@ class JpqlSpecificationTests { @Test void theRest35() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l WHERE l.shipped = FALSE @@ -856,7 +883,7 @@ class JpqlSpecificationTests { @Test void theRest36() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE @@ -869,7 +896,7 @@ class JpqlSpecificationTests { @Test void theRest37() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT o FROM Order o WHERE o.shippingAddress <> o.billingAddress @@ -879,7 +906,7 @@ class JpqlSpecificationTests { @Test void theRest38() { - JpqlQueryParser.parseQuery(""" + assertQuery(""" SELECT DISTINCT o FROM Order o JOIN o.lineItems l WHERE l.product.name = ?1 diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java index 556f75ebb..f54272b0a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java @@ -17,6 +17,7 @@ package org.springframework.data.jpa.repository.query; import static org.assertj.core.api.Assertions.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -450,7 +451,7 @@ class StringQueryUnitTests { assertThat( new StringQuery("select new com.example.Dto(a.foo, a.bar) from A a", false).hasConstructorExpression()) - .isTrue(); + .isTrue(); assertThat(new StringQuery("select new com.example.Dto (a.foo, a.bar) from A a", false).hasConstructorExpression()) .isTrue(); assertThat(new StringQuery("select a from A a", true).hasConstructorExpression()).isFalse(); @@ -490,8 +491,10 @@ class StringQueryUnitTests { checkProjection("select something from Entity something", "something", "single expression", false); checkProjection("select x, y, z from Entity something", "x, y, z", "tuple", false); - checkProjection("sect x, y, z from Entity something", "", "missing select", false); - checkProjection("select x, y, z fron Entity something", "", "missing from", false); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> checkProjection("sect x, y, z from Entity something", "", "missing select", false)); + assertThatExceptionOfType(BadJpqlGrammarException.class) + .isThrownBy(() -> checkProjection("select x, y, z fron Entity something", "", "missing from", false)); } void checkProjection(String query, String expected, String description, boolean nativeQuery) { @@ -615,9 +618,9 @@ class StringQueryUnitTests { assertThat(new StringQuery(testQuery, false) // .usesJdbcStyleParameters()) // - .describedAs(testQuery) // - .describedAs(testQuery) // - .isFalse(); + .describedAs(testQuery) // + .describedAs(testQuery) // + .isFalse(); } } @@ -716,7 +719,11 @@ class StringQueryUnitTests { private void checkHasNamedParameter(String query, boolean expected, String label, boolean nativeQuery) { - assertThat(new StringQuery(query, nativeQuery).hasNamedParameter()) // + List bindings = new ArrayList<>(); + StringQuery.ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query, + bindings, new StringQuery.Metadata()); + + assertThat(bindings.stream().anyMatch(it -> it.getIdentifier().hasName())) // .describedAs(String.format("<%s> (%s)", query, label)) // .isEqualTo(expected); }