You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by jr...@apache.org on 2009/11/03 23:49:30 UTC

svn commit: r832587 - in /openjpa/trunk/openjpa-jdbc/src/main: java/org/apache/openjpa/jdbc/schema/ java/org/apache/openjpa/jdbc/sql/ resources/org/apache/openjpa/jdbc/schema/

Author: jrbauer
Date: Tue Nov  3 22:48:43 2009
New Revision: 832587

URL: http://svn.apache.org/viewvc?rev=832587&view=rev
Log:
OPENJPA-1083 Fixed a mapping tool failure caused by the inability to discover and drop multi-column foreign key constraints.  Multi-column FK's were not getting dropped, but got added after clearing out the tables. Trying to add an existing FK caused an exception when using Oracle.

Modified:
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/ForeignKey.java
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java
    openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/schema/localizer.properties

Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/ForeignKey.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/ForeignKey.java?rev=832587&r1=832586&r2=832587&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/ForeignKey.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/ForeignKey.java Tue Nov  3 22:48:43 2009
@@ -26,6 +26,7 @@
 import java.util.List;
 
 import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.StringUtils;
 import org.apache.openjpa.jdbc.sql.DBDictionary;
 import org.apache.openjpa.lib.log.Log;
 import org.apache.openjpa.lib.util.Localizer;
@@ -753,7 +754,7 @@
             Schema schema = getTable().getSchema();
             ForeignKey[] fks = dbdict.getImportedKeys(conn.getMetaData(), 
                 conn.getCatalog(), schema.getName(), 
-                getTable().getName(), conn);
+                getTable().getName(), conn, false);
             for ( int i=0; i< fks.length; i++) {
                 Table localtable = schema.getTable(fks[i].getTableName());
                 Table pkTable = schema.getTable(
@@ -768,10 +769,33 @@
                     fkTemp.setDeferred(fks[i].isDeferred());
                     fkTemp.setDeleteAction(fks[i].getDeleteAction());
                 }
-                if( ! fkTemp.containsColumn(
-                    localtable.getColumn(fks[i].getColumnName(), dbdict)))
-                fkTemp.join(localtable.getColumn(fks[i].getColumnName(), dbdict), 
-                    pkTable.getColumn(fks[i].getPrimaryKeyColumnName(), dbdict));
+                if (fks[i].getColumns() == null || fks[i].getColumns().length == 0) {
+                    // Singular column foreign key 
+                    if( ! fkTemp.containsColumn(
+                        localtable.getColumn(fks[i].getColumnName(), dbdict)))
+                    fkTemp.join(localtable.getColumn(fks[i].getColumnName(), dbdict), 
+                        pkTable.getColumn(fks[i].getPrimaryKeyColumnName(), dbdict));
+                } else {
+                    // Add the multi-column foreign key, joining local and pk columns in
+                    // the temporary key
+                    Column[] locCols = fks[i].getColumns();
+                    Column[] pkCols = fks[i].getPrimaryKeyColumns();
+                    // Column counts must match
+                    if (locCols != null && pkCols != null & 
+                        locCols.length != pkCols.length) {
+                        Log log = dbdict.getLog();
+                        if (log.isTraceEnabled()) {
+                            log.trace(_loc.get("fk-column-mismatch"));
+                        }
+                    }
+                    for (int j = 0; j < locCols.length; j++) {
+                        if( ! fkTemp.containsColumn(
+                            localtable.getColumn(locCols[j].getName(), dbdict))) {
+                            fkTemp.join(localtable.getColumn(locCols[j].getName(), dbdict), 
+                                pkTable.getColumn(pkCols[j].getName(), dbdict));
+                        }
+                    }
+                }
                 if( equalsForeignKey(fkTemp))
                 {
                     if(addFK)
@@ -790,5 +814,96 @@
         }
         return retVal;
     }
+
+    /**
+     * Joins the column of a single column FK to this FK.
+     * @param fk
+     */
+    public void addColumn(ForeignKey fk) {
+        // Convert simple name based fk to a multi-column FK if necessary.
+        if (getColumns() == null || getColumns().length == 0) {
+            // If this FK is single column key, covert to a multi-column key
+            Column[] keyCols = createKeyColumns(this);
+            if (keyCols[0] != null && keyCols[1] != null) {
+                setPrimaryKeyColumnName(null);
+                setColumnName(null);
+                join(keyCols[0], keyCols[1]);
+            }
+        }
+        // Create the local and primary key columns from the fk and add them
+        // to this fk.
+        Column[] keyCols = createKeyColumns(fk);
+        if (keyCols[0] != null && keyCols[1] != null) {
+            join(keyCols[0], keyCols[1]);
+        }
+    }
     
