blob: d6e7e7bfe8bf8ad3abee2e4b9fa9922af4ef0b0a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.apache.openjpa.persistence.embed.compositepk;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import junit.framework.Assert;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
public class TestCompositePrimaryKeys extends SingleEMFTestCase {
// NOTE: There are 3 aspects to the fix to OPENJPA-2631, each being tested in some manner in the test
// methods below. The 3 aspects of the fix are:
//
// 1) Fix in ClassMapping which resolves the reported ClassCastEx.
// 2) After #1, things progressed further, but for some CriteriaBuilder tests incorrect SQL was created as follows:
// 2.1) An equals expression was created for only one of the columns in the composite PK. To
// resolve this a fix was made to class EqualExpression.
// 2.2) An extra parameter marker (?) was added to the SQL. To resolve this a fix was made to class Lit.
protected EntityManager em;
private EntityTransaction tx;
public void setUp() {
super.setUp(DROP_TABLES, Subject.class, SubjectKey.class, SubjectWithIdClass.class, Topic.class);
em = emf.createEntityManager();
tx = em.getTransaction();
tx.begin();
createData();
}
/*
* OpenJPA handles this test just fine with or without the fixes of OPENJPA-2631.
*/
public void testFindUsingFindOnSubjectKey() {
Subject s = createSubject();
Subject s2 = em.find(Subject.class, s.getKey());
verifySubject(s, s2);
}
/*
* OpenJPA handles this test just fine with or without the fixes of OPENJPA-2631. This works,
* compared to other tests, because a select is performed on the key class' fields.
*/
public void testFindUsingEqualsOnObjectJPQL() {
Subject s = createSubject();
TypedQuery<Subject> query = em.createQuery("select distinct s from Subject s where " +
"s.key.subjectNummer = :subjectNummer AND s.key.subjectTypeCode = " +
":subjectTypeCode", Subject.class);
query.setParameter("subjectNummer", s.getKey().getSubjectNummer());
query.setParameter("subjectTypeCode", s.getKey().getSubjectTypeCode());
Subject s2 = query.getSingleResult();
verifySubject(s, s2);
}
/*
* Just like the previous test, OpenJPA handles this test just fine with or without the
* fixes of OPENJPA-2631. This works, compared to other tests, because a select is
* performed on the key class' fields. This slight difference in this test compared to the
* previous test is that it traverses from Topic to the SubjectKey fields.
*/
public void testFindUsingJPQLEqualsOnSubjectKeyAttributes() {
Subject s = createSubject();
TypedQuery<Topic> query = em.createQuery("select distinct t from Topic t where t.subject.key.subjectNummer = " +
":subjectNummer AND t.subject.key.subjectTypeCode = :subjectTypeCode", Topic.class);
query.setParameter("subjectNummer", s.getKey().getSubjectNummer());
query.setParameter("subjectTypeCode", s.getKey().getSubjectTypeCode());
Topic topic = query.getSingleResult();
verifyResults(topic, s);
}
/*
* This test results in an EXPECTED exception:
*
* ArgumentException: An error occurred while parsing the query filter 'select distinct g from Topic g where
* t.subject.key = :subjectKey'. Error message: JPQL query does not support conditional expression over embeddable
* class. JPQL string: "key".
*
* The message in the exception tells it all. Per the spec, you can not do a compare on embeddables.
*/
public void testFindUsingJPQLEqualsOnSubjectKey() {
try {
em.createQuery("select distinct t from Topic t where t.subject.key = :subjectKey");
} catch (Throwable t) {
// An exception is EXPECTED!
Assert.assertTrue(t.getMessage().contains("does not support conditional expression"));
}
}
/*
* Prior to the fix #1 (see notes above), this fails on OJ with:
*
* java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey cannot be cast to
* [Ljava.lang.Object;]
* at org.apache.openjpa.jdbc.kernel.exps.Param.appendTo(Param.java:149)
*
* With fix #1, this test works fine.
*/
public void testFindSubjectUsingJPQLEqualsOnSubject() {
Subject s = createSubject();
TypedQuery<Subject> query = em.createQuery("select s from Subject s where s = :subject", Subject.class);
query.setParameter("subject", s);
Subject s2 = query.getSingleResult();
verifySubject(s, s2);
}
/*
* Prior to the fix #1 (see notes above), this fails on OJ with:
*
* java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey cannot be cast to
* [Ljava.lang.Object;]
* at org.apache.openjpa.jdbc.kernel.exps.Param.appendTo(Param.java:149)
*
* With fix #1, this test works fine.
*/
public void testFindUsingNamedQuery() {
Subject s = createSubject();
TypedQuery<Topic> q = em.createNamedQuery("bySubject", Topic.class);
q.setParameter("subject", s);
Topic topic = q.getSingleResult();
verifyResults(topic, s);
}
/*
* Prior to the fix #1 (see notes above), this fails on OJ with:
*
* java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey cannot be cast to
* [Ljava.lang.Object;]
* at org.apache.openjpa.jdbc.kernel.exps.Param.appendTo(Param.java:149)
*
* With fix #1, this test works fine.
*/
public void testFindUsingJPQLEqualsOnSubject() {
Subject s = createSubject();
TypedQuery<Topic> query =
em.createQuery("select distinct t from Topic t where t.subject = :subject", Topic.class);
query.setParameter("subject", s);
Topic topic = query.getSingleResult();
verifyResults(topic, s);
}
/*
* Prior to the fix #1 (see notes above), this fails on OJ with:
*
* java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey cannot be cast to
* [Ljava.lang.Object;]
* at org.apache.openjpa.jdbc.kernel.exps.Param.appendTo(Param.java:149)
*
* With fix #1, the CCEx is avoided/resolved. However, we then got an incorrectly generated SQL as follows:
*
* SELECT t0.SUBJECTNUMMER, t0.CODE_SUBJECTTYPE FROM SUBJECT t0 WHERE (t0.SUBJECTNUMMER = ?)
* optimize for 1 row [params=(int) 1]
*
* Notice that 't0.CODE_SUBJECTTYPE' is missing. With fix #2.1 this issue is resolved.
*
* The thing to note (which is different than the test 'findSubjectUsingCriteriaBuilderEquals' below) is that
* the Subject is treated as an OpenJPA 'Parameter' (see changes in EqualExpression). The test
* 'findSubjectUsingCriteriaBuilderEquals' below causes the Subject to be treated as a Lit. There is
* a bug in both cases, with an additional bug for the 'Lit' case.
*/
public void testFindSubjectUsingCriteriaBuilderEqualsAndParameter() {
Subject s = createSubject();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Subject> cq = builder.createQuery(Subject.class);
Root<Subject> subjectRoot = cq.from(Subject.class);
cq.select(subjectRoot);
ParameterExpression<Subject> param1 = builder.parameter(Subject.class, "subject");
Predicate subjectPredicate = builder.equal(subjectRoot, param1);
cq.where(subjectPredicate);
TypedQuery<Subject> query = em.createQuery(cq);
query.setParameter("subject", s);
Subject s2 = query.getSingleResult();
verifySubject(s, s2);
}
/*
* Prior to the fix #1 (see notes above), this fails on OJ with:
*
* Caused by: java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey
* cannot be cast to [Ljava.lang.Object;
* at org.apache.openjpa.jdbc.kernel.exps.Lit.appendTo(Lit.java:120)
*
* Notice the exception this time is in 'Lit'. Previous CCEx for the other tests have been in Param.
* With fix #1, the CCEx is avoided/resolved. However, we then got an incorrectly generated SQL as follows:
*
* SELECT t0.SUBJECTNUMMER, t0.CODE_SUBJECTTYPE FROM SUBJECT t0 WHERE (t0.SUBJECTNUMMER = ??)
* optimize for 1 row [params=(int) 1, (String) Type]
*
* Notice that 't0.CODE_SUBJECTTYPE' is missing, and there are two parameter markers. With fix #2.1 and
* #2.2, this issue is resolved.
*
* The other thing to note (which is different than the test 'findSubjectUsingCriteriaBuilderEqualsAndParameter'
* above) is that the Subject is treated as an OpenJPA 'Lit' (see changes in EqualExpression). The test
* 'findSubjectUsingCriteriaBuilderEqualsAndParameter' above treats the Subject as a Parameter. There is a bug in
* both cases, with an additional bug for the 'Lit' case.
*/
public void testFindSubjectUsingCriteriaBuilderEquals() {
Subject s = createSubject();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Subject> cq = builder.createQuery(Subject.class);
Root<Subject> subjectRoot = cq.from(Subject.class);
cq.select(subjectRoot);
Predicate subjectPredicate = builder.equal(subjectRoot, s);
// Before the fix of JIRA OPENJPA-2631, the following was a way to fix/work around the issue, in
// other words, selecting the individual fields of the PK worked fine....I'll leave this here but
// commented out for history sake:
// Predicate subjectPredicate1 = builder.equal(subjectRoot.get(Subject_.key).get(SubjectKey_.subjectNummer),
// subject.getKey().getSubjectNummer());
// Predicate subjectPredicate2 = builder.equal(subjectRoot.get(Subject_.key).get(SubjectKey_.subjectTypeCode),
// subject.getKey().getSubjectTypeCode());
// Predicate subjectPredicate = builder.and(subjectPredicate1,subjectPredicate2);
cq.where(subjectPredicate);
TypedQuery<Subject> query = em.createQuery(cq);
Subject s2 = query.getSingleResult();
verifySubject(s, s2);
}
/*
* For comparison, this test does the same CriteriaBuilder code on Topic (an entity
* with a single PK) as was done in the previous test to make sure it works.
*/
public void testFindTopicUsingCriteriaBuilderEquals() {
Topic t = new Topic();
t.setId(5);
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Topic> cq = builder.createQuery(Topic.class);
Root<Topic> topicRoot = cq.from(Topic.class);
cq.select(topicRoot);
Predicate topicPredicate = builder.equal(topicRoot, t);
cq.where(topicPredicate);
TypedQuery<Topic> query = em.createQuery(cq);
Topic topic = query.getSingleResult();
verifyResults(topic, createSubject());
}
/*
* Prior to the fix #1 (see notes above), this fails on OJ with:
*
* Caused by: java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey
* cannot be cast to [Ljava.lang.Object;
* at org.apache.openjpa.jdbc.kernel.exps.Lit.appendTo(Lit.java:120)
*
* Notice the exception this time is in 'Lit'. Previous CCEx for the other tests have been in Param.
* With fix #1, the CCEx is avoided/resolved. However, we then got an incorrectly generated SQL as follows:
*
* SELECT t0.ID, t1.SUBJECTNUMMER, t1.CODE_SUBJECTTYPE FROM TOPIC t0 LEFT OUTER JOIN SUBJECT t1 ON
* t0.SUBJECT_SUBJECTNUMMER =
* t1.SUBJECTNUMMER AND t0.SUBJECT_CODE_SUBJECTTYPE = t1.CODE_SUBJECTTYPE WHERE (t0.SUBJECT_SUBJECTNUMMER = ??)
* optimize for 1 row [params=(int) 1, (String) Type]
*
* Notice that 't0.CODE_SUBJECTTYPE' is missing, and there are two parameter markers. With fix #2.1 and
* #2.2, this issue is resolved.
*/
public void testFindUsingCriteriaBuilderEquals() {
Subject s = createSubject();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Topic> cq = builder.createQuery(Topic.class);
Root<Topic> topic = cq.from(Topic.class);
cq.select(topic).distinct(true);
Predicate topicPredicate = builder.equal(topic.get("subject"), s);
cq.where(topicPredicate);
TypedQuery<Topic> query = em.createQuery(cq);
Topic t = query.getSingleResult();
verifyResults(t, s);
}
/*
* Prior to the fix #1 (see notes above), this fails on OJ with:
*
* Caused by: java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey
* cannot be cast to [Ljava.lang.Object;
* at org.apache.openjpa.jdbc.kernel.exps.InExpression.orContains(InExpression.java:178)
*
* Notice this time the CCEx occurs in InExpression. With fix #1 the issue is resolved.
*/
public void testFindUsingJPQLInClauseOnSubject() {
Subject s = createSubject();
SubjectKey key = new SubjectKey(999, "Bla");
Subject s2 = new Subject();
s2.setKey(key);
List<Subject> subjectList = new ArrayList<Subject>();
subjectList.add(s);
subjectList.add(s2);
TypedQuery<Topic> query = em.createQuery(
"select distinct t from Topic t where t.subject in :subjectList", Topic.class);
query.setParameter("subjectList", subjectList);
Topic t = query.getSingleResult();
verifyResults(t, s);
}
/*
* Prior to the fix #1 (see notes above), this fails on OJ with:
*
* Caused by: java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey
* cannot be cast to [Ljava.lang.Object;
* at org.apache.openjpa.jdbc.kernel.exps.Lit.appendTo(Lit.java:120)
*
* Notice the exception this time is in 'Lit'. Previous CCEx for the other tests have been in Param.
*
* With fix #1, the CCEx is avoided/resolved. However, we then got an incorrectly generated SQL as follows:
*
* SELECT t0.ID, t1.SUBJECTNUMMER, t1.CODE_SUBJECTTYPE FROM TOPIC t0 LEFT OUTER JOIN SUBJECT t1 ON
* t0.SUBJECT_SUBJECTNUMMER =
* t1.SUBJECTNUMMER AND t0.SUBJECT_CODE_SUBJECTTYPE = t1.CODE_SUBJECTTYPE WHERE (t0.SUBJECT_SUBJECTNUMMER = ??)
* optimize for 1 row [params=(int) 1, (String) Type]
*
* Notice that 't0.CODE_SUBJECTTYPE' is missing, and there are two parameter markers. With fix #2.1 and
* #2.2, this issue is resolved.
*/
public void testFindUsingCriteriaBuilderInClauseOnSubject() {
Subject s = createSubject();
SubjectKey key = new SubjectKey(999, "Bla");
Subject s2 = new Subject();
s2.setKey(key);
List<Subject> subjectList = new ArrayList<Subject>();
subjectList.add(s);
subjectList.add(s2);
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Topic> cq = builder.createQuery(Topic.class);
Root<Topic> topic = cq.from(Topic.class);
cq.select(topic).distinct(true);
Predicate subjectInSubjectList = topic.get(Topic_.subject).in(subjectList);
cq.where(subjectInSubjectList);
TypedQuery<Topic> query = em.createQuery(cq);
Topic t = query.getSingleResult();
verifyResults(t, s);
}
/*
* This test works fine with or without the fixes. This was added as a comparison to the case
* where an @EmbeddedId is used. In other words, this query selects a Subject which uses
* a @IdClass (still considered an embeddable in OpenJPA).
*/
public void testFindUsingJPQLEqualsOnSubjectWithIdClass() {
SubjectWithIdClass s = new SubjectWithIdClass();
s.setSubjectNummer(1);
s.setSubjectTypeCode("Type");
TypedQuery<SubjectWithIdClass> query =
em.createQuery("select s from SubjectWithIdClass s where s = :subject", SubjectWithIdClass.class);
query.setParameter("subject", s);
SubjectWithIdClass s2 = query.getSingleResult();
Assert.assertNotNull(s2);
Assert.assertEquals(s.getSubjectNummer(), s2.getSubjectNummer());
Assert.assertEquals(s.getSubjectTypeCode(), s2.getSubjectTypeCode());
}
/*
* For this test, the CCEx is actually never hit with or without the fixes. However, incorrect
* SQL was generated as follows:
*
* SELECT t0.SUBJECTNUMMER, t0.CODE_SUBJECTTYPE FROM SUBJECT2 t0 WHERE
* (t0.SUBJECTNUMMER = ??) optimize for 1 row [params=(int) 1, (String) Type]}
*
* Notice that 't0.CODE_SUBJECTTYPE' is missing, and there is an extra parameter marker. With
* fix #2.1 and #2.2 this issue is resolved.
*/
public void testFindUsingCriteriaBuilderOnSubjectWithIdClass() {
SubjectWithIdClass s = new SubjectWithIdClass();
s.setSubjectNummer(1);
s.setSubjectTypeCode("Type");
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<SubjectWithIdClass> cq = builder.createQuery(SubjectWithIdClass.class);
Root<SubjectWithIdClass> subjectRoot = cq.from(SubjectWithIdClass.class);
cq.select(subjectRoot);
Predicate subjectPredicate = builder.equal(subjectRoot, s);
cq.where(subjectPredicate);
TypedQuery<SubjectWithIdClass> query = em.createQuery(cq);
SubjectWithIdClass s2 = query.getSingleResult();
Assert.assertNotNull(s2);
Assert.assertEquals(s.getSubjectNummer(), s2.getSubjectNummer());
Assert.assertEquals(s.getSubjectTypeCode(), s2.getSubjectTypeCode());
}
private void createData(){
Subject s = new Subject();
SubjectKey sk = new SubjectKey();
sk.setSubjectNummer(1);
sk.setSubjectType("Type2");
s.setKey(sk);
em.persist(s);
s = new Subject();
sk = new SubjectKey();
sk.setSubjectNummer(1);
sk.setSubjectType("Type");
s.setKey(sk);
em.persist(s);
Topic t = new Topic();
t.setId(5);
t.setSubject(s);
em.persist(t);
SubjectWithIdClass swic = new SubjectWithIdClass();
swic.setSubjectNummer(1);
swic.setSubjectTypeCode("Type");
em.persist(swic);
swic = new SubjectWithIdClass();
swic.setSubjectNummer(1);
swic.setSubjectTypeCode("Type2");
em.persist(swic);
em.flush();
}
private Subject createSubject() {
SubjectKey key = new SubjectKey(1, "Type");
Subject result = new Subject();
result.setKey(key);
return result;
}
public void verifyResults(Topic topic, Subject s) {
Assert.assertNotNull(topic);
Assert.assertEquals(new Integer(5), topic.getId());
Subject s2 = topic.getSubject();
verifySubject(s, s2);
}
public void verifySubject(Subject expected, Subject actual) {
Assert.assertNotNull(expected);
Assert.assertEquals(expected.getKey().getSubjectNummer(), actual.getKey().getSubjectNummer());
Assert.assertEquals(expected.getKey().getSubjectTypeCode(), actual.getKey().getSubjectTypeCode());
}
public void tearDown() {
if (tx != null && tx.isActive()) {
tx.rollback();
tx = null;
}
if (em != null && em.isOpen()) {
em.close();
em = null;
}
}
}