From 3d1e6b49d32211b426e67935b2f3d31f2b891f5f Mon Sep 17 00:00:00 2001 From: Radek Felcman Date: Tue, 9 Apr 2024 12:18:51 +0200 Subject: [PATCH] Jakarta Persistence 3.2 new feature - JPQL functions ID(), VERSION() (#2108) Implementation plus unit test according https://github.com/jakartaee/persistence/pull/596 There are two new JPQL functions: - `ID(...)` to fetch Entity `@Id` value. There is support for single or composite primary key - `VERSION(...)` to fetch attribute value from attribute marked by `@Version` annotation These functions are specific as they are exist in JPQL only, but not in SQL like other JPQL functions where are usually similar SQL function/procedure at the DB side. Signed-off-by: Radek Felcman --- .../jpa/jpql/ExpressionBuilderVisitor.java | 10 +- .../jpql/JPQLFunctionsAbstractBuilder.java | 105 +++++++ .../jpa/jpql/ReadAllQueryBuilder.java | 14 +- .../internal/jpa/jpql/ReportItemBuilder.java | 13 +- .../internal/jpa/jpql/TypeResolver.java | 10 +- .../jpa/test/version/TestVersioning.java | 4 +- .../main/resources/META-INF/persistence.xml | 3 +- .../jpql/advanced/JUnitJPQLFunctionsTest.java | 268 ++++++++++++++++++ .../jpa/jpql/AbstractGrammarValidator.java | 88 ++++++ .../jpa/jpql/AbstractSemanticValidator.java | 32 +++ .../jpa/jpql/JPQLQueryProblemMessages.java | 10 +- .../jpql/JPQLQueryProblemResourceBundle.java | 14 +- .../jpa/jpql/ParameterTypeVisitor.java | 14 +- .../parser/AbstractExpressionVisitor.java | 10 +- .../parser/AnonymousExpressionVisitor.java | 12 +- .../jpa/jpql/parser/Expression.java | 16 +- .../jpa/jpql/parser/ExpressionFactory.java | 18 ++ .../jpa/jpql/parser/ExpressionVisitor.java | 16 +- .../jpa/jpql/parser/IdExpression.java | 69 +++++ .../jpa/jpql/parser/IdExpressionBNF.java | 46 +++ .../jpa/jpql/parser/IdExpressionFactory.java | 59 ++++ .../jpa/jpql/parser/JPQLExpression.java | 42 ++- .../jpa/jpql/parser/JPQLGrammar3_2.java | 20 +- .../jpa/jpql/parser/VersionExpression.java | 70 +++++ .../jpa/jpql/parser/VersionExpressionBNF.java | 46 +++ .../jpql/parser/VersionExpressionFactory.java | 59 ++++ .../tools/AbstractContentAssistVisitor.java | 36 +++ .../AbstractActualJPQLQueryFormatter.java | 14 +- .../model/AbstractJPQLQueryFormatter.java | 36 ++- .../tools/model/BasicStateObjectBuilder.java | 30 +- .../query/AbstractStateObjectVisitor.java | 10 +- .../query/AnonymousStateObjectVisitor.java | 12 +- .../model/query/IdExpressionStateObject.java | 88 ++++++ .../tools/model/query/StateObjectVisitor.java | 16 +- .../query/VersionExpressionStateObject.java | 88 ++++++ .../jpql/tools/resolver/ResolverBuilder.java | 14 +- .../jpql/AbstractGrammarValidatorTest.java | 4 +- .../jpa/tests/jpql/JPQLQueries3_2.java | 17 +- .../jpa/tests/jpql/parser/JPQLParserTest.java | 38 ++- .../tests/jpql/parser/JPQLParserTester.java | 20 +- .../tests/jpql/parser/JPQLQueriesTest3_2.java | 60 +++- 41 files changed, 1489 insertions(+), 62 deletions(-) create mode 100644 foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/JPQLFunctionsAbstractBuilder.java create mode 100644 jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/test/java/org/eclipse/persistence/testing/tests/jpa/jpql/advanced/JUnitJPQLFunctionsTest.java create mode 100644 jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/IdExpression.java create mode 100644 jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/IdExpressionBNF.java create mode 100644 jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/IdExpressionFactory.java create mode 100644 jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/VersionExpression.java create mode 100644 jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/VersionExpressionBNF.java create mode 100644 jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/parser/VersionExpressionFactory.java create mode 100644 jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/model/query/IdExpressionStateObject.java create mode 100644 jpa/org.eclipse.persistence.jpa.jpql/src/main/java/org/eclipse/persistence/jpa/jpql/tools/model/query/VersionExpressionStateObject.java diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ExpressionBuilderVisitor.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ExpressionBuilderVisitor.java index 0c8bf65e6e3..fd8d1dcfa04 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ExpressionBuilderVisitor.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ExpressionBuilderVisitor.java @@ -178,7 +178,7 @@ * @author John Bracken */ @SuppressWarnings("nls") -final class ExpressionBuilderVisitor implements EclipseLinkExpressionVisitor { +final class ExpressionBuilderVisitor extends JPQLFunctionsAbstractBuilder implements EclipseLinkExpressionVisitor { /** * This visitor creates a list by retrieving either the single child or the children of the @@ -197,11 +197,6 @@ final class ExpressionBuilderVisitor implements EclipseLinkExpressionVisitor { */ private Comparator> numericTypeComparator; - /** - * The context used to query information about the application metadata. - */ - private final JPQLQueryContext queryContext; - /** * The EclipseLink {@link Expression} that represents a visited parsed * {@link org.eclipse persistence.jpa.query.parser.Expression Expression} @@ -226,9 +221,8 @@ final class ExpressionBuilderVisitor implements EclipseLinkExpressionVisitor { * cached information */ ExpressionBuilderVisitor(JPQLQueryContext queryContext) { - super(); + super(queryContext); this.type = new Class[1]; - this.queryContext = queryContext; } private void appendJoinVariables(org.eclipse.persistence.jpa.jpql.parser.Expression expression, diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/JPQLFunctionsAbstractBuilder.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/JPQLFunctionsAbstractBuilder.java new file mode 100644 index 00000000000..90e3c118458 --- /dev/null +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/JPQLFunctionsAbstractBuilder.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +// Contributors: +// Oracle - initial API and implementation +// +package org.eclipse.persistence.internal.jpa.jpql; + +import org.eclipse.persistence.descriptors.ClassDescriptor; +import org.eclipse.persistence.descriptors.VersionLockingPolicy; +import org.eclipse.persistence.internal.helper.DatabaseField; +import org.eclipse.persistence.jpa.jpql.parser.EclipseLinkAnonymousExpressionVisitor; +import org.eclipse.persistence.jpa.jpql.parser.IdExpression; +import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable; +import org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression; +import org.eclipse.persistence.jpa.jpql.parser.VersionExpression; +import org.eclipse.persistence.mappings.DatabaseMapping; + +import java.util.List; + +/** + * JPQL exclusive ID(), VERSION() functions/expressions are transformed there to StateFieldPathExpression. + * It should be used in the future for another JPQL functions/expressions which are not available at the DB level. + * E.g. For Entity e with idAttr as a primary key: SELECT ID(e) FROM Entity e -> SELECT e.idAttr FROM Entity e + * For Entity e with versionAttr as a version attribute: SELECT VERSION(e) FROM Entity e -> SELECT e.versionAttr FROM Entity e + * + * @author Radek Felcman + * @since 5.0 + */ +public abstract class JPQLFunctionsAbstractBuilder extends EclipseLinkAnonymousExpressionVisitor { + + /** + * The {@link JPQLQueryContext} is used to query information about the application metadata and + * cached information. + */ + final JPQLQueryContext queryContext; + + protected JPQLFunctionsAbstractBuilder(JPQLQueryContext queryContext) { + this.queryContext = queryContext; + } + + /** + * For Entity e with idAttr as a primary key: SELECT ID(e) FROM Entity e -> SELECT e.idAttr FROM Entity e + * + * @param expression The {@link IdExpression} to visit + */ + @Override + public void visit(IdExpression expression) { + //Fetch identification variable info + IdentificationVariable identificationVariable = (IdentificationVariable) expression.getExpression(); + String variableText = identificationVariable.getText(); + String variableName = identificationVariable.getVariableName(); + + //Get id attribute name + ClassDescriptor descriptor = this.queryContext.getDeclaration(variableName).getDescriptor(); + List primaryKeyFields = descriptor.getPrimaryKeyFields(); + String idAttributeName = getIdAttributeNameByField(descriptor.getMappings(), primaryKeyFields.get(0)); + StateFieldPathExpression stateFieldPathExpression = new StateFieldPathExpression(expression.getParent(), variableText + "." + idAttributeName); + expression.setStateFieldPathExpression(stateFieldPathExpression); + + //Continue with created StateFieldPathExpression + //It handle by ObjectBuilder booth @Id/primary key types (simple/composite) + expression.getStateFieldPathExpression().accept(this); + } + + /** + * For Entity e with versionAttr as a version attribute: SELECT VERSION(e) FROM Entity e -> SELECT e.versionAttr FROM Entity e + * + * @param expression The {@link VersionExpression} to visit + */ + @Override + public void visit(VersionExpression expression) { + //Fetch identification variable info + IdentificationVariable identificationVariable = (IdentificationVariable) expression.getExpression(); + String variableText = identificationVariable.getText(); + String variableName = identificationVariable.getVariableName(); + + //Get version attribute name + ClassDescriptor descriptor = this.queryContext.getDeclaration(variableName).getDescriptor(); + String versionAttributeName = ((VersionLockingPolicy) descriptor.getOptimisticLockingPolicy()).getVersionMapping().getAttributeName(); + StateFieldPathExpression stateFieldPathExpression = new StateFieldPathExpression(expression.getParent(), variableText + "." + versionAttributeName); + expression.setStateFieldPathExpression(stateFieldPathExpression); + + //Continue with created StateFieldPathExpression + expression.getStateFieldPathExpression().accept(this); + } + + private String getIdAttributeNameByField(List databaseMappings, DatabaseField field) { + for (DatabaseMapping mapping : databaseMappings) { + if (field.equals(mapping.getField()) || mapping.isPrimaryKeyMapping()) { + return mapping.getAttributeName(); + } + } + return null; + } +} diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ReadAllQueryBuilder.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ReadAllQueryBuilder.java index bf9b074be37..c2909aec213 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ReadAllQueryBuilder.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ReadAllQueryBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -16,7 +16,6 @@ package org.eclipse.persistence.internal.jpa.jpql; import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression; -import org.eclipse.persistence.jpa.jpql.parser.EclipseLinkAnonymousExpressionVisitor; import org.eclipse.persistence.jpa.jpql.parser.Expression; import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable; import org.eclipse.persistence.jpa.jpql.parser.NullExpression; @@ -36,19 +35,13 @@ * @author Pascal Filion * @author John Bracken */ -final class ReadAllQueryBuilder extends EclipseLinkAnonymousExpressionVisitor { +final class ReadAllQueryBuilder extends JPQLFunctionsAbstractBuilder { /** * The query that was created based on the type of select clause. */ ReadAllQuery query; - /** - * The {@link JPQLQueryContext} is used to query information about the application metadata and - * cached information. - */ - private final JPQLQueryContext queryContext; - /** * The {@link Expression} being visited. */ @@ -61,8 +54,7 @@ final class ReadAllQueryBuilder extends EclipseLinkAnonymousExpressionVisitor { * cached information */ ReadAllQueryBuilder(JPQLQueryContext queryContext) { - super(); - this.queryContext = queryContext; + super(queryContext); } private void initializeReadAllQuery() { diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ReportItemBuilder.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ReportItemBuilder.java index 717ec1d24fd..0e78b8e05d0 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ReportItemBuilder.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ReportItemBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2024 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the @@ -94,7 +94,7 @@ * @author John Bracken */ @SuppressWarnings("nls") -final class ReportItemBuilder extends EclipseLinkAnonymousExpressionVisitor { +final class ReportItemBuilder extends JPQLFunctionsAbstractBuilder { /** * The visitor responsible to visit the constructor items. @@ -111,12 +111,6 @@ final class ReportItemBuilder extends EclipseLinkAnonymousExpressionVisitor { */ private ReportQuery query; - /** - * The {@link JPQLQueryContext} is used to query information about the application metadata and - * cached information. - */ - private final JPQLQueryContext queryContext; - /** * If the select expression is aliased with a result variable, then temporarily cache it so it * can be used as the attribute name. @@ -138,10 +132,9 @@ final class ReportItemBuilder extends EclipseLinkAnonymousExpressionVisitor { * tree representation of the JPQL query */ ReportItemBuilder(JPQLQueryContext queryContext, ReportQuery query) { - super(); + super(queryContext); this.query = query; this.type = new Class[1]; - this.queryContext = queryContext; } private void addAttribute(String generateName, Expression queryExpression) { diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/TypeResolver.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/TypeResolver.java index 3fb85692ae6..0de7355b781 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/TypeResolver.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/TypeResolver.java @@ -162,7 +162,7 @@ * @author Pascal Filion */ @SuppressWarnings("nls") -final class TypeResolver implements EclipseLinkExpressionVisitor { +final class TypeResolver extends JPQLFunctionsAbstractBuilder implements EclipseLinkExpressionVisitor { /** * This visitor is responsible to retrieve the {@link CollectionExpression} if it is visited. @@ -179,11 +179,6 @@ final class TypeResolver implements EclipseLinkExpressionVisitor { */ private PathResolver pathResolver; - /** - * The context used to query information about the application metadata and cached information. - */ - private final JPQLQueryContext queryContext; - /** * The well defined type, which does not have to be calculated. */ @@ -201,8 +196,7 @@ final class TypeResolver implements EclipseLinkExpressionVisitor { * cached information */ TypeResolver(JPQLQueryContext queryContext) { - super(); - this.queryContext = queryContext; + super(queryContext); } /** diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/version/TestVersioning.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/version/TestVersioning.java index 562c54aa2eb..efe07b67720 100644 --- a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/version/TestVersioning.java +++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/version/TestVersioning.java @@ -43,11 +43,11 @@ public class TestVersioning { value = "UseNationalCharacterVaryingTypeForString=true")}) private EntityManagerFactory emf; - private final static String qStr1 = "UPDATE TemporalVersionedEntity " + + private final static String qStr1 = "UPDATE TemporalVersionedEntity " + "SET updatetimestamp = ?3 " + "WHERE id = ?1 AND updatetimestamp = ?2"; - private final static String qStr2 = "UPDATE TemporalVersionedEntity2 " + + private final static String qStr2 = "UPDATE TemporalVersionedEntity2 " + "SET version = ?3 " + "WHERE id = ?1 AND version = ?2"; diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/main/resources/META-INF/persistence.xml b/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/main/resources/META-INF/persistence.xml index 28ad1e40da0..48fdfa27094 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/main/resources/META-INF/persistence.xml +++ b/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/main/resources/META-INF/persistence.xml @@ -1,6 +1,6 @@