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/12/09 09:22:12 UTC

[1/2] cayenne git commit: CAY-1976 Slow performance of DbMerger

Repository: cayenne
Updated Branches:
  refs/heads/master 4e88f9afd -> c9793921b


CAY-1976 Slow performance of DbMerger


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/f3457df8
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/f3457df8
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/f3457df8

Branch: refs/heads/master
Commit: f3457df851a9432b4476245b5674342777414a32
Parents: 2d8ffba
Author: alexkolonitsky <Al...@gmail.com>
Authored: Mon Dec 8 19:52:31 2014 +0300
Committer: alexkolonitsky <Al...@gmail.com>
Committed: Mon Dec 8 19:52:31 2014 +0300

----------------------------------------------------------------------
 .../org/apache/cayenne/access/DbLoader.java     | 500 ++++++++++---------
 .../loader/ManyToManyCandidateEntity.java       |   3 +-
 .../cayenne/access/loader/filters/DbPath.java   |   6 +
 .../apache/cayenne/map/naming/ExportedKey.java  | 111 +++-
 .../java/org/apache/cayenne/merge/DbMerger.java |  48 +-
 .../org/apache/cayenne/access/DbLoaderIT.java   |  26 +-
 .../access/loader/filters/DbPathTest.java       |  18 +
 .../map/naming/LegacyNameGeneratorTest.java     |   4 +-
 .../merge/DropRelationshipToModelIT.java        |  30 +-
 .../org/apache/cayenne/merge/MergeCase.java     |  24 +-
 .../apache/cayenne/merge/MergerFactoryIT.java   |  47 +-
 .../cayenne/tools/dbimport/DbImportAction.java  |   3 +-
 .../map/naming/DefaultNameGeneratorTest.java    |   6 +-
 .../tools/dbimport/DbImportActionTest.java      |   6 +-
 .../InferRelationshipsControllerBase.java       |   2 +-
 .../dialog/objentity/ObjRelationshipInfo.java   |   8 +-
 16 files changed, 464 insertions(+), 378 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoader.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoader.java
index d759213..5537ac9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoader.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoader.java
@@ -26,13 +26,12 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
-import org.apache.cayenne.CayenneException;
 import org.apache.cayenne.access.loader.DbLoaderConfiguration;
 import org.apache.cayenne.access.loader.ManyToManyCandidateEntity;
 import org.apache.cayenne.access.loader.filters.EntityFilters;
@@ -48,7 +47,6 @@ import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.DbRelationshipDetected;
 import org.apache.cayenne.map.DetectedDbEntity;
-import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.Procedure;
 import org.apache.cayenne.map.ProcedureParameter;