+    /*
+     * Creates the local and primary key columns for a name-based fk. 
+     * @return Column[] element 0 is local column
+     *                  element 1 is the primary key in another table.
+     */
+    private static Column[] createKeyColumns(ForeignKey fk) {
+        Column fkCol = null;
+        if (!StringUtils.isEmpty(fk.getColumnName())) {
+            fkCol = new Column();
+            fkCol.setName(fk.getColumnName());
+            fkCol.setTableName(fk.getTableName());
+            fkCol.setSchemaName(fk.getSchemaName());
+        }
+        
+        Column pkCol = null;
+        if (!StringUtils.isEmpty(fk.getPrimaryKeyColumnName())) {
+            pkCol = new Column();
+            pkCol.setName(fk.getPrimaryKeyColumnName());
+            pkCol.setTableName(fk.getPrimaryKeyTableName());
+            pkCol.setSchemaName(fk.getPrimaryKeySchemaName());
+        }
+        return new Column[] { fkCol, pkCol };
+    }
+    
+    /*
+     * ForeignKey utility class which determines equality based upon the 
+     * non-column state of the keys.  
+     */
+    public static class FKMapKey {
+        
+        private ForeignKey _fk;
+
+        public FKMapKey(ForeignKey fk) {
+            _fk = fk;
+        }
+        public ForeignKey getFk() {
+            return _fk;
+        }
+
+        public int hashCode() {
+            return getFk().getName() != null ? getFk().getName().hashCode() : getFk().hashCode();
+        }
+        
+        public boolean equals(Object fkObj) {
+            if (fkObj == this) {
+                return true;
+            }
+            if (fkObj == null || !(fkObj instanceof FKMapKey)) {
+                return false;
+            }
+            ForeignKey fk = ((FKMapKey)fkObj).getFk();
+            if (getFk().getDeleteAction() != fk.getDeleteAction())
+                return false;
+            if (getFk().isDeferred() != fk.isDeferred())
+                return false;
+            if (!getFk().getName().equals(fk.getName())) {
+                return false;
+            }
+            // Assert PK table name and schema
+            if (!StringUtils.equals(getFk().getPrimaryKeySchemaName(), fk.getPrimaryKeySchemaName()) ||
+                !StringUtils.equals(getFk().getPrimaryKeyTableName(), fk.getPrimaryKeyTableName()) ||
+                !StringUtils.equals(getFk().getSchemaName(), fk.getSchemaName()) ||
+                !StringUtils.equals(getFk().getTableName(), fk.getTableName())) {
+                return false;
+            }
+            return true;
+        }
+    }
 }

Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java?rev=832587&r1=832586&r2=832587&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java Tue Nov  3 22:48:43 2009
@@ -86,6 +86,7 @@
 import org.apache.openjpa.jdbc.schema.Sequence;
 import org.apache.openjpa.jdbc.schema.Table;
 import org.apache.openjpa.jdbc.schema.Unique;
+import org.apache.openjpa.jdbc.schema.ForeignKey.FKMapKey;
 import org.apache.openjpa.kernel.Filters;
 import org.apache.openjpa.kernel.OpenJPAStateManager;
 import org.apache.openjpa.kernel.Seq;
@@ -4118,6 +4119,16 @@
     public ForeignKey[] getImportedKeys(DatabaseMetaData meta, String catalog,
         String schemaName, String tableName, Connection conn)
         throws SQLException {
+        return getImportedKeys(meta, catalog, schemaName, tableName, conn, true);
+    }
+
+    /**
+     * Reflect on the schema to return full foreign keys imported by the given
+     * table pattern.
+     */
+    public ForeignKey[] getImportedKeys(DatabaseMetaData meta, String catalog,
+        String schemaName, String tableName, Connection conn, boolean partialKeys)
+        throws SQLException {
         if (!supportsForeignKeys)
             return null;
         if (tableName == null && !supportsNullTableForGetImportedKeys)
@@ -4130,19 +4141,50 @@
                 getSchemaNameForMetadata(schemaName),
                 getTableNameForMetadata(tableName));
 
