Skip to content

Commit

Permalink
Jakarta Persistence 3.2 new feature - JPQL functions ID(), VERSION() (e…
Browse files Browse the repository at this point in the history
…clipse-ee4j#2108)

Implementation plus unit test according jakartaee/persistence#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 <radek.felcman@oracle.com>
  • Loading branch information
rfelcman authored Apr 9, 2024
1 parent 9c4b266 commit 3d1e6b4
Show file tree
Hide file tree
Showing 41 changed files with 1,489 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -197,11 +197,6 @@ final class ExpressionBuilderVisitor implements EclipseLinkExpressionVisitor {
*/
private Comparator<Class<?>> 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}
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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: <code>SELECT ID(e) FROM Entity e -> SELECT e.idAttr FROM Entity e</code>
* For Entity e with versionAttr as a version attribute: <code>SELECT VERSION(e) FROM Entity e -> SELECT e.versionAttr FROM Entity e</code>
*
* @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: <code>SELECT ID(e) FROM Entity e -> SELECT e.idAttr FROM Entity e</code>
*
* @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<DatabaseField> 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: <code>SELECT VERSION(e) FROM Entity e -> SELECT e.versionAttr FROM Entity e</code>
*
* @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<DatabaseMapping> databaseMappings, DatabaseField field) {
for (DatabaseMapping mapping : databaseMappings) {
if (field.equals(mapping.getField()) || mapping.isPrimaryKeyMapping()) {
return mapping.getAttributeName();
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -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.
*/
Expand All @@ -61,8 +54,7 @@ final class ReadAllQueryBuilder extends EclipseLinkAnonymousExpressionVisitor {
* cached information
*/
ReadAllQueryBuilder(JPQLQueryContext queryContext) {
super();
this.queryContext = queryContext;
super(queryContext);
}

private void initializeReadAllQuery() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
*/
Expand All @@ -201,8 +196,7 @@ final class TypeResolver implements EclipseLinkExpressionVisitor {
* cached information
*/
TypeResolver(JPQLQueryContext queryContext) {
super();
this.queryContext = queryContext;
super(queryContext);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--
Copyright (c) 2018, 2022 Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2018, 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
Expand Down Expand Up @@ -40,6 +40,7 @@
<class>org.eclipse.persistence.testing.models.jpa.advanced.Room</class>
<class>org.eclipse.persistence.testing.models.jpa.advanced.SmallProject</class>
<class>org.eclipse.persistence.testing.models.jpa.advanced.SimpleRoom</class>
<class>org.eclipse.persistence.testing.models.jpa.advanced.Vegetable</class>
<class>org.eclipse.persistence.testing.models.jpa.advanced.Woman</class>
<class>org.eclipse.persistence.testing.models.jpa.advanced.WorldRank</class>
<class>org.eclipse.persistence.testing.models.jpa.advanced.entities.EntyA</class>
Expand Down
Loading

0 comments on commit 3d1e6b4

Please sign in to comment.