Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[master] SELECT ID(entityvar) only returns part of IdClass - bug fix and unit tests #2384

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2025 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 @@ -19,6 +19,7 @@
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.Expression;
import org.eclipse.persistence.jpa.jpql.parser.IdExpression;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
import org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression;
Expand Down Expand Up @@ -63,13 +64,16 @@ public void visit(IdExpression expression) {
//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);

for (DatabaseField primaryKeyField : primaryKeyFields) {
String idAttributeName = getIdAttributeNameByField(descriptor.getMappings(), primaryKeyField);
StateFieldPathExpression stateFieldPathExpression = new StateFieldPathExpression(expression.getParent(), variableText + "." + idAttributeName);
expression.addStateFieldPathExpression(stateFieldPathExpression);
}
//Continue with created StateFieldPathExpression
//It handle by ObjectBuilder booth @Id/primary key types (simple/composite)
expression.getStateFieldPathExpression().accept(this);
//It's handled by ObjectBuilder booth @Id/primary key types (simple/composite)
for (Expression fieldPathExpression: expression.getStateFieldPathExpressions()) {
fieldPathExpression.accept(this);
}
}

/**
Expand All @@ -96,7 +100,14 @@ public void visit(VersionExpression expression) {

private String getIdAttributeNameByField(List<DatabaseMapping> databaseMappings, DatabaseField field) {
for (DatabaseMapping mapping : databaseMappings) {
if (field.equals(mapping.getField()) || mapping.isPrimaryKeyMapping()) {
if (mapping.getFields().size() > 1 && (field.equals(mapping.getField()) || mapping.isPrimaryKeyMapping())) {
//handle @EmbeddedId (composite primary key usually by AggregateObjectMapping)
return mapping.getAttributeName();
} else if ((field.equals(mapping.getField()) && mapping.isPrimaryKeyMapping())) {
//handle single @Id (simple primary key)
return mapping.getAttributeName();
} else if (field.equals(mapping.getField())) {
//handle multiple @Id(s) with mapped @IdClass
return mapping.getAttributeName();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2006, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2006, 2025 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 @@ -41,6 +41,7 @@
import org.eclipse.persistence.jpa.jpql.parser.EntryExpression;
import org.eclipse.persistence.jpa.jpql.parser.ExtractExpression;
import org.eclipse.persistence.jpa.jpql.parser.FunctionExpression;
import org.eclipse.persistence.jpa.jpql.parser.IdExpression;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
import org.eclipse.persistence.jpa.jpql.parser.IndexExpression;
import org.eclipse.persistence.jpa.jpql.parser.Join;
Expand Down Expand Up @@ -625,6 +626,15 @@ public void visit(ValueExpression expression) {
addAttribute(identificationVariable.getText(), queryExpression);
}

@Override
public void visit(IdExpression expression) {
super.visit(expression);
if (expression.getStateFieldPathExpressions().size() > 1) {
//if multiple @Id attributes exists
multipleSelects = true;
}
}

private void visitAbstractSelectClause(AbstractSelectClause expression) {

multipleSelects = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2025 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,14 +16,17 @@

import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;

import junit.framework.Test;
import junit.framework.TestSuite;

import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.sessions.DatabaseSession;
import org.eclipse.persistence.testing.framework.jpa.junit.JUnitTestCase;
import org.eclipse.persistence.testing.models.jpa.advanced.AdvancedTableCreator;
import org.eclipse.persistence.testing.models.jpa.advanced.Employee;
import org.eclipse.persistence.testing.models.jpa.advanced.EmployeePopulator;
import org.eclipse.persistence.testing.models.jpa.advanced.PhoneNumber;
import org.eclipse.persistence.testing.models.jpa.advanced.Vegetable;
import org.eclipse.persistence.testing.models.jpa.advanced.VegetablePK;
import org.eclipse.persistence.testing.tests.jpa.jpql.JUnitDomainObjectComparer;
Expand Down Expand Up @@ -86,6 +89,8 @@ public static Test suite() {
suite.addTest(new JUnitJPQLFunctionsTest("queryID04Test"));
suite.addTest(new JUnitJPQLFunctionsTest("queryID05CompositePKTest"));
suite.addTest(new JUnitJPQLFunctionsTest("queryID06CompositePKTest"));
suite.addTest(new JUnitJPQLFunctionsTest("queryID07CompositePKTestWithIdClass"));
suite.addTest(new JUnitJPQLFunctionsTest("queryID08CompositePKTestWithIdClass"));
suite.addTest(new JUnitJPQLFunctionsTest("queryVERSION1Test"));
suite.addTest(new JUnitJPQLFunctionsTest("queryVERSION2Test"));
suite.addTest(new JUnitJPQLFunctionsTest("queryVERSION3Test"));
Expand Down Expand Up @@ -204,6 +209,34 @@ public void queryID06CompositePKTest(){
assertEquals(VEGETABLE_ID, result[2]);
}

public void queryID07CompositePKTestWithIdClass(){
final PhoneNumber PHONE_EXPECTED = employeePopulator.employeeExample1().getPhoneNumbers().stream().findFirst().get();

EntityManager em = createEntityManager();
Query query = em.createQuery("SELECT ID(p) FROM PhoneNumber p WHERE p.id = :idParam AND p.type = :typeParam");
query.setParameter("idParam", PHONE_EXPECTED.getOwner().getId());
query.setParameter("typeParam", PHONE_EXPECTED.getType());
Object[] result = (Object[])query.getSingleResult();
assertNotNull(result);
//result array order is important too
assertEquals(PHONE_EXPECTED.getOwner().getId(), result[0]);
assertEquals(PHONE_EXPECTED.getType(), result[1]);
}

public void queryID08CompositePKTestWithIdClass(){
final PhoneNumber PHONE_EXPECTED = employeePopulator.employeeExample1().getPhoneNumbers().stream().findFirst().get();

EntityManager em = createEntityManager();
Query query = em.createQuery("SELECT ID(this) FROM PhoneNumber WHERE this.id = :idParam AND this.type = :typeParam");
query.setParameter("idParam", PHONE_EXPECTED.getOwner().getId());
query.setParameter("typeParam", PHONE_EXPECTED.getType());
Object[] result = (Object[])query.getSingleResult();
assertNotNull(result);
//result array order is important too
assertEquals(PHONE_EXPECTED.getOwner().getId(), result[0]);
assertEquals(PHONE_EXPECTED.getType(), result[1]);
}

public void queryVERSION1Test(){
final Employee EMPLOYEE_EXPECTED = employeePopulator.employeeExample1();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2025 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 @@ -15,6 +15,10 @@
//
package org.eclipse.persistence.jpa.jpql.parser;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* The result of an ID function expression is the Entity primary
* key. Argument must be an identification variable.
Expand All @@ -29,7 +33,7 @@ public final class IdExpression extends EncapsulatedIdentificationVariableExpres
/**
* The field path created as a result of transformation
*/
private StateFieldPathExpression stateFieldPathExpression;
private final Map<String, StateFieldPathExpression> stateFieldPathExpressions = new LinkedHashMap<>();

/**
* Creates a new <code>IdExpression</code>.
Expand All @@ -52,18 +56,19 @@ public JPQLQueryBNF getQueryBNF() {
}

/**
* Returns field path created as a result of transformation.
* Returns field paths created as a result of transformation.
*
* @return The path expression that is qualified by the identification variable
* @return The path expressions that is qualified by the identification variable.
* There should more items, than one as multiple entity fields should be marked with ${@code @Id} and ${@code @IdClass} is used.
*/
public StateFieldPathExpression getStateFieldPathExpression() {
return stateFieldPathExpression;
public Collection<StateFieldPathExpression> getStateFieldPathExpressions() {
return stateFieldPathExpressions.values();
}

/**
* Sets field path created as a result of transformation.
* Add field path created as a result of transformation.
*/
public void setStateFieldPathExpression(StateFieldPathExpression stateFieldPathExpression) {
this.stateFieldPathExpression = stateFieldPathExpression;
public void addStateFieldPathExpression(StateFieldPathExpression stateFieldPathExpression) {
this.stateFieldPathExpressions.put(stateFieldPathExpression.getText(), stateFieldPathExpression);
}
}
Loading