-            List importedKeyList = new ArrayList();
-            while (keys != null && keys.next())
-                importedKeyList.add(newForeignKey(keys));
+            List<ForeignKey> importedKeyList = new ArrayList<ForeignKey>();
+            Map<FKMapKey, ForeignKey> fkMap = new HashMap<FKMapKey, ForeignKey>();
+
+            while (keys != null && keys.next()) {
+                ForeignKey nfk = newForeignKey(keys);
+                if (!partialKeys) {
+                    ForeignKey fk = combineForeignKey(fkMap, nfk);
+                    // If the key returned != new key, fk col was combined
+                    // with existing fk.
+                    if (fk != nfk) {
+                        continue;
+                    }
+                }
+                importedKeyList.add(nfk);
+            }
             return (ForeignKey[]) importedKeyList.toArray
                 (new ForeignKey[importedKeyList.size()]);
         } finally {
-            if (keys != null)
+            if (keys != null) {
                 try {
                     keys.close();
                 } catch (Exception e) {
                 }
+            }
         }
     }
+    
+    /*
+     * Combines partial foreign keys into singular key
+     */
+    protected ForeignKey combineForeignKey(Map<FKMapKey, ForeignKey> fkMap,
+        ForeignKey fk) {
+        
+        FKMapKey fkmk = new FKMapKey(fk);
+        ForeignKey baseKey = fkMap.get(fkmk);
+        // Found the FK, add the additional column
+        if (baseKey != null) {
+            baseKey.addColumn(fk);
+            return baseKey;
+        }
+        // fkey is new
+        fkMap.put(fkmk, fk);
+        return fk;
+    }
 
     /**
      * Create a new foreign key from the information in the schema metadata.

Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java?rev=832587&r1=832586&r2=832587&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java Tue Nov  3 22:48:43 2009
@@ -34,6 +34,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -49,6 +50,7 @@
 import org.apache.openjpa.jdbc.schema.PrimaryKey;
 import org.apache.openjpa.jdbc.schema.Sequence;
 import org.apache.openjpa.jdbc.schema.Table;
+import org.apache.openjpa.jdbc.schema.ForeignKey.FKMapKey;
 import org.apache.openjpa.lib.jdbc.DelegatingDatabaseMetaData;
 import org.apache.openjpa.lib.jdbc.DelegatingPreparedStatement;
 import org.apache.openjpa.lib.util.J2DoPrivHelper;
@@ -756,7 +758,7 @@
     }
 
     public ForeignKey[] getImportedKeys(DatabaseMetaData meta, String catalog,
-        String schemaName, String tableName, Connection conn)
+        String schemaName, String tableName, Connection conn, boolean partialKeys)
         throws SQLException {
         StringBuffer delAction = new StringBuffer("DECODE(t1.DELETE_RULE").
             append(", 'NO ACTION', ").append(meta.importedKeyNoAction).
@@ -805,9 +807,20 @@
                 setString(stmnt, idx++, tableName.toUpperCase(), null);
             setTimeouts(stmnt, conf, false);
             rs = stmnt.executeQuery();
-            List fkList = new ArrayList();
-            while (rs != null && rs.next())
-                fkList.add(newForeignKey(rs));
+            List<ForeignKey> fkList = new ArrayList<ForeignKey>();            
+            Map<FKMapKey, ForeignKey> fkMap = new HashMap<FKMapKey, ForeignKey>();
+
+            while (rs != null && rs.next()) {
+                ForeignKey nfk = newForeignKey(rs);
+                if (!partialKeys) {
+                    ForeignKey fk = combineForeignKey(fkMap, nfk);
+                    // Only add the fk to the import list if it is new
+                    if (fk != nfk) {
+                        continue;
+                    }
+                }
+                fkList.add(nfk);
+            }
             return (ForeignKey[]) fkList.toArray
                 (new ForeignKey[fkList.size()]);
         } finally {

Modified: openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/schema/localizer.properties
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/schema/localizer.properties?rev=832587&r1=832586&r2=832587&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/schema/localizer.properties (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/schema/localizer.properties Tue Nov  3 22:48:43 2009
@@ -154,3 +154,5 @@
 no-column: Can not find column "{0}" in table "{1}"
 except-read-fk-name: An exception occurred when obtaining the foreign key \
     names from the database.  
+fk-column-mismatch: Unable to create multi-column foreign key. The key \
+    columns do not match primary keys in foreign table.