You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2014/10/29 19:40:40 UTC

[5/7] CAY-1946 CDbimport improvements

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/merge/AddColumnToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/AddColumnToDb.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/AddColumnToDb.java
index 2192203..77b7486 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/AddColumnToDb.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/merge/AddColumnToDb.java
@@ -1,30 +1,29 @@
-/*****************************************************************
- *   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
+/*
+ * 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
+ *      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.
- ****************************************************************/
+ *    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.CayenneRuntimeException;
 import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 
@@ -52,41 +51,8 @@ public class AddColumnToDb extends AbstractToDbToken.EntityAndColumn {
         QuotingStrategy context = adapter.getQuotingStrategy();
         appendPrefix(sqlBuffer, context);
 
-        // copied from JdbcAdapter.createTableAppendColumn
-        String[] types = adapter.externalTypesForJdbcType(getColumn().getType());
-        if (types == null || types.length == 0) {
-            String entityName = getColumn().getEntity() != null ? getColumn()
-                    .getEntity().getFullyQualifiedName() : "<null>";
-            throw new CayenneRuntimeException("Undefined type for attribute '"
-                    + entityName + "." + getColumn().getName() + "': " + getColumn().getType());
-        }
-
-        String type = types[0];
-        sqlBuffer.append(type);
-
-        // append size and precision (if applicable)
-        if (adapter.typeSupportsLength(getColumn().getType())) {
-            int len = getColumn().getMaxLength();
-            int scale = TypesMapping.isDecimal(getColumn().getType()) ? getColumn().getScale() : -1;
-
-            // sanity check
-            if (scale > len) {
-                scale = -1;
-            }
-
-            if (len > 0) {
-                sqlBuffer.append('(').append(len);
-
-                if (scale >= 0) {
-                    sqlBuffer.append(", ").append(scale);
-                }
-
-                sqlBuffer.append(')');
-            }
-        }
-
-        // use separate token to set value and not null if needed
-        // sqlBuffer.append(" NULL");
+        sqlBuffer.append(JdbcAdapter.getType(adapter, getColumn()));
+        sqlBuffer.append(JdbcAdapter.sizeAndPrecision(adapter, getColumn()));
 
         return Collections.singletonList(sqlBuffer.toString());
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/merge/CreateTableToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/CreateTableToModel.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/CreateTableToModel.java
index d2f538f..61a266a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/CreateTableToModel.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/merge/CreateTableToModel.java
@@ -72,30 +72,14 @@ public class CreateTableToModel extends AbstractToModelToken.Entity {
         String className = objEntityClassName;
         if (className == null) {
             // we should generate a className based on the objEntityName
-            String packageName = map.getDefaultPackage();
-            if (Util.isEmptyString(packageName)) {
-                packageName = "";
-            }
-            else if (!packageName.endsWith(".")) {
-                packageName = packageName + ".";
-            }
-            className = packageName + objEntityName;
+            className = map.getNameWithDefaultPackage(objEntityName);
         }
 
         objEntity.setClassName(className);
-        
         objEntity.setSuperClassName(map.getDefaultSuperclass());
         
         if (map.isClientSupported()) {
-            String clientPkg = map.getDefaultClientPackage();
-            if (clientPkg != null) {
-                if (!clientPkg.endsWith(".")) {
-                    clientPkg = clientPkg + ".";
-                }
-
-                objEntity.setClientClassName(clientPkg + objEntity.getName());
-            }
-
+            objEntity.setClientClassName(map.getNameWithDefaultClientPackage(objEntity.getName()));
             objEntity.setClientSuperClassName(map.getDefaultClientSuperclass());
         }
         

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/merge/DbMerger.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/DbMerger.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/DbMerger.java
index 55219f2..31cb65c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DbMerger.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/merge/DbMerger.java
@@ -1,27 +1,29 @@
-/*****************************************************************
- *   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
+/*
+ * 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
+ *      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.
- ****************************************************************/
+ *    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.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.DbLoader;
-import org.apache.cayenne.access.DefaultDbLoaderDelegate;
+import org.apache.cayenne.access.loader.DbLoaderConfiguration;
+import org.apache.cayenne.access.loader.DefaultDbLoaderDelegate;
+import org.apache.cayenne.access.loader.NameFilter;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.Attribute;
 import org.apache.cayenne.map.DataMap;
@@ -55,20 +57,13 @@ public class DbMerger {
     
     private final ValueForNullProvider valueForNull;
 
-    private final String schema;
-
     public DbMerger(MergerFactory factory) {
         this(factory, null);
     }
 
-    public DbMerger(MergerFactory factory, String schema) {
-        this(factory,  null, schema);
-    }
-
-    public DbMerger(MergerFactory factory, ValueForNullProvider valueForNull, String schema) {
+    public DbMerger(MergerFactory factory, ValueForNullProvider valueForNull) {
         this.factory = factory;
         this.valueForNull = valueForNull == null ? new EmptyValueForNullProvider() : valueForNull;
-        this.schema = schema;
     }
 
     /**
@@ -83,24 +78,24 @@ public class DbMerger {
      * Create and return a {@link List} of {@link MergerToken}s to alter the given
      * {@link DataNode} to match the given {@link DataMap}
      */
-    public List<MergerToken> createMergeTokens(DataNode dataNode, DataMap existing) {
-        return createMergeTokens(dataNode.getDataSource(), dataNode.getAdapter(), existing);
+    public List<MergerToken> createMergeTokens(DataNode dataNode, DataMap existing, DbLoaderConfiguration config) {
+        return createMergeTokens(dataNode.getDataSource(), dataNode.getAdapter(), existing, config);
     }
 
     /**
      * Create and return a {@link List} of {@link MergerToken}s to alter the given
      * {@link DataNode} to match the given {@link DataMap}
      */
-    public List<MergerToken> createMergeTokens(DbLoader dbLoader, DataMap existing) {
-        return createMergeTokens(existing, loadDataMapFromDb(dbLoader));
+    public List<MergerToken> createMergeTokens(DbLoader dbLoader, DataMap existing, DbLoaderConfiguration config) {
+        return createMergeTokens(existing, loadDataMapFromDb(dbLoader, config));
     }
 
     /**
      * Create and return a {@link List} of {@link MergerToken}s to alter the given
      * {@link DataNode} to match the given {@link DataMap}
      */
-    public List<MergerToken> createMergeTokens(DataSource dataSource, DbAdapter adapter, DataMap existingDataMap) {
-        return createMergeTokens(existingDataMap, loadDataMapFromDb(dataSource, adapter));
+    public List<MergerToken> createMergeTokens(DataSource dataSource, DbAdapter adapter, DataMap existingDataMap, DbLoaderConfiguration config) {
+        return createMergeTokens(existingDataMap, loadDataMapFromDb(dataSource, adapter, config));
     }
 
     /**
@@ -131,26 +126,17 @@ public class DbMerger {
         return tokens;
     }
 
-    private DataMap loadDataMapFromDb(DataSource dataSource, DbAdapter adapter) {
+    private DataMap loadDataMapFromDb(DataSource dataSource, DbAdapter adapter, DbLoaderConfiguration config) {
         Connection conn = null;
         try {
             conn = dataSource.getConnection();
 
-            final DbMerger merger = this;
-
             // TODO pass naming strategy
-            DbLoader dbLoader = new DbLoader(conn, adapter, new DefaultDbLoaderDelegate()) {
-
-                @Override
-                public boolean includeTableName(String tableName) {
-                    return merger.includeTableName(tableName);
-                }
-            };
-
-            return loadDataMapFromDb(dbLoader);
+            DbLoader dbLoader = new DbLoader(conn, adapter, new DefaultDbLoaderDelegate());
+            return loadDataMapFromDb(dbLoader, config);
         }
         catch (SQLException e) {
-            throw new CayenneRuntimeException("Can't load dataMap from db.", e);
+            throw new CayenneRuntimeException("Can't doLoad dataMap from db.", e);
         }
         finally {
             if (conn != null) {
@@ -164,10 +150,10 @@ public class DbMerger {
         }
     }
 
-    private DataMap loadDataMapFromDb(DbLoader dbLoader) {
+    private DataMap loadDataMapFromDb(DbLoader dbLoader, DbLoaderConfiguration config) {
         DataMap detectedDataMap = new DataMap();
         try {
-            dbLoader.load(detectedDataMap, null, schema, null, (String[]) null);
+            dbLoader.load(detectedDataMap, config, (String[]) null);
         } catch (SQLException e) {
             // TODO log
         }
@@ -190,6 +176,8 @@ public class DbMerger {
             String tableName = dbEntity.getName();
 
             if (!includeTableName(tableName)) {
+                // TODO we have to cut this entities in db loader
+                // TODO log
                 continue;
             }
 
@@ -359,20 +347,15 @@ public class DbMerger {
         Collection<DbAttribute> primaryKeyNew = dbEntity.getPrimaryKeys();
 
         String primaryKeyName = null;
-        if ((detectedEntity instanceof DetectedDbEntity)) {
+        if (detectedEntity instanceof DetectedDbEntity) {
             primaryKeyName = ((DetectedDbEntity) detectedEntity).getPrimaryKeyName();
         }
 
-        if (upperCaseEntityNames(primaryKeyOriginal).equals(
-                upperCaseEntityNames(primaryKeyNew))) {
+        if (upperCaseEntityNames(primaryKeyOriginal).equals(upperCaseEntityNames(primaryKeyNew))) {
             return null;
         }
 
-        return factory.createSetPrimaryKeyToDb(
-                dbEntity,
-                primaryKeyOriginal,
-                primaryKeyNew,
-                primaryKeyName);
+        return factory.createSetPrimaryKeyToDb(dbEntity, primaryKeyOriginal, primaryKeyNew, primaryKeyName);
     }
     
     private Set<String> upperCaseEntityNames(Collection<? extends Attribute> attrs) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/merge/SetColumnTypeToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetColumnTypeToDb.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/SetColumnTypeToDb.java
index a074188..466a9d9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetColumnTypeToDb.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/merge/SetColumnTypeToDb.java
@@ -1,21 +1,21 @@
-/*****************************************************************
- *   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
+/*
+ * 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
+ *      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.
- ****************************************************************/
+ *    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;
@@ -23,6 +23,7 @@ import java.util.List;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.map.DbAttribute;
@@ -57,41 +58,10 @@ public class SetColumnTypeToDb extends AbstractToDbToken.Entity {
     @Override
     public List<String> createSql(DbAdapter adapter) {
         StringBuffer sqlBuffer = new StringBuffer();
-       
         appendPrefix(sqlBuffer, adapter.getQuotingStrategy());
   
-        // copied from JdbcAdapter.createTableAppendColumn
-        String[] types = adapter.externalTypesForJdbcType(columnNew.getType());
-        if (types == null || types.length == 0) {
-            String entityName = columnNew.getEntity() != null ? columnNew
-                    .getEntity().getFullyQualifiedName() : "<null>";
-            throw new CayenneRuntimeException("Undefined type for attribute '"
-                    + entityName + "." + columnNew.getName() + "': " + columnNew.getType());
-        }
-
-        String type = types[0];
-        sqlBuffer.append(type);
-
-        // append size and precision (if applicable)
-        if (adapter.typeSupportsLength(columnNew.getType())) {
-            int len = columnNew.getMaxLength();
-            int scale = TypesMapping.isDecimal(columnNew.getType()) ? columnNew.getScale() : -1;
-
-            // sanity check
-            if (scale > len) {
-                scale = -1;
-            }
-
-            if (len > 0) {
-                sqlBuffer.append('(').append(len);
-
-                if (scale >= 0) {
-                    sqlBuffer.append(", ").append(scale);
-                }
-
-                sqlBuffer.append(')');
-            }
-        }
+        sqlBuffer.append(JdbcAdapter.getType(adapter, columnNew));
+        sqlBuffer.append(JdbcAdapter.sizeAndPrecision(adapter, columnNew));
 
         return Collections.singletonList(sqlBuffer.toString());
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/util/EntityMergeSupport.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/EntityMergeSupport.java b/cayenne-server/src/main/java/org/apache/cayenne/util/EntityMergeSupport.java
index 1b1fa90..e1555eb 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/util/EntityMergeSupport.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/util/EntityMergeSupport.java
@@ -46,20 +46,21 @@ import org.apache.cayenne.map.naming.ObjectNameGenerator;
  */
 public class EntityMergeSupport {
 
-    private static final Map<String, String> CLASS_TO_PRIMITVE;
+    private static final Map<String, String> CLASS_TO_PRIMITIVE;
 
     static {
-        CLASS_TO_PRIMITVE = new HashMap<String, String>();
-        CLASS_TO_PRIMITVE.put(Byte.class.getName(), "byte");
-        CLASS_TO_PRIMITVE.put(Long.class.getName(), "long");
-        CLASS_TO_PRIMITVE.put(Double.class.getName(), "double");
-        CLASS_TO_PRIMITVE.put(Boolean.class.getName(), "boolean");
-        CLASS_TO_PRIMITVE.put(Float.class.getName(), "float");
-        CLASS_TO_PRIMITVE.put(Short.class.getName(), "short");
-        CLASS_TO_PRIMITVE.put(Integer.class.getName(), "int");
+        CLASS_TO_PRIMITIVE = new HashMap<String, String>();
+        CLASS_TO_PRIMITIVE.put(Byte.class.getName(), "byte");
+        CLASS_TO_PRIMITIVE.put(Long.class.getName(), "long");
+        CLASS_TO_PRIMITIVE.put(Double.class.getName(), "double");
+        CLASS_TO_PRIMITIVE.put(Boolean.class.getName(), "boolean");
+        CLASS_TO_PRIMITIVE.put(Float.class.getName(), "float");
+        CLASS_TO_PRIMITIVE.put(Short.class.getName(), "short");
+        CLASS_TO_PRIMITIVE.put(Integer.class.getName(), "int");
     }
 
-    protected DataMap map;
+    private final DataMap map;
+
     protected boolean removeMeaningfulFKs;
     protected boolean removeMeaningfulPKs;
     protected boolean usePrimitives;
@@ -67,12 +68,12 @@ public class EntityMergeSupport {
     /**
      * Strategy for choosing names for entities, attributes and relationships
      */
-    protected ObjectNameGenerator nameGenerator;
+    private final ObjectNameGenerator nameGenerator;
 
     /**
      * Listeners of merge process.
      */
-    protected List<EntityMergeListener> listeners;
+    private final List<EntityMergeListener> listeners = new ArrayList<EntityMergeListener>();
 
     public EntityMergeSupport(DataMap map) {
         this(map, new LegacyNameGenerator(), true);
@@ -83,10 +84,9 @@ public class EntityMergeSupport {
      */
     public EntityMergeSupport(DataMap map, ObjectNameGenerator nameGenerator, boolean removeMeaningfulPKs) {
         this.map = map;
+        this.nameGenerator = nameGenerator;
         this.removeMeaningfulFKs = true;
-        this.listeners = new ArrayList<EntityMergeListener>();
         this.removeMeaningfulPKs = removeMeaningfulPKs;
-        this.nameGenerator = nameGenerator;
 
         /**
          * Adding a listener, so that all created ObjRelationships would have
@@ -102,7 +102,7 @@ public class EntityMergeSupport {
      * @return true if any ObjEntity has changed as a result of synchronization.
      * @since 1.2 changed signature to use Collection instead of List.
      */
-    public boolean synchronizeWithDbEntities(Collection<ObjEntity> objEntities) {
+    public boolean synchronizeWithDbEntities(Iterable<ObjEntity> objEntities) {
         boolean changed = false;
         for (ObjEntity nextEntity : objEntities) {
             if (synchronizeWithDbEntity(nextEntity)) {
@@ -147,71 +147,79 @@ public class EntityMergeSupport {
         boolean changed = false;
 
         // synchronization on DataMap is some (weak) protection
-        // against simultaneous modification of the map (like double-clicking on
-        // sync
-        // button)
+        // against simultaneous modification of the map (like double-clicking on sync button)
         synchronized (map) {
 
             if (removeFK(dbEntity)) {
-
-                // get rid of attributes that are now src attributes for
-                // relationships
-                for (DbAttribute da : getMeaningfulFKs(entity)) {
-                    ObjAttribute oa = entity.getAttributeForDbAttribute(da);
-                    while (oa != null) {
-                        String attrName = oa.getName();
-                        entity.removeAttribute(attrName);
-                        changed = true;
-                        oa = entity.getAttributeForDbAttribute(da);
-                    }
-                }
+                changed = getRidOfAttributesThatAreNowSrcAttributesForRelationships(entity);
             }
 
-            // add missing attributes
-            for (DbAttribute da : getAttributesToAdd(entity)) {
+            changed |= addMissingAttributes(entity);
+            changed |= addMissingRelationships(entity);
+        }
 
-                String attrName = nameGenerator.createObjAttributeName(da);
-                // avoid duplicate names
-                attrName = DefaultUniqueNameGenerator.generate(NameCheckers.objAttribute, entity, attrName);
+        return changed;
+    }
 
-                String type = TypesMapping.getJavaBySqlType(da.getType());
+    private boolean addMissingRelationships(ObjEntity entity) {
+        boolean changed = false;
+        for (DbRelationship dr : getRelationshipsToAdd(entity)) {
+            DbEntity targetEntity = dr.getTargetEntity();
 
-                if (usePrimitives) {
-                    String primitive = CLASS_TO_PRIMITVE.get(type);
-                    if (primitive != null) {
-                        type = primitive;
-                    }
-                }
+            for (Entity mappedTarget : map.getMappedEntities(targetEntity)) {
 
-                ObjAttribute oa = new ObjAttribute(attrName, type, entity);
-                oa.setDbAttributePath(da.getName());
-                entity.addAttribute(oa);
-                fireAttributeAdded(oa);
-                changed = true;
-            }
+                // avoid duplicate names
+                String relationshipName = nameGenerator.createObjRelationshipName(dr);
+                relationshipName = DefaultUniqueNameGenerator.generate(NameCheckers.objRelationship, entity, relationshipName);
 
-            // add missing relationships
-            for (DbRelationship dr : getRelationshipsToAdd(entity)) {
-                DbEntity targetEntity = (DbEntity) dr.getTargetEntity();
+                ObjRelationship or = new ObjRelationship(relationshipName);
+                or.addDbRelationship(dr);
+                or.setSourceEntity(entity);
+                or.setTargetEntity(mappedTarget);
+                entity.addRelationship(or);
 
-                for (Entity mappedTarget : map.getMappedEntities(targetEntity)) {
+                fireRelationshipAdded(or);
+                changed = true;
+            }
+        }
+        return changed;
+    }
 
-                    // avoid duplicate names
-                    String relationshipName = nameGenerator.createObjRelationshipName(dr);
-                    relationshipName = DefaultUniqueNameGenerator.generate(NameCheckers.objRelationship, entity, relationshipName);
+    private boolean addMissingAttributes(ObjEntity entity) {
+        boolean changed = false;
+        for (DbAttribute da : getAttributesToAdd(entity)) {
 
-                    ObjRelationship or = new ObjRelationship(relationshipName);
-                    or.addDbRelationship(dr);
-                    or.setSourceEntity(entity);
-                    or.setTargetEntity(mappedTarget);
-                    entity.addRelationship(or);
+            String attrName = DefaultUniqueNameGenerator.generate(NameCheckers.objAttribute, entity,
+                    nameGenerator.createObjAttributeName(da));
 
-                    fireRelationshipAdded(or);
-                    changed = true;
+            String type = TypesMapping.getJavaBySqlType(da.getType());
+            if (usePrimitives) {
+                String primitive = CLASS_TO_PRIMITIVE.get(type);
+                if (primitive != null) {
+                    type = primitive;
                 }
             }
+
+            ObjAttribute oa = new ObjAttribute(attrName, type, entity);
+            oa.setDbAttributePath(da.getName());
+            entity.addAttribute(oa);
+            fireAttributeAdded(oa);
+            changed = true;
         }
+        return changed;
+    }
 
+    private boolean getRidOfAttributesThatAreNowSrcAttributesForRelationships(ObjEntity entity) {
+        boolean changed = false;
+        for (DbAttribute da : getMeaningfulFKs(entity)) {
+            ObjAttribute oa = entity.getAttributeForDbAttribute(da);
+            while (oa != null) {
+                String attrName = oa.getName();
+                entity.removeAttribute(attrName);
+                changed = true;
+                oa = entity.getAttributeForDbAttribute(da);
+            }
+        }
         return changed;
     }
 
@@ -249,16 +257,11 @@ public class EntityMergeSupport {
 
         for (DbAttribute dba : dbEntity.getAttributes()) {
 
-            if (dba.getName() == null) {
-                continue;
-            }
-
-            if (objEntity.getAttributeForDbAttribute(dba) != null) {
+            if (dba.getName() == null || objEntity.getAttributeForDbAttribute(dba) != null) {
                 continue;
             }
 
             boolean removeMeaningfulPKs = removePK(dbEntity);
-
             if (removeMeaningfulPKs && dba.isPrimaryKey()) {
                 continue;
             }
@@ -330,28 +333,20 @@ public class EntityMergeSupport {
 
     protected List<DbRelationship> getRelationshipsToAdd(ObjEntity objEntity) {
         List<DbRelationship> missing = new ArrayList<DbRelationship>();
-        for (DbRelationship dbrel : objEntity.getDbEntity().getRelationships()) {
+        for (DbRelationship dbRel : objEntity.getDbEntity().getRelationships()) {
             // check if adding it makes sense at all
-            if (dbrel.getName() == null) {
+            if (dbRel.getName() == null) {
                 continue;
             }
 
-            if (objEntity.getRelationshipForDbRelationship(dbrel) == null) {
-                missing.add(dbrel);
+            if (objEntity.getRelationshipForDbRelationship(dbRel) == null) {
+                missing.add(dbRel);
             }
         }
 
         return missing;
     }
 
-    public DataMap getMap() {
-        return map;
-    }
-
-    public void setMap(DataMap map) {
-        this.map = map;
-    }
-
     /**
      * @since 1.2
      */
@@ -391,8 +386,8 @@ public class EntityMergeSupport {
      * Notifies all listeners that an ObjAttribute was added
      */
     protected void fireAttributeAdded(ObjAttribute attr) {
-        for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).objAttributeAdded(attr);
+        for (EntityMergeListener listener : listeners) {
+            listener.objAttributeAdded(attr);
         }
     }
 
@@ -400,19 +395,12 @@ public class EntityMergeSupport {
      * Notifies all listeners that an ObjRelationship was added
      */
     protected void fireRelationshipAdded(ObjRelationship rel) {
-        for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).objRelationshipAdded(rel);
+        for (EntityMergeListener listener : listeners) {
+            listener.objRelationshipAdded(rel);
         }
     }
 
     /**
-     * Sets new naming strategy for reverse engineering
-     */
-    public void setNameGenerator(ObjectNameGenerator strategy) {
-        this.nameGenerator = strategy;
-    }
-
-    /**
      * @return naming strategy for reverse engineering
      */
     public ObjectNameGenerator getNameGenerator() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderPartialTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderPartialTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderPartialTest.java
index 72d6fce..8b75c37 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderPartialTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderPartialTest.java
@@ -26,7 +26,7 @@ import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.access.loader.DefaultDbLoaderDelegate;
 import org.apache.cayenne.unit.di.server.ServerCase;
 import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
 import org.apache.cayenne.unit.di.server.UseServerRuntime;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderTest.java
index b8898c4..baf484e 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderTest.java
@@ -23,6 +23,7 @@ import java.sql.Types;
 import java.util.Collection;
 import java.util.List;
 
+import org.apache.cayenne.access.loader.DbLoaderConfiguration;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.TypesMapping;
@@ -41,6 +42,7 @@ import org.apache.cayenne.unit.di.server.UseServerRuntime;
 @UseServerRuntime(ServerCase.TESTMAP_PROJECT)
 public class DbLoaderTest extends ServerCase {
 
+    public static final DbLoaderConfiguration CONFIG = new DbLoaderConfiguration();
     @Inject
     private ServerRuntime runtime;
 
@@ -86,7 +88,7 @@ public class DbLoaderTest extends ServerCase {
 
         String tableLabel = adapter.tableTypeForTable();
 
-        List<DbEntity> tables = loader.getTables(null, null, "%", new String[] { tableLabel });
+        List<DbEntity> tables = loader.getTables(new DbLoaderConfiguration(), new String[] { tableLabel });
 
         assertNotNull(tables);
 
@@ -105,20 +107,21 @@ public class DbLoaderTest extends ServerCase {
     public void testLoadWithMeaningfulPK() throws Exception {
 
         DataMap map = new DataMap();
-        String tableLabel = adapter.tableTypeForTable();
+        String[] tableLabel = { adapter.tableTypeForTable() };
 
         loader.setCreatingMeaningfulPK(true);
 
-        List<DbEntity> testLoader = loader.getTables(null, null, "artist", new String[] { tableLabel });
-        if (testLoader.size() == 0) {
-            testLoader = loader.getTables(null, null, "ARTIST", new String[] { tableLabel });
+        List<DbEntity> testLoader = loader.getTables(CONFIG, tableLabel);
+        if (testLoader.isEmpty()) {
+            testLoader = loader.getTables(CONFIG, tableLabel);
         }
 
-        loader.loadDbEntities(map, testLoader);
+        List<DbEntity> entities = loader.loadDbEntities(map, CONFIG, testLoader);
+        loader.loadObjEntities(map, CONFIG, entities);
 
-        loader.loadObjEntities(map);
         ObjEntity artist = map.getObjEntity("Artist");
         assertNotNull(artist);
+
         ObjAttribute id = artist.getAttribute("artistId");
         assertNotNull(id);
     }
@@ -141,7 +144,7 @@ public class DbLoaderTest extends ServerCase {
         String tableLabel = adapter.tableTypeForTable();
 
         // *** TESTING THIS ***
-        loader.loadDbEntities(map, loader.getTables(null, null, "%", new String[] { tableLabel }));
+        List<DbEntity> entities = loader.loadDbEntities(map, CONFIG, loader.getTables(CONFIG, new String[]{tableLabel}));
 
         assertDbEntities(map);
 
@@ -150,12 +153,12 @@ public class DbLoaderTest extends ServerCase {
         }
 
         // *** TESTING THIS ***
-        loader.loadDbRelationships(map);
+        loader.loadDbRelationships(map, CONFIG, entities);
 
         if (supportsFK) {
             Collection<DbRelationship> rels = getDbEntity(map, "ARTIST").getRelationships();
             assertNotNull(rels);
-            assertTrue(rels.size() > 0);
+            assertTrue(!rels.isEmpty());
 
             // test one-to-one
             rels = getDbEntity(map, "PAINTING").getRelationships();
@@ -182,7 +185,7 @@ public class DbLoaderTest extends ServerCase {
 
         // *** TESTING THIS ***
         loader.setCreatingMeaningfulPK(false);
-        loader.loadObjEntities(map);
+        loader.loadObjEntities(map, CONFIG, entities);
 
         assertObjEntities(map);
 
@@ -277,7 +280,7 @@ public class DbLoaderTest extends ServerCase {
         assertNotNull(blobEnt);
         // BLOBs should be mapped as byte[]
         ObjAttribute blobAttr = blobEnt.getAttribute("blobCol");
-        assertNotNull("BlobTest.blobCol failed to load", blobAttr);
+        assertNotNull("BlobTest.blobCol failed to doLoad", blobAttr);
         assertEquals("byte[]", blobAttr.getType());
         ObjEntity clobEnt = map.getObjEntity("ClobTest");
         assertNotNull(clobEnt);
@@ -359,19 +362,200 @@ public class DbLoaderTest extends ServerCase {
         }
     }
 
-    private String msgForTypeMismatch(DbAttribute origAttr, DbAttribute newAttr) {
+    private static String msgForTypeMismatch(DbAttribute origAttr, DbAttribute newAttr) {
         return msgForTypeMismatch(origAttr.getType(), newAttr);
     }
 
-    private String msgForTypeMismatch(int origType, DbAttribute newAttr) {
+    private static String msgForTypeMismatch(int origType, DbAttribute newAttr) {
         String nt = TypesMapping.getSqlNameByType(newAttr.getType());
         String ot = TypesMapping.getSqlNameByType(origType);
         return attrMismatch(newAttr.getName(), "expected type: <" + ot + ">, but was <" + nt + ">");
     }
 
-    private String attrMismatch(String attrName, String msg) {
-        StringBuffer buf = new StringBuffer();
-        buf.append("[Error loading attribute '").append(attrName).append("': ").append(msg).append("]");
-        return buf.toString();
+    private static String attrMismatch(String attrName, String msg) {
+        return "[Error loading attribute '" + attrName + "': " + msg + "]";
+    }
+
+/*
+    TODO
+
+    @Test
+    public void testCreateLoader() throws Exception {
+
+        DbLoader loader = parameters.createLoader(mock(DbAdapter.class), connection,
+                mock(DbLoaderDelegate.class));
+        assertNotNull(loader);
+        assertSame(connection, loader.getConnection());
+
+        assertTrue(loader.includeTableName("dummy"));
+    }
+
+    @Test
+    public void testCreateLoader_IncludeExclude() throws Exception {
+        DbImportConfiguration parameters = new DbImportConfiguration();
+        parameters.setIncludeTables("a,b,c*");
+
+        DbLoader loader1 = parameters.createLoader(mock(DbAdapter.class), mock(Connection.class),
+                mock(DbLoaderDelegate.class));
+
+        assertFalse(loader1.includeTableName("dummy"));
+        assertFalse(loader1.includeTableName("ab"));
+        assertTrue(loader1.includeTableName("a"));
+        assertTrue(loader1.includeTableName("b"));
+        assertTrue(loader1.includeTableName("cd"));
+
+        parameters.setExcludeTables("cd");
+
+        DbLoader loader2 = parameters.createLoader(mock(DbAdapter.class), mock(Connection.class),
+                mock(DbLoaderDelegate.class));
+
+        assertFalse(loader2.includeTableName("dummy"));
+        assertFalse(loader2.includeTableName("ab"));
+        assertTrue(loader2.includeTableName("a"));
+        assertTrue(loader2.includeTableName("b"));
+        assertFalse(loader2.includeTableName("cd"));
+        assertTrue(loader2.includeTableName("cx"));
     }
+
+
+    @Test
+    public void testCreateLoader_MeaningfulPk_Default() throws Exception {
+        DbImportConfiguration parameters = new DbImportConfiguration();
+        assertNull(parameters.getMeaningfulPkTables());
+
+        DbLoader loader1 = parameters.createLoader(mock(DbAdapter.class), mock(Connection.class),
+                mock(DbLoaderDelegate.class));
+
+        DataMap map = new DataMap();
+
+        DbEntity e1 = new DbEntity("e1");
+        DbAttribute pk = new DbAttribute("pk", Types.INTEGER, e1);
+        pk.setPrimaryKey(true);
+        e1.addAttribute(pk);
+        DbAttribute nonPk = new DbAttribute("nonPk", Types.INTEGER, e1);
+        e1.addAttribute(nonPk);
+
+        map.addDbEntity(e1);
+
+        // DbLoader is so ugly and hard to test..
+        Field dbEntityList = DbLoader.class.getDeclaredField("dbEntityList");
+        dbEntityList.setAccessible(true);
+        List<DbEntity> entities = (List<DbEntity>) dbEntityList.get(loader1);
+        entities.add(e1);
+
+        loader1.loadObjEntities(map, entities);
+
+        ObjEntity oe1 = map.getObjEntity("E1");
+        assertEquals(1, oe1.getAttributes().size());
+        assertNotNull(oe1.getAttribute("nonPk"));
+    }
+
+    @Test
+    public void testCreateLoader_MeaningfulPk_Specified() throws Exception {
+        DbImportConfiguration parameters = new DbImportConfiguration();
+        parameters.setMeaningfulPkTables("a*");
+
+        DbLoader loader1 = parameters.createLoader(mock(DbAdapter.class), mock(Connection.class),
+                mock(DbLoaderDelegate.class));
+
+        // DbLoader is so ugly and hard to test..
+        Field dbEntityList = DbLoader.class.getDeclaredField("dbEntityList");
+        dbEntityList.setAccessible(true);
+        Collection<DbEntity> entities = (List<DbEntity>) dbEntityList.get(loader1);
+
+        DataMap map = new DataMap();
+
+        DbEntity e1 = new DbEntity("e1");
+        DbAttribute pk = new DbAttribute("pk", Types.INTEGER, e1);
+        pk.setPrimaryKey(true);
+        e1.addAttribute(pk);
+        DbAttribute nonPk = new DbAttribute("nonPk", Types.INTEGER, e1);
+        e1.addAttribute(nonPk);
+
+        map.addDbEntity(e1);
+        entities.add(e1);
+
+        DbEntity a1 = new DbEntity("a1");
+        DbAttribute apk = new DbAttribute("pk", Types.INTEGER, a1);
+        apk.setPrimaryKey(true);
+        a1.addAttribute(apk);
+        DbAttribute anonPk = new DbAttribute("nonPk", Types.INTEGER, a1);
+        a1.addAttribute(anonPk);
+
+        map.addDbEntity(a1);
+        entities.add(a1);
+
+        loader1.loadObjEntities(map, entities);
+
+        ObjEntity oe1 = map.getObjEntity("E1");
+        assertEquals(1, oe1.getAttributes().size());
+        assertNotNull(oe1.getAttribute("nonPk"));
+
+        ObjEntity oe2 = map.getObjEntity("A1");
+        assertEquals(2, oe2.getAttributes().size());
+        assertNotNull(oe2.getAttribute("nonPk"));
+        assertNotNull(oe2.getAttribute("pk"));
+    }
+
+    @Test
+    public void testCreateLoader_UsePrimitives_False() throws Exception {
+        DbImportConfiguration parameters = new DbImportConfiguration();
+        parameters.setUsePrimitives(false);
+
+        DbLoader loader1 = parameters.createLoader(mock(DbAdapter.class), mock(Connection.class),
+                mock(DbLoaderDelegate.class));
+
+        DataMap map = new DataMap();
+
+        DbEntity e1 = new DbEntity("e1");
+        DbAttribute nonPk = new DbAttribute("nonPk", Types.INTEGER, e1);
+        e1.addAttribute(nonPk);
+
+        map.addDbEntity(e1);
+
+        // DbLoader is so ugly and hard to test..
+        Field dbEntityList = DbLoader.class.getDeclaredField("dbEntityList");
+        dbEntityList.setAccessible(true);
+        List<DbEntity> entities = (List<DbEntity>) dbEntityList.get(loader1);
+        entities.add(e1);
+
+        loader1.loadObjEntities(map, entities);
+
+        ObjEntity oe1 = map.getObjEntity("E1");
+
+        ObjAttribute oa1 = oe1.getAttribute("nonPk");
+        assertEquals("java.lang.Integer", oa1.getType());
+    }
+
+    @Test
+    public void testCreateLoader_UsePrimitives_True() throws Exception {
+        DbImportConfiguration parameters = new DbImportConfiguration();
+        parameters.setUsePrimitives(true);
+
+        DbLoader loader1 = parameters.createLoader(mock(DbAdapter.class), mock(Connection.class),
+                mock(DbLoaderDelegate.class));
+
+        DataMap map = new DataMap();
+
+        DbEntity e1 = new DbEntity("e1");
+        DbAttribute nonPk = new DbAttribute("nonPk", Types.INTEGER, e1);
+        e1.addAttribute(nonPk);
+
+        map.addDbEntity(e1);
+
+        // DbLoader is so ugly and hard to test..
+        Field dbEntityList = DbLoader.class.getDeclaredField("dbEntityList");
+        dbEntityList.setAccessible(true);
+        List<DbEntity> entities = (List<DbEntity>) dbEntityList.get(loader1);
+        entities.add(e1);
+
+        loader1.loadObjEntities(map, entities);
+
+        ObjEntity oe1 = map.getObjEntity("E1");
+
+        ObjAttribute oa1 = oe1.getAttribute("nonPk");
+        assertEquals("int", oa1.getType());
+    }
+*/
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/java/org/apache/cayenne/access/ManyToManyCandidateEntityTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/ManyToManyCandidateEntityTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/ManyToManyCandidateEntityTest.java
deleted file mode 100644
index 7be3c3d..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/ManyToManyCandidateEntityTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*****************************************************************
- *   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 junit.framework.TestCase;
-
-import org.apache.cayenne.configuration.ConfigurationNameMapper;
-import org.apache.cayenne.configuration.ConfigurationTree;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.configuration.DataMapLoader;
-import org.apache.cayenne.configuration.DefaultConfigurationNameMapper;
-import org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader;
-import org.apache.cayenne.configuration.XMLDataMapLoader;
-import org.apache.cayenne.di.AdhocObjectFactory;
-import org.apache.cayenne.di.Binder;
-import org.apache.cayenne.di.ClassLoaderManager;
-import org.apache.cayenne.di.DIBootstrap;
-import org.apache.cayenne.di.Injector;
-import org.apache.cayenne.di.Module;
-import org.apache.cayenne.di.spi.DefaultAdhocObjectFactory;
-import org.apache.cayenne.di.spi.DefaultClassLoaderManager;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.Relationship;
-import org.apache.cayenne.map.naming.LegacyNameGenerator;
-import org.apache.cayenne.resource.URLResource;
-
-import java.net.URL;
-import java.util.ArrayList;
-
-public class ManyToManyCandidateEntityTest extends TestCase {
-
-    private DataMap map;
-
-    @Override
-    public void setUp() throws Exception {
-        Module testModule = new Module() {
-
-            public void configure(Binder binder) {
-                binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class);
-                binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
-                binder.bind(DataMapLoader.class).to(XMLDataMapLoader.class);
-                binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
-            }
-        };
-
-        Injector injector = DIBootstrap.createInjector(testModule);
-
-        // create and initialize loader instance to test
-        XMLDataChannelDescriptorLoader loader = new XMLDataChannelDescriptorLoader();
-        injector.injectMembers(loader);
-
-        String testConfigName = "relationship-optimisation";
-        URL url = getClass().getResource("cayenne-" + testConfigName + ".xml");
-
-        ConfigurationTree<DataChannelDescriptor> tree = loader.load(new URLResource(url));
-
-        map = tree.getRootNode().getDataMap(testConfigName);
-    }
-
-    public void testMatchingForManyToManyEntity() throws Exception {
-        ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
-
-        assertNotNull(ManyToManyCandidateEntity.build(manyToManyEntity));
-    }
-
-    public void testMatchingForNotManyToManyEntity() throws Exception {
-        ObjEntity entity = map.getObjEntity("Table1");
-
-        assertNull(ManyToManyCandidateEntity.build(entity));
-    }
-
-    public void testOptimisationForManyToManyEntity() {
-        ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
-
-        ManyToManyCandidateEntity.build(manyToManyEntity).optimizeRelationships(new LegacyNameGenerator());
-
-        ObjEntity table1Entity = map.getObjEntity("Table1");
-        ObjEntity table2Entity = map.getObjEntity("Table2");
-
-        assertEquals(1, table1Entity.getRelationships().size());
-        assertEquals(table2Entity, new ArrayList<Relationship>(table1Entity.getRelationships()).get(0)
-                .getTargetEntity());
-
-        assertEquals(1, table2Entity.getRelationships().size());
-        assertEquals(table1Entity, new ArrayList<Relationship>(table2Entity.getRelationships()).get(0)
-                .getTargetEntity());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntityTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntityTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntityTest.java
new file mode 100644
index 0000000..4348edf
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntityTest.java
@@ -0,0 +1,106 @@
+/*****************************************************************
+ *   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.loader;
+
+import junit.framework.TestCase;
+
+import org.apache.cayenne.configuration.ConfigurationNameMapper;
+import org.apache.cayenne.configuration.ConfigurationTree;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DataMapLoader;
+import org.apache.cayenne.configuration.DefaultConfigurationNameMapper;
+import org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader;
+import org.apache.cayenne.configuration.XMLDataMapLoader;
+import org.apache.cayenne.di.AdhocObjectFactory;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.ClassLoaderManager;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.di.spi.DefaultAdhocObjectFactory;
+import org.apache.cayenne.di.spi.DefaultClassLoaderManager;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.Relationship;
+import org.apache.cayenne.map.naming.LegacyNameGenerator;
+import org.apache.cayenne.resource.URLResource;
+
+import java.net.URL;
+import java.util.ArrayList;
+
+public class ManyToManyCandidateEntityTest extends TestCase {
+
+    private DataMap map;
+
+    @Override
+    public void setUp() throws Exception {
+        Module testModule = new Module() {
+
+            public void configure(Binder binder) {
+                binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class);
+                binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
+                binder.bind(DataMapLoader.class).to(XMLDataMapLoader.class);
+                binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
+            }
+        };
+
+        Injector injector = DIBootstrap.createInjector(testModule);
+
+        // create and initialize loader instance to test
+        XMLDataChannelDescriptorLoader loader = new XMLDataChannelDescriptorLoader();
+        injector.injectMembers(loader);
+
+        String testConfigName = "relationship-optimisation";
+        URL url = getClass().getResource("cayenne-" + testConfigName + ".xml");
+
+        ConfigurationTree<DataChannelDescriptor> tree = loader.load(new URLResource(url));
+
+        map = tree.getRootNode().getDataMap(testConfigName);
+    }
+
+    public void testMatchingForManyToManyEntity() throws Exception {
+        ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
+
+        assertNotNull(ManyToManyCandidateEntity.build(manyToManyEntity));
+    }
+
+    public void testMatchingForNotManyToManyEntity() throws Exception {
+        ObjEntity entity = map.getObjEntity("Table1");
+
+        assertNull(ManyToManyCandidateEntity.build(entity));
+    }
+
+    public void testOptimisationForManyToManyEntity() {
+        ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
+
+        ManyToManyCandidateEntity.build(manyToManyEntity).optimizeRelationships(new LegacyNameGenerator());
+
+        ObjEntity table1Entity = map.getObjEntity("Table1");
+        ObjEntity table2Entity = map.getObjEntity("Table2");
+
+        assertEquals(1, table1Entity.getRelationships().size());
+        assertEquals(table2Entity, new ArrayList<Relationship>(table1Entity.getRelationships()).get(0)
+                .getTargetEntity());
+
+        assertEquals(1, table2Entity.getRelationships().size());
+        assertEquals(table1Entity, new ArrayList<Relationship>(table2Entity.getRelationships()).get(0)
+                .getTargetEntity());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/DbPathTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/DbPathTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/DbPathTest.java
new file mode 100644
index 0000000..60d4933
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/DbPathTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.loader.filters;
+
+import org.junit.Test;
+
+import static org.apache.cayenne.access.loader.filters.FiltersFactory.path;
+import static org.junit.Assert.*;
+
+public class DbPathTest {
+
+    @Test
+    public void testIsCover() throws Exception {
+        assertTrue(path(null, null).isCover(path("Hello", "World")));
+        assertTrue(path(null, null).isCover(path("Hello", null)));
+        assertTrue(path(null, null).isCover(path(null, null)));
+        assertTrue(path(null, "Yo").isCover(path("Yo!", "Yo")));
+        assertTrue(path(null, "Yo").isCover(path(null, "Yo")));
+
+        assertFalse(path(null, "Yo!").isCover(path(null, "Yo!!")));
+        assertFalse(path("aa", "Yo!").isCover(path(null, "Yo!!")));
+        assertFalse(path("aaa", "Yo!").isCover(path("aa", "Yo!!")));
+
+        assertTrue(path("aa", null).isCover(path("aa", null)));
+        assertTrue(path("aa", null).isCover(path("aa", "bb")));
+        assertTrue(path("aa", "Yo!").isCover(path("aa", "Yo!")));
+        assertFalse(path("aa", "Yo!").isCover(path("aa", "Yo!!")));
+
+        assertFalse(path("", "APP").isCover(path(null, null)));
+
+        assertTrue(path(null, "schema_01").isCover(path("", "schema_01")));
+        assertTrue(path(null, "schema_01").isCover(path(null, "schema_01")));
+        assertFalse(path(null, "schema_01").isCover(path("", "schema_02")));
+        assertFalse(path(null, "schema_02").isCover(path("", "schema_01")));
+    }
+
+    @Test
+    public void testToString() throws Exception {
+        assertEquals("%", path(null, null).toString());
+        assertEquals("/schema", path("", "schema").toString());
+        assertEquals("%/schema", path(null, "schema").toString());
+        assertEquals("catalog/schema", path("catalog", "schema").toString());
+        assertEquals("catalog//table", path("catalog", "", "table").toString());
+        assertEquals("catalog/%/table", path("catalog", null, "table").toString());
+        assertEquals("//table", path("", "", "table").toString());
+        assertEquals("%/%/table", path(null, null, "table").toString());
+        assertEquals("%", path(null, null, null).toString());
+        assertEquals("c/%/", path("c", null, "").toString());
+    }
+
+    @Test
+    public void testMerge() throws Exception {
+        DbPath path1 = path(null, null);
+        DbPath path2 = path("", "APP");
+        assertEquals(path1, path1.merge(path2));
+        assertEquals(path1, path2.merge(path1));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/EntityFiltersTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/EntityFiltersTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/EntityFiltersTest.java
new file mode 100644
index 0000000..5b936ac
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/EntityFiltersTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.loader.filters;
+
+import org.junit.Test;
+
+import static org.apache.cayenne.access.loader.filters.FilterFactory.*;
+import static org.junit.Assert.*;
+
+public class EntityFiltersTest {
+
+    @Test
+    public void testJoinWithEmpty() throws Exception {
+        EntityFilters filter1 = new EntityFilters(null, null, null, null);
+        EntityFilters filter2 = new EntityFilters(null, include("table"), include("column"), include("procedure"));
+
+        assertEquals(filter2, filter1.join(filter2));
+        assertEquals(filter2, filter2.join(filter1));
+    }
+
+    @Test
+    public void testJoinExcludeInclude() throws Exception {
+        EntityFilters filter1 = new EntityFilters(null, exclude("table"), exclude("column"), exclude("procedure"));
+        EntityFilters filter2 = new EntityFilters(null, include("table"), include("column"), include("procedure"));
+
+        assertEquals(new EntityFilters(null,
+                        list(exclude("table"), include("table")),
+                        list(exclude("column"), include("column")),
+                        list(exclude("procedure"), include("procedure"))),
+                filter1.join(filter2));
+        assertEquals(new EntityFilters(null,
+                        list(include("table"), exclude("table")),
+                        list(include("column"), exclude("column")),
+                        list(include("procedure"), exclude("procedure"))),
+                filter2.join(filter1));
+    }
+
+    @Test
+    public void testEquals() throws Exception {
+        EntityFilters filters = new EntityFilters(new DbPath(), NULL, NULL, NULL);
+        assertTrue(filters.tableFilter().equals(NULL));
+        assertTrue(filters.columnFilter().equals(NULL));
+        assertTrue(filters.procedureFilter().equals(NULL));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/FiltersConfigTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/FiltersConfigTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/FiltersConfigTest.java
new file mode 100644
index 0000000..c6097ec
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/FiltersConfigTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.loader.filters;
+
+import org.junit.Test;
+
+import java.util.Iterator;
+import java.util.List;
+
+import static org.apache.cayenne.access.loader.filters.FilterFactory.*;
+import static org.apache.cayenne.access.loader.filters.FiltersFactory.path;
+import static org.apache.cayenne.access.loader.filters.FiltersFactory.eFilters;
+import static org.junit.Assert.*;
+
+public class FiltersConfigTest {
+
+    @Test
+    public void testSorting() throws Exception {
+        FiltersConfig filters = new FiltersConfig(
+                entityFilter("", ""),
+                entityFilter("aaa", ""),
+                entityFilter("aa", ""),
+                entityFilter("aa", "a"),
+                entityFilter("aa", "aa"),
+                entityFilter("aa", "aa"),
+                entityFilter("b", "b")
+        );
+
+        Iterator<DbPath> iterator = filters.getDbPaths().iterator();
+        assertEquals(path("", ""), iterator.next());
+        assertEquals(path("aa", ""), iterator.next());
+        assertEquals(path("aa", "a"), iterator.next());
+        assertEquals(path("aa", "aa"), iterator.next());
+        assertEquals(path("aaa", ""), iterator.next());
+        assertEquals(path("b", "b"), iterator.next());
+    }
+
+    private EntityFilters entityFilter(String s, String s1) {
+        return new EntityFilters(new DbPath(s, s1), include("IncludeTable"), TRUE, TRUE);
+    }
+
+    @Test
+    public void testActionsWithEmptyCatalog() throws Exception {
+        FiltersConfig filters = new FiltersConfig(
+                entityFilter(null, null),
+                entityFilter("aaa", null),
+                entityFilter("aa", null)
+        );
+
+        List<DbPath> actions = filters.pathsForQueries();
+        assertEquals(1L, actions.size());
+        assertEquals(path(), actions.get(0));
+    }
+
+    @Test
+    public void testActionsWithEmptySchemas() throws Exception {
+        FiltersConfig filters = new FiltersConfig(
+                entityFilter("aaa", null),
+                entityFilter("aaa", "11"),
+                entityFilter("aa", null),
+                entityFilter("aa", "a"),
+                entityFilter("aa", "aa"),
+                entityFilter("aa", "aa")
+        );
+
+
+        List<DbPath> actions = filters.pathsForQueries();
+        assertEquals(2L, actions.size());
+        assertEquals(path("aa", null), actions.get(0));
+        assertEquals(path("aaa", null), actions.get(1));
+    }
+
+    @Test
+    public void testActionsWithSchemas() throws Exception {
+        FiltersConfig filters = new FiltersConfig(
+                entityFilter("aaa", ""),
+                entityFilter("aa", "a"),
+                entityFilter("aa", "aa"),
+                entityFilter("aa", "aa"),
+                entityFilter("aa", "b"),
+                entityFilter("aa", "b"),
+                entityFilter("aa", "b")
+        );
+
+
+        List<DbPath> actions = filters.pathsForQueries();
+        assertEquals(4L, actions.size());
+        assertEquals(path("aa", "a"), actions.get(0));
+        assertEquals(path("aa", "aa"), actions.get(1));
+        assertEquals(path("aa", "b"), actions.get(2));
+        assertEquals(path("aaa", ""), actions.get(3));
+    }
+
+    @Test
+    public void testActionsWithSchemasAndEmptyCatalog() throws Exception {
+        FiltersConfig filters = new FiltersConfig(
+                entityFilter("", "a"),
+                entityFilter("", "aa"),
+                entityFilter("", "aa"),
+                entityFilter("", "b"),
+                entityFilter("", "b"),
+                entityFilter("", "b")
+        );
+
+
+        List<DbPath> actions = filters.pathsForQueries();
+        assertEquals(3L, actions.size());
+        assertEquals(path("", "b"), actions.get(2));
+        assertEquals(path("", "aa"), actions.get(1));
+        assertEquals(path("", "a"), actions.get(0));
+
+    }
+
+    @Test
+    public void testFiltersOneFilter() throws Exception {
+        FiltersConfig filters = new FiltersConfig(
+                eFilters(path("", "a"), include("table")),
+                entityFilter("", "aa"),
+                entityFilter("", "aa"),
+                entityFilter("", "b"),
+                entityFilter("", "b"),
+                entityFilter("", "b")
+        );
+
+        assertEquals(eFilters(path("", "a"), include("table")),
+                     filters.filter(path("", "a")));
+    }
+
+    @Test
+    public void testFiltersJoinFilters() throws Exception {
+        FiltersConfig filters = new FiltersConfig(
+                eFilters(path("", "a"), include("table")),
+                eFilters(path("", "a"), exclude("table")),
+                entityFilter("", "aa"),
+                entityFilter("", "aa")
+        );
+
+        assertEquals(eFilters(path("", "a"), list(include("table"), exclude("table"))),
+                filters.filter(path("", "a")));
+
+        assertEquals(entityFilter("", "aa"), filters.filter(path("", "aa")));
+    }
+
+    @Test
+    public void testFiltersJoinFiltersWithNull() throws Exception {
+        FiltersConfig filters = new FiltersConfig(
+                eFilters(path("", "a"), include("table")),
+                eFilters(path("", "a"), exclude("table")),
+                eFilters(path("", "a"), null)
+        );
+
+        assertEquals(eFilters(path("", "a"), list(include("table"), exclude("table"))),
+                     filters.filter(path("", "a")));
+    }
+
+    @Test
+    public void testFiltersTopLevelTables() throws Exception {
+        FiltersConfig filters = new FiltersConfig(
+            eFilters(path(null, null), include("TableName"))
+        );
+
+        assertEquals(eFilters(path(null, null), include("TableName")),
+                     filters.filter(path("", "APP")));
+    }
+
+    @Test
+    public void testFiltersFor2Schemas() throws Exception {
+        FiltersConfig filters = new FiltersConfig(
+                eFilters(path(null, "schema_01"), include("TableName_01")),
+                eFilters(path(null, "schema_02"), include("TableName_01"))
+        );
+
+        assertEquals(
+                eFilters(path(null, "schema_01"), include("TableName_01")),
+                filters.filter(path("", "schema_01")));
+
+        assertEquals("In case we don't have filter that cover path we should return null filter ",
+                eFilters(path("", "app"), null),
+                filters.filter(path("", "app")));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/FiltersFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/FiltersFactory.java b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/FiltersFactory.java
new file mode 100644
index 0000000..1ef7428
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/FiltersFactory.java
@@ -0,0 +1,44 @@
+/*
+ * 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.loader.filters;
+
+/**
+ * @since 3.2.
+ */
+public class FiltersFactory {
+
+    public static DbPath path(String catalog, String schema, String table) {
+        return new DbPath(catalog, schema, table);
+    }
+
+    public static DbPath path(String catalog, String schema) {
+        return new DbPath(catalog, schema);
+    }
+
+    public static DbPath path() {
+        return new DbPath();
+    }
+
+    public static EntityFilters eFilters(DbPath path, Filter<String> tableFilter) {
+        return new EntityFilters(path, tableFilter, null, null);
+    }
+    public static EntityFilters eFilters(DbPath path, Filter<String> table, Filter<String> column, Filter<String> proc) {
+        return new EntityFilters(path, table, column, proc);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/java/org/apache/cayenne/access/loader/mapper/DbTypeTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/mapper/DbTypeTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/mapper/DbTypeTest.java
new file mode 100644
index 0000000..8b2e8d4
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/mapper/DbTypeTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.loader.mapper;
+
+import org.junit.Test;
+
+import java.util.Iterator;
+import java.util.TreeSet;
+
+import static org.junit.Assert.*;
+
+public class DbTypeTest {
+
+    @Test
+    public void testCompareTo() throws Exception {
+        TreeSet<DbType> set = new TreeSet<DbType>();
+        set.add(new DbType("type-01", null, null, null, null));
+        set.add(new DbType("type-02", null, null, null, null));
+        set.add(new DbType("type-02", 1, null, null, null));
+        set.add(new DbType("type-02", 2, null, null, null));
+        set.add(new DbType("type-02", 2, null, null, true));
+        set.add(new DbType("type-02", 2, null, null, false));
+        set.add(new DbType("type-02", 2, null, 5, null));
+        set.add(new DbType("type-02", 2, null, 5, false));
+        set.add(new DbType("type-02", 2, null, 5, true));
+        set.add(new DbType("type-02", null, 8, 5, true));
+        set.add(new DbType("type-02", null, 9, 5, true));
+
+        Iterator<DbType> iterator = set.iterator();
+        assertEquals(new DbType("type-02", 2, null, 5, true), iterator.next());
+        assertEquals(new DbType("type-02", 2, null, 5, false), iterator.next());
+        assertEquals(new DbType("type-02", null, 9, 5, true), iterator.next());
+        assertEquals(new DbType("type-02", null, 8, 5, true), iterator.next());
+        assertEquals(new DbType("type-02", 2, null, 5, null), iterator.next());
+        assertEquals(new DbType("type-02", 2, null, null, true), iterator.next());
+        assertEquals(new DbType("type-02", 2, null, null, false), iterator.next());
+        assertEquals(new DbType("type-02", 2, null, null, null), iterator.next());
+        assertEquals(new DbType("type-02", 1, null, null, null), iterator.next());
+        assertEquals(new DbType("type-02", null, null, null, null), iterator.next());
+        assertEquals(new DbType("type-01", null, null, null, null), iterator.next());
+    }
+
+    @Test
+    public void testCover() throws Exception {
+        DbType typeJava = new DbType("java");
+        assertTrue(typeJava.isCover(typeJava));
+        assertTrue(typeJava.isCover(new DbType("java", 1, 1, 1, null)));
+        assertTrue(typeJava.isCover(new DbType("java", 1, null, null, null)));
+        assertTrue(typeJava.isCover(new DbType("java", null, 1, null, null)));
+        assertTrue(typeJava.isCover(new DbType("java", null, null, 1, null)));
+        assertTrue(typeJava.isCover(new DbType("java", null, null, null, true)));
+        assertTrue(typeJava.isCover(new DbType("java", null, null, null, false)));
+        assertFalse(typeJava.isCover(new DbType("java1", null, null, null, null)));
+
+        DbType typeWithLength = new DbType("java", 1, null, null, null);
+        assertTrue(typeWithLength.isCover(typeWithLength));
+        assertTrue(typeWithLength.isCover(new DbType("java", 1, null, 1, null)));
+        assertTrue(typeWithLength.isCover(new DbType("java", 1, null, 1, true)));
+        assertTrue(typeWithLength.isCover(new DbType("java", 1, null, null, true)));
+        assertTrue(typeWithLength.isCover(new DbType("java", 1, 1, null, true)));
+        assertFalse(typeWithLength.isCover(new DbType("java", 2, null, null, null)));
+        assertFalse(typeWithLength.isCover(new DbType("java", null, null, null, true)));
+        assertFalse(typeWithLength.isCover(new DbType("java1", 2, null, null, null)));
+
+        DbType typeWithLengthAndNotNull = new DbType("java", 1, null, null, true);
+        assertTrue(typeWithLength.isCover(typeWithLengthAndNotNull));
+        assertTrue(typeWithLength.isCover(new DbType("java", 1, null, 1, true)));
+        assertTrue(typeWithLength.isCover(new DbType("java", 1, 1, 1, true)));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/java/org/apache/cayenne/merge/MergeCase.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/MergeCase.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/MergeCase.java
index dc8a349..1305229 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/MergeCase.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/merge/MergeCase.java
@@ -26,6 +26,7 @@ import java.util.Arrays;
 import java.util.List;
 
 import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.loader.DbLoaderConfiguration;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.di.Inject;
@@ -98,7 +99,7 @@ public abstract class MergeCase extends ServerCase {
     }
 
     protected DbMerger createMerger(MergerFactory mergerFactory, ValueForNullProvider valueForNullProvider) {
-        return new DbMerger(mergerFactory, valueForNullProvider, null) {
+        return new DbMerger(mergerFactory, valueForNullProvider) {
 
             @Override
             public boolean includeTableName(String tableName) {
@@ -108,7 +109,7 @@ public abstract class MergeCase extends ServerCase {
     }
 
     protected List<MergerToken> createMergeTokens() {
-        return createMerger(node.getAdapter().mergerFactory()).createMergeTokens(node, map);
+        return createMerger(node.getAdapter().mergerFactory()).createMergeTokens(node, map, new DbLoaderConfiguration());
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/resources/org/apache/cayenne/access/cayenne-relationship-optimisation.xml
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/resources/org/apache/cayenne/access/cayenne-relationship-optimisation.xml b/cayenne-server/src/test/resources/org/apache/cayenne/access/cayenne-relationship-optimisation.xml
deleted file mode 100644
index d4fea49..0000000
--- a/cayenne-server/src/test/resources/org/apache/cayenne/access/cayenne-relationship-optimisation.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<domain project-version="6">
-	<map name="relationship-optimisation"/>
-</domain>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/resources/org/apache/cayenne/access/loader/cayenne-relationship-optimisation.xml
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/resources/org/apache/cayenne/access/loader/cayenne-relationship-optimisation.xml b/cayenne-server/src/test/resources/org/apache/cayenne/access/loader/cayenne-relationship-optimisation.xml
new file mode 100644
index 0000000..d4fea49
--- /dev/null
+++ b/cayenne-server/src/test/resources/org/apache/cayenne/access/loader/cayenne-relationship-optimisation.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<domain project-version="6">
+	<map name="relationship-optimisation"/>
+</domain>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/resources/org/apache/cayenne/access/loader/relationship-optimisation.map.xml
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/resources/org/apache/cayenne/access/loader/relationship-optimisation.map.xml b/cayenne-server/src/test/resources/org/apache/cayenne/access/loader/relationship-optimisation.map.xml
new file mode 100644
index 0000000..e68645f
--- /dev/null
+++ b/cayenne-server/src/test/resources/org/apache/cayenne/access/loader/relationship-optimisation.map.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<data-map xmlns="http://cayenne.apache.org/schema/3.0/modelMap"
+	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	 xsi:schemaLocation="http://cayenne.apache.org/schema/3.0/modelMap http://cayenne.apache.org/schema/3.0/modelMap.xsd"
+	 project-version="6">
+	<property name="defaultPackage" value="com.objectstyle"/>
+	<db-entity name="table1" catalog="many_to_many_test">
+		<db-attribute name="id1" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
+		<db-attribute name="table1col" type="VARCHAR" length="45"/>
+	</db-entity>
+	<db-entity name="table1_table2" catalog="many_to_many_test">
+		<db-attribute name="fk1" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
+		<db-attribute name="fk2" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
+	</db-entity>
+	<db-entity name="table2" catalog="many_to_many_test">
+		<db-attribute name="id2" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
+		<db-attribute name="table2col" type="VARCHAR" length="45"/>
+	</db-entity>
+	<obj-entity name="Table1" className="com.objectstyle.Table1" dbEntityName="table1">
+		<obj-attribute name="table1col" type="java.lang.String" db-attribute-path="table1col"/>
+	</obj-entity>
+	<obj-entity name="Table1Table2" className="com.objectstyle.Table1Table2" dbEntityName="table1_table2">
+	</obj-entity>
+	<obj-entity name="Table2" className="com.objectstyle.Table2" dbEntityName="table2">
+		<obj-attribute name="table2col" type="java.lang.String" db-attribute-path="table2col"/>
+	</obj-entity>
+	<db-relationship name="table1Table2Array" source="table1" target="table1_table2" toDependentPK="true" toMany="true">
+		<db-attribute-pair source="id1" target="fk1"/>
+	</db-relationship>
+	<db-relationship name="toTable1" source="table1_table2" target="table1" toMany="false">
+		<db-attribute-pair source="fk1" target="id1"/>
+	</db-relationship>
+	<db-relationship name="toTable2" source="table1_table2" target="table2" toMany="false">
+		<db-attribute-pair source="fk2" target="id2"/>
+	</db-relationship>
+	<db-relationship name="table1Table2Array" source="table2" target="table1_table2" toDependentPK="true" toMany="true">
+		<db-attribute-pair source="id2" target="fk2"/>
+	</db-relationship>
+	<obj-relationship name="table1Table2Array" source="Table1" target="Table1Table2" deleteRule="Deny" db-relationship-path="table1Table2Array"/>
+	<obj-relationship name="toTable1" source="Table1Table2" target="Table1" deleteRule="Nullify" db-relationship-path="toTable1"/>
+	<obj-relationship name="toTable2" source="Table1Table2" target="Table2" deleteRule="Nullify" db-relationship-path="toTable2"/>
+	<obj-relationship name="table1Table2Array" source="Table2" target="Table1Table2" deleteRule="Deny" db-relationship-path="table1Table2Array"/>
+</data-map>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/test/resources/org/apache/cayenne/access/relationship-optimisation.map.xml
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/resources/org/apache/cayenne/access/relationship-optimisation.map.xml b/cayenne-server/src/test/resources/org/apache/cayenne/access/relationship-optimisation.map.xml
deleted file mode 100644
index e68645f..0000000
--- a/cayenne-server/src/test/resources/org/apache/cayenne/access/relationship-optimisation.map.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<data-map xmlns="http://cayenne.apache.org/schema/3.0/modelMap"
-	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	 xsi:schemaLocation="http://cayenne.apache.org/schema/3.0/modelMap http://cayenne.apache.org/schema/3.0/modelMap.xsd"
-	 project-version="6">
-	<property name="defaultPackage" value="com.objectstyle"/>
-	<db-entity name="table1" catalog="many_to_many_test">
-		<db-attribute name="id1" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
-		<db-attribute name="table1col" type="VARCHAR" length="45"/>
-	</db-entity>
-	<db-entity name="table1_table2" catalog="many_to_many_test">
-		<db-attribute name="fk1" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
-		<db-attribute name="fk2" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
-	</db-entity>
-	<db-entity name="table2" catalog="many_to_many_test">
-		<db-attribute name="id2" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
-		<db-attribute name="table2col" type="VARCHAR" length="45"/>
-	</db-entity>
-	<obj-entity name="Table1" className="com.objectstyle.Table1" dbEntityName="table1">
-		<obj-attribute name="table1col" type="java.lang.String" db-attribute-path="table1col"/>
-	</obj-entity>
-	<obj-entity name="Table1Table2" className="com.objectstyle.Table1Table2" dbEntityName="table1_table2">
-	</obj-entity>
-	<obj-entity name="Table2" className="com.objectstyle.Table2" dbEntityName="table2">
-		<obj-attribute name="table2col" type="java.lang.String" db-attribute-path="table2col"/>
-	</obj-entity>
-	<db-relationship name="table1Table2Array" source="table1" target="table1_table2" toDependentPK="true" toMany="true">
-		<db-attribute-pair source="id1" target="fk1"/>
-	</db-relationship>
-	<db-relationship name="toTable1" source="table1_table2" target="table1" toMany="false">
-		<db-attribute-pair source="fk1" target="id1"/>
-	</db-relationship>
-	<db-relationship name="toTable2" source="table1_table2" target="table2" toMany="false">
-		<db-attribute-pair source="fk2" target="id2"/>
-	</db-relationship>
-	<db-relationship name="table1Table2Array" source="table2" target="table1_table2" toDependentPK="true" toMany="true">
-		<db-attribute-pair source="id2" target="fk2"/>
-	</db-relationship>
-	<obj-relationship name="table1Table2Array" source="Table1" target="Table1Table2" deleteRule="Deny" db-relationship-path="table1Table2Array"/>
-	<obj-relationship name="toTable1" source="Table1Table2" target="Table1" deleteRule="Nullify" db-relationship-path="toTable1"/>
-	<obj-relationship name="toTable2" source="Table1Table2" target="Table2" deleteRule="Nullify" db-relationship-path="toTable2"/>
-	<obj-relationship name="table1Table2Array" source="Table2" target="Table1Table2" deleteRule="Deny" db-relationship-path="table1Table2Array"/>
-</data-map>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-tools/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-tools/pom.xml b/cayenne-tools/pom.xml
index 835b909..d48a654 100644
--- a/cayenne-tools/pom.xml
+++ b/cayenne-tools/pom.xml
@@ -66,6 +66,12 @@
 			<groupId>org.apache.ant</groupId>
 			<artifactId>ant</artifactId>
 		</dependency>
+        <dependency>
+            <groupId>org.apache.ant</groupId>
+            <artifactId>ant-testutil</artifactId>
+            <version>1.9.4</version>
+            <scope>test</scope>
+        </dependency>
 
 		<dependency>
 			<groupId>commons-collections</groupId>
@@ -115,6 +121,16 @@
 			<groupId>net.java.dev.inflector</groupId>
 			<artifactId>inflector</artifactId>
 		</dependency>
+        <dependency>
+            <groupId>org.apache.derby</groupId>
+            <artifactId>derby</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>xmlunit</groupId>
+            <artifactId>xmlunit</artifactId>
+            <scope>test</scope>
+        </dependency>
 
 	</dependencies>
 	
@@ -131,6 +147,16 @@
 				</executions>
 			</plugin>
             <plugin>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
                 <artifactId>maven-checkstyle-plugin</artifactId>
                 <!--<configuration>
                     <suppressionsLocation>${project.basedir}/cayenne-checkstyle-suppression.xml</suppressionsLocation>