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); }