/*****************************************************************
* 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.cayenne.access;
import org.apache.cayenne.Cayenne;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.query.SQLTemplate;
import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.query.SortOrder;
import org.apache.cayenne.test.jdbc.TableHelper;
import org.apache.cayenne.testdo.inheritance_people.AbstractPerson;
import org.apache.cayenne.testdo.inheritance_people.Address;
import org.apache.cayenne.testdo.inheritance_people.ClientCompany;
import org.apache.cayenne.testdo.inheritance_people.CustomerRepresentative;
import org.apache.cayenne.testdo.inheritance_people.Department;
import org.apache.cayenne.testdo.inheritance_people.Employee;
import org.apache.cayenne.testdo.inheritance_people.Manager;
import org.apache.cayenne.testdo.inheritance_people.PersonNotes;
import org.apache.cayenne.unit.di.DataChannelInterceptor;
import org.apache.cayenne.unit.di.UnitTestClosure;
import org.apache.cayenne.unit.di.server.PeopleProjectCase;
import org.junit.Before;
import org.junit.Test;
import java.sql.Types;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
public class SingleTableInheritanceIT extends PeopleProjectCase {
@Inject
private DataContext context;
@Inject
private DataContext context2;
@Inject
private DataChannelInterceptor queryBlocker;
private TableHelper tPerson;
private TableHelper tAddress;
private TableHelper tClientCompany;
private TableHelper tDepartment;
@Before
public void setUp() {
tAddress = new TableHelper(dbHelper, "ADDRESS");
tAddress.setColumns("ADDRESS_ID", "CITY", "PERSON_ID");
tClientCompany = new TableHelper(dbHelper, "CLIENT_COMPANY");
tClientCompany.setColumns("CLIENT_COMPANY_ID", "NAME");
tDepartment = new TableHelper(dbHelper, "DEPARTMENT");
tDepartment.setColumns("DEPARTMENT_ID", "NAME");
tPerson = new TableHelper(dbHelper, "PERSON").setColumns("PERSON_ID", "NAME", "PERSON_TYPE", "SALARY",
"CLIENT_COMPANY_ID", "DEPARTMENT_ID").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.CHAR,
Types.FLOAT, Types.INTEGER, Types.INTEGER);
}
private void create2PersonDataSet() throws Exception {
tPerson.insert(1, "E1", "EE", null, null, null);
tPerson.insert(2, "E2", "EM", null, null, null);
}
private void create5PersonDataSet() throws Exception {
tPerson.insert(1, "E1", "EE", null, null, null);
tPerson.insert(2, "E2", "EM", null, null, null);
tPerson.insert(3, "E3", "EE", null, null, null);
tPerson.insert(4, "E4", "EM", null, null, null);
tPerson.insert(5, "E5", "EE", null, null, null);
}
private void createSelectDataSet() throws Exception {
tPerson.insert(1, "e1", "EE", 20000, null, null);
tPerson.insert(2, "e2", "EE", 25000, null, null);
tPerson.insert(3, "e3", "EE", 28000, null, null);
tPerson.insert(4, "m1", "EM", 30000, null, null);
tPerson.insert(5, "m2", "EM", 40000, null, null);
tClientCompany.insert(1, "Citibank");
tPerson.insert(6, "c1", "C", null, 1, null);
}
private void createEmployeeAddressDataSet() throws Exception {
tPerson.insert(1, "e1", "EE", 20000, null, null);
tAddress.insert(1, "New York", 1);
}
private void createManagerAddressDataSet() throws Exception {
tPerson.insert(4, "m1", "EM", 30000, null, null);
tAddress.insert(1, "New York", 4);
}
private void createRepCompanyDataSet() throws Exception {
tClientCompany.insert(1, "Citibank");
tPerson.insert(6, "c1", "C", null, 1, null);
}
private void createDepartmentEmployeesDataSet() throws Exception {
tDepartment.insert(1, "Accounting");
tPerson.insert(7, "John", "EE", 25000, null, 1);
tPerson.insert(8, "Susan", "EE", 50000, null, 1);
tPerson.insert(9, "Kelly", "EM", 100000, null, 1);
}
@Test
public void testMatchingOnSuperAttributes() throws Exception {
create2PersonDataSet();
// fetch on leaf, but match on a super attribute
SelectQuery select = new SelectQuery(Manager.class);
select.andQualifier(AbstractPerson.NAME.eq("E2"));
List<Manager> results = context.performQuery(select);
assertEquals(1, results.size());
assertEquals("E2", results.get(0).getName());
}
@Test
public void testMatchingOnSuperAttributesWithPrefetch() throws Exception {
create2PersonDataSet();
// fetch on leaf, but match on a super attribute
SelectQuery select = new SelectQuery(Employee.class);
select.addPrefetch(Employee.TO_DEPARTMENT.disjoint());
select.andQualifier(AbstractPerson.NAME.eq("E2"));
List<Manager> results = context.performQuery(select);
assertEquals(1, results.size());
assertEquals("E2", results.get(0).getName());
}
@Test
public void testPaginatedQueries() throws Exception {
create5PersonDataSet();
SelectQuery select = new SelectQuery(AbstractPerson.class);
select.addOrdering(
"db:" + AbstractPerson.PERSON_ID_PK_COLUMN,
SortOrder.ASCENDING);
select.setPageSize(3);
List<AbstractPerson> results = context.performQuery(select);
assertEquals(5, results.size());
assertTrue(results.get(0) instanceof Employee);
// this is where things would blow up per CAY-1142
assertTrue(results.get(1) instanceof Manager);
assertTrue(results.get(3) instanceof Manager);
assertTrue(results.get(4) instanceof Employee);
}
@Test
public void testRelationshipToAbstractSuper() {
context
.performGenericQuery(new SQLTemplate(
AbstractPerson.class,
"INSERT INTO PERSON (PERSON_ID, NAME, PERSON_TYPE) VALUES (1, 'AA', 'EE')"));
context.performGenericQuery(new SQLTemplate(
PersonNotes.class,
"INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (1, 'AA', 1)"));
PersonNotes note = Cayenne.objectForPK(context, PersonNotes.class, 1);
assertNotNull(note);
assertNotNull(note.getPerson());
assertTrue(note.getPerson() instanceof Employee);
}
@Test
public void testRelationshipAbstractFromSuperPrefetchingJoint() {
context
.performGenericQuery(new SQLTemplate(
AbstractPerson.class,
"INSERT INTO PERSON (PERSON_ID, NAME, PERSON_TYPE) VALUES (3, 'AA', 'EE')"));
context.performGenericQuery(new SQLTemplate(
PersonNotes.class,
"INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (3, 'AA', 3)"));
context.performGenericQuery(new SQLTemplate(
PersonNotes.class,
"INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (4, 'BB', 3)"));
SelectQuery query = new SelectQuery(AbstractPerson.class);
query.addPrefetch(AbstractPerson.NOTES.joint());
final AbstractPerson person = (AbstractPerson) Cayenne.objectForQuery(
context,
query);
assertTrue(person instanceof Employee);
queryBlocker.runWithQueriesBlocked(new UnitTestClosure() {
public void execute() {
assertEquals(2, person.getNotes().size());
String[] names = new String[2];
names[0] = person.getNotes().get(0).getNotes();
names[1] = person.getNotes().get(1).getNotes();
List<String> nameSet = Arrays.asList(names);
assertTrue(nameSet.contains("AA"));
assertTrue(nameSet.contains("BB"));
}
});
}
@Test
public void testRelationshipAbstractFromSuperPrefetchingDisjoint() {
context
.performGenericQuery(new SQLTemplate(
AbstractPerson.class,
"INSERT INTO PERSON (PERSON_ID, NAME, PERSON_TYPE) VALUES (3, 'AA', 'EE')"));
context.performGenericQuery(new SQLTemplate(
PersonNotes.class,
"INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (3, 'AA', 3)"));
context.performGenericQuery(new SQLTemplate(
PersonNotes.class,
"INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (4, 'BB', 3)"));
SelectQuery query = new SelectQuery(AbstractPerson.class);
query.addPrefetch(AbstractPerson.NOTES.disjoint());
final AbstractPerson person = (AbstractPerson) Cayenne.objectForQuery(
context,
query);
assertTrue(person instanceof Employee);
queryBlocker.runWithQueriesBlocked(new UnitTestClosure() {
public void execute() {
assertEquals(2, person.getNotes().size());
String[] names = new String[2];
names[0] = person.getNotes().get(0).getNotes();
names[1] = person.getNotes().get(1).getNotes();
List<String> nameSet = Arrays.asList(names);
assertTrue(nameSet.contains("AA"));
assertTrue(nameSet.contains("BB"));
}
});
}
@Test
public void testRelationshipAbstractToSuperPrefetchingDisjoint() {
context
.performGenericQuery(new SQLTemplate(
AbstractPerson.class,
"INSERT INTO PERSON (PERSON_ID, NAME, PERSON_TYPE) VALUES (2, 'AA', 'EE')"));
context.performGenericQuery(new SQLTemplate(
PersonNotes.class,
"INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (2, 'AA', 2)"));
context.performGenericQuery(new SQLTemplate(
PersonNotes.class,
"INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (3, 'BB', 2)"));
SelectQuery query = new SelectQuery(PersonNotes.class);
query.addPrefetch(PersonNotes.PERSON.disjoint());
query.addOrdering(PersonNotes.NOTES.asc());
List<PersonNotes> notes = context.performQuery(query);
assertEquals(2, notes.size());
final PersonNotes note = notes.get(0);
queryBlocker.runWithQueriesBlocked(new UnitTestClosure() {
public void execute() {
assertEquals("AA", note.getPerson().getName());
}
});
}
@Test
public void testRelationshipAbstractToSuperPrefetchingJoint() {
context
.performGenericQuery(new SQLTemplate(
AbstractPerson.class,
"INSERT INTO PERSON (PERSON_ID, NAME, PERSON_TYPE) VALUES (3, 'AA', 'EE')"));
context.performGenericQuery(new SQLTemplate(
PersonNotes.class,
"INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (3, 'AA', 3)"));
SelectQuery query = new SelectQuery(PersonNotes.class);
query.addPrefetch(PersonNotes.PERSON.joint());
final PersonNotes note = (PersonNotes) Cayenne.objectForQuery(context, query);
queryBlocker.runWithQueriesBlocked(new UnitTestClosure() {
public void execute() {
assertEquals("AA", note.getPerson().getName());
}
});
}
@Test
public void testSave() throws Exception {
ClientCompany company = context.newObject(ClientCompany.class);
company.setName("Boeing");
CustomerRepresentative rep = context.newObject(CustomerRepresentative.class);
rep.setName("Joe Schmoe");
rep.setToClientCompany(company);
rep.setPersonType("C");
Employee employee = context.newObject(Employee.class);
employee.setName("Our Joe Schmoe");
employee.setPersonType("E");
context.commitChanges();
context.invalidateObjects(company, rep, employee);
SelectQuery query = new SelectQuery(CustomerRepresentative.class);
List<?> reps = context2.performQuery(query);
assertEquals(1, reps.size());
assertEquals(1, countObjectOfClass(reps, CustomerRepresentative.class));
}
/**
* Tests that to-one relationship produces correct subclass.
*/
@Test
public void testEmployeeAddress() throws Exception {
createEmployeeAddressDataSet();
List<?> addresses = context.performQuery(new SelectQuery(Address.class));
assertEquals(1, addresses.size());
Address address = (Address) addresses.get(0);
assertSame(Employee.class, address.getToEmployee().getClass());
}
/**
* Tests that to-one relationship produces correct subclass.
*/
@Test
public void testManagerAddress() throws Exception {
createManagerAddressDataSet();
List<?> addresses = context.performQuery(new SelectQuery(Address.class));
assertEquals(1, addresses.size());
Address address = (Address) addresses.get(0);
Employee e = address.getToEmployee();
assertSame(Manager.class, e.getClass());
}
@Test
public void testCAY592() throws Exception {
createManagerAddressDataSet();
List<?> addresses = context.performQuery(new SelectQuery(Address.class));
assertEquals(1, addresses.size());
Address address = (Address) addresses.get(0);
Employee e = address.getToEmployee();
// CAY-592 - make sure modification of the address in a parallel context
// doesn't mess up the Manager
e = (Employee) Cayenne.objectForPK(context2, e.getObjectId());
address = e.getAddresses().get(0);
assertSame(e, address.getToEmployee());
address.setCity("XYZ");
assertSame(e, address.getToEmployee());
}
/**
* Tests that to-one relationship produces correct subclass.
*/
@Test
public void testRepCompany() throws Exception {
createRepCompanyDataSet();
List<?> companies = context.performQuery(new SelectQuery(ClientCompany.class));
assertEquals(1, companies.size());
ClientCompany company = (ClientCompany) companies.get(0);
List<?> reps = company.getRepresentatives();
assertEquals(1, reps.size());
assertSame(CustomerRepresentative.class, reps.get(0).getClass());
}
/**
* Tests that to-many relationship produces correct subclasses.
*/
@Test
public void testDepartmentEmployees() throws Exception {
createDepartmentEmployeesDataSet();
List<?> departments = context.performQuery(new SelectQuery(Department.class));
assertEquals(1, departments.size());
Department dept = (Department) departments.get(0);
List<?> employees = dept.getEmployees();
assertEquals(3, employees.size());
assertEquals(3, countObjectOfClass(employees, Employee.class));
assertEquals(1, countObjectOfClass(employees, Manager.class));
}
@Test
public void testSelectInheritanceResolving() throws Exception {
createSelectDataSet();
SelectQuery query = new SelectQuery(AbstractPerson.class);
List<?> abstractPpl = context.performQuery(query);
assertEquals(6, abstractPpl.size());
assertEquals(1, countObjectOfClass(abstractPpl, CustomerRepresentative.class));
assertEquals(5, countObjectOfClass(abstractPpl, Employee.class));
assertEquals(2, countObjectOfClass(abstractPpl, Manager.class));
}
/**
* Returns a number of objects of a particular class and subclasses in the list.
*/
private int countObjectOfClass(List<?> objects, Class<?> aClass) {
int i = 0;
for (Object next : objects) {
if (aClass.isAssignableFrom(next.getClass())) {
i++;
}
}
return i;
}
}