diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/ForeignKey.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/ForeignKey.java index 40df305ea..50804ac50 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/ForeignKey.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/ForeignKey.java @@ -19,6 +19,7 @@ package org.apache.openjpa.jdbc.schema; import java.sql.DatabaseMetaData; +import java.sql.Connection; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -28,6 +29,7 @@ import org.apache.commons.lang.ObjectUtils; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.lib.util.StringDistance; import org.apache.openjpa.util.InvalidStateException; +import org.apache.openjpa.jdbc.sql.DBDictionary; /** * Represents a database foreign key; may be a logical key with no @@ -738,4 +740,51 @@ public class ForeignKey return false; return true; } + + /** + * Return the name of the foreignkey constraint as defined in the database. + */ + public String loadNameFromDB(DBDictionary dbdict, Connection conn) { + if( isLogical() || getTable() == null) + return null; + String retVal = null; + try{ + Schema schema = getTable().getSchema(); + ForeignKey[] fks = dbdict.getImportedKeys(conn.getMetaData(), + conn.getCatalog(), schema.getName(), + getTable().getName(), conn); + for ( int i=0; i< fks.length; i++) { + Table localtable = schema.getTable(fks[i].getTableName()); + Table pkTable = schema.getTable( + fks[i].getPrimaryKeyTableName()); + boolean addFK = false; + ForeignKey fkTemp = localtable.getForeignKey( + fks[i].getName()); + if( fkTemp == null) { + addFK=true; + fkTemp = localtable.addForeignKey( + fks[i].getName()); + fkTemp.setDeferred(fks[i].isDeferred()); + fkTemp.setDeleteAction(fks[i].getDeleteAction()); + } + if( ! fkTemp.containsColumn( + localtable.getColumn(fks[i].getColumnName()))) + fkTemp.join(localtable.getColumn(fks[i].getColumnName()), + pkTable.getColumn(fks[i].getPrimaryKeyColumnName())); + if( equalsForeignKey(fkTemp)) + { + if(addFK) + localtable.removeForeignKey(fkTemp); + retVal = fks[i].getName(); + break; + } + if(addFK) + localtable.removeForeignKey(fkTemp); + } + } catch(Exception ex){ + // TO DO -- It would be nice to log a warning here. + } + return retVal; + } + } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java index 8cdf00c6c..6b0db81f4 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java @@ -423,7 +423,7 @@ public class SchemaTool { } Table[] tableArray = (Table[]) tables.toArray(new Table[tables.size()]); String[] sql = _conf.getDBDictionaryInstance() - .getDeleteTableContentsSQL(tableArray); + .getDeleteTableContentsSQL(tableArray,_ds.getConnection()); if (!executeSQL(sql)) _log.warn(_loc.get("delete-table-contents")); } @@ -1083,7 +1083,7 @@ public class SchemaTool { */ public boolean dropForeignKey(ForeignKey fk) throws SQLException { - return executeSQL(_dict.getDropForeignKeySQL(fk)); + return executeSQL(_dict.getDropForeignKeySQL(fk,_ds.getConnection())); } /** diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java index b77a26707..55c3bc0a3 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java @@ -2173,33 +2173,36 @@ public class DBDictionary * Databases with more optimal ways of deleting the contents of several * tables should override this method. */ - public String[] getDeleteTableContentsSQL(Table[] tables) { - Collection sql = new ArrayList(); + public String[] getDeleteTableContentsSQL(Table[] tables,Connection conn) { + Collection sql = new ArrayList(); // collect and drop non-deferred physical restrict constraints, and // collect the DELETE FROM statements - Collection deleteSQL = new ArrayList(tables.length); - Collection restrictConstraints = new LinkedHashSet(); + Collection deleteSQL = new ArrayList(tables.length); + Collection restrictConstraints = + new LinkedHashSet(); for (int i = 0; i < tables.length; i++) { ForeignKey[] fks = tables[i].getForeignKeys(); for (int j = 0; j < fks.length; j++) { if (!fks[j].isLogical() && !fks[j].isDeferred() && fks[j].getDeleteAction() == ForeignKey.ACTION_RESTRICT) restrictConstraints.add(fks[j]); - String[] constraintSQL = getDropForeignKeySQL(fks[j]); - sql.addAll(Arrays.asList(constraintSQL)); } deleteSQL.add("DELETE FROM " + tables[i].getFullName()); } + for(ForeignKey fk : restrictConstraints) { + String[] constraintSQL = getDropForeignKeySQL(fk,conn); + sql.addAll(Arrays.asList(constraintSQL)); + } + // add the delete statements after all the constraint mutations sql.addAll(deleteSQL); // add the deleted constraints back to the schema - for (Iterator iter = restrictConstraints.iterator(); iter.hasNext(); ) { - String[] constraintSQL = - getAddForeignKeySQL((ForeignKey) iter.next()); + for (ForeignKey fk : restrictConstraints) { + String[] constraintSQL = getAddForeignKeySQL(fk); sql.addAll(Arrays.asList(constraintSQL)); } @@ -3448,9 +3451,16 @@ public class DBDictionary * Returns ALTER TABLE <table name> DROP CONSTRAINT * <fk name> by default. */ - public String[] getDropForeignKeySQL(ForeignKey fk) { - if (fk.getName() == null) - return new String[0]; + public String[] getDropForeignKeySQL(ForeignKey fk, Connection conn) { + if (fk.getName() == null) { + String[] retVal; + String fkName = fk.loadNameFromDB(this,conn); + retVal = (fkName == null) ? new String[0] : + new String[]{ "ALTER TABLE " + + getFullName(fk.getTable(), false) + + " DROP CONSTRAINT " + fkName }; + return retVal; + } return new String[]{ "ALTER TABLE " + getFullName(fk.getTable(), false) + " DROP CONSTRAINT " + fk.getName() }; diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/MySQLDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/MySQLDictionary.java index cd4429a6e..70735bf56 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/MySQLDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/MySQLDictionary.java @@ -222,10 +222,10 @@ public class MySQLDictionary return ret; } - public String[] getDeleteTableContentsSQL(Table[] tables) { + public String[] getDeleteTableContentsSQL(Table[] tables,Connection conn) { // mysql >= 4 supports more-optimal delete syntax if (!optimizeMultiTableDeletes) - return super.getDeleteTableContentsSQL(tables); + return super.getDeleteTableContentsSQL(tables,conn); else { StringBuffer buf = new StringBuffer(tables.length * 8); buf.append("DELETE FROM "); diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestForeignKeyCountViolation.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestForeignKeyCountViolation.java new file mode 100644 index 000000000..6398a2df2 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestForeignKeyCountViolation.java @@ -0,0 +1,183 @@ +/* + * 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.jdbc.kernel; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.persistence.EntityManager; +import javax.sql.DataSource; + +import org.apache.openjpa.jdbc.conf.JDBCConfiguration; +import org.apache.openjpa.jdbc.schema.ForeignKey; +import org.apache.openjpa.jdbc.schema.Table; +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +/** + * Test that makes sure the number of foreign keys are same on a table after + * calling loadNameFromDB method of ForeignKey class. + * + */ +public class TestForeignKeyCountViolation extends SingleEMFTestCase { + + private JDBCConfiguration _conf; + + public void setUp() { + super.setUp(EntityF.class, EntityG.class); + Map props = new HashMap(System.getProperties()); + _conf = (JDBCConfiguration) emf.getConfiguration(); + } + + public void testFKCount() throws SQLException { + EntityManager em = emf.createEntityManager(); + Table tableG = getMapping(EntityG.class).getTable(); + tableG.addForeignKey(); + int b4Count = tableG.getForeignKeys().length; + + EntityF f = new EntityF(); + f.setId(1); + + List listG = new ArrayList(); + EntityG g1 = new EntityG(); + g1.setId(1); + listG.add(g1); + g1.setEntityF(f); + + EntityG g2 = new EntityG(); + g2.setId(2); + listG.add(g2); + g2.setEntityF(f); + + EntityG g3 = new EntityG(); + g3.setId(3); + listG.add(g3); + g3.setEntityF(f); + + EntityG g4 = new EntityG(); + g4.setId(4); + listG.add(g4); + g4.setEntityF(f); + + f.setListG(listG); + em.getTransaction().begin(); + em.persist(f); + em.persist(g1); + em.persist(g2); + em.persist(g3); + em.persist(g4); + em.getTransaction().commit(); + + ForeignKey fks[] = tableG.getForeignKeys(); + + DataSource ds = (DataSource) _conf.getConnectionFactory(); + Connection c = ds.getConnection(_conf.getConnectionUserName(), + _conf.getConnectionPassword()); + + for (int i=0; i< fks.length; i++) { + fks[i].loadNameFromDB( + _conf.getDBDictionaryInstance(), c); + } + + assertEquals(b4Count, tableG.getForeignKeys().length); + em.close(); + } + + public void testFKNamefromDB()throws SQLException { + + EntityManager em = emf.createEntityManager(); + Table tableG = getMapping(EntityG.class).getTable(); + tableG.addForeignKey(); + + EntityF f = new EntityF(); + f.setId(1); + + List listG = new ArrayList(); + EntityG g1 = new EntityG(); + g1.setId(1); + listG.add(g1); + g1.setEntityF(f); + + EntityG g2 = new EntityG(); + g2.setId(2); + listG.add(g2); + g2.setEntityF(f); + + EntityG g3 = new EntityG(); + g3.setId(3); + listG.add(g3); + g3.setEntityF(f); + + EntityG g4 = new EntityG(); + g4.setId(4); + listG.add(g4); + g4.setEntityF(f); + + f.setListG(listG); + em.getTransaction().begin(); + em.persist(f); + em.persist(g1); + em.persist(g2); + em.persist(g3); + em.persist(g4); + em.getTransaction().commit(); + + DataSource ds = (DataSource) _conf.getConnectionFactory(); + Connection c = ds.getConnection(_conf.getConnectionUserName(), + _conf.getConnectionPassword()); + + ForeignKey fkfromDB[] = _conf.getDBDictionaryInstance().getImportedKeys( + c.getMetaData(), c.getCatalog() , tableG.getSchemaName() + , tableG.getName(), c); + + + ArrayList fkListfromDB = new ArrayList(); + ArrayList fkListfromTable = new ArrayList(); + + for (int i=0; i< fkfromDB.length; i++) { + fkListfromDB.add(fkfromDB[i].getName()); + } + + ForeignKey fks[] = tableG.getForeignKeys(); + for (int i=0; i< fks.length; i++) { + String fkNamefromDB =fks[i].loadNameFromDB( + _conf.getDBDictionaryInstance(), c); + if( fkNamefromDB != null) + fkListfromTable.add(fkNamefromDB); + } + + assertEquals(fkListfromDB.toArray().length, + fkListfromTable.toArray().length); + + Collections.sort(fkListfromTable); + Collections.sort(fkListfromDB); + + for(int i=0; i< fkListfromDB.size(); i++) + { + assertEquals(fkListfromDB.get(i),fkListfromTable.get(i)); + } + + em.close(); + } + +}