/***************************************************************** * 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; } }