You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by to...@apache.org on 2007/12/19 15:53:21 UTC

svn commit: r605558 - in /cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src: main/java/org/apache/cayenne/access/ main/java/org/apache/cayenne/dba/hsqldb/ main/java/org/apache/cayenne/dba/postgres/ main/java/org/apache/cayenne/map/ main/java/...

Author: torehalset
Date: Wed Dec 19 06:53:20 2007
New Revision: 605558

URL: http://svn.apache.org/viewvc?rev=605558&view=rev
Log:
CAY-937: Database Schema Migration does not create FK constraints 
 implemented for PostgreSQL, Derby and hsql

Added:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/DbRelationshipDetected.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AddRelationshipToDb.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AddRelationshipToModel.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropRelationshipToDb.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropRelationshipToModel.java
Modified:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DbLoader.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLMergerFactory.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/dba/postgres/PostgresMergerFactory.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AbstractToDbToken.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AbstractToModelToken.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DbMerger.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropTableToDb.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/MergerFactory.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/merge/MergerFactoryTest.java

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DbLoader.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DbLoader.java?rev=605558&r1=605557&r2=605558&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DbLoader.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DbLoader.java Wed Dec 19 06:53:20 2007
@@ -42,6 +42,7 @@
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.DbRelationshipDetected;
 import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.Procedure;
@@ -556,7 +557,7 @@
             // is found in the result set (which should be ordered by table name among
             // other things)
             DbRelationship forwardRelationship = null;
-            DbRelationship reverseRelationship = null;
+            DbRelationshipDetected reverseRelationship = null;
             DbEntity fkEntity = null;
 
             do {
@@ -570,6 +571,7 @@
 
                     // start new entity
                     String fkEntityName = rs.getString("FKTABLE_NAME");
+                    String fkName = rs.getString("FK_NAME");
 
                     fkEntity = map.getDbEntity(fkEntityName);
 
@@ -592,10 +594,11 @@
                         forwardRelationship.setTargetEntity(fkEntity);
                         pkEntity.addRelationship(forwardRelationship);
 
-                        reverseRelationship = new DbRelationship(uniqueRelName(
+                        reverseRelationship = new DbRelationshipDetected(uniqueRelName(
                                 fkEntity,
                                 pkEntName,
                                 false));
+                        reverseRelationship.setFkName(fkName);
                         reverseRelationship.setToMany(false);
                         reverseRelationship.setSourceEntity(fkEntity);
                         reverseRelationship.setTargetEntity(pkEntity);

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLMergerFactory.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLMergerFactory.java?rev=605558&r1=605557&r2=605558&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLMergerFactory.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLMergerFactory.java Wed Dec 19 06:53:20 2007
@@ -18,8 +18,14 @@
  ****************************************************************/
 package org.apache.cayenne.dba.hsqldb;
 
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.merge.DropRelationshipToDb;
 import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.merge.MergerToken;
 import org.apache.cayenne.merge.SetColumnTypeToDb;
@@ -34,8 +40,8 @@
 
         return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
 
+            @Override
             protected void appendPrefix(StringBuffer sqlBuffer) {
-                // http://www.postgresql.org/docs/8.2/static/sql-altertable.html
                 sqlBuffer.append("ALTER TABLE ");
                 sqlBuffer.append(entity.getFullyQualifiedName());
                 sqlBuffer.append(" ALTER ");
@@ -44,4 +50,31 @@
             }
         };
     }
+
+    @Override
+    public MergerToken createDropRelationshipToDb(
+            final DbEntity entity,
+            final DbRelationship rel) {
+
+        return new DropRelationshipToDb(entity, rel) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                String fkName = getFkName();
+
+                if (fkName == null) {
+                    return Collections.emptyList();
+                }
+
+                StringBuilder buf = new StringBuilder();
+                buf.append("ALTER TABLE ");
+                buf.append(entity.getFullyQualifiedName());
+                buf.append(" DROP CONSTRAINT ");
+                buf.append(fkName);
+
+                return Collections.singletonList(buf.toString());
+            }
+        };
+    }
+
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/dba/postgres/PostgresMergerFactory.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/dba/postgres/PostgresMergerFactory.java?rev=605558&r1=605557&r2=605558&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/dba/postgres/PostgresMergerFactory.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/dba/postgres/PostgresMergerFactory.java Wed Dec 19 06:53:20 2007
@@ -18,8 +18,14 @@
  ****************************************************************/
 package org.apache.cayenne.dba.postgres;
 
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.merge.DropRelationshipToDb;
 import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.merge.MergerToken;
 import org.apache.cayenne.merge.SetColumnTypeToDb;
