/*
* 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.jdbc.update;
import javax.persistence.EntityManager;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.persistence.test.CombinatorialPersistenceTestCase;
/**
* Tests for SQL statement ordering capabilities of different update strategies
* for a Parent-Child model against different physical database constraints.
*
* SQL statement ordering is influenced by
* 1. In-memory schema model: The in-memory schema model can be aware of logical
* or physical foreign keys.
* a) This is configured by <code>jdbc.SchemaFactory</code> property setting
* to <code>native(ForeignKeys=true|false)</code> which makes OpenJPA to
* read physical foreign key information from database
* b) @ForeignKey annotation on the relation -- OpenJPA then considers
* logical foreign key
*
* 2. Physical Schema: The database schema can be defined with physical
* foreign keys. This is configured by <code>jdbc.MappingDefaults</code>
* property setting to <code>ForeignKeyDeleteAction</code>
*
* 3. Update Strategy: the update manager is configured by
* <code>jdbc.UpdateManager</code>
*
* 4. Order of persistence operation: The order in which the application calls
* persistence operations such as persist() or remove(). In this test, we
* control this by PersistOrder enum. This application order is maintained
* if the update manager is set to <code>'operation-order'</code>.
*
*
* This test case also demonstrates how to write a test case that runs with
* multiple combination of configurations. Each configuration discussed above
* has multiple possible values and testing all possible combination can be
* an arduous task. The {@link CombinatorialPersistenceTestCase combinatorial}
* test case utility helps to auto-generate these multiple configurations and
* execute the same test with all the combinations.
*
* @author Pinaki Poddar
*
*/
public class TestParentChild extends CombinatorialPersistenceTestCase {
// Each of these property keys can take multiple possible values
private static String Key_UpdateManager = "openjpa.jdbc.UpdateManager";
private static String Key_SchemaFactory = "openjpa.jdbc.SchemaFactory";
private static String Key_MappingDefaults = "openjpa.jdbc.MappingDefaults";
private static String Key_PersistOrder = "persist-order";
private static String[] Option_MappingDefaults = {
"ForeignKeyDeleteAction=restrict, JoinForeignKeyDeleteAction=restrict",
"ForeignKeyDeleteAction=none, JoinForeignKeyDeleteAction=none" };
private static String[] Option_SchemaFactory = {
"native(ForeignKeys=false)",
"native(ForeignKeys=true)" };
private static String[] Option_UpdateManager = {
"operation-order",
"constraint" };
private static enum PersistOrder {
IMPLICIT_CASCADE,
CHILD_THEN_PARENT,
PARENT_THEN_CHILD
};
// The options are added in a static block, so that we can count on
// total number of combinations before the test is set up.
static {
getHelper().addOption(Key_MappingDefaults, Option_MappingDefaults);
getHelper().addOption(Key_SchemaFactory, Option_SchemaFactory);
getHelper().addOption(Key_UpdateManager, Option_UpdateManager);
// The last argument tells that this is a runtime option. So the
// values are included to generate combinations but are excluded
// from generating OpenJPA configuration.
getHelper().addOption(Key_PersistOrder, PersistOrder.values(), true);
}
public void setUp() {
// The options can also be added in setup() as well but then
// coutTestCase() will only record test methods and not multiply them
// with number of configuration combinations the same tests will run.
getHelper().addOption(Key_MappingDefaults, Option_MappingDefaults);
getHelper().addOption(Key_SchemaFactory, Option_SchemaFactory);
getHelper().addOption(Key_UpdateManager, Option_UpdateManager);
getHelper().addOption(Key_PersistOrder, PersistOrder.values(), true);
sql.clear();
super.setUp(DROP_TABLES, Parent.class, Child.class);
}
/**
* This test will run in 2*2*2*3 = 24 times with different configurations.
*/
public void testInsert() {
Parent parent = createData(getPersistOrder(), 3);
validateData(parent.getId(), 3);
// verification can be challenging under multiple configuration options
// see the methods as exemplars how verification can vary based on
// configuration.
assertLogicalOrPhysicalForeignKey();
assertPhysicalForeignKeyCreation();
}
Parent createData(PersistOrder order, int nChild) {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Parent parent = new Parent();
parent.setName("parent");
for (int i = 1; i <= nChild; i++)
parent.newChild("Child-" + i);
switch (order) {
case IMPLICIT_CASCADE:
em.persist(parent);
break;
case CHILD_THEN_PARENT:
for (Child child : parent.getChildren()) {
em.persist(child);
}
em.persist(parent);
break;
case PARENT_THEN_CHILD:
em.persist(parent);
for (Child child : parent.getChildren()) {
em.persist(child);
}
break;
default:
throw new RuntimeException("Bad order " + order);
}
em.getTransaction().commit();
em.clear();
return parent;
}
void validateData(Object pid, int childCount) {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Parent parent = em.find(Parent.class, pid);
assertNotNull(parent);
assertEquals(childCount, parent.getChildren().size());
em.getTransaction().rollback();
}
/**
* Asserts that foreign key constraint will be defined on the database
* for certain combinations of configurations.
*/
void assertPhysicalForeignKeyCreation() {
String regex = "ALTER TABLE .* ADD FOREIGN KEY \\(PARENT_ID\\) "
+ "REFERENCES Parent \\(id\\)(\\sDEFERRABLE)?";
if (getMappingDefaults().contains("restrict")) {
assertSQL(regex);
} else {
assertNotSQL(regex);
}
}
/**
* Asserts that update SQL will be issued to set the foreign key value
* after the insert under some configuration.
*/
void assertPostInsertUpdate() {
if (getPersistOrder().equals(PersistOrder.CHILD_THEN_PARENT)
&& getMappingDefaults().contains("restrict")) {
assertSQL("UPDATE .* SET PARENT_ID .* WHERE .*");
}
}
/**
* Asserts that foreign key will be logical or physical under different
* combination of configuration.
*/
void assertLogicalOrPhysicalForeignKey() {
ForeignKey fk = getChildParentForeignKey();
boolean physicalKeyExists = getMappingDefaults().contains("restrict");
boolean keyRead = getSchemaFactory().contains("ForeignKeys=true");
if (physicalKeyExists && keyRead)
assertFalse(fk.isLogical());
else if (keyRead)
assertTrue(fk.isLogical());
}
ForeignKey getChildParentForeignKey() {
MetaDataRepository repos = emf.getConfiguration()
.getMetaDataRepositoryInstance();
ClassMetaData child = repos.getCachedMetaData(Child.class);
FieldMapping parent = (FieldMapping) child.getField("parent");
return parent.getForeignKey();
}
PersistOrder getPersistOrder() {
return (PersistOrder) getHelper().getOption(Key_PersistOrder);
}
String getMappingDefaults() {
return getHelper().getOptionAsString(Key_MappingDefaults);
}
String getSchemaFactory() {
return getHelper().getOptionAsString(Key_SchemaFactory);
}
}