@@ -74,12 +72,6 @@ public class DbLoader {
     public static final String WILDCARD = "%";
     public static final String WILDCARD_PATTERN = ".*";
 
-    /**
-     * CAY-479 - need to track which entities which are skipped during loading from db since it it already present in
-     * dataMap and haven't marked for overriding so that relationships to non-skipped entities can be loaded
-     */
-    private Set<DbEntity> skippedEntities = new HashSet<DbEntity>();
-
     private final Connection connection;
     private final DbAdapter adapter;
     private final DbLoaderDelegate delegate;
@@ -223,19 +215,31 @@ public class DbLoader {
      * @return
      * @since 3.2
      */
-    public List<DbEntity> getTables(DbLoaderConfiguration config, String[] types)
+    public Map<DbPath, Map<String, DbEntity>> getTables(DbLoaderConfiguration config, String[] types)
             throws SQLException {
+        if (types == null || types.length == 0) {
+            types = getDefaultTableTypes();
+        }
 
-        List<DbEntity> tables = new LinkedList<DbEntity>();
+        Map<DbPath, Map<String, DbEntity>> tables = new HashMap<DbPath, Map<String, DbEntity>>();
         FiltersConfig filters = config.getFiltersConfig();
         for (DbPath path : filters.pathsForQueries()) {
-            tables.addAll(getDbEntities(filters, path, types));
+            tables.put(path, getDbEntities(filters, path, types));
         }
 
         return tables;
     }
 
-    private List<DbEntity> getDbEntities(FiltersConfig filters, DbPath dbPath, String[] types) throws SQLException {
+    /**
+     *
+     * @param filters
+     * @param dbPath
+     * @param types
+     * @return Map<TableName, DbEntity>
+     *
+     * @throws SQLException
+     */
+    private Map<String, DbEntity> getDbEntities(FiltersConfig filters, DbPath dbPath, String[] types) throws SQLException {
         if (LOGGER.isDebugEnabled()) {
             LOGGER.debug("Read tables: catalog=" + dbPath.catalog + ", schema=" + dbPath.schema + ", types="
                     + Arrays.toString(types));
@@ -243,7 +247,7 @@ public class DbLoader {
 
         ResultSet rs = getMetaData().getTables(dbPath.catalog, dbPath.schema, WILDCARD, types);
 
-        List<DbEntity> tables = new ArrayList<DbEntity>();
+        Map<String, DbEntity> tables = new HashMap<String, DbEntity>();
         try {
             while (rs.next()) {
                 // Oracle 9i and newer has a nifty recycle bin feature... but we don't
@@ -265,7 +269,7 @@ public class DbLoader {
                 table.setSchema(schema);
 
                 if (filters.filter(new DbPath(catalog, schema)).tableFilter().isInclude(table)) {
-                    tables.add(table);
+                    tables.put(name, table);
                 }
             }
         } finally {
@@ -284,81 +288,48 @@ public class DbLoader {
      *            The list of org.apache.cayenne.ashwood.dbutil.Table objects
      *            for which DbEntities must be created.  @return false if loading must be immediately aborted.
      */
-    public List<DbEntity> loadDbEntities(DataMap map, DbLoaderConfiguration config, Collection<? extends DbEntity> tables) throws SQLException {
+    public List<DbEntity> loadDbEntities(DataMap map, DbLoaderConfiguration config, Map<DbPath, Map<String, DbEntity>> tables) throws SQLException {
         /** List of db entities to process. */
+
         List<DbEntity> dbEntityList = new ArrayList<DbEntity>();
-        for (DbEntity dbEntity : tables) {
-
-            // Check if there already is a DbEntity under such name
-            // if so, consult the delegate what to do
-            DbEntity oldEnt = map.getDbEntity(dbEntity.getName());
-            if (oldEnt != null) {
-                if (delegate == null) {
-                    break; // no delegate, don't know what to do, cancel import
-                    // TODO continue?
+        for (Map.Entry<DbPath, Map<String, DbEntity>> tablesMap : tables.entrySet()) {
+            for (DbEntity dbEntity : tablesMap.getValue().values()) {
+
+                // Check if there already is a DbEntity under such name
+                // if so, consult the delegate what to do
+                DbEntity oldEnt = map.getDbEntity(dbEntity.getName());
+                if (oldEnt != null) {
+                    map.removeDbEntity(oldEnt.getName(), true);
                 }
+                map.addDbEntity(dbEntity);
 
-                try {
-                    if (delegate.overwriteDbEntity(oldEnt)) {
-                        LOGGER.debug("Overwrite: " + oldEnt.getName());
-                        map.removeDbEntity(oldEnt.getName(), true);
-                        delegate.dbEntityRemoved(oldEnt);
-                    } else {
-                        LOGGER.debug("Keep old: " + oldEnt.getName());
-
-                        // cay-479 - need to track entities that were not loaded for
-                        // relationships exported to entities that were
-                        skippedEntities.add(oldEnt);
-                        continue;
-                    }
-                } catch (CayenneException ex) {
-                    LOGGER.debug("Load canceled.");
-
-                    return null; // cancel immediately
+                // notify delegate
+                if (delegate != null) {
+                    delegate.dbEntityAdded(dbEntity);
                 }
-            }
-
-
-
-            map.addDbEntity(dbEntity);
 
-            // notify delegate
-            if (delegate != null) {
-                delegate.dbEntityAdded(dbEntity);
+                // delegate might have thrown this entity out... so check if it is still
+                // around before continuing processing
+                if (map.getDbEntity(dbEntity.getName()) == dbEntity) {
+                    dbEntityList.add(dbEntity);
+                }
             }
-            loadDbAttributes(config.getFiltersConfig(), dbEntity);
 
-            // delegate might have thrown this entity out... so check if it is still
-            // around before continuing processing
-            if (map.getDbEntity(dbEntity.getName()) == dbEntity) {
-                dbEntityList.add(dbEntity);
-            }
-        }
+            loadDbAttributes(config.getFiltersConfig(), tablesMap.getKey(), tablesMap.getValue());
 
-        // get primary keys for each table and store it in dbEntity
-        getPrimaryKeysForEachTableAndStoreItInDbEntity(map, tables);
-
-        // cay-479 - iterate skipped DbEntities to populate exported keys
-        for (DbEntity skippedEntity : skippedEntities) {
-            loadDbRelationships(map, skippedEntity, config);
+            // get primary keys for each table and store it in dbEntity
+            getPrimaryKeyForTable(tablesMap.getValue());
         }
 
         return dbEntityList;
-
     }
 
-    private void getPrimaryKeysForEachTableAndStoreItInDbEntity(DataMap map, Collection<? extends DbEntity> tables)
-            throws SQLException {
-
-        for (DbEntity dbEntity : map.getDbEntities()) {
-            if (!tables.contains(dbEntity)) { // TODO is it ok? equals is not overridden
-                continue;
-            }
-
+    private void getPrimaryKeyForTable(Map<String, DbEntity> tables) throws SQLException {
+        for (DbEntity dbEntity : tables.values()) {
             ResultSet rs = getMetaData().getPrimaryKeys(dbEntity.getCatalog(), dbEntity.getSchema(), dbEntity.getName());
             try {
                 while (rs.next()) {
-                    String columnName = rs.getString(4); // COLUMN_NAME
+                    String columnName = rs.getString("COLUMN_NAME");
                     DbAttribute attribute = dbEntity.getAttribute(columnName);
 
                     if (attribute != null) {
@@ -370,10 +341,11 @@ public class DbLoader {
                         LOGGER.warn("Can't locate attribute for primary key: " + columnName);
                     }
 
-                    String pkName = rs.getString(6); // PK_NAME
+                    String pkName = rs.getString("PK_NAME");
                     if (pkName != null && dbEntity instanceof DetectedDbEntity) {
                         ((DetectedDbEntity) dbEntity).setPrimaryKeyName(pkName);
                     }
+
                 }
             } finally {
                 rs.close();
@@ -381,8 +353,8 @@ public class DbLoader {
         }
     }
 
-    private void loadDbAttributes(FiltersConfig filters, DbEntity dbEntity) throws SQLException {
-        ResultSet rs = getMetaData().getColumns(dbEntity.getCatalog(), dbEntity.getSchema(), dbEntity.getName(), "%");
+    private void loadDbAttributes(FiltersConfig filters, DbPath path, Map<String, DbEntity> entities) throws SQLException {
+        ResultSet rs = getMetaData().getColumns(path.catalog, path.schema, WILDCARD, WILDCARD);
 
         try {
             while (rs.next()) {
@@ -390,25 +362,30 @@ public class DbLoader {
                 // returns duplicate record sets for the same table, messing up table
                 // names. E.g. for the system table "WK$_ATTR_MAPPING" columns are
                 // returned twice - as "WK$_ATTR_MAPPING" and "WK$$_ATTR_MAPPING"... Go figure
-
                 String tableName = rs.getString("TABLE_NAME");
-                if (!dbEntity.getName().equals(tableName)) {
-                    LOGGER.info("Incorrectly returned columns for '" + tableName + ", skipping.");
+                DbEntity dbEntity = entities.get(tableName);
+                if (dbEntity == null) {
+                    if (LOGGER.isDebugEnabled()) {
+                        LOGGER.debug("Skip column for '" + tableName + "." + rs.getString("COLUMN_NAME") + ".");
+                    }
                     continue;
                 }
 
                 DbAttribute attr = loadDbAttribute(rs);
                 attr.setEntity(dbEntity);
-                DbPath path = new DbPath(dbEntity.getCatalog(), dbEntity.getSchema(), tableName);
                 Filter<DbAttribute> filter = filters.filter(path).columnFilter();
                 if (!filter.isInclude(attr)) {
                     if (LOGGER.isDebugEnabled()) {
-                        LOGGER.debug("Importing: attribute '" + attr.getEntity().getName() + "." + attr.getName()
-                                + "' is skipped (Path: " + path + "; Filter: " + filter + ")");
+                        LOGGER.debug("Skip column for '" + attr.getEntity().getName() + "." + attr.getName()
+                                + "' (Path: " + path + "; Filter: " + filter + ")");
                     }
                     continue;
                 }
 
+                // override existing attributes if it comes again
+                if (dbEntity.getAttribute(attr.getName()) != null) {
+                    dbEntity.removeAttribute(attr.getName());
+                }
                 dbEntity.addAttribute(attr);
             }
         } finally {
@@ -498,110 +475,137 @@ public class DbLoader {
         return new EntityMergeSupport(map, nameGenerator, !creatingMeaningfulPK);
     }
 
-    /** Loads database relationships into a DataMap. */
-    protected void loadDbRelationships(DataMap map, DbLoaderConfiguration config, Iterable<DbEntity> entities) throws SQLException {
-        for (DbEntity pkEntity : entities) {
-            loadDbRelationships(map, pkEntity, config);
-        }
-    }
-
-    protected void loadDbRelationships(DataMap map, DbEntity entity, DbLoaderConfiguration config) throws SQLException {
+    protected void loadDbRelationships(DbLoaderConfiguration config, Map<DbPath, Map<String, DbEntity>> tables) throws SQLException {
         // Get all the foreign keys referencing this table
-        ResultSet rs;
 
-        try {
-            rs = getMetaData().getExportedKeys(entity.getCatalog(), entity.getSchema(), entity.getName());
-        } catch (SQLException cay182Ex) {
-            // Sybase-specific - the line above blows on VIEWS, see CAY-182.
-            LOGGER.info("Error getting relationships for '" + entity.getName() + "', ignoring.");
-            return;
+        for (Map.Entry<DbPath, Map<String, DbEntity>> pathEntry : tables.entrySet()) {
+            Map<String, Set<ExportedKey>> keys = loadExportedKeys(config, pathEntry.getKey(), pathEntry.getValue());
+            for (Map.Entry<String, Set<ExportedKey>> entry : keys.entrySet()) {
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Process keys for: " + entry.getKey());
+                }
+
+                Set<ExportedKey> exportedKeys = entry.getValue();
+                ExportedKey key = exportedKeys.iterator().next();
+                if (key == null) {
+                    throw new IllegalStateException();
+                }
+
+                DbEntity pkEntity = pathEntry.getValue().get(key.getPKTableName());
+                if (pkEntity == null) {
+                    skipRelationLog(key, key.getPKTableName());
+                    continue;
+                }
+
+                DbEntity fkEntity = pathEntry.getValue().get(key.getFKTableName());
+                if (fkEntity == null) {
+                    skipRelationLog(key, key.getFKTableName());
+                    continue;
+                }
+
+                DbRelationship forwardRelationship = new DbRelationship(generateName(pkEntity, key, true));
+                forwardRelationship.setSourceEntity(pkEntity);
+                forwardRelationship.setTargetEntity(fkEntity);
+
+                DbRelationshipDetected reverseRelationship = new DbRelationshipDetected(generateName(fkEntity, key, false));
+                reverseRelationship.setFkName(key.getFKName());
+                reverseRelationship.setSourceEntity(fkEntity);
+                reverseRelationship.setTargetEntity(pkEntity);
+                reverseRelationship.setToMany(false);
+                fkEntity.addRelationship(reverseRelationship);
+
+                boolean toPK = createAndAppendJoins(exportedKeys, pkEntity, fkEntity, forwardRelationship, reverseRelationship);
+
+                forwardRelationship.setToDependentPK(toPK);
+
+                boolean isOneToOne = toPK && fkEntity.getPrimaryKeys().size()
+                        == forwardRelationship.getJoins().size();
+
+                forwardRelationship.setToMany(!isOneToOne);
+                forwardRelationship.setName(generateName(pkEntity, key, !isOneToOne));
+                pkEntity.addRelationship(forwardRelationship);
+            }
         }
+    }
 
-        try {
-            // these will be initialized every time a new target entity is found
-            // in the result set (which should be ordered by table name among other things)
-            DbRelationship forwardRelationship = null;
-            DbRelationshipDetected reverseRelationship = null;
-            DbEntity fkEntity = null;
-            ExportedKey key = null;
+    private boolean createAndAppendJoins(Set<ExportedKey> exportedKeys, DbEntity pkEntity, DbEntity fkEntity, DbRelationship forwardRelationship, DbRelationshipDetected reverseRelationship) {
+        boolean toPK = true;
+        for (ExportedKey exportedKey : exportedKeys) {
+            // Create and append joins
+            String pkName = exportedKey.getPKColumnName();
+            String fkName = exportedKey.getFKColumnName();
+
+            // skip invalid joins...
+            DbAttribute pkAtt = pkEntity.getAttribute(pkName);
+            if (pkAtt == null) {
+                LOGGER.info("no attribute for declared primary key: " + pkName);
+                continue;
+            }
 
-            while (rs.next()) {
-                key = ExportedKey.extractData(rs);
+            DbAttribute fkAtt = fkEntity.getAttribute(fkName);
+            if (fkAtt == null) {
+                LOGGER.info("no attribute for declared foreign key: " + fkName);
+                continue;
+            }
 
-                short keySeq = rs.getShort("KEY_SEQ");
-                if (keySeq == 1) {
+            forwardRelationship.addJoin(new DbJoin(forwardRelationship, pkName, fkName));
+            reverseRelationship.addJoin(new DbJoin(reverseRelationship, fkName, pkName));
 
-                    if (forwardRelationship != null) {
-                        postProcessMasterDbRelationship(forwardRelationship, key);
-                        forwardRelationship = null;
-                    }
+            if (!pkAtt.isPrimaryKey()) {
+                toPK = false;
+            }
+        }
+        return toPK;
+    }
 
-                    // start new entity
-                    String fkEntityName = key.getFKTableName();
+    private Map<String, Set<ExportedKey>> loadExportedKeys(DbLoaderConfiguration config, DbPath dbPath, Map<String, DbEntity> tables) throws SQLException {
+        Map<String, Set<ExportedKey>> keys = new HashMap<String, Set<ExportedKey>>();
 
-                    fkEntity = map.getDbEntity(fkEntityName);
-                    DbPath path = new DbPath(entity.getCatalog(), entity.getSchema(), entity.getName());
-                    if (!config.getFiltersConfig().filter(path).tableFilter().isInclude(fkEntity)) {
-                        continue;
-                    }
+        for (DbEntity dbEntity : tables.values()) {
+            ResultSet rs;
+            try {
+                rs = getMetaData().getExportedKeys(dbPath.catalog, dbPath.schema, dbEntity.getName());
+            } catch (SQLException cay182Ex) {
+                // Sybase-specific - the line above blows on VIEWS, see CAY-182.
+                LOGGER.info("Error getting relationships for '" + dbPath + "', ignoring. "
+                        + cay182Ex.getMessage(), cay182Ex);
+                return new HashMap<String, Set<ExportedKey>>();
+            }
 
+            try {
+                while (rs.next()) {
+                    ExportedKey key = ExportedKey.extractData(rs);
+
+                    DbEntity fkEntity = tables.get(key.getFKTableName());
                     if (fkEntity == null) {
-                        LOGGER.info("FK warning: no entity found for name '" + fkEntityName + "'");
-                    } else if (skippedEntities.contains(entity) && skippedEntities.contains(fkEntity)) {
-                        // cay-479 - don't doLoad relationships between two skipped entities.
+                        skipRelationLog(key, key.getFKTableName());
                         continue;
-                    } else {
-                        // init relationship
-                        forwardRelationship = new DbRelationship(generateName(entity, key, true));
-                        forwardRelationship.setSourceEntity(entity);
-                        forwardRelationship.setTargetEntity(fkEntity);
-                        entity.addRelationship(forwardRelationship);
-
-                        reverseRelationship = new DbRelationshipDetected(generateName(fkEntity, key, false));
-                        reverseRelationship.setFkName(key.getFKName());
-                        reverseRelationship.setToMany(false);
-                        reverseRelationship.setSourceEntity(fkEntity);
-                        reverseRelationship.setTargetEntity(entity);
-                        fkEntity.addRelationship(reverseRelationship);
                     }
-                }
-
-                if (fkEntity != null) {
-                    // Create and append joins
-                    String pkName = key.getPKColumnName();
-                    String fkName = key.getFKColumnName();
-
-                    // skip invalid joins...
-                    DbAttribute pkAtt = entity.getAttribute(pkName);
-                    if (pkAtt == null) {
-                        LOGGER.info("no attribute for declared primary key: " + pkName);
+                    DbPath path = new DbPath(fkEntity.getCatalog(), fkEntity.getSchema(), fkEntity.getName());
+                    if (!config.getFiltersConfig().filter(path).tableFilter().isInclude(fkEntity)) {
                         continue;
                     }
 
-                    DbAttribute fkAtt = fkEntity.getAttribute(fkName);
-                    if (fkAtt == null) {
-                        LOGGER.info("no attribute for declared foreign key: " + fkName);
-                        continue;
-                    }
+                    Set<ExportedKey> exportedKeys = keys.get(key.getStrKey());
+                    if (exportedKeys == null) {
+                        exportedKeys = new TreeSet<ExportedKey>();
 
-                    if (forwardRelationship != null) {
-                        forwardRelationship.addJoin(new DbJoin(forwardRelationship, pkName, fkName));
-                    }
-                    if (reverseRelationship != null) {
-                        reverseRelationship.addJoin(new DbJoin(reverseRelationship, fkName, pkName));
+                        keys.put(key.getStrKey(), exportedKeys);
                     }
-
+                    exportedKeys.add(key);
                 }
-            }
 
-            if (forwardRelationship != null) {
-                postProcessMasterDbRelationship(forwardRelationship, key);
-                forwardRelationship = null;
+            } finally {
+                rs.close();
             }
-
-        } finally {
-            rs.close();
         }
+        return keys;
+    }
+
+    private void skipRelationLog(ExportedKey key, String tableName) {
+        // if (LOGGER.isDebugEnabled()) {
+        LOGGER.info("Skip relation: '" + key + "' because table '" + tableName + "' not found");
+        // }
     }
 
     private String generateName(DbEntity entity, ExportedKey key, boolean toMany) {
@@ -615,29 +619,7 @@ public class DbLoader {
      * called on relationships from PK to FK, not the reverse ones.
      */
     protected void postProcessMasterDbRelationship(DbRelationship relationship, ExportedKey key) {
-        boolean toPK = true;
-        List<DbJoin> joins = relationship.getJoins();
-
-        for (DbJoin join : joins) {
-            if (!join.getTarget().isPrimaryKey()) {
-                toPK = false;
-                break;
-            }
-
-        }
-
-        relationship.setToDependentPK(toPK);
-        relationship.setToMany(!(toPK && relationship.getTargetEntity().getPrimaryKeys().size() == joins.size()));
 
-        // if this is really to-one we need to rename the relationship
-        if (!relationship.isToMany()) {
-            Entity source = relationship.getSourceEntity();
-            source.removeRelationship(relationship.getName());
-            relationship.setName(DefaultUniqueNameGenerator.generate(NameCheckers.dbRelationship, source,
-                    nameGenerator.createDbRelationshipName(key, false)));
-
-            source.addRelationship(relationship);
-        }
     }
 
     /**
@@ -731,8 +713,8 @@ public class DbLoader {
         config.setFiltersConfig(new FiltersConfig(new EntityFilters(
                 new DbPath(null, schemaPattern), transformPatternToFilter(tablePattern), TRUE, NULL)));
         config.setTableTypes(tableTypes);
-        
-        load(dataMap, config);
+
+        load(dataMap, config, tableTypes);
         return dataMap;
     }
 
@@ -753,21 +735,35 @@ public class DbLoader {
      *
      * @since 4.0
      */
-	public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
+    public void load(DataMap dataMap, DbLoaderConfiguration config, String... tableTypes) throws SQLException {
 
-		String[] tableTypes = config.getTableTypes() == null ? this.getDefaultTableTypes() : config.getTableTypes();
-		List<DbEntity> entities = loadDbEntities(dataMap, config, getTables(config, tableTypes));
+        Map<DbPath, Map<String, DbEntity>> tables = getTables(config, tableTypes);
+        List<DbEntity> entities = loadDbEntities(dataMap, config, tables);
 
-		if (entities != null) {
-			loadDbRelationships(dataMap, config, entities);
-			loadObjEntities(dataMap, config, entities);
+        if (entities != null) {
+            loadDbRelationships(config, tables);
 
-			flattenManyToManyRelationships(dataMap);
-			fireObjEntitiesAddedEvents(dataMap);
-		}
+            loadObjEntities(dataMap, config, entities);
+            flattenManyToManyRelationships(dataMap);
+            fireObjEntitiesAddedEvents(dataMap);
+        }
+    }
 
-		loadProcedures(dataMap, config);
-	}
+    /**
+     * Performs database reverse engineering to match the specified catalog,
+     * schema, table name and table type patterns and fills the specified
+     * DataMap object with DB and object mapping info.
+     *
+     * @since 3.2
+     */
+    public DataMap load(DbLoaderConfiguration config) throws SQLException {
+
+        DataMap dataMap = new DataMap();
+        load(dataMap, config, config.getTableTypes());
+        loadProcedures(dataMap, config);
+
+        return dataMap;
+    }
 
     /**
      * Loads database stored procedures into the DataMap.
@@ -809,7 +805,7 @@ public class DbLoader {
             return procedures;
         }
 
-        loadProceduresColumns(procedures);
+        loadProceduresColumns(config, procedures);
 
         for (Procedure procedure : procedures.values()) {
             dataMap.addProcedure(procedure);
@@ -818,65 +814,75 @@ public class DbLoader {
         return procedures;
     }
 
-    private void loadProceduresColumns(Map<String, Procedure> procedures) throws SQLException {
-        ResultSet columnsRS = getMetaData().getProcedureColumns(null, null, null, null); // TODO catalog, schema
-        try {
-            while (columnsRS.next()) {
-
-                String schema = columnsRS.getString("PROCEDURE_SCHEM");
-                String name = columnsRS.getString("PROCEDURE_NAME");
-                String key = (schema == null ? "" : schema + '.') + name ;
-                Procedure procedure = procedures.get(key);
-                if (procedure == null) {
-                    continue;
-                }
-
-                String columnName = columnsRS.getString("COLUMN_NAME");
+    private void loadProceduresColumns(DbLoaderConfiguration config, Map<String, Procedure> procedures) throws SQLException {
+        for (DbPath dbPath : config.getFiltersConfig().pathsForQueries()) {
+            ResultSet columnsRS = getMetaData().getProcedureColumns(dbPath.catalog, dbPath.schema, null, null);
+            try {
+                while (columnsRS.next()) {
 
-                // skip ResultSet columns, as they are not described in Cayenne procedures yet...
-                short type = columnsRS.getShort("COLUMN_TYPE");
-                if (type == DatabaseMetaData.procedureColumnResult) {
-                    LOGGER.debug("skipping ResultSet column: " + key + "." + columnName);
-                }
+                    String schema = columnsRS.getString("PROCEDURE_SCHEM");
+                    String name = columnsRS.getString("PROCEDURE_NAME");
+                    String key = (schema == null ? "" : schema + '.') + name ;
+                    Procedure procedure = procedures.get(key);
+                    if (procedure == null) {
+                        continue;
+                    }
 
-                if (columnName == null) {
-                    if (type == DatabaseMetaData.procedureColumnReturn) {
-                        LOGGER.debug("null column name, assuming result column: " + key);
-                        columnName = "_return_value";
-                        procedure.setReturningValue(true);
-                    } else {
-                        LOGGER.info("invalid null column name, skipping column : " + key);
+                    ProcedureParameter column = loadProcedureParams(columnsRS, key, procedure);
+                    if (column == null) {
                         continue;
                     }
+                    procedure.addCallParameter(column);
                 }
+            } finally {
+                columnsRS.close();
+            }
+        }
+    }
 
-                int columnType = columnsRS.getInt("DATA_TYPE");
+    private ProcedureParameter loadProcedureParams(ResultSet columnsRS, String key, Procedure procedure) throws SQLException {
+        String columnName = columnsRS.getString("COLUMN_NAME");
 
-                // ignore precision of non-decimal columns
-                int decimalDigits = -1;
-                if (TypesMapping.isDecimal(columnType)) {
-                    decimalDigits = columnsRS.getShort("SCALE");
-                    if (columnsRS.wasNull()) {
-                        decimalDigits = -1;
-                    }
-                }
+        // skip ResultSet columns, as they are not described in Cayenne procedures yet...
+        short type = columnsRS.getShort("COLUMN_TYPE");
+        if (type == DatabaseMetaData.procedureColumnResult) {
+            LOGGER.debug("skipping ResultSet column: " + key + "." + columnName);
+        }
 
-                ProcedureParameter column = new ProcedureParameter(columnName);
-                int direction = getDirection(type);
-                if (direction != -1) {
-                    column.setDirection(direction);
-                }
+        if (columnName == null) {
+            if (type == DatabaseMetaData.procedureColumnReturn) {
+                LOGGER.debug("null column name, assuming result column: " + key);
+                columnName = "_return_value";
+                procedure.setReturningValue(true);
+            } else {
+                LOGGER.info("invalid null column name, skipping column : " + key);
+                return null;
+            }
+        }
 
-                column.setType(columnType);
-                column.setMaxLength(columnsRS.getInt("LENGTH"));
-                column.setPrecision(decimalDigits);
+        int columnType = columnsRS.getInt("DATA_TYPE");
 
-                column.setProcedure(procedure);
-                procedure.addCallParameter(column);
+        // ignore precision of non-decimal columns
+        int decimalDigits = -1;
+        if (TypesMapping.isDecimal(columnType)) {
+            decimalDigits = columnsRS.getShort("SCALE");
+            if (columnsRS.wasNull()) {
+                decimalDigits = -1;
             }
-        } finally {
-            columnsRS.close();
         }
+
+        ProcedureParameter column = new ProcedureParameter(columnName);
+        int direction = getDirection(type);
+        if (direction != -1) {
+            column.setDirection(direction);
+        }
+
+        column.setType(columnType);
+        column.setMaxLength(columnsRS.getInt("LENGTH"));
+        column.setPrecision(decimalDigits);
+
+        column.setProcedure(procedure);
+        return column;
     }
 
     private static int getDirection(short type) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/cayenne-server/src/main/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntity.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntity.java
index 6e392d8..792c8a8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntity.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntity.java
@@ -109,7 +109,8 @@ public class ManyToManyCandidateEntity {
                 null,
                 rel2.getTargetEntity().getName(),
                 rel2.getTargetAttributes().iterator().next().getName(),
-                null);
+                null,
+                (short) 1);
 
         ObjRelationship newRelationship = new ObjRelationship();
         newRelationship.setName(DefaultUniqueNameGenerator.generate(NameCheckers.objRelationship, srcEntity,

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/DbPath.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/DbPath.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/DbPath.java
index 813ce94..eb4a863 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/DbPath.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/DbPath.java
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.cayenne.access.loader.filters;
 
+import org.apache.cayenne.map.DbEntity;
+
 import java.util.regex.Pattern;
 
 /**
@@ -150,4 +152,8 @@ public class DbPath implements Comparable<DbPath> {
             return null;
         }
     }
+
+    public static DbPath build(DbEntity entity) {
+        return new DbPath(entity.getCatalog(), entity.getSchema(), entity.getName());
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/cayenne-server/src/main/java/org/apache/cayenne/map/naming/ExportedKey.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/naming/ExportedKey.java b/cayenne-server/src/main/java/org/apache/cayenne/map/naming/ExportedKey.java
index 1c1c885..42697fe 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/naming/ExportedKey.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/naming/ExportedKey.java
@@ -18,6 +18,10 @@
  ****************************************************************/
 package org.apache.cayenne.map.naming;
 
+import org.apache.cayenne.util.EqualsBuilder;
+import org.apache.cayenne.util.HashCodeBuilder;
+import org.apache.commons.lang.builder.CompareToBuilder;
+
 import java.sql.ResultSet;
 import java.sql.SQLException;
 
@@ -26,45 +30,49 @@ import java.sql.SQLException;
  * in database. It can be used for creating names for relationships
  * 
  */
-public class ExportedKey {
+public class ExportedKey implements Comparable {
     /**
      * Name of source table
      */
-    String pkTable;
-    
+    public final String pkTable;
+
     /**
      * Name of source column
      */
-    String pkColumn;
-    
+    public final String pkColumn;
+
     /**
      * Name of destination table
      */
-    String fkTable;
+    public final String fkTable;
     
     /**
      * Name of destination column
      */
-    String fkColumn;
+    public final String fkColumn;
     
     /**
      * Name of foreign key (might be null)
      */
-    String fkName;
-    
+    public final String fkName;
+
     /**
      * Name of primary key (might be null)
      */
-    String pkName;
-    
+    public final String pkName;
+
+
+    public final short keySeq;
+
     public ExportedKey(String pkTable, String pkColumn, String pkName,
-            String fkTable, String fkColumn, String fkName) {
+                       String fkTable, String fkColumn, String fkName, short keySeq) {
        this.pkTable  = pkTable;
        this.pkColumn = pkColumn;
        this.pkName   = pkName;
        this.fkTable  = fkTable;
        this.fkColumn = fkColumn;
        this.fkName   = fkName;
+       this.keySeq = keySeq;
     }
     
     /**
@@ -75,16 +83,15 @@ public class ExportedKey {
      * DataBaseMetaData.getExportedKeys(...) 
      */
     public static ExportedKey extractData(ResultSet rs) throws SQLException {
-        ExportedKey key = new ExportedKey(
+        return new ExportedKey(
                 rs.getString("PKTABLE_NAME"),
                 rs.getString("PKCOLUMN_NAME"),
                 rs.getString("PK_NAME"),
                 rs.getString("FKTABLE_NAME"),
                 rs.getString("FKCOLUMN_NAME"),
-                rs.getString("FK_NAME")
+                rs.getString("FK_NAME"),
+                rs.getShort("KEY_SEQ")
         );
-        
-        return key;
     }
     
     /**
@@ -128,4 +135,76 @@ public class ExportedKey {
     public String getFKName() {
         return fkName;
     }
+
+    public short getKeySeq() {
+        return keySeq;
+    }
+
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj.getClass() != getClass()) {
+            return false;
+        }
+        ExportedKey rhs = (ExportedKey) obj;
+        return new EqualsBuilder()
+                .append(this.pkTable, rhs.pkTable)
+                .append(this.pkColumn, rhs.pkColumn)
+                .append(this.fkTable, rhs.fkTable)
+                .append(this.fkColumn, rhs.fkColumn)
+                .append(this.fkName, rhs.fkName)
+                .append(this.pkName, rhs.pkName)
+                .append(this.keySeq, rhs.keySeq)
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder()
+                .append(pkTable)
+                .append(pkColumn)
+                .append(fkTable)
+                .append(fkColumn)
+                .append(fkName)
+                .append(pkName)
+                .append(keySeq)
+                .toHashCode();
+    }
+
+    @Override
+    public int compareTo(Object obj) {
+        if (obj == null || !obj.getClass().equals(getClass())) {
+            throw new IllegalArgumentException();
+        }
+        if (obj == this) {
+            return 0;
+        }
+
+        ExportedKey rhs = (ExportedKey) obj;
+        return new CompareToBuilder()
+                .append(pkTable, rhs.pkTable)
+                .append(pkName, rhs.pkName)
+                .append(fkTable, rhs.fkTable)
+                .append(fkName, rhs.fkName)
+                .append(keySeq, rhs.keySeq)
+                .append(pkColumn, rhs.pkColumn)
+                .append(fkColumn, rhs.fkColumn)
+                .toComparison();
+    }
+
+    @Override
+    public String toString() {
+        return getStrKey() + " # " + keySeq
+                + "(" + pkColumn + " <- " + fkColumn + ")";
+    }
+
+    public String getStrKey() {
+        return pkTable + "." + pkName + " <- " + fkTable + "." + fkName;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/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 f036511..ef93589 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
@@ -36,6 +36,9 @@ import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.DbLoader;
 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.access.loader.filters.DbPath;
+import org.apache.cayenne.access.loader.filters.FiltersConfig;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.Attribute;
 import org.apache.cayenne.map.DataMap;
@@ -67,14 +70,6 @@ public class DbMerger {
     }
 
     /**
-     * A method that return true if the given table name should be included. The default
-     * implementation include all tables.
-     */
-    public boolean includeTableName(String tableName) {
-        return true;
-    }
-
-    /**
      * Create and return a {@link List} of {@link MergerToken}s to alter the given
      * {@link DataNode} to match the given {@link DataMap}
      */
@@ -87,7 +82,7 @@ public class DbMerger {
      * {@link DataNode} to match the given {@link DataMap}
      */
     public List<MergerToken> createMergeTokens(DbLoader dbLoader, DataMap existing, DbLoaderConfiguration config) {
-        return createMergeTokens(existing, loadDataMapFromDb(dbLoader, config));
+        return createMergeTokens(existing, loadDataMapFromDb(dbLoader, config), config.getFiltersConfig());
     }
 
     /**
@@ -95,18 +90,18 @@ public class DbMerger {
      * {@link DataNode} to match the given {@link DataMap}
      */
     public List<MergerToken> createMergeTokens(DataSource dataSource, DbAdapter adapter, DataMap existingDataMap, DbLoaderConfiguration config) {
-        return createMergeTokens(existingDataMap, loadDataMapFromDb(dataSource, adapter, config));
+        return createMergeTokens(existingDataMap, loadDataMapFromDb(dataSource, adapter, config), config.getFiltersConfig());
     }
 
     /**
      * 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(DataMap existing, DataMap loadedFomDb) {
+    public List<MergerToken> createMergeTokens(DataMap existing, DataMap loadedFomDb, FiltersConfig filtersConfig) {
 
         loadedFomDb.setQuotingSQLIdentifiers(existing.isQuotingSQLIdentifiers());
 
-        List<MergerToken> tokens = createMergeTokens(existing.getDbEntities(), loadedFomDb.getDbEntities());
+        List<MergerToken> tokens = createMergeTokens(filter(existing, filtersConfig), loadedFomDb.getDbEntities());
 
 
         // sort. use a custom Comparator since only toDb tokens are comparable by now
@@ -126,6 +121,16 @@ public class DbMerger {
         return tokens;
     }
 
+    private Collection<DbEntity> filter(DataMap existing, FiltersConfig filtersConfig) {
+        Collection<DbEntity> existingFiltered = new LinkedList<DbEntity>();
+        for (DbEntity entity : existing.getDbEntities()) {
+            if (filtersConfig.filter(DbPath.build(entity)).tableFilter().isInclude(entity)) {
+                existingFiltered.add(entity);
+            }
+        }
+        return existingFiltered;
+    }
+
     private DataMap loadDataMapFromDb(DataSource dataSource, DbAdapter adapter, DbLoaderConfiguration config) {
         Connection conn = null;
         try {
@@ -151,14 +156,13 @@ public class DbMerger {
     }
 
     private DataMap loadDataMapFromDb(DbLoader dbLoader, DbLoaderConfiguration config) {
-        DataMap detectedDataMap = new DataMap();
         try {
-            dbLoader.load(detectedDataMap, config);
+            return dbLoader.load(config);
         } catch (SQLException e) {
             // TODO log
         }
 
-        return detectedDataMap;
+        return new DataMap();
     }
 
     /**
@@ -175,12 +179,6 @@ public class DbMerger {
         for (DbEntity dbEntity : existing) {
             String tableName = dbEntity.getName();
 
-            if (!includeTableName(tableName)) {
-                // TODO we have to cut this entities in db loader
-                // TODO log
-                continue;
-            }
-
             // look for table
             DbEntity detectedEntity = findDbEntity(loadedFromDb, tableName);
             if (detectedEntity == null) {
@@ -207,10 +205,6 @@ public class DbMerger {
         // drop table
         // TODO: support drop table. currently, too many tables are marked for drop
         for (DbEntity e : dbEntitiesToDrop) {
-            if (!includeTableName(e.getName())) {
-                continue;
-            }
-
             tokens.add(factory.createDropTableToDb(e));
         }
 
@@ -320,10 +314,6 @@ public class DbMerger {
         List<MergerToken> tokens = new LinkedList<MergerToken>();
 
         for (DbRelationship rel : dbEntity.getRelationships()) {
-            if (!includeTableName(rel.getTargetEntityName())) {
-                continue;
-            }
-            
             if (findDbRelationship(detectedEntity, rel) == null) {
                 AddRelationshipToDb token = (AddRelationshipToDb)
                         factory.createAddRelationshipToDb(dbEntity, rel);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderIT.java
index b6ba66e..40347be 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderIT.java
@@ -19,11 +19,8 @@
 
 package org.apache.cayenne.access;
 
-import java.sql.Types;
-import java.util.Collection;
-import java.util.List;
-
 import org.apache.cayenne.access.loader.DbLoaderConfiguration;
+import org.apache.cayenne.access.loader.filters.DbPath;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.TypesMapping;
@@ -45,13 +42,11 @@ import org.junit.Test;
 
 import java.sql.Types;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 @UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
 public class DbLoaderIT extends ServerCase {
@@ -104,7 +99,8 @@ public class DbLoaderIT extends ServerCase {
 
         String tableLabel = adapter.tableTypeForTable();
 
-        List<DbEntity> tables = loader.getTables(new DbLoaderConfiguration(), new String[] { tableLabel });
+        Collection<DbEntity> tables = loader.getTables(new DbLoaderConfiguration(), new String[] { tableLabel })
+                .values().iterator().next().values();
 
         assertNotNull(tables);
 
@@ -128,7 +124,7 @@ public class DbLoaderIT extends ServerCase {
 
         loader.setCreatingMeaningfulPK(true);
 
-        List<DbEntity> testLoader = loader.getTables(CONFIG, tableLabel);
+        Map<DbPath, Map<String, DbEntity>> testLoader = loader.getTables(CONFIG, tableLabel);
         if (testLoader.isEmpty()) {
             testLoader = loader.getTables(CONFIG, tableLabel);
         }
@@ -171,7 +167,13 @@ public class DbLoaderIT extends ServerCase {
         }
 
         // *** TESTING THIS ***
-        loader.loadDbRelationships(map, CONFIG, entities);
+        HashMap<DbPath, Map<String, DbEntity>> tables = new HashMap<DbPath, Map<String, DbEntity>>();
+        HashMap<String, DbEntity> value = new HashMap<String, DbEntity>();
+        for (DbEntity e : entities) {
+            value.put(e.getName(), e);
+        }
+        tables.put(new DbPath(), value);
+        loader.loadDbRelationships(CONFIG, tables);
 
         if (supportsFK) {
             Collection<DbRelationship> rels = getDbEntity(map, "ARTIST").getRelationships();

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/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
index 60d4933..2781ea6 100644
--- 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
@@ -21,6 +21,9 @@ package org.apache.cayenne.access.loader.filters;
 
 import org.junit.Test;
 
+import java.util.Set;
+import java.util.TreeSet;
+
 import static org.apache.cayenne.access.loader.filters.FiltersFactory.path;
 import static org.junit.Assert.*;
 
@@ -72,4 +75,19 @@ public class DbPathTest {
         assertEquals(path1, path1.merge(path2));
         assertEquals(path1, path2.merge(path1));
     }
+
+    @Test
+    public void testEquals() throws Exception {
+        Set<DbPath> pathes = new TreeSet<DbPath>();
+        pathes.add(path("q", "w"));
+        pathes.add(path("q", null));
+        pathes.add(path("q", ""));
+        pathes.add(path("", ""));
+        pathes.add(path("q", "w"));
+        pathes.add(path("q", "w"));
+        pathes.add(path("q", "w"));
+        pathes.add(path("q", "w"));
+
+        assertEquals(4, pathes.size());
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/cayenne-server/src/test/java/org/apache/cayenne/map/naming/LegacyNameGeneratorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/map/naming/LegacyNameGeneratorTest.java b/cayenne-server/src/test/java/org/apache/cayenne/map/naming/LegacyNameGeneratorTest.java
index 133c176..7773f28 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/map/naming/LegacyNameGeneratorTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/map/naming/LegacyNameGeneratorTest.java
@@ -32,12 +32,12 @@ public class LegacyNameGeneratorTest {
         LegacyNameGenerator strategy = new LegacyNameGenerator();
         
         ExportedKey key = new ExportedKey("ARTIST", "ARTIST_ID", null,
-                "PAINTING", "ARTIST_ID", null);
+                "PAINTING", "ARTIST_ID", null, (short) 1);
         assertEquals(strategy.createDbRelationshipName(key, false), "toArtist"); 
         assertEquals(strategy.createDbRelationshipName(key, true), "paintingArray");
         
         key = new ExportedKey("PERSON", "PERSON_ID", null,
-                "PERSON", "MOTHER_ID", null);
+                "PERSON", "MOTHER_ID", null, (short) 1);
         assertEquals(strategy.createDbRelationshipName(key, false), "toPerson"); 
         assertEquals(strategy.createDbRelationshipName(key, true), "personArray");
         

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/cayenne-server/src/test/java/org/apache/cayenne/merge/DropRelationshipToModelIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/DropRelationshipToModelIT.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/DropRelationshipToModelIT.java
index 7f08a0e..e235109 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/DropRelationshipToModelIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/merge/DropRelationshipToModelIT.java
@@ -125,21 +125,21 @@ public class DropRelationshipToModelIT extends MergeCase {
 		assertSame(objRel1To2, objRel2To1.getReverseRelationship());
 		assertSame(objRel2To1, objRel1To2.getReverseRelationship());
 
-		// remove relationship and fk from model, merge to db and read to model
-		dbEntity2.removeRelationship(rel2To1.getName());
-		dbEntity1.removeRelationship(rel1To2.getName());
-		dbEntity2.removeAttribute(e2col2.getName());
-		List<MergerToken> tokens = createMergeTokens();
-		assertTokens(tokens, 2, 1);
-		for (MergerToken token : tokens) {
-			if (token.getDirection().isToDb()) {
-				execute(token);
-			}
-		}
-		assertTokensAndExecute(0, 0);
-		dbEntity2.addRelationship(rel2To1);
-		dbEntity1.addRelationship(rel1To2);
-		dbEntity2.addAttribute(e2col2);
+        // remove relationship and fk from model, merge to db and read to model
+        dbEntity2.removeRelationship(rel2To1.getName());
+        dbEntity1.removeRelationship(rel1To2.getName());
+        dbEntity2.removeAttribute(e2col2.getName());
+        List<MergerToken> tokens = createMergeTokens();
+        assertTokens(tokens, 3, 0);
+        for (MergerToken token : tokens) {
+            if (token.getDirection().isToDb()) {
+                execute(token);
+            }
+        }
+        assertTokensAndExecute(0, 0);
+        dbEntity2.addRelationship(rel2To1);
+        dbEntity1.addRelationship(rel1To2);
+        dbEntity2.addAttribute(e2col2);
 
 		// try do use the merger to remove the relationship in the model
 		tokens = createMergeTokens();

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/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 8c999db..258f8a9 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
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.merge;
 
+import static org.apache.cayenne.access.loader.filters.FilterFactory.*;
 import static org.junit.Assert.assertEquals;
 
 import java.sql.Connection;
@@ -29,6 +30,9 @@ import java.util.List;
 
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.loader.DbLoaderConfiguration;
+import org.apache.cayenne.access.loader.filters.DbPath;
+import org.apache.cayenne.access.loader.filters.EntityFilters;
+import org.apache.cayenne.access.loader.filters.FiltersConfig;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.di.Inject;
@@ -71,8 +75,6 @@ public abstract class MergeCase extends ServerCase {
 
 	protected DataMap map;
 
-	private static List<String> TABLE_NAMES = Arrays.asList("ARTIST", "PAINTING", "NEW_TABLE", "NEW_TABLE2");
-
 	@Override
 	public void cleanUpDB() throws Exception {
 		dbHelper.update("ARTGROUP").set("PARENT_GROUP_ID", null, Types.INTEGER).execute();
@@ -100,19 +102,15 @@ public abstract class MergeCase extends ServerCase {
 	}
 
 	protected DbMerger createMerger(MergerFactory mergerFactory, ValueForNullProvider valueForNullProvider) {
-		return new DbMerger(mergerFactory, valueForNullProvider) {
-
-			@Override
-			public boolean includeTableName(String tableName) {
-				return TABLE_NAMES.contains(tableName.toUpperCase());
-			}
-		};
+		return new DbMerger(mergerFactory, valueForNullProvider);
 	}
 
-	protected List<MergerToken> createMergeTokens() {
-		return createMerger(node.getAdapter().mergerFactory())
-				.createMergeTokens(node, map, new DbLoaderConfiguration());
-	}
+    protected List<MergerToken> createMergeTokens() {
+        DbLoaderConfiguration loaderConfiguration = new DbLoaderConfiguration();
+        loaderConfiguration.setFiltersConfig(new FiltersConfig(new EntityFilters(DbPath.EMPTY, include("ARTIST|GALLERY|PAINTING|NEW_TABLE2?"), TRUE, TRUE)));
+
+        return createMerger(node.getAdapter().mergerFactory()).createMergeTokens(node, map, loaderConfiguration);
+    }
 
 	/**
 	 * Remote binary pk {@link DbEntity} for {@link DbAdapter} not supporting

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/cayenne-server/src/test/java/org/apache/cayenne/merge/MergerFactoryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/MergerFactoryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/MergerFactoryIT.java
index 6deaaa3..409f1c9 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/MergerFactoryIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/merge/MergerFactoryIT.java
@@ -187,19 +187,9 @@ public class MergerFactoryIT extends MergeCase {
 
         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.BIGINT, dbEntity);
-        column3.setMandatory(false);
-        dbEntity.addAttribute(column3);
+        attr(dbEntity, "ID", Types.INTEGER, true, true);
+        attr(dbEntity, "NAME", Types.VARCHAR, false, false).setMaxLength(10);
+        attr(dbEntity, "ARTIST_ID", Types.BIGINT, false, false);
 
         map.addDbEntity(dbEntity);
 
@@ -229,7 +219,8 @@ public class MergerFactoryIT extends MergeCase {
         dbEntity.removeRelationship(r1.getName());
         artistDbEntity.removeRelationship(r2.getName());
         resolver.refreshMappingCache();
-        assertTokensAndExecute(1, 1);
+        assertTokensAndExecute(2, 0);
+//        assertTokensAndExecute(1, 1);
         assertTokensAndExecute(0, 0);
 
         // clear up
@@ -251,20 +242,9 @@ public class MergerFactoryIT extends MergeCase {
         assertTokensAndExecute(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.BIGINT, dbEntity);
-        column3.setMandatory(false);
-        dbEntity.addAttribute(column3);
+        attr(dbEntity, "ID", Types.INTEGER, true, true);
+        attr(dbEntity, "NAME", Types.VARCHAR, false, false).setMaxLength(10);
+        attr(dbEntity, "ARTIST_ID", Types.BIGINT, false, false);
 
         map.addDbEntity(dbEntity);
 
@@ -297,7 +277,7 @@ public class MergerFactoryIT extends MergeCase {
         dbEntity.removeRelationship(r1.getName());
         artistDbEntity.removeRelationship(r2.getName());
         resolver.refreshMappingCache();
-        assertTokensAndExecute(1, 1);
+        assertTokensAndExecute(2, 0);
         assertTokensAndExecute(0, 0);
 
         // clear up
@@ -311,4 +291,13 @@ public class MergerFactoryIT extends MergeCase {
         assertTokensAndExecute(1, 0);
         assertTokensAndExecute(0, 0);
     }
+
+    private static DbAttribute attr(DbEntity dbEntity, String name, int type, boolean mandatory, boolean primaryKey) {
+        DbAttribute column1 = new DbAttribute(name, type, dbEntity);
+        column1.setMandatory(mandatory);
+        column1.setPrimaryKey(primaryKey);
+
+        dbEntity.addAttribute(column1);
+        return column1;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportAction.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportAction.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportAction.java
index 8499b20..e3c54d5 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportAction.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportAction.java
@@ -105,7 +105,8 @@ public class DbImportAction {
         } else {
             MergerFactory mergerFactory = adapter.mergerFactory();
 
-            List<MergerToken> mergeTokens = new DbMerger(mergerFactory).createMergeTokens(existing, loadedFomDb);
+            List<MergerToken> mergeTokens = new DbMerger(mergerFactory).createMergeTokens(existing, loadedFomDb,
+                    config.getDbLoaderConfig().getFiltersConfig());
             if (mergeTokens.isEmpty()) {
                 logger.info("No changes to import.");
                 return;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/cayenne-tools/src/test/java/org/apache/cayenne/map/naming/DefaultNameGeneratorTest.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/test/java/org/apache/cayenne/map/naming/DefaultNameGeneratorTest.java b/cayenne-tools/src/test/java/org/apache/cayenne/map/naming/DefaultNameGeneratorTest.java
index 74fe191..fe5ed37 100644
--- a/cayenne-tools/src/test/java/org/apache/cayenne/map/naming/DefaultNameGeneratorTest.java
+++ b/cayenne-tools/src/test/java/org/apache/cayenne/map/naming/DefaultNameGeneratorTest.java
@@ -32,17 +32,17 @@ public class DefaultNameGeneratorTest {
         DefaultNameGenerator strategy = new DefaultNameGenerator();
         
         ExportedKey key = new ExportedKey("ARTIST", "ARTIST_ID", null,
-                "PAINTING", "ARTIST_ID", null);
+                "PAINTING", "ARTIST_ID", null, (short) 1);
         assertEquals(strategy.createDbRelationshipName(key, false), "artist"); 
         assertEquals(strategy.createDbRelationshipName(key, true), "paintings");
         
         key = new ExportedKey("PERSON", "PERSON_ID", null,
-                "PERSON", "MOTHER_ID", null);
+                "PERSON", "MOTHER_ID", null, (short) 1);
         assertEquals(strategy.createDbRelationshipName(key, false), "mother"); 
         assertEquals(strategy.createDbRelationshipName(key, true), "people");
         
         key = new ExportedKey("PERSON", "PERSON_ID", null,
-                "ADDRESS", "SHIPPING_ADDRESS_ID", null);
+                "ADDRESS", "SHIPPING_ADDRESS_ID", null, (short) 1);
         assertEquals(strategy.createDbRelationshipName(key, false), "shippingAddress"); 
         assertEquals(strategy.createDbRelationshipName(key, true), "addresses");
         

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportActionTest.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportActionTest.java b/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportActionTest.java
index a2fb0f1..018ebb8 100644
--- a/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportActionTest.java
+++ b/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportActionTest.java
@@ -88,7 +88,7 @@ public class DbImportActionTest {
 
         DbLoader dbLoader = new DbLoader(null, null, null) {
             @Override
-            public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
+            public void load(DataMap dataMap, DbLoaderConfiguration config, String ... tables) throws SQLException {
                  new DataMapBuilder(dataMap).withDbEntities(2).build();
             }
 
@@ -129,7 +129,7 @@ public class DbImportActionTest {
     public void testImportWithFieldChanged() throws Exception {
         DbLoader dbLoader = new DbLoader(null, null, null) {
             @Override
-            public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
+            public void load(DataMap dataMap, DbLoaderConfiguration config, String ... tables) throws SQLException {
                 new DataMapBuilder(dataMap).with(
                         dbEntity("ARTGROUP").attributes(
                                 dbAttr("GROUP_ID").typeInt().primaryKey(),
@@ -198,7 +198,7 @@ public class DbImportActionTest {
     public void testImportWithoutChanges() throws Exception {
         DbLoader dbLoader = new DbLoader(null, null, null) {
             @Override
-            public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
+            public void load(DataMap dataMap, DbLoaderConfiguration config, String ... tables) throws SQLException {
                 new DataMapBuilder(dataMap).with(
                         dbEntity("ARTGROUP").attributes(
                                 dbAttr("NAME").typeVarchar(100).mandatory()

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/autorelationship/InferRelationshipsControllerBase.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/autorelationship/InferRelationshipsControllerBase.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/autorelationship/InferRelationshipsControllerBase.java
index aec9ee3..7956a66 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/autorelationship/InferRelationshipsControllerBase.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/autorelationship/InferRelationshipsControllerBase.java
@@ -220,7 +220,7 @@ public class InferRelationshipsControllerBase extends CayenneController {
             String pkColumn,
             String fkTable,
             String fkColumn) {
-        return new ExportedKey(pkTable, pkColumn, null, fkTable, fkColumn, null);
+        return new ExportedKey(pkTable, pkColumn, null, fkTable, fkColumn, null, (short) 1);
     }
 
     public List<InferredRelationship> getSelectedEntities() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f3457df8/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfo.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfo.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfo.java
index 4db2356..14cbe94 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfo.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfo.java
@@ -346,12 +346,8 @@ public class ObjRelationshipInfo extends CayenneController implements TreeSelect
                 .getInstance()
                 .getLastUsedStrategies()
                 .get(0)).createDbRelationshipName(
-                new ExportedKey(targetModel.getSource().getName(),
-                                null,
-                                null,
-                                targetModel.getTarget().getName(),
-                                null,
-                                null),
+                new ExportedKey(targetModel.getSource().getName(), null, null,
+                                targetModel.getTarget().getName(), null, null, (short) 1),
                 targetModel.isToMany()));
 
         // note: NamedObjectFactory doesn't set source or target, just the name


[2/2] cayenne git commit: CAY-1976 Slow performance of DbMerger

Posted by aa...@apache.org.
CAY-1976 Slow performance of DbMerger

Merge branch '46'


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/c9793921
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/c9793921
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/c9793921

Branch: refs/heads/master
Commit: c9793921b1bbdc7b26f808d7bdcee6085226b6ea
Parents: 4e88f9a f3457df
Author: aadamchik <aa...@apache.org>
Authored: Tue Dec 9 11:13:56 2014 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Tue Dec 9 11:21:48 2014 +0300

----------------------------------------------------------------------
 .../org/apache/cayenne/access/DbLoader.java     | 500 ++++++++++---------
 .../loader/ManyToManyCandidateEntity.java       |   3 +-
 .../cayenne/access/loader/filters/DbPath.java   |   6 +
 .../apache/cayenne/map/naming/ExportedKey.java  | 111 +++-
 .../java/org/apache/cayenne/merge/DbMerger.java |  48 +-
 .../org/apache/cayenne/access/DbLoaderIT.java   |  26 +-
 .../access/loader/filters/DbPathTest.java       |  18 +
 .../map/naming/LegacyNameGeneratorTest.java     |   4 +-
 .../merge/DropRelationshipToModelIT.java        |  30 +-
 .../org/apache/cayenne/merge/MergeCase.java     |  24 +-
 .../apache/cayenne/merge/MergerFactoryIT.java   |  47 +-
 .../cayenne/tools/dbimport/DbImportAction.java  |   3 +-
 .../map/naming/DefaultNameGeneratorTest.java    |   6 +-
 .../tools/dbimport/DbImportActionTest.java      |   6 +-
 .../InferRelationshipsControllerBase.java       |   2 +-
 .../dialog/objentity/ObjRelationshipInfo.java   |   8 +-
 16 files changed, 464 insertions(+), 378 deletions(-)
----------------------------------------------------------------------