@@ -27,10 +33,14 @@
 public class PostgresMergerFactory extends MergerFactory {
 
     @Override
-    public MergerToken createSetColumnTypeToDb(final DbEntity entity, DbAttribute columnOriginal, final DbAttribute columnNew) {
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
 
         return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
 
+            @Override
             protected void appendPrefix(StringBuffer sqlBuffer) {
                 // http://www.postgresql.org/docs/8.2/static/sql-altertable.html
                 sqlBuffer.append("ALTER TABLE ");
@@ -38,6 +48,33 @@
                 sqlBuffer.append(" ALTER ");
                 sqlBuffer.append(columnNew.getName());
                 sqlBuffer.append(" TYPE ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createDropRelationshipToDb(
+            final DbEntity entity,
+            final DbRelationship rel) {
+
+        return new DropRelationshipToDb(entity, rel) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                String fkName = getFkName();
+                
+                if (fkName == null) {
+                    return Collections.emptyList();
+                }
+
+                // http://www.postgresql.org/docs/8.2/static/sql-altertable.html
+                StringBuilder buf = new StringBuilder();
+                buf.append("ALTER TABLE ");
+                buf.append(entity.getFullyQualifiedName());
+                buf.append(" DROP CONSTRAINT ");
+                buf.append(fkName);
+
+                return Collections.singletonList(buf.toString());
             }
         };
     }

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/DbRelationshipDetected.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/DbRelationshipDetected.java?rev=605558&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/DbRelationshipDetected.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/DbRelationshipDetected.java Wed Dec 19 06:53:20 2007
@@ -0,0 +1,49 @@
+/*****************************************************************
+ *   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.map;
+
+
+/**
+ * A subclass of {@link DbRelationship} to hold some extra runtime information.
+ * 
+ * @author halset
+ */
+public class DbRelationshipDetected extends DbRelationship {
+
+    private String fkName;
+
+    public DbRelationshipDetected(String uniqueRelName) {
+        super(uniqueRelName);
+    }
+
+    /**
+     * Set the name of the underlying foreign key. Typically FK_NAME from jdbc metadata.
+     */
+    public void setFkName(String fkName) {
+        this.fkName = fkName;
+    }
+
+    /**
+     * Get the name of the underlying foreign key. Typically FK_NAME from jdbc metadata.
+     */
+    public String getFkName() {
+        return fkName;
+    }
+
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AbstractToDbToken.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AbstractToDbToken.java?rev=605558&r1=605557&r2=605558&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AbstractToDbToken.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AbstractToDbToken.java Wed Dec 19 06:53:20 2007
@@ -40,6 +40,17 @@
         }
     }
 
+    @Override
+    public String toString() {
+        StringBuilder ts = new StringBuilder();
+        ts.append(getTokenName());
+        ts.append(' ');
+        ts.append(getTokenValue());
+        ts.append(' ');
+        ts.append(getDirection());
+        return ts.toString();
+    }
+
     public abstract List<String> createSql(DbAdapter adapter);
 
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AbstractToModelToken.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AbstractToModelToken.java?rev=605558&r1=605557&r2=605558&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AbstractToModelToken.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AbstractToModelToken.java Wed Dec 19 06:53:20 2007
@@ -30,4 +30,15 @@
         return MergeDirection.TO_MODEL;
     }
 
+    @Override
+    public String toString() {
+        StringBuilder ts = new StringBuilder();
+        ts.append(getTokenName());
+        ts.append(' ');
+        ts.append(getTokenValue());
+        ts.append(' ');
+        ts.append(getDirection());
+        return ts.toString();
+    }
+
 }

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AddRelationshipToDb.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AddRelationshipToDb.java?rev=605558&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AddRelationshipToDb.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AddRelationshipToDb.java Wed Dec 19 06:53:20 2007
@@ -0,0 +1,71 @@
+/*****************************************************************
+ *   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.merge;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cayenne.access.DbGenerator;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+
+public class AddRelationshipToDb extends AbstractToDbToken {
+
+    private DbEntity entity;
+    private DbRelationship rel;
+
+    public AddRelationshipToDb(DbEntity entity, DbRelationship rel) {
+        this.entity = entity;
+        this.rel = rel;
+    }
+
+    /**
+     * @see DbGenerator#createConstraintsQueries(org.apache.cayenne.map.DbEntity)
+     */
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        // TODO: skip FK to a different DB
+
+        if (!rel.isToMany() && rel.isToPK() && !rel.isToDependentPK()) {
+            String fksql = adapter.createFkConstraint(rel);
+            if (fksql != null) {
+                return Collections.singletonList(fksql);
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    public MergerToken createReverse(MergerFactory factory) {
+        return factory.createDropRelationshipToModel(entity, rel);
+    }
+
+    public String getTokenName() {
+        return "Add Relationship";
+    }
+
+    public String getTokenValue() {
+        StringBuilder s = new StringBuilder();
+        s.append(rel.getSourceEntity().getName());
+        s.append("->");
+        s.append(rel.getTargetEntityName());
+        return s.toString();
+    }
+
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AddRelationshipToModel.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AddRelationshipToModel.java?rev=605558&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AddRelationshipToModel.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/AddRelationshipToModel.java Wed Dec 19 06:53:20 2007
@@ -0,0 +1,55 @@
+/*****************************************************************
+ *   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.merge;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+
+public class AddRelationshipToModel extends AbstractToModelToken {
+
+    private DbEntity entity;
+    private DbRelationship rel;
+
+    public AddRelationshipToModel(DbEntity entity, DbRelationship rel) {
+        this.entity = entity;
+        this.rel = rel;
+    }
+
+    public MergerToken createReverse(MergerFactory factory) {
+        return factory.createDropRelationshipToDb(entity, rel);
+    }
+
+    public void execute(MergerContext mergerContext) {
+        entity.addRelationship(rel);
+        // TODO: add reverse as well?
+    }
+
+    public String getTokenName() {
+        return "Add Relationship";
+    }
+
+    public String getTokenValue() {
+        StringBuilder s = new StringBuilder();
+        s.append(rel.getSourceEntity().getName());
+        s.append("->");
+        s.append(rel.getTargetEntityName());
+        return s.toString();
+    }
+
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DbMerger.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DbMerger.java?rev=605558&r1=605557&r2=605558&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DbMerger.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DbMerger.java Wed Dec 19 06:53:20 2007
@@ -23,6 +23,7 @@
 import java.sql.SQLException;
 import java.sql.Types;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -38,6 +39,8 @@
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.ObjEntity;
 
 /**
@@ -91,11 +94,16 @@
                 DbEntity detectedEntity = findDbEntity(detectedDataMap, tableName);
                 if (detectedEntity == null) {
                     tokens.add(factory.createCreateTableToDb(dbEntity));
+                    // TODO: does this work properly with createReverse?
+                    for (DbRelationship rel : dbEntity.getRelationships()) {
+                        tokens.add(factory.createAddRelationshipToDb(dbEntity, rel));
+                    }
                     continue;
                 }
                 dbEntityToDropByName.remove(detectedEntity.getName());
 
                 checkRows(tokens, dbEntity, detectedEntity);
+                checkRelationships(adapter, tokens, dbEntity, detectedEntity);
             }
 
             // drop table
@@ -183,6 +191,65 @@
         }
     }
 
+    private void checkRelationships(
+            DbAdapter adapter,
+            List<MergerToken> tokens,
+            DbEntity dbEntity,
+            DbEntity detectedEntity) {
+
+        // relationships to drop
+        for (DbRelationship detected : detectedEntity.getRelationships()) {
+            if (findDbRelationship(dbEntity, detected) == null) {
+
+                // alter detected relationship to match entity and attribute names.
+                // (case sensitively)
+
+                DbEntity targetEntity = findDbEntity(dbEntity.getDataMap(), detected
+                        .getTargetEntityName());
+                if (targetEntity == null) {
+                    continue;
+                }
+
+                detected.setSourceEntity(dbEntity);
+                detected.setTargetEntity(targetEntity);
+
+                // manipulate the joins to match the DbAttributes in the model
+                for (DbJoin join : detected.getJoins()) {
+                    DbAttribute sattr = findDbAttribute(dbEntity, join.getSourceName());
+                    if (sattr != null) {
+                        join.setSourceName(sattr.getName());
+                    }
+                    DbAttribute tattr = findDbAttribute(targetEntity, join
+                            .getTargetName());
+                    if (tattr != null) {
+                        join.setTargetName(tattr.getName());
+                    }
+                }
+
+                MergerToken token = factory
+                        .createDropRelationshipToDb(dbEntity, detected);
+                if (detected.isToMany()) {
+                    // default toModel as we can not do drop a toMany in the db. only
+                    // toOne are represented using foreign key
+                    token = token.createReverse(factory);
+                }
+                tokens.add(token);
+            }
+        }
+
+        // relationships to add
+        for (DbRelationship rel : dbEntity.getRelationships()) {
+            if (findDbRelationship(detectedEntity, rel) == null) {
+                // TODO: very ugly. perhaps MergerToken should have a .isNoOp()?
+                AbstractToDbToken t = (AbstractToDbToken) factory
+                        .createAddRelationshipToDb(dbEntity, rel);
+                if (!t.createSql(adapter).isEmpty()) {
+                    tokens.add(factory.createAddRelationshipToDb(dbEntity, rel));
+                }
+            }
+        }
+    }
+
     /**
      * case insensitive search for a {@link DbEntity} in a {@link DataMap} by name
      */
@@ -206,6 +273,55 @@
             }
         }
         return null;
+    }
+
+    /**
+     * search for a {@link DbRelationship} like rel in the given {@link DbEntity}
+     */
+    private DbRelationship findDbRelationship(DbEntity entity, DbRelationship rel) {
+        for (DbRelationship candidate : entity.getRelationships()) {
+            if (equalDbJoinCollections(candidate.getJoins(), rel.getJoins())) {
+                return candidate;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return true if the two unordered {@link Collection}s of {@link DbJoin}s are
+     * equal. Entity and Attribute names are compared case insensitively.
+     */
+    private static boolean equalDbJoinCollections(
+            Collection<DbJoin> j1s,
+            Collection<DbJoin> j2s) {
+        if (j1s.size() != j2s.size()) {
+            return false;
+        }
+
+        for (DbJoin j1 : j1s) {
+            for (DbJoin j2 : j2s) {
+                // check entity name
+                if (!j1.getSource().getEntity().getName().equalsIgnoreCase(
+                        j2.getSource().getEntity().getName())) {
+                    continue;
+                }
+                if (!j1.getTarget().getEntity().getName().equalsIgnoreCase(
+                        j2.getTarget().getEntity().getName())) {
+                    continue;
+                }
+                // check attribute name
+                if (!j1.getSourceName().equalsIgnoreCase(j2.getSourceName())) {
+                    continue;
+                }
+                if (!j1.getTargetName().equalsIgnoreCase(j2.getTargetName())) {
+                    continue;
+                }
+
+                return true;
+            }
+        }
+
+        return false;
     }
 
     private static final class LoaderDelegate implements DbLoaderDelegate {

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropRelationshipToDb.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropRelationshipToDb.java?rev=605558&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropRelationshipToDb.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropRelationshipToDb.java Wed Dec 19 06:53:20 2007
@@ -0,0 +1,79 @@
+/*****************************************************************
+ *   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.merge;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.DbRelationshipDetected;
+
+public class DropRelationshipToDb extends AbstractToDbToken {
+
+    private DbEntity entity;
+    private DbRelationship rel;
+
+    public DropRelationshipToDb(DbEntity entity, DbRelationship rel) {
+        this.entity = entity;
+        this.rel = rel;
+    }
+    
+    public String getFkName() {
+        if (rel instanceof DbRelationshipDetected) {
+            return ((DbRelationshipDetected) rel).getFkName();
+        }
+        return null;
+    }
+
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        String fkName = getFkName();
+        
+        if (fkName == null) {
+            return Collections.emptyList();
+        }
+
+        StringBuilder buf = new StringBuilder();
+        buf.append("ALTER TABLE ");
+        buf.append(entity.getFullyQualifiedName());
+        buf.append(" DROP FOREIGN KEY ");
+        buf.append(fkName);
+
+        return Collections.singletonList(buf.toString());
+    }
+
+    public MergerToken createReverse(MergerFactory factory) {
+        return factory.createAddRelationshipToModel(entity, rel);
+    }
+
+    public String getTokenName() {
+        return "Drop Relationship";
+    }
+
+    public String getTokenValue() {
+        StringBuilder s = new StringBuilder();
+        s.append(rel.getSourceEntity().getName());
+        s.append("->");
+        s.append(rel.getTargetEntityName());
+        return s.toString();
+    }
+
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropRelationshipToModel.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropRelationshipToModel.java?rev=605558&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropRelationshipToModel.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropRelationshipToModel.java Wed Dec 19 06:53:20 2007
@@ -0,0 +1,55 @@
+/*****************************************************************
+ *   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.merge;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+
+public class DropRelationshipToModel extends AbstractToModelToken {
+
+    private DbEntity entity;
+    private DbRelationship rel;
+
+    public DropRelationshipToModel(DbEntity entity, DbRelationship rel) {
+        this.entity = entity;
+        this.rel = rel;
+    }
+
+    public MergerToken createReverse(MergerFactory factory) {
+        return factory.createAddRelationshipToDb(entity, rel);
+    }
+
+    public void execute(MergerContext mergerContext) {
+        // TODO: is this correct?
+        entity.removeRelationship(rel.getName());
+    }
+
+    public String getTokenName() {
+        return "Drop Relationship";
+    }
+
+    public String getTokenValue() {
+        StringBuilder s = new StringBuilder();
+        s.append(rel.getSourceEntity().getName());
+        s.append("->");
+        s.append(rel.getTargetEntityName());
+        return s.toString();
+    }
+
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropTableToDb.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropTableToDb.java?rev=605558&r1=605557&r2=605558&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropTableToDb.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/DropTableToDb.java Wed Dec 19 06:53:20 2007
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.merge;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -34,7 +35,11 @@
 
     @Override
     public List<String> createSql(DbAdapter adapter) {
-        return Collections.singletonList(adapter.dropTable(entity));
+        List<String> sqls = new ArrayList<String>();
+        sqls.addAll(adapter.getPkGenerator().dropAutoPkStatements(
+                Collections.singletonList(entity)));
+        sqls.add(adapter.dropTable(entity));
+        return sqls;
     }
 
     public String getTokenName() {

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/MergerFactory.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/MergerFactory.java?rev=605558&r1=605557&r2=605558&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/MergerFactory.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/merge/MergerFactory.java Wed Dec 19 06:53:20 2007
@@ -21,6 +21,7 @@
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
 
 /**
  * All {@link MergerToken}s should be created from a {@link MergerFactory} obtained from
@@ -91,5 +92,21 @@
             DbAttribute columnOriginal,
             DbAttribute columnNew) {
         return new SetColumnTypeToDb(entity, columnOriginal, columnNew);
+    }
+    
+    public MergerToken createAddRelationshipToDb(DbEntity entity, DbRelationship rel) {
+        return new AddRelationshipToDb(entity, rel);
+    }
+
+    public MergerToken createAddRelationshipToModel(DbEntity entity, DbRelationship rel) {
+        return new AddRelationshipToModel(entity, rel);
+    }
+
+    public MergerToken createDropRelationshipToDb(DbEntity entity, DbRelationship rel) {
+        return new DropRelationshipToDb(entity, rel);
+    }
+
+    public MergerToken createDropRelationshipToModel(DbEntity entity, DbRelationship rel) {
+        return new DropRelationshipToModel(entity, rel);
     }
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/merge/MergerFactoryTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/merge/MergerFactoryTest.java?rev=605558&r1=605557&r2=605558&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/merge/MergerFactoryTest.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/merge/MergerFactoryTest.java Wed Dec 19 06:53:20 2007
@@ -23,11 +23,9 @@
 import java.sql.Statement;
 import java.sql.Types;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 
 import org.apache.cayenne.CayenneDataObject;
-import org.apache.cayenne.Persistent;
 import org.apache.cayenne.access.DataContext;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.QueryLogger;
@@ -35,32 +33,35 @@
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.ObjAttribute;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.unit.CayenneCase;
 
 public class MergerFactoryTest extends CayenneCase {
 
-    /**
-     * Check that an up to date database are detected as up to date.
-     */
-    public void testUpToDateToDb() throws Exception {
+    private DataNode node;
+    private DataMap map;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
         deleteTestData();
         createTestData("testArtists");
-        DataNode node = getDomain().getDataNodes().iterator().next();
-        DataMap map = getDomain().getMap("testmap");
+        node = getDomain().getDataNodes().iterator().next();
+        map = getDomain().getMap("testmap");
         filterDataMap(node, map);
 
-        assertInSync(node, map);
+        DbMerger merger = new DbMerger();
+        List<MergerToken> tokens = merger.createMergeTokens(node, map);
+        execute(map, node, tokens);
+
+        assertTokensAndExecute(node, map, 0, 0);
     }
 
     public void testAddAndDropColumnToDb() throws Exception {
-        deleteTestData();
-        createTestData("testArtists");
-        DataNode node = getDomain().getDataNodes().iterator().next();
-        DataMap map = getDomain().getMap("testmap");
-        filterDataMap(node, map);
-
         DbEntity dbEntity = map.getDbEntity("PAINTING");
         assertNotNull(dbEntity);
 
@@ -69,25 +70,18 @@
         column.setMandatory(false);
         column.setMaxLength(10);
         dbEntity.addAttribute(column);
-        mergeToDb(node, map, 1);
+        assertTokensAndExecute(node, map, 1, 0);
 
         // try merge once more to check that is was merged
-        assertInSync(node, map);
+        assertTokensAndExecute(node, map, 0, 0);
 
         // remove it from model and db
         dbEntity.removeAttribute(column.getName());
-        mergeToDb(node, map, 1);
-
-        assertInSync(node, map);
+        assertTokensAndExecute(node, map, 1, 0);
+        assertTokensAndExecute(node, map, 0, 0);
     }
 
     public void testChangeVarcharSizeToDb() throws Exception {
-        deleteTestData();
-        createTestData("testArtists");
-        DataNode node = getDomain().getDataNodes().iterator().next();
-        DataMap map = getDomain().getMap("testmap");
-        filterDataMap(node, map);
-
         DbEntity dbEntity = map.getDbEntity("PAINTING");
         assertNotNull(dbEntity);
 
@@ -96,33 +90,27 @@
         column.setMandatory(false);
         column.setMaxLength(10);
         dbEntity.addAttribute(column);
-        mergeToDb(node, map, 1);
+        assertTokensAndExecute(node, map, 1, 0);
 
         // check that is was merged
-        assertInSync(node, map);
+        assertTokensAndExecute(node, map, 0, 0);
 
         // change size
         column.setMaxLength(20);
 
         // merge to db
-        mergeToDb(node, map, 1);
+        assertTokensAndExecute(node, map, 1, 0);
 
         // check that is was merged
-        assertInSync(node, map);
+        assertTokensAndExecute(node, map, 0, 0);
 
         // clean up
         dbEntity.removeAttribute(column.getName());
-        mergeToDb(node, map, 1);
-        assertInSync(node, map);
+        assertTokensAndExecute(node, map, 1, 0);
+        assertTokensAndExecute(node, map, 0, 0);
     }
 
     public void testMultipleTokensToDb() throws Exception {
-        deleteTestData();
-        createTestData("testArtists");
-        DataNode node = getDomain().getDataNodes().iterator().next();
-        DataMap map = getDomain().getMap("testmap");
-        filterDataMap(node, map);
-
         DbEntity dbEntity = map.getDbEntity("PAINTING");
         assertNotNull(dbEntity);
 
@@ -134,40 +122,32 @@
         column2.setMandatory(false);
         column2.setMaxLength(10);
         dbEntity.addAttribute(column2);
-        mergeToDb(node, map, 2);
+        assertTokensAndExecute(node, map, 2, 0);
 
         // check that is was merged
-        assertInSync(node, map);
+        assertTokensAndExecute(node, map, 0, 0);
 
         // change size
         column1.setMaxLength(20);
         column2.setMaxLength(30);
 
         // merge to db
-        mergeToDb(node, map, 2);
+        assertTokensAndExecute(node, map, 2, 0);
 
         // check that is was merged
-        assertInSync(node, map);
+        assertTokensAndExecute(node, map, 0, 0);
 
         // clean up
         dbEntity.removeAttribute(column1.getName());
         dbEntity.removeAttribute(column2.getName());
-        mergeToDb(node, map, 2);
-        assertInSync(node, map);
+        assertTokensAndExecute(node, map, 2, 0);
+        assertTokensAndExecute(node, map, 0, 0);
     }
 
     public void testAddTableToDb() throws Exception {
-
-        deleteTestData();
-
-        createTestData("testArtists");
-        DataNode node = getDomain().getDataNodes().iterator().next();
-        DataMap map = getDomain().getMap("testmap");
-        filterDataMap(node, map);
-
         dropTableIfPresent(node, "NEW_TABLE");
 
-        assertInSync(node, map);
+        assertTokensAndExecute(node, map, 0, 0);
 
         DbEntity dbEntity = new DbEntity("NEW_TABLE");
 
@@ -183,9 +163,9 @@
 
         map.addDbEntity(dbEntity);
 
-        mergeToDb(node, map, 1);
-        assertInSync(node, map);
-        
+        assertTokensAndExecute(node, map, 1, 0);
+        assertTokensAndExecute(node, map, 0, 0);
+
         ObjEntity objEntity = new ObjEntity("NewTable");
         objEntity.setDbEntity(dbEntity);
         ObjAttribute oatr1 = new ObjAttribute("name");
@@ -193,7 +173,7 @@
         oatr1.setType("java.lang.String");
         objEntity.addAttribute(oatr1);
         map.addObjEntity(objEntity);
-        
+
         // try to insert some rows to check that pk stuff is working
         DataContext ctxt = createDataContext();
         for (int i = 0; i < 5; i++) {
@@ -210,34 +190,183 @@
         assertNull(map.getObjEntity(objEntity.getName()));
         assertNull(map.getDbEntity(dbEntity.getName()));
         assertFalse(map.getDbEntities().contains(dbEntity));
-        // TODO: mergeToDb(node, map, 1);
-        assertInSync(node, map);
+        assertTokensAndExecute(node, map, 0, 0);
     }
 
-    private void assertInSync(DataNode node, DataMap map) {
-        DbMerger merger = new DbMerger();
-        List tokens = merger.createMergeTokens(node, map);
-        /*
-         * if (tokens.size() > 0) { QueryLogger.log("should be up to date, but missing:\n" +
-         * tokens.createSql(node.getAdapter())); }
-         */
-        assertEquals(0, tokens.size());
+    public void testAddForeignKeyWithTable() throws Exception {
+        dropTableIfPresent(node, "NEW_TABLE");
+
+        assertTokensAndExecute(node, map, 0, 0);
+
+        DbEntity dbEntity = new DbEntity("NEW_TABLE");
+
+        DbAttribute column1 = new DbAttribute("ID", Types.INTEGER, dbEntity);
+        column1.setMandatory(true);
+        column1.setPrimaryKey(true);
+        dbEntity.addAttribute(column1);
+
+        DbAttribute column2 = new DbAttribute("NAME", Types.VARCHAR, dbEntity);
+        column2.setMaxLength(10);
+        column2.setMandatory(false);
+        dbEntity.addAttribute(column2);
+
+        DbAttribute column3 = new DbAttribute("ARTIST_ID", Types.INTEGER, dbEntity);
+        column3.setMandatory(false);
+        dbEntity.addAttribute(column3);
+
+        map.addDbEntity(dbEntity);
+
+        DbEntity artistDbEntity = map.getDbEntity("ARTIST");
+        assertNotNull(artistDbEntity);
+
+        // relation from new_table to artist
+        DbRelationship r1 = new DbRelationship("toArtistR1");
+        r1.setSourceEntity(dbEntity);
+        r1.setTargetEntity(artistDbEntity);
+        r1.setToMany(false);
+        r1.addJoin(new DbJoin(r1, "ARTIST_ID", "ARTIST_ID"));
+        dbEntity.addRelationship(r1);
+
+        // relation from artist to new_table
+        DbRelationship r2 = new DbRelationship("toNewTableR2");
+        r2.setSourceEntity(artistDbEntity);
+        r2.setTargetEntity(dbEntity);
+        r2.setToMany(true);
+        r2.addJoin(new DbJoin(r2, "ARTIST_ID", "ARTIST_ID"));
+        artistDbEntity.addRelationship(r2);
+
+        assertTokensAndExecute(node, map, 2, 0);
+        assertTokensAndExecute(node, map, 0, 0);
+
+        DataContext ctxt = createDataContext();
+
+        // remove relationships
+        dbEntity.removeRelationship(r1.getName());
+        artistDbEntity.removeRelationship(r2.getName());
+        ctxt.getEntityResolver().clearCache();
+        assertTokensAndExecute(node, map, 1, 1);
+        assertTokensAndExecute(node, map, 0, 0);
+
+        // clear up
+        // map.removeObjEntity(objEntity.getName(), true);
+        map.removeDbEntity(dbEntity.getName(), true);
+        ctxt.getEntityResolver().clearCache();
+        // assertNull(map.getObjEntity(objEntity.getName()));
+        assertNull(map.getDbEntity(dbEntity.getName()));
+        assertFalse(map.getDbEntities().contains(dbEntity));
+        assertTokensAndExecute(node, map, 0, 0);
     }
 
-    private void mergeToDb(DataNode node, DataMap map, int expectedChanges)
-            throws Exception {
+    public void testAddForeignKeyAfterTable() throws Exception {
+        dropTableIfPresent(node, "NEW_TABLE");
+
+        assertTokensAndExecute(node, map, 0, 0);
+
+        DbEntity dbEntity = new DbEntity("NEW_TABLE");
+
+        DbAttribute column1 = new DbAttribute("ID", Types.INTEGER, dbEntity);
+        column1.setMandatory(true);
+        column1.setPrimaryKey(true);
+        dbEntity.addAttribute(column1);
+
+        DbAttribute column2 = new DbAttribute("NAME", Types.VARCHAR, dbEntity);
+        column2.setMaxLength(10);
+        column2.setMandatory(false);
+        dbEntity.addAttribute(column2);
+
+        DbAttribute column3 = new DbAttribute("ARTIST_ID", Types.INTEGER, dbEntity);
+        column3.setMandatory(false);
+        dbEntity.addAttribute(column3);
+
+        map.addDbEntity(dbEntity);
+
+        DbEntity artistDbEntity = map.getDbEntity("ARTIST");
+        assertNotNull(artistDbEntity);
+        
+        assertTokensAndExecute(node, map, 1, 0);
+        assertTokensAndExecute(node, map, 0, 0);
+
+        // relation from new_table to artist
+        DbRelationship r1 = new DbRelationship("toArtistR1");
+        r1.setSourceEntity(dbEntity);
+        r1.setTargetEntity(artistDbEntity);
+        r1.setToMany(false);
+        r1.addJoin(new DbJoin(r1, "ARTIST_ID", "ARTIST_ID"));
+        dbEntity.addRelationship(r1);
+
+        // relation from artist to new_table
+        DbRelationship r2 = new DbRelationship("toNewTableR2");
+        r2.setSourceEntity(artistDbEntity);
+        r2.setTargetEntity(dbEntity);
+        r2.setToMany(true);
+        r2.addJoin(new DbJoin(r2, "ARTIST_ID", "ARTIST_ID"));
+        artistDbEntity.addRelationship(r2);
+
+        assertTokensAndExecute(node, map, 1, 0);
+        assertTokensAndExecute(node, map, 0, 0);
+
+        DataContext ctxt = createDataContext();
+
+        // remove relationships
+        dbEntity.removeRelationship(r1.getName());
+        artistDbEntity.removeRelationship(r2.getName());
+        ctxt.getEntityResolver().clearCache();
+        assertTokensAndExecute(node, map, 1, 1);
+        assertTokensAndExecute(node, map, 0, 0);
+
+        // clear up
+        // map.removeObjEntity(objEntity.getName(), true);
+        map.removeDbEntity(dbEntity.getName(), true);
+        ctxt.getEntityResolver().clearCache();
+        // assertNull(map.getObjEntity(objEntity.getName()));
+        assertNull(map.getDbEntity(dbEntity.getName()));
+        assertFalse(map.getDbEntities().contains(dbEntity));
+        assertTokensAndExecute(node, map, 0, 0);
+    }
+    
+    private void assertTokensAndExecute(
+            DataNode node,
+            DataMap map,
+            int expectedToDb,
+            int expectedToModel) throws Exception {
         DbMerger merger = new DbMerger();
-        List tokens = merger.createMergeTokens(node, map);
-        /*
-         * if (expectedChanges != tokens.size()) {
-         * QueryLogger.log(tokens.createSql(node.getAdapter())); }
-         */
-        assertEquals(expectedChanges, tokens.size());
-        if (tokens.size() > 0) {
+        List<MergerToken> tokens = merger.createMergeTokens(node, map);
+
+        assertTokens(tokens, expectedToDb, expectedToModel);
+        if (!tokens.isEmpty()) {
             execute(map, node, tokens);
         }
     }
 
+    private void assertTokens(
+            List<MergerToken> tokens,
+            int expectedToDb,
+            int expectedToModel) {
+        int actualToDb = 0;
+        int actualToModel = 0;
+        for (MergerToken token : tokens) {
+            if (token.getDirection().equals(MergeDirection.TO_DB)) {
+                actualToDb++;
+            }
+            else if (token.getDirection().equals(MergeDirection.TO_MODEL)) {
+                actualToModel++;
+            }
+        }
+        logTokens(tokens);
+        assertEquals("tokens to db", expectedToDb, actualToDb);
+        assertEquals("tokens to model", expectedToModel, actualToModel);
+    }
+
+    private void logTokens(List<MergerToken> tokens) {
+        for (MergerToken token : tokens) {
+            QueryLogger.log("token: " + token.toString());
+            if (token instanceof AbstractToDbToken) {
+                QueryLogger.log("  \\-->  "
+                        + ((AbstractToDbToken) token).createSql(node.getAdapter()));
+            }
+        }
+    }
+
     private void dropTableIfPresent(DataNode node, String tableName) {
         DbEntity entity = new DbEntity(tableName);
         AbstractToDbToken t = (AbstractToDbToken) node
@@ -266,17 +395,13 @@
             return;
         }
 
-        List entitiesToRemove = new ArrayList();
+        List<DbEntity> entitiesToRemove = new ArrayList<DbEntity>();
 
-        for (Iterator it = map.getDbEntities().iterator(); it.hasNext();) {
-            DbEntity ent = (DbEntity) it.next();
+        for (DbEntity ent : map.getDbEntities()) {
 
             if (excludeBinPK) {
-                boolean skip = false;
-                Iterator attrs = ent.getAttributes().iterator();
-                while (attrs.hasNext()) {
+                for (DbAttribute attr : ent.getAttributes()) {
                     // check for BIN PK or FK to BIN Pk
-                    DbAttribute attr = (DbAttribute) attrs.next();
                     if (attr.getType() == Types.BINARY
                             || attr.getType() == Types.VARBINARY
                             || attr.getType() == Types.LONGVARBINARY) {
@@ -291,18 +416,17 @@
             }
         }
 
-        for (Iterator it = entitiesToRemove.iterator(); it.hasNext();) {
-            DbEntity e = (DbEntity) it.next();
+        for (DbEntity e : entitiesToRemove) {
+            QueryLogger.log("filter away " + e.getName());
             map.removeDbEntity(e.getName(), true);
         }
-    }
 
-    private void execute(DataMap map, DataNode node, List tokens) throws Exception {
+    }
 
+    private void execute(DataMap map, DataNode node, List<MergerToken> tokens)
+            throws Exception {
         MergerContext mergerContext = new ExecutingMergerContext(map, node);
-
-        for (Iterator it = tokens.iterator(); it.hasNext();) {
-            MergerToken tok = (MergerToken) it.next();
+        for (MergerToken tok : tokens) {
             tok.execute(mergerContext);
         }
 
@@ -331,8 +455,10 @@
         }
     }
 
-    /*
-     * protected void tearDown() throws Exception { super.tearDown(); deleteTestData(); }
-     */
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        deleteTestData();
+    }
 
 }